Javascript new及其衍生

1.new操作符的原理

1
2
3
4
5
6
7
8
9
10
function new(f) {
var object = {};
object.__proto__ = f.prototype;

return function() {
f.apply(object, arguments);

return object;
}
}

从上述代码可以看出new操作符的基本原理:

  • 第一步:新建一个对象object
  • 第二步:向对象添加一个名为__proto__的属性,指向构造函数的原型
  • 第三步:返回一个闭包
  • 第四步:立即执行闭包,返回这个新对象。在闭包中,构造函数的作用域(this)为新对象object,执行构造函数可以为object添加新属性
  • 第五步:返回新对象object

具体来说:

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
//构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype.getName = function() {
return this.name;
}

// 使用new操作符:
// 返回新对象,并且新对象的被关联到构造函数的原型(prototype)
var person = new Person('frank', 20);
log(person.name); // frank
log(person.age); // 20
log(person.getName()); // frank

// 直接执行构造函数:
// 由于函数不返回任何数据,默认返回undefined,
// 而name和age属性被添加到全局作用域
var person2 = Person('peggy', 18);
log(person2); // undefined

// 使用apply方式执行构造函数:
// 可以获取到属性,但是无法关联到原型(prototype)
var person3 = {};
Person.apply(person3, ['peggy', 18]);
log(person.name); // peggy
log(person.age); // 18
log(person.getName()); // Uncaught TypeError: person3.getName is not a function

2.作用域链
当我们执行一个函数A的时候,JS引擎会为A生成一个执行环境,刚开始这个执行环境只有arguments一个可访问对象,还有一个指向外部执行环境的指针,这个外部执行环境也有一个指针指向它的外部执行环境,直到延续到全局执行环境。这样就生成了A的作用域链。当我们访问A中的一个变量v时,如果v不存在于当前的执行环境,则会通过作用域链查找上一个执行环境,如果还不存在v,则会继续通过作用域链查找,直到全局执行环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 假设这是全局执行环境:__GlobalEnvironment__

var a = 1; // 即__GlobalEnvironment__.a = 1;

function A() {
// 假设这是函数A的执行环境:__AEnvironment__
// 此时A的执行环境会有一个指针指向__GlobalEnvironment__

var b = 2; // 即__AEnvironment__.b = 2;
log(a); // 1

return function B() {
// 假设这是函数B的执行环境:__BEnvironment__
// 此时B的执行环境会有一个指针指向__AEnvironment__
var c = 3; // 即__BEnvironment__.b = 2;
log(b); //2
}
}

var a = A(); // 返回一个闭包
a(); // 打印b

通过上述代码也可以看到闭包就是作用域链实现的。

3.原型链
在JS中,每定义一个函数,就会为这个函数自动生成一个原型属性(prototype),当我们使用new操作符通过一个构造函数创建一个实例的时候,这个实例会有一个内部指针(__proto__)指向构造函数的原型对象。当我们访问实例的属性时,如果实例本身不存在这个属性,就会查找构造函数的原型。如果还没找到这个属性但是这个原型恰好也有一个内部指针(__proto__),则会继续查找原型的原型对象,这样就形成了一个原型链。如何让这个原型有一个__proto__指针呢,那就是让这个原型也是某一个构造函数的实例。看下代码:

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
function Animal(kind) {
this.kind = kind;
}

Animal.prototype.getKind = function() {
return this.kind;
}

function Cat(name) {
this.name = name;
}

// 通过将Cat的原型指向Animal的实例来继承Animal
Cat.prototype = new Animal('cat');

// 定义Cat自身的原型方法
Cat.prototype.getName = function() {
return this.name;
}

var cat = new Cat('Catty');
// 通过cat的原型访问getName
cat.getName(); // Catty

// 通过cat的原型链访问getKind
cat.getKind(); // cat

通过原型链就可以实现继承了。