Appearance
- 在 JavaScript 中,是通过遍历原型链的方式,来访问对象的方法和属性,简单的说就是我自身没有我就通过'proto' 找到我的构造函数的'prototype'上,构造函数原型对象没有他就去找构造函数上'proto' 链接的原型对象一直都没找到的情况下,最后找到null 终止
- 在原型链上查找属性比较耗时,对性能有副作用,这在性能要求苛刻的情况下很重要。另外试图访问不存在的属性时会遍历整个原型链。
- js属性遮蔽可以理解成就近原则
js
function A (name) {
this.name = name
}
A.prototype.name = "原型对象上的name 属性"
const a = new A('w')
console.log(a.name); // w
- 会找离自身最近属性对应值
hasOwnProperty
-- 属性是否是原型对象上,证明打印是自己的而不是原型对象prototype上的,使用hasOwnProperty返回的是true 则使用的是实例对象自己的,false怎相反
js
function Person() {}
// 每一个函数都有一个原型属性prototype
// 他们都会指向实例对象因此在Person这个
// 构造函数的prototype加属性即可创建的对象共享
Person.prototype.name = 'wang'
const p1 = new Person()
// 当前的name 到底是p1的还是 Person的
console.log(p1.hasOwnProperty('name'))
const p2 = new Person()
p2.name = 'p2'
// 当前的name 到底是p1的还是 Person的
console.log(p2.hasOwnProperty('name'))
打印结果:
false
true
继承的几种方式
原型链继承
- 新实例无法向父类构造函数传参。
- 所有新实例共享父类实例的属性,导致两个实例使用同一个原型对象,如果属性是引用类型,当一个实例修改属性时,另一个实例也会受到影响。
- 通过直接打印对象看不到共享的属性,因为这些属性被挂载在原型链上,调用时如果实例自身没有这些属性,会访问原型链上的属性。
javascript
// 第一种 原型链继承
function Parent() {
this.name = "parent";
this.play = [1, 2, 3];
}
function Child() {
this.type = "child";
}
Child.prototype = new Parent();
const child1 = new Child();
const child2 = new Child();
child1.play.push(12345);
console.log(child1.play, child2.play); // [1, 2, 3, 12345] [1, 2, 3, 12345]
构造函数继承
- 通过 apply() 和 call() 方法改变函数执行时
this
指向,只有当new Child
时才执行,给当前this
调用了一个赋值属性的封装方法。
javascript
function Parent(age) {
this.name = "parent";
this.age = age;
this.play = [1, 2, 3];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(age) {
Parent.call(this, age);
this.type = "child";
}
const child1 = new Child(1);
const child2 = new Child(10);
child1.play.push(12345);
console.log(child1.play, child2.play); // [1, 2, 3, 12345] [1, 2, 3]
// 报错
// console.log(child1.getName());
组合继承
- 结合原型链继承和构造函数继承,将二者优点结合,但会导致
Parent
执行两次,一次在创建子类原型时,一次在子类构造函数内部。 - 所有子类实例会拥有两份父类的属性,一份在实例自身,另一份在子类原型对象中。访问属性时优先访问实例自身的属性。
javascript
function Parent(age) {
this.name = "parent";
this.age = age;
this.play = [1, 2, 3];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(age) {
Parent.call(this, age);
this.type = "child";
}
// 执行一次 Parent 构造函数
Child.prototype = new Parent();
// 手动挂上构造器,指向自己的构造函数
Child.prototype.constructor = Child;
const child1 = new Child(1); // 触发函数执行第二次 Parent.call(this, age)
const child2 = new Child(10);
child1.play.push(12345);
console.log(child1.play, child2.play); // [1, 2, 3, 12345] [1, 2, 3]
原型继承
道格拉斯·克罗克福德提出的一种实现继承的方法,不使用严格意义上的构造函数,通过 Object.create
方法基于已有对象创建新对象。 这种方法并没有使用严格意义上的构造函数。它的想法是借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。为了达到这个目的,他给出了如下函数。 先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。从本质上讲,object()对传入其中的对象执行了一次浅复制。 克罗克福德主张的这种原型式继承,要求你必须有一个对象可以作为另一个对象的基础
这类思想就是脱离之前构造函数的想法,而是直接给对象做继承,虽然每次伪造了一个 构造函数但对其包装并未暴露,相当于都挂载到原型了导致数据共享问题,无法实现复用,属于自己的属性需要给create 第二个方法传参
javascript
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
const parent4 = {
name: "parent4",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
const person4 = Object.create(parent4);
person4.name = "tom";
person4.friends.push("jerry");
const person5 = Object.create(parent4);
person5.friends.push("lucy");
console.log(person4.name); // tom
console.log(person4.getName() === person4.name); // true
console.log(person5.name); // parent4
console.log(person4.friends); // [ 'p1', 'p2', 'p3', 'jerry', 'lucy' ]
console.log(person5.friends); // [ 'p1', 'p2', 'p3', 'jerry', 'lucy' ]
寄生式继承
寄生式继承是与原型继承相关的一种思想,通过工厂模式添加当前实例的属性和方法。
javascript
const parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
function clone(original) {
const clone = Object.create(original);
clone.getFriends = function() {
return this.friends;
};
return clone;
}
const person5 = clone(parent5);
console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // [ 'p1', 'p2', 'p3' ]
寄生组合继承
- 组合继承的优化方案,避免构造函数执行两次的问题。
- 利用
Object.create
挂载原型,减少一次父类实例创建,在子类构造函数使用call
继承父类属性。
javascript
function clone(parent, child) {
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
function Parent6() {
this.name = "parent6";
this.play = [1, 2, 3];
}
Parent6.prototype.getName = function() {
return this.name;
};
function Child6() {
Parent6.call(this);
this.friends = "child6";
}
clone(Parent6, Child6);
Child6.prototype.getFriends = function() {
return this.friends;
};
const person6 = new Child6();
console.log(person6); // Child6 { name: 'parent6', play: [ 1, 2, 3 ], friends: 'child6' }
console.log(person6.getName()); // parent6
console.log(person6.getFriends()); // child6