JavaScript进阶笔记(五):构造函数、原型和原型链

一、什么是构造函数?

constructor 返回创建实例对象时构造函数的引用。此属性的值是对函数本身的引用,而不是一个包含函数名称的字符串。

构造函数和普通函数没有区别,不同点是构造函数使用 new 来生成实例对象,直接调用就是普通函数。

注意:构造函数首字母通常大写。

1.1 Symbol 是构造函数吗?

Symbol 是基本数据类型,他不支持 new Symbol() 操作。

生成 Symbol 实例直接使用 Symbol() 即可。

虽然不是构造函数,但可以获取到 constructor 属性值。

1
2
3
var sym = Symbol('sym')
sym.constructor
// ƒ Symbol() { [native code] }

这个 constructor 是哪里来的?其实是 Symbol 原型上的。Symbol.prototype.construnctor 返回创建实例原型的函数。

1.2 constructor 值是只读的吗?

分为两种情况:属性值为基本数据类型是只读。属性值为引用类型是可以修改的。

可以直接对 constructor 进行赋值。

new 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
function create() {
// 1、创建一个空的对象
var obj = new Object(),
// 2、获得构造函数,同时删除 arguments 中第一个参数
Con = [].shift.call(arguments);
// 3、链接到原型,obj 可以访问构造函数原型中的属性
Object.setPrototypeOf(obj, Con.prototype);
// 4、绑定 this 实现继承,obj 可以访问到构造函数中的属性
var ret = Con.apply(obj, arguments);
// 5、优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
};

二、原型

JavaScript 是一种基于原型的语言 (prototype-based language),这个和 Java 等基于类的语言不一样。

每个对象拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype 属性上,而非对象实例本身。

1
2
3
4
5
function Person () {
this.name = 'owenli'
this.age = 12
}
Person.prototype.constructor === Person // true

构造函数有个指针指向原型,原型有个指针指向构造函数。

2.1 __proto__

在原型(Person.prototype)中有个 __proto__ 属性,它是访问器属性。通过它可以访问内部的 [[Prototype]][[Prototype]]是对象的内部属性,外部代码无法直接访问。

1
2
var p = new Person()
p.__proto__ === Person.prototype // true

p.__proto__ 可以直接访问到对象的原型,__proto__是每个实例都有的属性。prototype是构造函数的属性。它们指向同一个对象。

注意__proto__是 ES6 的标准,兼容性问题和性能问题,推荐使用 Object.getPrototypeOf()。考虑性能问题,避免修改 [[prototype]]

如果创建一个新对象,同时继承另一个对象的 [[prototype]],推荐用 Object.create()

优化 new 的实现:

1
2
3
4
5
6
7
8
9
10
function create() {
// 1、获得构造函数,同时删除 arguments 中第一个参数
Con = [].shift.call(arguments);
// 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性
var obj = Object.create(Con.prototype);
// 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性
var ret = Con.apply(obj, arguments);
// 4、优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
};

三、原型链

每个对象拥有一个原型对象,通过 proto 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null。这种关系被称为原型链 (prototype chain),通过原型链一个对象会拥有定义在其他对象中的属性和方法。

1
2
var p = new Person()
p.constructor === Person

p 实例也有 constructor,并不是。实例本身是没有 constructor 属性的,是通过原型链向上查找 __proto__ ,最终查找到 constructor 属性,该属性指向 Person

1
2
3
4
5
p.constructor === Person //true
p.__proto__ === Person.prototype //true
p.__proto__.__proto__ === Object.prototype //true
p.__proto__.__proto__.__proto__ === null // true

小结

  • Symbol 作为构造函数来说并不完整,因为不支持语法 new Symbol(),但其原型上拥有 constructor 属性,即 Symbol.prototype.constructor
  • 引用类型 constructor 属性值是可以修改的,但是对于基本类型来说是只读的,当然 null 和 undefined 没有 constructor 属性。
  • __proto__ 是每个实例上都有的属性,prototype 是构造函数的属性,在实例上并不存在,所以这两个并不一样,但 p.__proto__Parent.prototype 指向同一个对象。
  • __proto__ 属性在 ES6 时被标准化,但因为性能问题并不推荐使用,推荐使用 Object.getPrototypeOf()
  • 每个对象拥有一个原型对象,通过 __proto__ 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null,这就是原型链。

参考

木易杨前端进阶