2019读书计划(4)——《Javascript高级程序设计(第三版 )》

《Javascript高级程序设计(第三版 )》这本书从刚毕业就在手上,到这篇文章之前,始终在前几章徘徊。现在抽空把看过的重要章节记录下,方便快速复习巩固

第1章 Javascript简介

1.2 Javascript实现

  • 一个完整的Javascript实现由三个不同的部分组成:核心(ECMAScript)、文档对象模型(DOM)和浏览器对象模型(BOM)。
  • 常见的 Web 浏览器知识 ECMAScript 实现的宿主环境之一,其他环境包括 Node、Adobe Flash
  • DOM:是针对 XML 经过扩展用于 HTML 的API(提供访问和操作网页内容的方法和接口 ;DOM级别:DOM1、DOM2、DOM3)
  • BOM:控制浏览器显示的页面以外部分(提供与浏览器交互的方法和接口)

第2章 在HTML中使用Javascript

2.1 <script>元素

  • async:可选。表示==不阻塞页面,下载并且执行脚本==。不能保证执行的顺序。只对外部脚本文件有效。
  • defer:可选。表示==立即下载脚本,脚本可延迟到文档被全部解析和显示后再执行==。规范要求,按照脚本加载顺序执行(现实当中不一定)。只对外部脚本文件有效。
  • script 脚本中不要嵌入出现”“ 字符串,会被错误识别为结束标签。正确写法是:”</script>”。

image

2.4 <noscript>元素

浏览器不支持脚本或者脚本被禁用,会显示<noscript>中的内容。

第3章 基本概念

3.1 语法

3.1.4 严格模式

ECMAScriipt 5 引入严格模式,在脚本或者函数顶部添加:

1
2
3
4
5
"use strict";

function fn(){
"use strict";
}

3.3 变量

  • 严格模式下,给未经声明的变量赋值,会抛出「ReferenceError」错误
  • var声明变量存在变量提升

3.4 数据类型

5种简单数据类型(也称基本数据类型、原始数据类型):Undefined、Null、Boolean、Number和String。

一种复杂数据类型:Object

Undefined只有一个值,即Undefined。var声明变量但未初始化时,变量的值就是Undefined

null只有一个值,即null,null值表示一个空对象指针

3.4.1 typeof操作符(不是函数)

检测给定变量的数据类型,返回值有:

  • undefined——变量值未定义
  • boolean——变量值是布尔值
  • string——变量值是字符串
  • number——变量值是数值
  • object——变量值是对象或者null
  • function——变量值是函数

3.4.5 Number类型

NaN

  • 任何涉及NaN的操作都会返回NaN
  • NaN与任何值都不相等,包括NaN本身

isNaN(params)函数:任何不能被转换成数值的值都会返回true

数值转换

  • parseInt()应该在第二个参数指明进制。
  • parseFloat()只解析十进制值

3.4.7 Object类型

Object 的每个实例都具有下列属性和方法:

  • constructor()——保存着用于创建当前对象的函数
  • hasOwnProperty()——检查给定的属性在当前对象实例中(而不是在实例 的原型中)是否存在,参数为字符串
  • isPrototypeOf()——检查传入的对象是否是传入对象的原型,即obj1.isPrototypeOf(obj2): obj1 是不是在 obj2 的原型链上
  • propertyIsEnumerable(name)——检测name是否能够使用 for-in 语句来枚举
  • toLocaleString()——返回对象的字符串表示,该字符串与执行环境的地区对应
  • toString()——返回对象的字符串表示
  • valueOf()——返回对象的字符串、数值或布尔值表示。通常与 toString()方法的返回值相同。

3.5 操作符

3.5.1 一元操作符

1.递增和递减操作符

  • 执行前置递增(递减)操作时,变量的值先改变再求值

    1
    2
    3
    4
    var age = 29;
    var AgeNext = --age + 2
    console.log(age) // 28
    console.log(AgeNext) // 30
  • 执行后置递增(递减)操作时,变量先求值在执行后置操作

1
2
3
4
var num1 = 2;
var nnuum2 = 20
var num3 = num1-- + nuum2 // 22
var num4 = num1 + nuum2 // 21

2.一元加减操作符

  • 对非数值类型会执行类型转换

3.5.2 位操作符

ECMAScript中的所有数字都以EEE-754 64 位格式存储。但操作符不直接操作64位的值,而是先把64位转换成32位的整数,然后执行操作,最后将结果转换回64位

负数使用二进制补码存储,计算补码分三步:1、求这个数绝对值的二进制码;2、求二进制反码(即将 0换成1,将1换成 0);3、得到的二进制反码加1.

1.按位非(NOT)

~: 返回数值的反码(本质:操作数的负值减1)

1
2
3
var num1 = 25; // 二进制 00000011001
var num2 = ~num1 // 11111100110
console.log(num2) // -26

2.按位与(AND)

&:按位与就是将两个数值的每一位对齐,两个数值的对应位==都为1==时才返回1,任何一位是0,都返回0;

1
2
3
4
5
6
7
var result = 25 & 4 // 输出 1
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3 = 0000 0000 0000 0000 0000 0000 0000 0011
----------------------------------------------
AND= 0000 0000 0000 0000 0000 0000 0000 0001

只有最后一位同时都是1,所以输出 1

3.按位或(OR)

|:两个数值的对应位==都为0时==才返回0,任何一位是1,都返回1;

1
2
3
4
5
6
var result = 25 & 4 // 输出 27
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3 = 0000 0000 0000 0000 0000 0000 0000 0011
----------------------------------------------
OR = 0000 0000 0000 0000 0000 0000 0001 1011

4.按位异或(XOR)

^:对应位上==只有一个 1== 时才返回 1,如果对 应的两位都是 1 或都是 0,则返回 0

1
2
3
4
5
6
var result = 25 ^ 4 // 输出 26
25 = 0000 0000 0000 0000 0000 0000 0001 1001
3 = 0000 0000 0000 0000 0000 0000 0000 0011
----------------------------------------------
XOR= 0000 0000 0000 0000 0000 0000 0001 1010

5.左移

<<:将数值的所有位向左移动指定的位数。左移操作会==以 0 来填充这些空位==.左移不会影响操作数的符号位

1
2
var oldValue = 2; // 等于二进制的10
var newValue = oldValue << 5; // 等于二进制的1000000,十进制的64

6.有符号的右移

>>:数值向右移动,==用符号位的值来填充所有空位==

1
2
var oldValue = 64; // 等于二进制的1000000
var newValue = oldValue >> 5; // 等于二进制的10 ,即十进制的2

7.无符号右移

>>>:将数值的所有 32 位都向右移动,对正 数来说,无符号右移的结果与有符号右移相同。对负数来说,首先,无符号右移是以 0 来填充空位.其次,无符号右移操作符会把负数的二进制码当成正数的二进制码。

1
2
3
4
5
6
7
8
var oldValue = 64;
var newValue = oldValue >>> 5;
// 等于二进制的1000000
// 等于二进制的10 ,即十进制的2


var oldValue = -64; // 等于二进制的11111111111111111111111111000000
var newValue = oldValue >>> 5; // 等于十进制的 134217726

3.5.3 布尔操作符

1. 逻辑非

!:将它的操作数转换为一个布尔值,然后再 对其求反

!! ==>得到该值的真正布尔值

3.5.10 逗号操作符

1
2
3
4
5
 // 声明多个变量:
var num1=1, num2=2, num3=3;
// 在用于赋值时,逗号 操作符总会返回表达式中的最后一项,
var num = (5, 1, 4, 8, 0); // num的值为0

3.6.5 for-in语句

精准的迭代语句,可以用来枚举对象的属性,由于ECMAScript中对象的属性没有顺序,所以输出的属性名的顺序是不可预测的

使用 for-in 循环之前,先检测==确认该对象的值不是 null 或 undefined==,ECMAScript 5 之前会报错。

3.6.6 label语句

使用 label 语句可以在代码中添加标签,以便将来使用

break 和 continue 语句与 label 语句联合使用,多用在循环嵌套的情况下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 // 与break配合使用
var num = 0;
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) {
if (i == 5 && j == 5) {
break;
}
num++; }
}
alert(num); //95
var num = 0;
outermost:
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) {
if (i == 5 && j == 5) {
break outermost;
}
num++; }
}
alert(num); //55

// 与continue配合使用
var num = 0;
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) { if (i == 5 && j == 5) {
continue;
}
num++; }
}
alert(num); //99

var num = 0;
outermost:
for (var i=0; i < 10; i++) {
for (var j=0; j < 10; j++) { if (i == 5 && j == 5) {
continue outermost;
}
num++; }
}
alert(num); //95

==注意:== 虽然联用 break、continue 和 label 语句能够执行复杂的操作,但如果使用过度,也会给调试 带来麻烦。在此,我们建议如果使用 label 语句,一定要使用描述性的标签,同时不要嵌套过多的循环。

3.6.8 with语句

with 语句的作用是将代码的作用域设置到一个特定的对象中

1
2
3
4
5
6
7
8
9
var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href;
// 上面几行代码都包含 location 对象。如果使用 with 语句,可以把上面的代码改写成如下所示:
with(location){
var qs = search.substring(1);
var hostName = hostname;
var url = href;
}

由于大量使用 with 语句会导致性能下降,同时也会给调试代码造成困难,因此
在开发大型应用程序时,不建议使用 with 语句

3.7 函数

return 语句可以不带任何返回值,默认返回值是undefined

arguments对象是类数组对象,通过length获取传入参数的个数,用中括号语法访问它的每一个参数。严格模式下,重写 arguments 的值会导致语法错误(代码将不会执行)。

第4章 变量、作用域和内存问题

4.1 基本类型和引用类型的值

5 种 基本数据类型:Undefined、Null、Boolean、Number 和 String,==按值访问==

引用类型的值是按引用访问的

4.1.2 复制变量值

如果从一个变量向另一个变量==复制基本类型的值==,会在变量对象上==创建一个新值==,然后把该值复制 到为新变量分配的位置上

当从一个变量向另一个变量==复制引用类型的值==时,值的副本实际上是一个==指针==,指向存储在堆中的一 个对象,复制操作结束后,两个变量实际上将==引用同一个对象==,==改变其中一个变量,就会影响另 一个变量==

**==ECMAScript 中所有函数的参数都是按值传递的==**:在向参数==传递基本类型==的值时,被==传递的值==会被复制给一个局部变量。在向参数==传递引用类型的值==时,会把 这个值==在内存中的地址复制给一个局部变量==,因此这个局部变量的变化会反映在函数的外部

1
2
3
4
5
6
7
8
function setName(obj) { 
obj.name = "Nicholas";
obj = new Object();
obj.name = "Greg";
}
var person = new Object();
setName(person);
alert(person.name); //"Nicholas",如果是按引用传递,则应该是输出"Greg"

局部对象会在函数执行完毕后立即被销毁

4.1.4 检测类型

基本数据类型:typeof;引用类型检测:instanceof

4.2 执行环境和作用域

作用域链的用途:保证对执行环境有权访问的所有变量和函数的有序访问

4.2.1 延长作用域链

在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。

  • try-catch 语句的 catch 块;
  • with 语句

对 with 语句来说,会将指定的对象添加到
作用域链中。对 catch 语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。

4.3 垃圾收集

4.3.1 标记清除(常用的)

所有变量打标记;去掉环境中变量的标记,以及被环境中变量引用变量的标记;之后,清除还有标记的变量

当变量进入环境(例如,在函
数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其 标记为“离开环境”。

4.3.2 引用计数

跟踪每个变量引用次数,被引用的变量就加 1;如果此变量又取了另一个变量,减 1。

4.3.3 性能问题

IE 的垃圾收集器是根据内存分配量运行的,达到临界值就会运行.如果一个脚本中包含那么多变 量,那么该脚本很可能会在其生命周期中一直保有那么多的变量.造成垃圾收集器频繁地运行

IE7后,临界值是动态修正,如果 垃圾收集例程回收的内存分配量低于 15%,临界值就会加倍;如果 例程回收了 85%的内存分配量,则将各种临界值重置回默认值。

4.3.4 管理内存

解除引用(dereferencing):一旦数据不再有用,通过将其值设置为 null 来释放其引用。

解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离 执行环境,以便垃圾收集器下次运行时将其回收。

第5章 引用类型

5.1 Object类型

创建object实例:

  • new 操作符后跟 Object 构造函数:var person = new Object();
  • 使用对象字面量表示法: var person = {};

5.2 Array类型

创建数组:

  • 使用 Array 构造函数:var colors = new Array();
  • 使用[]

数组的 length 属性可读写。数组不存在的位置的值为undefined

5.2.1 检测数组

ES5使用Array.isArray()。instanceof不适用于网页包含多个框架,两个以上不同的全局执行环境,存在两个以上不同版本的 Array 构造函数,从一个向另一个传入数组构造函数,严格意义上并不相等

5.2.3 栈方法

栈是一种 LIFO(Last-In-First-Out, 后进先出)的数据结构,插入和移除只发生在栈顶。

  • push()方法接收任意数量的参数,添加到数组末尾,==并返回修改后数组的长度==
  • pop()方法从数组末尾移除最后一项,==返回移除的项==

5.2.4 队列方法

队列是一种 FIFO(First-In-First-Out, 先进先出)的数据结构,发生在列表的前端和末端

  • shift()方法==移除==数组中的第一个项并==返回该项==
  • unshift()方法在数组前端==添加==任意个项并==返回新数组的长度==

5.2.5 重排序方法

  • reverse()方法会反转数组项的顺序
  • sort()不传参数时按照字符编码的顺序进行排序,传入一个函数时,函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等 则返回 0,如果第一个参数应该位于第二个之后则返回一个正数
1
2
3
4
5
6
7
8
9
10
// arr元素为数值类型
let arr = [1,3,2,7,5,30,10,26]
function compareUp(value1, value2){
return value1 - value2;
}
function compareDown(value1, value2){
return value2 - value1;
}
arr.sort(compareUp) // 升序排列(数字从小到大)
arr.sort(compareDown) // 降序排列(数字从大到小)

5.2.6 操作方法

前两个方法不影响原数组,第三个方法会影响原数组。

  • concat()会==创建一个新的数组==,参数会被自动展开
  • slice()会==创建一个新数组==,可进行删除(2个参数),插入(3个参数,第二个为0),替换(3个参数)操作
  • splice()方法返回一个数组,该数组中包含从原始数组中删除的项(如果没有删除任何 项,则返回一个空数组)

5.2.8 迭代方法

以下方法均不会修改数组中的包含的值

  • every():==每一项==都返回true,则返回true。
  • some():==任一项==都返回true,则返回true
  • filter():返回执行结果为true的项组成的数组
  • forEach():没有返回值
  • map():返回执行后的结果组成的==新数组==

5.2.9 归并方法

使用 reduce()还是 reduceRight(),主要取决于要从哪头开始遍历数组。除此之外,它们完全 相同。

  • reduce():数组从第一个开始,逐个向后遍历
  • reduceRight(): 数组从最后一个开始,向前遍历
1
2
3
4
5
6
7
8
9
10
var values = [1,2,3,4,5];
var sum = values.reduce(function(prev, cur, index, array){
return prev + cur;
});
alert(sum); //15
var values = [1,2,3,4,5];
var sum = values.reduceRight(function(prev, cur, index, array){
return prev + cur;
});
alert(sum); //15

5.4 RegExp类型

RegExp 构造 函数的模式参数是字符串,某些情况下要对字符进行双重转义,例如\n(字符\在字符串中通常被转义为\\

RegExp 的每个实例都具有下列属性:

  • global:布尔值,表示是否设置了 g 标志
  • ignoreCase:布尔值,表示是否设置了 i 标志。
  • lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从 0 算起。
  • multiline:布尔值,表示是否设置了 m 标志。
  • source:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回

5.5.2 函数声明与函数表达式

1
2
3
4
5
6
7
8
9
// 函数声明
function sum (num1, num2) {
return num1 + num2;
}

// 函数表达式
var sum = function(num1, num2){
return num1 + num2;
};

解析器在向执行环 境中加载数据时:

  • 解析器会率先读取函数声明。
  • 函数表达式,则必须等到解析器执行到它所在的代码行,才会真 正被解释执行

对代码求值时,声明函数会被js引擎提升到顶部

5.5.4 函数内部属性

在函数内部,有两个特殊的对象:argumentsthis

arguments的主要用途是保存函数参数,该对象有个callee的属性,指向拥有arguments对象的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 阶乘
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * factorial(num-1)
}
}

// 优化:解除函数的执行与函数名 factorial的耦合
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * arguments.callee(num-1)
} }

// 例子
var trueFactorial = factorial;
factorial = function(){
return 0;
};
alert(trueFactorial(5)); //120
alert(factorial(5)); //0

this 引用的是函数据以执行的环境对象

函数名仅仅是一个包含指针的变量

ECMAScript 5 也规范化了另一个函数对象的属性caller,这个属性中保存着==调用当前函数的函数的引用==, 如果是在全局作用域中调用当前函数,它的值为 null

严格模式下:访问 arguments.calleearguments.caller 会导致错误。

严格模式下: 不能为函数的 caller 属性赋值,否则会导致错误。

非严格模式下: arguments.caller 的值是undefined

5.5.5 函数属性和方法

每个函数都包含两个 属性:lengthprototype

prototype是不可枚举的,不能用 for-in 枚举

每个函数都包含两个非继承而来的方法:apply()call()

5.6 基本包装类型

ECMAScript提供3个特殊的引用类型:Boolean、Number 和 String

1
2
3
4
5
6
var s1 = "some text";
var s2 = s1.substring(2);
// s1是基本类型,不是对象,但是调用了substring方法,是因为js引擎自动进行了一系列处理
// (1) 创建 String 类型的一个实例;
// (2) 在实例上调用指定的方法;
// (3) 销毁这个实例

引用类型与基本包装类型的主要区别就是对象的生存期

自动创建的基本包装类型的对象,只存在于代码执行的瞬间,然后立即被销毁。

1
2
3
4
// 例子
var s1 = "some text";
s1.color = "red";
alert(s1.color); //undefined

注意!!:使用new调用基本包装类型的构造函数,与直接调用同名的转型函数是不一样的;

1
2
3
var value = "25";
var number = Number(value); //转型函数 alert(typeof number); //"number"
var obj = new Number(value); //构造函数 alert(typeof obj); //"object"

5.6.1 Boolean类型

基本类型与引用类型的布尔值有两个区别:

  • typeof 操作符对基本类型返回”boolean”, 而对引用类型返回”object”
  • 由于 Boolean 对象是 Boolean 类型的实例,所以使用 instanceof 操作符测试 Boolean 对象会返回 true,而测试基本类型的布尔值则返回 false
1
2
3
4
5
6
var falseObject = new Boolean(false);
var falseValue = false;
alert(typeoffalseObject);
object alert(typeof falseValue); //boolean
alert(falseObject instanceof Boolean); //true
alert(falseValue instanceof Boolean); //false

==建议永远不要使用Boolean对象==

5.6.2 Number类型

  • toFixed(小数位数),能够自动舍入的特性,可以表示带有 0 到 20 个小数位的数值(IE8 及之前版本不能正确 舍入范围在{(-0.94,-0.5],[0.5,0.94)}之间的值)
  • toExponential(小数位数),转化为科学计数法,返回字符串。

5.6.3 String类型

  • String.fromCharCode(…charcodes): 将字符编码转化为字符串。
  • String.charCodeAt(index): 将 index 的字符转化为字符编码。

string.match(pattern) 返回一个数组

第6章 面向对象的程序设计

ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”

6.1.1 属性类型

通过Object.defineProperty(属性所在的对象,属性的名字,一个描述符对象)方法,修改属性默认的特性(在IE8以上的版本中使用)

1
2
3
4
5
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
  1. 数据属性

    可以多次调用 Object.defineProperty() 修改下列属性,除了将 configurable 设置为 false。

  • Configurable:表示能否通过 delete 删除属性,默认值为 true
  • Enumerable: 表示能否通过 for-in 循环返回属性,默认值为 true
  • Writable: 表示能否修改属性的值,默认值为 true
  • Value: 包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefined。
  1. 访问器属性

访问器属性不能直接定义,必须使用 Object.defineProperty()来定义,只设置Get(Set)时,意味着属性是不能写(意味着属性是不能读)

  • Configurable:表示能否通过 delete 删除属性,默认值为 true
  • Enumerable: 表示能否通过 for-in 循环返回属性,默认值为 true
  • Get: 在读取属性时调用的函数。默认值为 undefined
  • Set: 在写入属性时调用的函数。默认值为 undefined。

6.2 创建对象

使用Object 构造函数或对象字面量创建对象,缺点:使用同
一个接口创建很多对象,会产生大量的重复代码

6.2.1 工厂模式

解决了创建 多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o; }
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

console.log(person1 instanceof Object); // true
//(从这里体现没有解决对象识别问题)
console.log(person1 instanceof createPerson) // false

6.2.2 构造函数模式

与工厂函数相比的区别:

  • 没有显式地创建对象
  • 直接将属性和方法赋给了 this 对象
  • 没有 return 语句。

创建 Person 的新实例,必须使用 new 操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
}; }
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true
// 构造函数的实例可以被标识为一种特定的类型

构造函数的主要问题:每个方法都要在每个实例上重新创建一遍。

person1 和 person2 都有一个名为 sayName()的方法,但那 两个方法不是同一个 Function 的实例

为了解决上述问题,将上述代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

该方法出现了新的问题:sayName()是定义在全局作用域中的函数, 在全局作用域中定义的函数实际上只能被某个对象调用。于是出现了原型模式

6.2.3 原型模式

每个函数都有一个prototype(原型)属性,它是一个指针,指向一个对象,包含可以由特定类型的==所有实例==共享的属性和方法

  1. 理解原型对象

创建一个新函数时,会默认创建一个prototype属性,指向函数的原型对象,默认所有原型对象会有一个constructor属性,constructor属性包含一个指向 prototype 属性所在函数的指针.

1
2
3
4
// 例子
function Person(){
}
Person.prototype.constructor 指向 Person

可以通过对象实例访问保存在原型中的值,但不能通过对象实例重写原型中的值。

1
2
3
4
5
6
7
8
9
10
11
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer"; 12 Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例 alert(person2.name); //"Nicholas"——来自原型

person1.hasOwnProperty(属性名)方法可以检测一个属性是存在于实例中(返回true)还是原型中(返回false)。

  1. 原型与 in 操作符

    两种方式使用 in 操作符:单独使用和在 for-in 循环中使用

单独使用时,in 操作符会在通 过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中

1
2
3
4
5
function Person(){
}
Person.prototype.name = "Nicholas";
var person1 = new Person();
alert("name" in person1); //true

同时使用 hasOwnProperty()方法和 in 操作符,就可以确定该属性到底是存在于对象中,还是存在于 原型中

1
2
3
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
}

for-in 循环,返回的是==所有能够通过对象访问的、可枚举的(enumerated)属性==,其中 既包括存在于==实例中==的属性,也包括存在于==原型中==的属性,屏蔽了原型中不可枚举属性(即将 [[Enumerable]]标记为 false 的属性)的实例属性也会在 for-in 循环中返回.

使用 ECMAScript 5 的 Object.keys()方法,取得对象上==所有可枚举==的实例属性,返回一个包含所有可枚举属性的字符串==数组==

  1. 原型的动态性

    实例与原型 之间的连接只不过是一个指针,而非一个副本,在实例中没有找到的属性会继续去原型中搜索

1
2
3
4
5
var friend = new Person();
Person.prototype.sayHi = function(){
alert("hi");
};
friend.sayHi(); //"hi"(没有问题!)

如果创建实例后再重写原型,会有不一样的情况

注意:实例中的指针仅指向原型,而不是指向构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(){
}
var friend = new Person();
// 重写原型,此时就切断了构造函数与最初原型的联系
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error

6.2.4 组合使用构造函数模式和原型模式

构造函数: 用于定义实例属性。

原型模式: 用于定义共享属性和方法

6.2.6 寄生构造函数模式

除了使用 new 操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实 是一模一样的

说明: 返回的对象与构造函数或者与构造函数的原型属 性之间没有关系,不能依赖 instanceof 操作符来确定对象类型

6.2.7 稳妥构造函数模式

稳妥对象,指的是没有公共属性,而且其方法也不引用 this 的对象。

稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:

  • 一是新创建对象的 实例方法不引用 this;
  • 二是不使用 new 操作符调用构造函数。

在JavaScript构造函数中:如果return值类型,那么对构造函数没有影响,实例化对象返回空对象;如果return引用类型(数组,函数,对象),那么实例化对象就会返回该引用类型;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Super(a){
this.a=a;
return 123;
}
Super.prototype.sayHello=function(){
console.log("Hello")
}
function Super1(a){
this.a=a;
return {a:2};
}
Super1.prototype.sayHello=function(){
console.log("Hello")
}
console.log(new Super(1)); //返回Super{a:1},有原型方法sayHello
console.log(new Super1(2)); //返回Object{a:2},没有原型方法sayHello

6.3 继承

接口继承:继承方法签名

实现继承:继承实际的方法

ECMAScript 只支持实现继承,主要是依靠原型链 来实现的

第7章 函数表达式

定义函数的 方式有两种:一种是函数声明,另一种就是函数表达式

函数声明,它的一个重要特征就是==函数声明提升==

7.1 递归

经典递归

1
2
3
4
5
6
7
function factorial(num){
if (num <= 1){
return 1;
} else { 6
return num * factorial(num-1);
}
}

上述代码会出现一个问题

1
2
3
var anotherFactorial = factorial; 
factorial = null;
alert(anotherFactorial(4)); //出错!

将以上代码再次修改:
arguments.callee 是一个指向正在执行的函数的指针

1
2
3
4
5
6
function factorial(num){
if (num <= 1){
return 1;
} else {
return num * arguments.callee(num-1);
} }

但在严格模式下,不能通过脚本访问 arguments.callee

于是再次修改:

1
2
3
4
5
6
var factorial = (function f(num){
if (num <= 1){
return 1;
} else {
return num * f(num-1);
} });

7.2 闭包

闭包是指:有权访问另一个函数作用域中的变量的函数。作用域得到了延长。

7.3 模仿块级作用域

1
2
3
(function(){ 
//这里是块级作用域
})();

整理完毕!!!