JavaScript忍者秘籍-第七章

面向对象与原型

###理解原型

在 JS 中,可以通过原型实现继承

原型的概念很简单.当查找属性时,若对象本身不具有该属性,则会查找原型上是否有该属性.

通过操作符 in 我们可以测试对象是否拥有某属性

Object.setPrototypeOf(a,b)方法将 b 对象设置为第一个对象的原型,对象的原型属性是内置属性(__proto__)

每个对象都可以有一个原型,每个对象的原型也可以拥有一个原型,以此类推.形成了原型链

对象构造器与原型

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
26
27
28
29
30
31
32
33
function Ninja() {
}
console.log(Ninja) //[Function: Ninja]
console.log(typeof Ninja.prototype) //object
//Ninja.prototype 是对象 函数的prototype是对象
Ninja.prototype.swingSword = function () {
return true
}
console.log("-------")
console.log(Ninja) //[Function: Ninja]
console.log(Ninja.prototype) //Ninja { swingSword: [Function] }

//ninja1=Ninja() ninja1 undefine 加上()之后ninja1的赋值是函数的返回值
//ninja1=Ninja ninja1 [Function: Ninja]

console.log("-------")
const ninja1 = Ninja()
console.log(ninja1)
const ninja2 = new Ninja()
console.log("--112-----")
console.log(ninja2.__proto__) //Ninja { swingSword: [Function] }
console.log(Ninja.prototype) //Ninja { swingSword: [Function] }
console.log(Ninja) //[Function: Ninja]
console.log(Ninja.prototype.constructor)//[Function: Ninja]
// console.log(Ninja.prototype === ninja2.__proto__)
// console.log(Ninja.prototype.__proto__.__proto__)
//Ninja.prototype === ninja2.__proto__
//函数对应的prototype是 new 出来的对象的__proto__
//Ninja.prototype.__proto__ == {}
//Ninja.prototype.__proto__.__proto__ == null
console.log(ninja2)
console.log(ninja2.swingSword)
console.log(ninja2.swingSword())

通过 new 操作符调用函数意味着作为构造器调用

  • 每一个函数都具有一个原型对象(Ninja.prototype)
  • 每一个函数的原型都具有一个 constructor 属性,该属性指向函数本身(Ninja.prototype.constructor === Ninja)
  • constructor对象的原型(NInja.prototype)设置为新创建的对象的原型

我们创建的每一个函数都具有一个新的原型对象.最初的原型对象只有一个属性,即 constructor属性.该属性指向函数本身

实例属性和原型属性之间的区别

实例会隐藏原型中与实例方法重名的方法

实例中可以查找到的属性也不会查找原型

JS 动态特性的副作用

对象与函数原型之间引用关系是在对象创建时建立的,如果原型在代码过程中被篡改,新创建的对象将引用新的原型,原来旧的对象保持着原有的原型的引用

1
var log = console.log.bind(this)

因为后面大量使用了console.log()所以可以将其简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var log = console.log.bind(this)

function Ninja() {
this.swung = true
}
const ninja1 = new Ninja()
Ninja.prototype.swingSword = function () {
return this.swung
}
log(ninja1.__proto__) //Ninja { swingSword: [Function] }
Ninja.prototype = {
pierce : function () {
return true
}
}
log(ninja1.__proto__) //Ninja { swingSword: [Function] }
const ninja2 = new Ninja()
log(ninja2.__proto__) //{ pierce: [Function: pierce] }

函数的原型可以被任意调换,而已经构建的实例引用旧的原型

通过构造函数实现对象类型

通过使用 constructor 属性,我们可以访问创建该对象时使用的函数.这个特性可以用于类型校验

1
2
3
4
5
function Ninja() {
}
const ninja1 = new Ninja()
log(ninja1 instanceof Ninja)
log(ninja1.constructor === Ninja)

instanceof:提供了一种用于检测一个实例是否由特定构造函数创建的方法

因为所有实例对象都可以访问 constructor 属性所以可以使用 const ninja2 = new ninja1.constructor()来初始化

实现继承

我们真正想要实现的是一个完整的原型链.创建这样的原型链的最佳技术方案是一个对象的原型直接是另一个对象的实例:

SubClass.prototype = new SuperClass()

通过执行 instanceof 操作符,我们可以判定函数是否继承原型链上的对象功能

instanceof 操作符的实质是:检查操作符右边的函数的原型是否存在于操作符左边的对象的原型链上

!!!不建议直接使用 Person 的原型对象作为 Ninja 的原型(Ninja.prototype = Person.prototype)这样做会导致在 Person 原型上所发生的所有变化都被同步到 Ninja 原型上(Person 原型与 Ninja 原型是同一个对象)一定会有不良的副作用

重写 constructor 属性的问题

通过设置 Person 实例对象作为 Ninja 构造器的原型时,我们已经丢失了 Ninja 与 Ninja 初始原型之间的关联.这个问题需要修复.

调整属性的配置信息可以使用内置的Object.defineProperty() .

将配置项 enumerable 设为 false, 在 for-in 循环中无法遍历该属性.

在 ES6使用 class 关键字

底层仍然基于原型继承

实现”静态”方法

使用Static关键字

或者:

1
2
3
4
5
6
7
function Ninja() {
}
Ninja.xxxcompare = function (ninja1, ninja2) {
}
const ninja1 = new Ninja()
const ninja2 = new Ninja()
log(ninja1.constructor.xxxcompare()) //找不到的,静态方法