原型和原型链
JavaScript 中的对象有一个特殊的 [[Prototype]]
内置属性,就是对其他对象的引用,如果在对象上没有找到需要的属性或方法,就会继续在 [[Prototype]]
关联的对象上查找,层层向上找直到找到或者到对象原型为null时结束,这一系列 [[Prototype]]
链接就是原型链。
__proto__
引用了 [[Prototype]]
,可以通过 __proto__.__proto__...
来遍历原型链。
所有普通的 [[Prototype]]
链最终都会指向内置的 Object.prototype。
var obj = {}
Object.__proto__ === Object.prototype // true
Object.getPrototypeOf(obj) === Object.prototype // true
Object.getPrototypeOf(Object.prototype) // null
Object.getPrototypeOf(Function.prototype) === Object.prototype // true
当试图引用对象的属性时会触发 [[Get]]
操作,对于默认的 [[Get]]
,第一步是检查对象本身是否具有这个属性,如果无法在对象本身找到,就会继续访问对象的 [[Prototype]]
链。
var obj = {
id: 1,
name: 'xx'
}
// Object.create()会创建一个对象并把这个对象的 [[Prototype]] 关联到指定的对象
var obj2 = Object.create(obj)
obj2 // {}
obj2.id // 1
obj2.name // 'xx'
以上例子,创建了一个关联到 obj 的对象 obj2,obj2 的 [[Prototype]]
指向 obj,obj2 本身是个空对象,访问 obj2.id 在 obj2 本身没有找到 id 这个属性,访问到了 [[Prototype]]
找到了这个属性。

属性设置和屏蔽


var obj = {
a: 1
}
var _obj = Object.create(obj)
obj.b = 2
// obj上没有b这个属性,则添加b,此时obj -> { a: 1, b: 2 }
_obj.a = 100
// _obj -> { a: 100 },obj -> { a: 1, b: 2 } 此为上图的1
Object.defineProperty(obj, 'b', {
writable: false
})
// 将obj中的b设置为只读
_obj.b = 300
// _obj -> { a: 100 },此时不会在_obj上创建属性b
Object.defineProperty(_obj, 'b', { value: 200 })
// _obj -> { a: 100, b: 200 }
只读属性会阻止 [[Prototype]]
链下层隐式创建同名属性,这样做主要是为了模拟类属性的继承。但实际上并不会发生类似的继承复制,而且这个限制只存在于=赋值中,使 Object.defineProperty() 并不会受到影响。
JS中的‘类’
function Foo() {
...
}
var a = new Foo()
Object.getPrototypeOf(a) === Foo.prototype; // true
a.constructor == Foo // true
调用 new Foo() 时会创建 a,其中的一步就是给 a 一个内部的 [[Prototype]]
链接,关联到 Foo.prototype 指向的那个对象
- js 中只有对象
- js 中没有复制机制
- 函数本身并不是构造函数,js 中的‘构造函数’可以理解为
带 new 的函数调用
ES6 的 Class
ES6 的 Class 只是一个语法糖,是 ES5 的构造函数的一层包装
// es5
function es5Foo(x, y) {
this.x = x;
this.y = y;
}
es5Foo.prototype.add = function() {
return this.x + this.y;
}
var f1 = new es5Foo(1, 2);
// es6
class es6Foo {
constructor(x, y) {
this.x = x;
this.y = y;
}
add() {
return this.x + this.y;
}
}
var f2 = new es6Foo(1, 2);
类的所有方法都定义在类的 prototype 上,prototype 的 constructor 指向类本身,这与 ES5 一致
es5Foo.prototype.constructor === es5Foo // true
Object.getPrototypeOf(f1) === es5Foo.prototype // true
es6Foo.prototype.constructor === es6Foo // true
Object.getPrototypeOf(f2) === es6Foo.prototype // true
类的内部所有定义的方法都是不可枚举的,这与 ES5 不同
Object.keys(es5Foo.prototype) // ['add']
Object.keys(es6Foo.prototype) // []
new.target: ES6 为 new 命令引入了一个 new.target 属性,该属性一般用在构造函数之中,返回 new 命令作用于的那个构造函数,如果构造函数不是通过 new 命令或 Reflect.construct() 调用的, new.target 会返回 undefined
- 可以用来确保构造函数只能通过 new 调用
- 可以用来写不能单独使用、必须继承后才能用的类
参考:Class 的基本语法
原型继承
js 中并没有复制机制,new 操作不是创建一个类的多个实例,而是可以创建多个对象并把它们的[[Prototype]]
关联到同一个对象,这个机制就是 js 中的原型继承,通过让一个对象的原型指向另一个对象实现继承
继承方式:
- 原型链继承
- 构造函数继承
- 组合继承
- 原型式继承
- 寄生组合式继承
参考:MDN 继承和原型链