说明:

此摘要笔记系列是我最近看《JavaScript高级程序设计(第3版)》随手所记。
里面分条列举了一些我认为重要的、需要记下的、对我有帮助的点,是按照我看的顺序来的。
摘要笔记本身没有系统性,没有全面性可言,写在这里供有一定基础的前端开发者参考交流。
里面的知识点、例子大部分来源于本书,或者延伸出来的,都经过我的测试是对的,但是没办法保证100%正确,如果有人看到错误的地方,希望指出来,谢谢。


关于继承

1. 原型链概述

function Grandpa () {
  this.name = 'Grandpa'
}
Grandpa.prototype.sayName = function () {
  console.log(this.name)
}
function Parent () {
  this.name = 'Parent'
}
Parent.prototype = new Grandpa()
Parent.prototype.sayHello = function () {
  console.log('hello')
}
var ming = new Parent()
var li = new Parent()
ming.sayName() // 'Parent'
ming.sayHello() // 'hello'
ming.sayHello === li.sayHello // true
ming.sayName === li.sayName // true
ming instanceof Parent // true
ming instanceof Grandpa // true
ming.constructor === Parent // false 因为在上面重新了 Parent 的原型,而且没有重新指定 constructor
ming.constructor === Grandpa // true 因为上面没有重写 Grandpa 的原型
ming.__proto__ === Parent.prototype // true
ming.__proto__ === Grandpa.prototype // false
Parent.prototype.__proto__ === Grandpa.prototype // true

2. 确定原型和实例的关系

两种方法: instanceof 操作符和 isPrototypeOf(),
如上例:

Parent.prototype.isPrototypeOf(ming) // true
Grandpa.prototype.isPrototypeOf(ming) // true
Object.prototype.isPrototypeOf(ming) // true

3. 给原型添加方法的代码一定放在替换原型的语句之后,否则无效

4. 原型链的问题

问题1. 引用类型的继承在多个实例之间会共享,无论是原型中添加的属性还是构造函数中添加的属性。如:
function Grandpa () {
  this.friends = ['ming', 'li']
}
function Parent () {
}
Parent.prototype = new Grandpa()
var ming = new Parent()
var li = new Parent()
ming.friends.push('wang')
console.log(ming.friends) // ['ming', 'li', 'wang']
console.log(li.friends) // ['ming', 'li', 'wang']
ming.friends === li.friends // true
问题2. 在创建子类型的实例中,不能向超类型的构造函数中传递参数

6. 借用构造函数(或叫伪造对象或经典继承)

可以解决引用类型的继承在多个实例之间共享的问题
如:

function Grandpa () {
  this.friends = ['ming', 'li']
}
function Aunt () {
  Grandpa.call(this)
}
var ming = new Aunt()
var li = new Aunt()
ming.friends // ['ming', 'li']
ming.friends.push('wang')
li.friends // ['ming', 'li']
ming.friends // ['ming', 'li', 'wang']

7. 借用构造函数可以解决不能向超类型的构造函数中传递参数的问题。

如:

function Grandpa (name) {
  this.name = name
}
function Parent (name, age) {
  Grandpa.call(this, name)
  this.age = age
}
var ming = new Parent('ming', 15)
ming.age // 15
ming.name // 'ming'

注意:这种情况,为了避免超类型的构造函数(Grandpa)重写子类型(Parnt)的属性,应在调用超类型构造函数后,再添加应该在子类型中定义的属性

8. 借用构造函数的问题。

问题1. 函数没有复用,占用内存多。
问题2. 超类型原型中定义的方法,对子类型而言是不可见的,结果只能使用构造函数模式。

9. 组合继承

有时候也叫伪经典继承。思路是,使用原型链对原型属性和方法进行继承,借用构造函数实现对实例属性的继承。
如:

function Grandpa (name) {
  this.name = name
  this.friends = ['li']
}
Grandpa.prototype.sayName = function () {
  console.log(this.name)
}
function Parent (name, age) {
  Grandpa.call(this, name)
  this.age = age
}
Parent.prototype = new Grandpa()
Parent.prototype.sayAge = function () {
  console.log(this.age)
}
var ming = new Parent('ming', 12)
var wang = new Parent('wang', 15)
ming.name // 'ming'
ming.friends // ['li']
ming.friends.push('zhang')
wang.friends // ['li']
ming.sayName() // 'ming'
ming.sayAge() // 12
ming.sayName === wang.sayName // true
ming.sayAge === wang.sayAge // true
ming instanceof Parent // true
ming instanceof Grandpa // true

10. 原型式继承

function object (o) {
  function F () {}
  F.prototype = o
  return new F()
}
实际上,object() 对传入其中的对象执行了一次浅拷贝。如:
var person = {
  name: 'wang',
  friends: ['li']
}
var person1 = object(person)
person1.name // 'wang'
person1.friends // ['li']
person1.name = 'zhang'
person.name // 'wang'
var person2 = object(person)
person1.friends.push('yang')
person2.friends // ['li', 'yang']
person2.friends === person.friends // true
person1.friends === person.friends // true

这种原型式继承,必须有一个对象作为另一个对象的基础。把基础对象传入 object() 函数,把得到的对象根据具体需求做修改即可。
但是别忘了所有得到的对象引用类型是共享的。

es5 新增了 Object.create() 方法规范了原型式继承。

这个方法接受两个参数,一个用作新对象的原型对象,一个(可选的)为新对象定义额外属性的对象。
在传入一个参数的情况下, Object.create() 和 object() 的行为相同。
Object.create()的第二个参数与 Object.defineProperties() 的第二个参数格式相同:每个属性都是通过自己的描述符来定义的。
这种方式指定的任何属性都会覆盖原型对象上的同名属性。
如:

var person = {
  name: 'wang',
  friends: ['li']
}
var person1 = Object.create(person, {
  name: {
    value: 'zhang'
  },
  friends: {
    value: ['a', 'b']
  }
})
person1.name // 'zhang'
person1.friends // ['a', 'b']
person.friends // ['li']
// 支持 Object.create() 的浏览器有 IE 9+,Firefox 4+, Safari 5+, Opera 912+, Chrome.

11. 寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路,和工厂模式类似。如:

function createAnother (original) {
  var clone = object(original)
  clone.sayHi = function () {
    console.log('hi')
  }
  return clone
}
var person = {
  name: 'wang',
  friends: ['li']
}
var person1 = createAnother(person)
person1.friends // ['li']
person1.sayHi() // 'hi'

新的对象person1不仅具有person的所有属性和方法,还有自己的 sayHi() 方法。
示范的 object() 函数不是必须的,任何能够返回新对象的函数都适用于此模式。
注意:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。

12. 组合继承的问题。

无论什么情况下都会调用两次超类型的构造函数。一次是在创建子类型原型的时候,一次是在子类型构造函数内部。
如:

function Grandpa (name) {
  this.name = name
  this.friends = ['ming']
}
Grandpa.prototype.sayName = function () {
  console.log(this.name)
}
function Parent (name, age) {
  Grandpa.call(this, name)
  this.age = age
}
Parent.prototype = new Grandpa() // 第一次调用超类型的构造函数 Grandpa(),
// 此时原型上有一个friends属性(值是['ming'])和name属性(值undefined,因为没有传参)
Parent.prototype.sayHi = function () {
  console.log('hi')
}
var wang = new Parent('wang', 15) // 第二次调用超类型的构造函数 Grandpa(),在 Parent() 函数内部调用,
// 这一步的操作, Parent 的实例 wang 的属性会覆盖 Parent 的原型上的同名属性 name

12. 寄生组合式继承

基本模式如下:

function inheritPrototype (subType, superType) {
  var prototype = object(superType.prototype)
  prototype.constructor = subType
  subType.prototype = prototype
}
function Grandpa (name) {
  this.name = name
  this.friends = ['ming']
}
Grandpa.prototype.sayName = function () {
  console.log(this.name)
}
function Parent (name, age) {
  Grandpa.call(this, name)
  this.age = age
}
inheritPrototype(Parent, Grandpa) // 这里没有调用超类型的构造函数 Grandpa() ,但是这里调用了一个函数内部的构造函数
Parent.prototype.sayHi = function () {
  console.log('hi')
}
var li = new Parent('li', 20) // 调用超类型的构造函数 Grandpa(),在 Parent() 函数内部调用

这种模式的好处是只调用一次 Grandpa() 构造函数,因此避免了在 Parent.prototype 上创建不必要的、多余的属性。
作者讲,开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。

继承部分全部结束

wfc_666
643 声望31 粉丝

专注全栈