[[Prototype]]
在 JavaScript 中,对象具有一个隐藏属性 [[Prototype]],它指向另一个对象(或者 null),称为原型对象。当我们尝试访问对象缺失的属性或方法时,JavaScript 会自动从原型对象上找。
这个属性是隐藏在内部的,我们无法直接访问,但是,浏览器给我们提供了访问它的方式:__proto__
。
let person = {
name: 'Tom',
age: 18
};
let student = {
studentId: 1
};
student.__proto__ = person;
console.log(student.name); // 'Tom'
给对象添加原型对象可以称为原型继承,这个继承关系可以一直串联,形成原型链。
注意:
- 引用不能形成闭环,否则 JavaScript 会报错
__proto__
是 [[Prototype]] 的 getter/setter,它们并不是一个东西,实现不相同for...in
会遍历继承的属性,但是 Object.keys() 不会F.prototype
如果 F.prototype 是一个对象,那么 new 操作符会使用它为新对象设置 [[prototype]]。 ```javascript let person = { name: ‘Tom’, age: 18 }; function Student(studentId) { this.studentId = studentId; } Student.prototype = person;
let student = new Student(1); console.log(student.name); // ‘Tom’ console.log(student.proto === Student.prototype); // true
注意:只有在指定了 F.prototype 的值之后 new 出来的对象才有效果,之前已经存在的保持自身的值<br />当然,如果我们没有指定 F.prototype,它会有一个默认值,默认值中只有有一个属性 constructor,指向构造函数本身。
```javascript
function Student() {}
console.log(Student.prototype.constructor === Student); // true
对于一开始的 Student.prototype = person;
,我们直接重写了它的整个 prototype,使得 constructor 丢失了。
有关原型方法
Object.getPrototype(obj)
Object.setPrototype(obj, proto)
Object.create(proto [, descriptors])
利用给定的 proto 和属性描述创建一个空对象
let person = {
name: 'Tom',
age: 18
};
let student = Object.create(person, {
studentId: {
value: 1
}
});
console.log(student.studentId); // 1
继承
原型链
function Parent() {
this.parentProperty = 'parentProperty';
}
Parent.prototype.getParentProperty = function () {
return this.parentProperty;
}
function Son() {
this.sonProperty = 'sonProperty';
}
Son.prototype = new Parent();
Son.prototype.getSonProperty = function () {
return this.sonProperty;
}
let instance = new Son();
console.log(instance.getParentProperty()); // 'parentProperty'
原型链实现继承存在一个问题:如果父构造函数包含引用类型时,会在所有实例中共享。
盗用构造函数
function Parent() {
this.nums = [1, 2, 3];
}
function Son() {
Parent.call(this);
}
let instance1 = new Son();
instance1.nums.push(4);
console.log(instance1.nums); // [1, 2, 3, 4]
let instance2 = new Son();
console.log(instance2.nums); // [1, 2, 3]
盗用构造函数使得每个实例不再共享父构造函数的引用类型的值,但是没法重用原型的方法,必须在构造函数中创建。
组合继承
function Parent(name) {
this.name = name;
this.nums = [1, 2, 3];
}
Parent.prototype.sayName = function () {
console.log(this.name);
}
function Son(name, age) {
Parent.call(this, name);
this.age = age;
}
Son.prototype = new Parent();
Son.prototype.sayAge = function () {
console.log(this.age);
}
let instance1 = new Son('Alice', 20);
instance1.nums.push(4);
console.log(instance1.nums); // [1, 2, 3, 4]
instance1.sayName(); // 'Alice'
instance1.sayAge(); // 20
let instance2 = new Son('Bob', 18);
console.log(instance2.nums); // [1, 2, 3]
instance2.sayName(); // 'Bob'
instance2.sayAge(); // 18
原型式继承
function object(o) {
function F() {};
F.prototype = o;
return new F();
}
这个主要用于在现有对象的基础上再创建一个新对象,新对象的原型就是 o。
ES5 新增的方法 Object.create() 就是这个东西的规范化,并且支持属性描述符定义属性。
寄生式继承
function createAnother(original) {
let clone = object(original); // 通过调用函数创建一个新对象
clone.sayHi = function () { // 以某种方式增强对象
console.log('Hi');
};
return clone; // 返回这个对象
}
终极方案:寄生式组合继承
function inheritPrototype(parent, son) {
let prototype = Object.create(parent.prototype);
prototype.constructor = son;
son.prototype = prototype;
}
function Parent() {}
function Son() {
Parent.call(this);
}
inheritPrototype(Parent, Son);