原型链与类的继承
原型链与类的继承
Prototype Chaining | 原型链
原型 与继承是null
为止__proto__
属性所指向的那个对象__proto__
与prototype
,需要明晰的是,__proto__
的继承,而不是
const b = new Foo(20);
const c = new Foo(30);

prototype
是一个__proto__
指向
proto is the actual object that is used in the lookup chain to resolve methods, etc. prototypeis the object that is used to build proto when you create an object with new:
(new Foo().__proto__ === Foo.prototype(new Foo()).prototype) === undefined;
更直观的表述方式是,在

继承属性
// 假定我们有个对象o,并且o所在的原型链如下:
// {a:1, b:2} ---> {b:3, c:4} ---> null
// 'a'和'b'是o自身的属性.
// 该例中,用"对象.[[Prototype]]"来表示这个对象的原型.
// 这只是一个纯粹的符号表示(ECMAScript标准中也这样使用),不能在实际代码中使用.
console.log(o.a); // 1
// a是o的自身属性吗?是的,该属性的值为1
console.log(o.b); // 2
// b是o的自身属性吗?是的,该属性的值为2
// o.[[Prototype]]上还有一个'b'属性,但是它不会被访问到.这种情况称为"属性遮蔽".
console.log(o.c); // 4
// c是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有.
// c是o.[[Prototype]]的自身属性吗?是的,该属性的值为4
console.log(o.d); // undefined
// d是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有.
// d是o.[[Prototype]]的自身属性吗?不是,那看看o.[[Prototype]].[[Prototype]]上有没有.
// o.[[Prototype]].[[Prototype]]为null,原型链已到顶端,没有d属性,返回undefined
继承方法
this
指向的是当前继承原型的对象,而不是继承的函数所在的原型对象。
const o = {
a: 2,
m: function () {
return this.a + 1;
},
};
console.log(o.m()); // 3
// 当调用 o.m 时,'this'指向了o.
const p = Object.create(o);
// p是一个对象, p.[[Prototype]]是o.
p.a = 12; // 创建p的自身属性a.
console.log(p.m()); // 13
// 调用p.m时, 'this'指向 p. 'this.a'则是12.
在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。遍历对象的属性时,原型链上的每个属性都是可枚举的。检测对象的属性是定义在自身上还是在原型链上,有必要使用 hasOwnProperty 方法,该方法由所有对象继承自 Object.proptotype
。hasOwnProperty 是
Prototype Operation(原型操作)
class A {
say() {
console.log("It's A#say.");
}
}
class B extends A {
say() {
console.log("It's B#say.");
}
run() {
this.say();
super.say();
}
}
const b = new B();
b.run.call({
say() {
console.log("call");
},
});
console.log("===");
const obj = {
say() {
console.log("It's obj.say.");
},
run() {
this.say();
super.say();
},
};
Object.setPrototypeOf(obj, {
say() {
console.log("It's proto.say.");
},
});
obj.run();
class A {}
class B extends A {
constructor() {
return {};
}
}
console.log(new B());
// 删去 return {},则会报异常 ReferenceError: this is not defined
super(…); is basically sugar for this = new ParentConstructor(…);. Where ParentConstructor is the extended class, and this = is the initialisation of the this keyword (well, given that that’s forbidden syntax, there’s a bit more than sugar to it). And actually it will inherit from the proper new.target.prototype instead of ParentConstructor.prototype like it would from new. So no, how it works under the hood does not compare to ES5 at all, this is really a new feature in ES6 classes (and finally enables us to properly subclass builtins).
function Foo() {
getName = function () {
console.log(1);
};
return this;
}
Foo.getName = function () {
console.log(2);
};
Foo.prototype.getName = function () {
console.log(3);
};
const getName = function () {
console.log(4);
};
function getName() {
console.log(5);
}
// 2
Foo.getName();
// 4
getName();
// 1
Foo().getName();
// 1
getName();
// 2
new Foo.getName();
// 3
new Foo().getName();
// 3
new new Foo().getName();
class A {
say() {
console.log("It's A#say.");
}
}
class B extends A {
say() {
console.log("It's B#say.");
}
run() {
this.say();
super.say();
}
}
const b = new B();
b.run.call({
say() {
console.log("call");
},
});
console.log("===");
const obj = {
say() {
console.log("It's obj.say.");
},
run() {
this.say();
super.say();
},
};
Object.setPrototypeOf(obj, {
say() {
console.log("It's proto.say.");
},
});
obj.run();
class A {}
class B extends A {
constructor() {
return {};
}
}
console.log(new B());
// 删去 return {},则会报异常 ReferenceError: this is not defined
super(…); is basically sugar for this = new ParentConstructor(…);. Where ParentConstructor is the extended class, and this = is the initialisation of the this keyword (well, given that that’s forbidden syntax, there’s a bit more than sugar to it). And actually it will inherit from the proper new.target.prototype instead of ParentConstructor.prototype like it would from new. So no, how it works under the hood does not compare to ES5 at all, this is really a new feature in ES6 classes (and finally enables us to properly subclass builtins).
继承方式
原型链继承
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log("hello, my name is " + this.name);
};
function Man() {}
Man.prototype = new Person("pursue");
const man1 = new Man();
man1.say(); //hello, my name is pursue
const man2 = new Man();
console.log(man1.say === man2.say); //true
console.log(man1.name === man2.name); //true
这种继承方式很直接,为了获取
利用构造函数继承
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log("hello, my name is " + this.name);
};
function Man(name, age) {
Person.apply(this, arguments);
}
//Man.prototype = new Person('pursue');
const man1 = new Man("joe");
const man2 = new Man("david");
console.log(man1.name === man2.name); //false
man1.say(); //say is not a function
这里子类的在构造函数里利用了
组合继承
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log("hello, my name is " + this.name);
};
function Man(name, age) {
Person.apply(this, arguments);
}
Man.prototype = new Person();
const man1 = new Man("joe");
const man2 = new Man("david");
console.log(man1.name === man2.name); //false
console.log(man1.say === man2.say); //true
man1.say(); //hello, my name is joe
需要注意的是
寄生组合继承
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log("hello, my name is " + this.name);
};
function Man(name, age) {
Person.apply(this, arguments);
}
Man.prototype = Object.create(Person.prototype); //a.
Man.prototype.constructor = Man; //b.
const man1 = new Man("pursue");
const man2 = new Man("joe");
console.log(man1.say == man2.say); //true
console.log(man1.name == man2.name); //false
其实寄生组合继承和上面的组合继承区别仅在于构造子类原型对象的方式上
function create(obj) {
function T() {}
T.prototype = obj;
return new T();
}
因此,
function A(a) {
this.varA = a;
}
// 以上函数 A 的定义中,既然 A.prototype.varA 总是会被 this.varA 遮蔽,
// 那么将 varA 加入到原型(prototype)中的目的是什么?
A.prototype = {
varA: null, // 既然它没有任何作用,干嘛不将 varA 从原型(prototype)去掉?
// 也许作为一种在隐藏类中优化分配空间的考虑?
// https://developers.google.com/speed/articles/optimizing-javascript#Initializing instance variables
// 将会验证如果 varA 在每个实例不被特别初始化会是什么情况。
doSomething: function () {
// ...
},
};
function B(a, b) {
A.call(this, a);
this.varB = b;
}
B.prototype = Object.create(A.prototype, {
varB: {
value: null,
enumerable: true,
configurable: true,
writable: true,
},
doSomething: {
value: function () {
// override
A.prototype.doSomething.apply(this, arguments); // call super
// ...
},
enumerable: true,
configurable: true,
writable: true,
},
});
B.prototype.constructor = B;
const b = new B();
b.doSomething();
类继承技巧
属性丢失现象
在
class Base {
constructor(data = {}) {
Object.assign(this, data);
}
}
class A extends Base {
a = null;
constructor(data = {}) {
super(data);
}
}
new A({ a: 1 }); // { a: null }
但是这种方式会发现,传入的参数值是在父类中被赋值到当前实例,而又被子类的默认值给覆盖了。在
class Base {
constructor(data = {}) {
this._defineProps(data);
}
_defineProps(props) {
Object.entries(props).forEach(([prop, value]) =>
Object.defineProperty(this, prop, {
get() {
return value;
},
set(newValue) {
if (newValue !== undefined) {
value = newValue;
}
},
})
);
}
}
class A extends Base {
a = 2;
constructor(data = {}) {
super(data);
}
}
console.log(new A({ a: 1 }));