原型继承就是可以使一个对象可以使用另一个对象上面的某一些属性,要求是这个对象没有这个属性。如果有这个属性,就直接使用自己的了(访问器属性除外)。
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
比如上面的两个对象, animal
和 rabbit
,我想要 rabbit 可以使用 eats 这个属性。不再改变 rabbit 的情况下怎么做呢?方法有很多,我们一个个来看看。
这个属性是 JavaScript
的一个隐藏属性,他的值只能有两种情况, null
或者是另一个对象的引用
。注意了,这里是引用,而不是拷贝,对象的引用容易出现的问题这里就不多少了。
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
console.log(rabbit.eats);
rabbit.__proto__ = animal; // 设置 rabbit.[[Prototype]] = animal'
console.log(rabbit.eats);
// 因为对象的引用,导致 animal 多了一个属性 name
rabbit.__proto__.name = "jack";
console.log(animal);
在这儿我们可以说 “animal
是 rabbit
的原型”,或者说 “rabbit
的原型是从 animal
继承而来的”。
因此,如果 animal
有许多有用的属性和方法,那么它们将自动地变为在 rabbit
中可用。这种属性被称为“继承”。
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
console.log(rabbit.eats);
rabbit.__proto__ = animal; // 设置 rabbit.[[Prototype]] = animal'
console.log(rabbit.eats);
// 因为对象的引用,导致 animal 多了一个属性 name
rabbit.__proto__.name = "jack";
console.log(animal);
// 很长很长的原型链
let longer = {
longer: 10,
__proto__: rabbit
};
console.log(longer.eats);
这里只有两个限制:
__proto__
,JavaScript 会抛出错误。__proto__
的值可以是对象,也可以是 null
。而其他的类型都会被忽略。注意:__proto__
是 [[Prototype]]
的因历史原因而留下来的 getter/setter。它们两个本质上是不一样的。不过__proto__
的确是有些过时了。现在我们一般使用 Object.getPrototypeOf/Object.setPrototypeOf
来取代 __proto__
去 get/set 原型。不过两者都能用,而且 __proto__
更简便一些。
let animal = {
eats: true,
walk() {
/* rabbit 不会使用此方法 */
}
};
let rabbit = {
__proto__: animal
};
rabbit.walk = function() {
alert("Rabbit! Bounce-bounce!");
};
rabbit.walk(); // Rabbit! Bounce-bounce!
原型 animal
上又一个 walk
函数,对象 rabbit
的原型是继承于 animal
。 现在在 rabbit
上添加一个 名字为 walk
的函数,此时在调用这个函数。就不会在原型上去找,因为自身就有这个方法。
但是,访问器(get/set)属性是一个例外。
let user = {
name: "John",
surname: "Smith",
set fullName(value) {
[this.name, this.surname] = value.split(" ");
},
get fullName() {
return `${this.name} ${this.surname}`;
}
};
let admin = {
__proto__: user,
isAdmin: true
};
alert(admin.fullName); // John Smith
// setter triggers!
admin.fullName = "Alice Cooper";
alert(admin.fullName); // Alice Cooper,admin 的内容被修改了
alert(user.fullName); // John Smith,user 的内容被保护了
如果对象上面添加的属性是原型的设置的访问器属性,那么这个对象上面的属性就会作用于原型上,直接调用原型的getter/setter
。
注意最后面的两行代码。 admin
和 user
的fullName
是不同的。那为什么回不同呢?原因就是因为访问器属性中的 this
。这里始终记住一点: this 的指向始终指向 . 符号前面的对象。简单来说就是谁调用那么就指向谁。(箭头函数(访问器属性不能使用箭头函数)与使用了call,apply,bind的函数除外)。
遍历对象这里说说 for...in
循环与 Object.keys
的区别。
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
// Object.keys 只返回自己的 key
console.log(Object.keys(rabbit)); // jumps
// for..in 会遍历自己以及继承的键
for(let prop in rabbit) console.log(prop); // jumps,然后是 eats
区别就如上面的代码注释中的那样。所以,在 Object.keys
方法出现以前,我们都需要使用 obj.hasOwnProperty(key) 来进行判断。所有的对象都有 hasOwnProperty
属性,都是从 Object
对象上面继承的哦
function Fn() {}
console.log(Fn.prototype); // { constructor: Fn() {} }
console.log(Fn.prototype.constructor); // FN() {}
const fn = new Fn();
console.log(fn.__proto__); // { constructor: Fn() {} }
每一个函数都有一个 prototype
属性,这个属性的值**不一定**
等于 {constructor: XXX() {}}
。因为我们可以手动修改它。
Fn.prototype = {name: 'Jack'};
所以我们不要这样去写,而是在 prototype
上面去添加属性
Fn.prototype.name = 'Jack';
或者修改了以后重新指定它的 constructor
Fn.prototype = {
constructor: Fn,
name: 'Jack'
};
构造函数的 prototype
值是 { constructor: Fn() {} }
。构造函数的实例值也是 { constructor: Fn() {} }
。但是两者是不一样的(内存地址不同吧)。但是他们的 constructor
是一样的,都是指向这个构造函数。
function Fn() {}
let fn = new Fn();
console.log(Fn.prototype === fn.__prpto__) // false
console.log(Fn.prototype.constructor === fn.constructor) // true
所以,当我们不知道某个对象的构造函数的时候,可以使用 constructor
来创建(注意没有被修改)。
function Rabbit(name) {
this.name = name;
alert(name);
}
let rabbit = new Rabbit("White Rabbit");
let rabbit2 = new rabbit.constructor("Black Rabbit");
JS原型链有一个很经典的图