再看JavaScript,那些遗漏或易混淆的知识点(4)

原型与继承

原型继承就是可以使一个对象可以使用另一个对象上面的某一些属性,要求是这个对象没有这个属性。如果有这个属性,就直接使用自己的了(访问器属性除外)。

1
2
3
4
5
6
let animal = {
eats: true
};
let rabbit = {
jumps: true
};

比如上面的两个对象, animalrabbit ,我想要 rabbit 可以使用 eats 这个属性。不再改变 rabbit 的情况下怎么做呢?方法有很多,我们一个个来看看。

[[Prototype]]

这个属性是 JavaScript 的一个隐藏属性,他的值只能有两种情况, null 或者是另一个对象的引用 。注意了,这里是引用,而不是拷贝,对象的引用容易出现的问题这里就不多少了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);

JavaScript 原型与继承

在这儿我们可以说 “animal 是 rabbit 的原型”,或者说 “rabbit 的原型是从 animal 继承而来的”。

因此,如果 animal 有许多有用的属性和方法,那么它们将自动地变为在 rabbit 中可用。这种属性被称为“继承”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);

这里只有两个限制:

  1. 引用不能形成闭环。如果我们试图在一个闭环中分配 __proto__,JavaScript 会抛出错误。
  2. __proto__ 的值可以是对象,也可以是 null。而其他的类型都会被忽略。

注意__proto__[[Prototype]] 的因历史原因而留下来的 getter/setter。它们两个本质上是不一样的。不过__proto__ 的确是有些过时了。现在我们一般使用 Object.getPrototypeOf/Object.setPrototypeOf 来取代 __proto__ 去 get/set 原型。不过两者都能用,而且 __proto__ 更简便一些。

在对象上添加原型上面已有的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)属性是一个例外。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

注意最后面的两行代码。 adminuserfullName 是不同的。那为什么回不同呢?原因就是因为访问器属性中的 this这里始终记住一点: this 的指向始终指向 . 符号前面的对象。简单来说就是谁调用那么就指向谁。(箭头函数(访问器属性不能使用箭头函数)与使用了call,apply,bind的函数除外)。

遍历对象

遍历对象这里说说 for...in 循环与 Object.keys 的区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.prototype

1
2
3
4
5
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() {}} 。因为我们可以手动修改它。

1
Fn.prototype = {name: 'Jack'};

所以我们不要这样去写,而是在 prototype 上面去添加属性

1
Fn.prototype.name = 'Jack';

或者修改了以后重新指定它的 constructor

1
2
3
4
Fn.prototype = {
constructor: Fn,
name: 'Jack'
};

构造函数的 prototype 值是 { constructor: Fn() {} } 。构造函数的实例值也是 { constructor: Fn() {} }。但是两者是不一样的(内存地址不同吧)。但是他们的 constructor 是一样的,都是指向这个构造函数。

1
2
3
4
function Fn() {}
let fn = new Fn();
console.log(Fn.prototype === fn.__prpto__) // false
console.log(Fn.prototype.constructor === fn.constructor) // true

所以,当我们不知道某个对象的构造函数的时候,可以使用 constructor 来创建(注意没有被修改)。

1
2
3
4
5
6
7
8
function Rabbit(name) {
this.name = name;
alert(name);
}

let rabbit = new Rabbit("White Rabbit");

let rabbit2 = new rabbit.constructor("Black Rabbit");

JS原型链有一个很经典的图

https://pic-go-1253455210.cos.ap-chengdu.myqcloud.com/blog/JS%E5%8E%9F%E5%9E%8B%E9%93%BE.jpg

文章作者: 踏浪
文章链接: https://www.lyt007.cn/技术/再看JavaScript,那些遗漏或易混淆的知识点-4.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 平凡的生活,不平凡的人生
支付宝
微信打赏