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