原型及原型链
JS原型是为其他对象提供共享访问属性的对象,在创建对象时,每个对象都创建了一个隐式引用指向他的原型对象或者null。原型也是一个对象,也有他的原型,构成了原型链。
原型链的作用
在访问一个对象的属性的时候,会访问其原型链,它本身是原型链的第一个元素。检查它是否含有要查找的属性,如果包含则返回属性值,否则查找原型链上的第二个元素,以此类推,知道最后一个元素null,返回undefined
如何实现原型继承
显式实现
Object.create(a)
Object.setPrototypeOf(a,b)
隐式实现
另一种是通过 constructor 构造函数,在使用 new 关键字实例化时,会自动继承 constructor 的 prototype 对象,作为实例的原型。
手动实现一个new
new出来的对象能够:
- 访问构造函数里的属性;
- 访问构造函数原型上的属性;
function myNew(constructor, ...args) {
const obj = Object.create(constructor.prototype);
const result = constructor.apply(obj, args);
if (typeof result === 'object' && result !== null) {
return result;
}
return obj;
}
其中const obj = Object.create(constructor.prototype) 等价于:
const F = function() {};
F.prototype = constructor.prototype;
const obj = new F();
自己实现一个继承
实现继承有三个需要注意的点:
- 构造函数中调用父亲构造函数,先父后子(为了在子中复制一份父亲的属性供自己所有);
- 设置原型关系,将父类实例设置为自己的原型。
- 修改子类原型构造函数的指向,只想子类构造函数自身。 代码如下:
// 父类person
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function() {
console.log('hi,my name is: '+ this.name + ', my age is: '+this.age);
}
// 子类Man,继承父类Person
function Man(name, age) {
Person.call(this, name, age);
this.sex = 'male';
}
Man.prototype = new Person();
// 有一个缺点,就是要创建一个无用的Person对象,占用内存,可改用以下方式:
// const F = function() {};
// F.prototype = Person.prototype;
// Man.prototype = new F();
// 上面三行等价于 Man.prototype = Object.create(Person.prototype);
Man.prototype.constructor = Man;
理解instanceof并手动实现
obj instanceof Constructor 用来判断Constructor.prototype是否存在obj的原型链上。
手写实现如下:
function myInstanceof(obj, Constructor) {
let current = Object.getPrototypeOf(obj);
while(current !== null) {
if (current === Constructor.prototype) { return true; }
current = Object.getPrototypeOf(current);
}
return false;
}
/ 定义构造函数
function C(){}
function D(){}
var o = new C();
console.log(myInstanceof(o, C)); // true,因为 Object.getPrototypeOf(o) === C.prototype
console.log(myInstanceof(o, D)); // false,因为 D.prototype 不在 o 的原型链上
console.log(myInstanceof(o, Object)); // true,因为 Object.prototype.isPrototypeOf(o) 返回 true