js基础(七) 面向对象

7/25/2017 javaScript面试

# 知识点一 单例模式

  • 对象数据类型的作用
    • 把描述一个事物(同一个对象)的属性和方法放在一个内存空间下,起到了分组的作用,这样不同事物之间的属性即使属性名相同,相互也不会发生冲突
  • 我们把这种分组编写代码的模式叫做 单例模式
var person1 = {
  name: 'zhangsan',
  age: 18
}

var person2 = {
  name: 'lisi',
  age: 48
}
1
2
3
4
5
6
7
8
9
  1. 在单例模式中我们把 person1person2 也叫做 命名空间
  2. 单例模式是一种项目开发中经常使用的模式,因为项目中我们可以使用单例模式来进行 模块化开发
  3. **模块化开发:**对于一个相对来说比较大的项目,需要对人协作的开发,我们一般情况下会根据当前项目的需求划分成几个功能模块,每个人负责一部分,同时开发,最后把每个人的代码进行合并

# 知识点二 工厂模式

  • 单例模式虽然解决了分组的作用,但是不能实现批量的生产,属于手工作业模式

  • 工厂模式:把实现同一件事情的相同代码放到一个函数中,以后如果再想实现这个功能,不需要重新的编写这些代码了,只需要执行当前的函数即可

function create(name, age) {
  var obj = {
    name: name,
    age: age,
    write: function() {
      console.log(`${this.name} can write js`)
    }
  }
  return obj
}
var p1 = create('p1', 18)
p1.write()
var p2 = create('p2', 28)
p2.write()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 工厂模式 --> 函数的封装 --> 低耦合高内聚

  • 低耦合高内聚:减少页面中冗余代码,提高代码的重复利用率

  • js 是一门轻量级的脚本 编程语言html+css 不属于编程语言,属于标记语言

  • 所有的编程语言( .net 、C#、php、Java、c、c++、vb、vf、oc、......)都是面向对象开发的 --> 继承、封装、多态

    • 继承:子类继承父类的属性和方法
    • 多态:当前方法的多种形态,在后台语言中多态包含重写(Override )与重载(Overload )
      • 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
      • 重载(overloading ) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同

image.png

注意js 中不存在重载,方法名一样的话,后面的会把前面的覆盖掉,最后只保留一个。

js 中有一个操作类似重载但不是重载,我们可以根据传递参数的不一样,实现不同的功能,例如:

function sum(num) {
  if(typeof num === 'undefined')
    return 0;
  return num
}
sum(100)
sum()
1
2
3
4
5
6
7

# 知识点三 构造函数模式

# 概述

  • 构造函数模式:目的就是为了创建一个自定义类,并且创建这个类的实例

    • 在构造函数模式中 new Fn() 执行,如果 Fn 不需要传递参数的话,后面的小括号可以省略
    • this 的问题:在类中出现的 this.xxx = xxx 中的 this 都是当前类的实例,而某一个属性值(方法),方法中的 this 需要看方法执行的时候,前面是否有 . 才能知道 this 是谁
    • 类有普通函数的一面,当函数执行的时候, var xxx 其实只是当前形成的私有作用域中的私有变量而已,它和我们的当前类的实例没有任何的关系,只有 this.xxx = xxx 才相当于给当前类的实例增加私有的属性和方法,才和我们的当前类的实例有关系
    • 在构造函数模式中,浏览器会默认的把我们当前类的的实例返回(返回的是一个对象数据类型的值),如果我们手动写了 return 返回
      • 返回的是一个基本数据类型的值,当前实例是不变的,例如 return 100 ,即返回的还是当前类的实例
      • 返回的是一个引用数据类型的值,当前的实例会被自己返回的值替换掉,例如 return { name: 'p1' } ,返回的就不是当前类的实例了,而是对象 { name:'p1' }
    • 检测某一个实例是否属于当前类的实例,用 instanceof ,例如 p2 instaceof CreateJsPerson2 --> true,同时p2 instaceof Object --> true,因为所有的实例都是对象数据类型,而每一个对象数据类型都是 Object 这个内置类的一个实例,所以 p2Object 的一个实例
  • 构造函数模式和工厂模式的区别

    • 执行的时候
      • 普通函数执行--> createJsPerson()
      • 构造函数执行--> new createJsPerson() ,通过 new 执行后,我们的 createJsPerson 就是一个类了,而函数执行返回值(p1 )就是createJsPerson 这个类的一个实例
    • 在函数代码执行的时候
      • 相同:都是形成一个私有的作用域,然后执行顺序为:形参赋值 --> 预解释 --> 代码自上而下执行(类和普通函数一样,也有普通的一面)
      • 不同:
        • 在代码执行之前,不用自己手动的创建对象,浏览器会默认的创建一个对象数据类型的值(这个对象其实就是我们当前类的一个实例)
        • 接下来代码从上到下执行,以当前的实例为执行的主体(this 代表当前的实例),然后分别的把属性名和属性值赋值给当前的实例
        • 最后浏览器会把默认的创建的实例返回
  • js 中所有的类都是函数数据类型的,它通过new 执行变成了一个类,但它本是也是一个普通的函数

  • js 中所有的实例都是对象数据类型的

  • 在类中给实例增加的属性( this.xxx)属于当前实例的私有的属性,实例和实例之间是单独的个体,所有私有的属性不是同一个,即 p1p2 都是 CreateJsPerson2 这个类的实例,所以都拥有 write 这个方法,但是不同实例之间的方法是不一样的

  • 检测数据类型

    • typeof 有自己的局限性,不能区分 Object 下的对象、数组、正则等
    • instanceof 可以检测当前实例属性哪个类
    • in 检测某个属性是否属于当前对象,例如 write in p2 --> true,不管私有的属性还是公有的属性,都可以用 in 来检测
    • hasOwnProperty 用来检测某个属性是否为当前对象的私有属性,这个方法只能检测私有属性,例如 p2.hasOwnProperty('write') --> true
    • isPrototypeOf() 方法测试一个对象是否存在另一个对象的原型链上,语法 object1.isPrototypeOf(Object2)
  • 构造函数模式中拥有了实例的概念,并且实例和实例之间是相互独立的,这个过程称实例识别

// 普通函数
function CreateJsPerson(name, age) {
  var obj = {}
  obj.name = name
  obj.age = age
  obj.write = function() {
      console.log(`${obj.name} can write js`)
    }
  return obj
}
var p = CreateJsPerson('p', 18)

// 构造函数
function CreateJsPerson2(name, age) {
  var num = 100 // 私有变量,和当前类的实例没有任何的关系
  // this 是当前类的实例
  this.name = name
  this.age = age
  this.write = function() {
    // this 需要看write执行的时候才能知道
      console.log(`${this.name} can write js`)
    }
}
var p1 = new CreateJsPerson2('p1', 18)
var p2 = new CreateJsPerson2('p2', 28)
p2.write() // 方法中的 this 是 p2,this.name --> p2
var write = p2.write
write() // 方法中的 this 是 window,this.name --> undefined
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

# 手写new

function realizeNew () {
  //创建一个新对象
  let obj  = {};
  //获得构造函数
  let Constructor = [].shift.call(arguments);
  //链接到原型(给obj这个新生对象的原型指向它的构造函数的原型)
  obj.__proto__ = Constructor.prototype;
  //绑定this
  let result = Constructor.apply(obj,arguments);
  //确保new出来的是一个对象
  return typeof result === "object" ? result : obj
}

function myNew(Obj,...args){
  var obj = Object.create(Obj.prototype);//使用指定的原型对象及其属性去创建一个新的对象
  Obj.apply(obj,args); // 绑定 this 到obj, 设置 obj 的属性
  return obj; // 返回实例
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 参考

js的原型和原型链 (opens new window)

# 知识点四 原型(链)模式

// 构造函数
function CreateJsPerson(name, age) {
  this.name = name
  this.age = age
}
CreateJsPerson.prototype.write = function() {
  console.log(`${this.name} can write js`)
}
var p1 = new CreateJsPerson('p1', 18)
var p2 = new CreateJsPerson('p2', 28)

console.log(p1.write === p2.write) // true
1
2
3
4
5
6
7
8
9
10
11
12
  • 原型模式:解决了方法或者属性公有问题,即把实例之间相同的属性和方法提取成公有的属性和方法,想让谁公有就把它放在构造函数的 prototype 属性上,例如: CreateJsPerson.prototype.write

  • 相关概念

    1. 每一个函数数据类型(普通函数、类)都有一个天生自带的属性 prototype ,它存储的值是一个对象数据类型的值,浏览器默认为其开辟一个 堆内存
    2. prototype 上浏览器天生给它加了一个属性 constructor (构造函数),属性值是当前函数(类)的本身(只有浏览器默认给prototype开辟的这个堆内存才有constructor属性)
    3. 每一个数据对象类型(普通的对象、实例、prototype 等)也天生自带一个属性 __proto__,这个属性值是当前实例所属类的原型(prototype)
  • Objectjs 中所有对象数据类型的基类(最顶层的类)
    1. 实例通过 __proto__ 可以向上级查找,不管有多少级总能找到 Object
    2. Object.prototype 上没有 __proto__ 这个属性
  • 原型链模式
    1. 通过 对象名.属性名 的方式获取属性值的时候,首先在对象的私有的属性上进行查找,如果私有中存在这个属性,则获取的是私有的属性值;如果私有的没有,则通过 __proto__ 找到所属类的原型(类的原型上定义的属性和方法都是当前实例公有的属性和方法),原型上存在的话,获取的是公有属性的值;如果原型上也没有,则继续通过原型上的 __proto__ 继续向上查找,一直找到 Object.prototype 为止
    2. 上述的查找机制就是原型链模式
  • 注意 在IE浏览器中,我们原型模式也是同样的原理,但是IE 浏览器怕你通过 __proto__ 把公有的修改,禁止我们使用 __proto__
function Fn() {
  this.x = 100;
  this.y = 200;
  this.sum = function() {}
}
Fn.prototype.getX = function() {
  console.log(`${this.x}`)
}
Fn.prototype.getY = function() {
  console.log(`${this.y}`)
}
Fn.prototype.sum = function() {}
var f1 = new Fn;
var f2 = new Fn;

console.log(Fn.prototype.constructor === Fn) // true
f1.hasOwnProperty === f1.__proto__.__proto__.hasOwnProperty // true
f1.hasOwnProperty === Object.hasOwnProperty // true

// 修改私有/公有属性/方法
f1.sum = function() {} // 修改私有方法
f1.__proto__.sum = function() {} // // 修改公有方法
Fn.prototype.sum = function() {} // // 修改公有方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

image.png

  • 在原型模式中,this 常用的两种情况
    1. 在类中出现的 this.xxx 中的 this 是当前类的一个实例
    2. 某一个方法中的 this ,看执行的时候 . 前面是谁 this 就是谁
      1. 需要先确定 this 的指向(this 是谁)
      2. this 替换成对应的代码
      3. 按照原型链查找的机制,一步步的查找结果
function Fn() {
  this.x = 100;
  this.y = 200
  this.getY = function() {
     console.log(`${this.y}`)
  }
}
Fn.prototype = {
  constructor: Fn,
  y: 300,
  getX: function() {
    console.log(`${this.x}`)
  },
  getY: function() {
    console.log(`${this.y}`)
  }
}
var f = new Fn;
// this是实例f
// 即:console.log(`${this.x}`) --> console.log(`${f.x}`)
// 所以:x 是 100
f.getX() 
// this是Fn的原型
// 即:console.log(`${this.x}`) --> console.log(`${f.__proto__.x}`) --> console.log(`${Fn.prototype.x}`) 
// 所以:x 是 undefined
f.__proto__.getX() 
// this是Fn的原型
// 即:console.log(`${this.y}`) --> console.log(`${f.__proto__.y}`) --> console.log(`${Fn.prototype.y}`) 
// 所以:x 是 300
f.__proto__.getY() 
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
  • 链式编程
    • 原理: sortArray.prototype 上的公有的方法,而数组aryArray 这个类的一个实例,所以ary 可以使用sort 方法
      • sort 执行完成的返回值是一个排序后的 数组 ,可以继续执行 reverse
      • reverse 执行完成的返回值是一个数组,可以继续执行 pop
      • pop 执行完成返回的值是被删除的那个元素,不是一个数组,所以再执行push 报错
Array.prototype.myUnique = function() {
  var obj = {}
  for(var i = 0; i < this.length; i++) {
    var cur = this[i]
    if(obj[cur] === cur) {
      this[i] = this[this.length - 1]
      this.length--
      i--
      continue
    }
    obj[cur] = cur
  }
  obj = null
  return this
}

var ary = [12, 23, 23, 13, 12, 13, 23, 13, 12]
ary.myUnique().sort((a, b) => a-b).reverse().pop()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • 批量设置公有属性
    • 起别名的方式
    • 重构原型对象的方式:
      • 自己新开辟一个堆内存,存储我们公有的属性和方法,把浏览器原来给 Fn.prototype 开辟的那个替换掉
      • 只有浏览器天生给Fn.prototype 开辟的堆内存里面才有 constructor ,而我们自己开辟的这个堆内存没有这个属性,这样 constructor 指向就不是 Fn 而是 Object ,为了和原来的保持一致,我们需要手动的增加 constructor 指向
      • 用这种方式给内置类增加公有属性,浏览器会屏蔽掉这个修改原型对象的操作,但是我们可以通过 Array.prototype.sort 这种方式修改内置类原型上的原有方法,所以我们以后在内置类的原型上增加方法,命名都需要加特殊的前缀
function Fn() {
  this.x = 100;
  this.y = 200;
  this.z = 300;
}
var f = new Fn;

// 普通方式设置公有属性
Fn.prototype.getX = function() {
  console.log(`${this.x}`)
}
Fn.prototype.getY = function() {
  console.log(`${this.y}`)
}
Fn.prototype.getZ = function() {
  console.log(`${this.z}`)
}

// 起别名方式设置公有属性
var pro = Fn.prototype
pro.getX = function() {
  console.log(`${this.x}`)
}
pro.getY = function() {
  console.log(`${this.y}`)
}
pro.getZ = function() {
  console.log(`${this.z}`)
}

// 重构原型对象的方式
Fn.prototype = {
  constructor: Fn,
  a: function() {},
  b: function() {},
  getX: function() {
    console.log(`${this.x}`)
  },
  getY: function() {
    console.log(`${this.y}`)
  },
  getZ: function() {
    console.log(`${this.z}`)
  }
}
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
34
35
36
37
38
39
40
41
42
43
44
45

image.png

# 知识点五 继承

# 方式一 原型继承

常用

  • 原型继承是js 中最常用的一种继承方式

  • 子类B 想要继承父类A 中的所用的属性和方法(私有+公有),只要让 B.prototype = new A 即可

  • 特点:它是把父类中私有的+公有的都继承到了子类原型上(子类公有的)

  • 核心:原型继承并不是把父类(A )中的属性和方法克隆一份一模一样的给子类(B ),而是让子类(B )和父类(A )之间增加了原型链的链接,以后子类的实例(b )想要父类(A )中的方法(getX ),需要一级级的向上查找来使用

  • 特点:

    • 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
    • 父类新增原型方法/原型属性,子类都能访问到
    • 简单,易于实现
  • 缺点:

    • 来自原型对象的所有属性被所有实例共享
    • 创建子类实例时,无法向父类构造函数传参
// 父类
function A() {
  this.x = 100
}
A.prototype.getX = function() {
  console.log(this.x)
}
// 子类
function B() {
  this.y = 300
  this.x = 200
}
B.prototype = new A;
B.prototype.constructor = B
B.prototype.getY = function() {
  console.log(this.y)
}

// 测试
var b = new B
b.getX()
b.getY()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

image.png

# 方式二 **借用构造函数继承(**call继承)

  • 把父类私有的属性和方法克隆一份一模一样的作为子类私有的属性

  • 核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

  • 特点:

    • 解决了原型继承中,子类实例共享父类引用属性的问题
    • 创建子类实例时,可以向父类传递参数
    • 可以实现多继承(call多个父类对象)
  • 缺点:

    • 实例并不是父类的实例,只是子类的实例
    • 只能继承父类的实例属性和方法,不能继承原型属性/方法
    • 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
// 父类
function A() {
  this.x = 100
}
A.prototype.getX = function() {
  console.log(this.x)
}

// 子类
function B() {
  A.call(this) // 把A执行让A里面的this变为B的实例即b
  this.y = 300
}
B.prototype.getY = function() {
  console.log(this.y)
}

var b = new B
console.log(b.x) // 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 方式三 冒充对象继承(拷贝继承)

  • 核心:把父类私有的+公有的属性和方法克隆一份一模一样的作为子类私有的属性

  • 特点:

    • 支持多继承
  • 缺点:

    • 效率较低,内存占用高(因为要拷贝父类的属性)
    • 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
// 父类
function A() {
  this.x = 100
}
A.prototype.getX = function() {
  console.log(this.x)
}

// 子类
function B() {
  this.y = 200
  var temp = new A
  for (var key in temp) {
    this[key] = temp[key]
  }
  temp = null
}
B.prototype.getY = function() {
  console.log(this.y)
}

var b = new B
console.log(b.getX()) // 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 方式四 组合继承

  • 原型继承+借用构造函数继承(call 继承)

  • 核心:使用原型链实现对原型方法的继承,而通过借用构造函数来实现对实例属性的继承

  • 特点:

    • 弥补了借用构造函数继承(call 继承)的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
    • 既是子类的实例,也是父类的实例
    • 不存在引用属性共享问题
    • 可传参
    • 函数可复用
  • 缺点:

    • 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
// 父类
function A() {
  this.x = 100
}
A.prototype.getX = function() {
  console.log(this.x)
}

// 子类
function B() {
  A.call(this)
  this.y = 300
}
B.prototype = new A
B.prototype.constructor = B
B.prototype.getY = function() {
  console.log(this.y)
}

var b = new B
console.log(b.getX()) // 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 方式五 寄生组合继承

常用

  • 寄生组合继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法

  • 核心:子类构造函数复制父类的自身属性和方法,子类原型只接收父类的原型属性和方法

  • 特点:

    • 堪称完美
  • 缺点:

    • 实现复杂
// 父类
function A() {
  this.x = 100
}
A.prototype.getX = function() {
  console.log(this.x)
}

// 子类
function B() {
  A.call(this)
  this.y = 300
}

function createObject(o) {
  function fn() {}
  fn.prototype = o
  return new fn
}
// B.prototype = Object.create(A.prototype) // IE 8 不兼容
B.prototype = createObject(A.prototype) // IE 8 兼容
B.prototype.constructor = B
B.prototype.getY = function() {
  console.log(this.y)
}

var b = new B
console.log(b.getX()) // 100
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

# 方式六 中间类继承

// 例如求平均值,去掉一个最大值,去掉一个最小值
function avgFn() {
  console.log(arguments)
  arguments.__proto__ = Array.prototype
  arguments.sort((a, b) => a - b)
  arguments.pop()
  arguments.shift()
  return (eval(arguments.join('+')) / arguments.length).toFixed(2)
}
console.log(avgFn(10, 20, 30, 10, 30, 40, 40))
1
2
3
4
5
6
7
8
9
10

# 知识点六 小技巧

# for...in...

for...in... 循环在遍历的时候,默认的会把自己私有的和在它所属类原型上扩展的属性和方法都可以遍历到,但是一般情况下遍历一个对象只需要遍历私有的即可,我们可以使用以下的判断进行处理

Object.prototype.aaa = function() {}
var obj = { name: 'test', age: 18 }

// 不常用
for (var key in obj) {
  if (obj.propertyIsEnumerable(key)) {
    console.log(key) // name, age
  }
}

// 常用
for (var key in obj) { 
  if (obj.hasOwnProperty(key)) {
    console.log(key) // name, age
  }
}
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

可以看出 for in 应用于数组循环返回的是数组的下标和数组的属性和原型上的方法和属性,而 for in 应用于对象循环返回的是对象的属性名和原型中的方法和属性。

使用 for in 也可以遍历数组,但是会存在以下问题:

  1. index索引为字符串型数字,不能直接进行几何运算
  2. 遍历顺序有可能不是按照实际数组的内部顺序
  3. 使用for in会遍历数组所有的可枚举属性,包括原型。例如上栗的原型方法method和name属性
  4. for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值

所以for in 更适合遍历对象,不要使用for in 遍历数组。

# for...of..

for..of 适用遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合.但是不能遍历对象,因为没有迭代器对象.与forEach() 不同的是,它可以正确响应break、continuereturn 语句

for-of 循环不支持普通对象,但如果你想迭代一个对象的属性,你可以用for-in 循环(这也是它的本职工作)或内建的Object.keys() 方法

// 对象
Object.prototype.sayHello = function () {
  console.log('Hello');
}
var myObject = {
  name: 'zhangsan',
  age: 10
}

for (let key of myObject) { // myObject is not iterable
  console.log(key); 
}

// 数组
Array.prototype.sayHello = function () {
  console.log("Hello");
}
var myArray = [1, 200, 3, 400, 100];
for (let key of myArray) {
  console.log(key); // 1, 200, 3, 400, 10
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

为Object实现迭代器协议

Object.prototype[Symbol.iterator]=function () {
  var index=0;
  var arr=[];
  for (var item in this){
    arr.push(this[item])
  }
  //迭代器对象
  return{
    next:function () {
      //迭代对象
      return{
        value:arr[index++],
        done:index>arr.length
      }
    }
  }
}

var obj={
  name:'Nikki',
  age:18,
  add:'earth'
}

for(v of obj) {
  console.log(v)
}
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

for in 和for of的区别 (opens new window)

# Object.create

  • Object.create(proObj) 方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__ ,即要把 proObj 作为这个对象的原型。
function Fn() {
  this.name = 'test'
  this.age = 18
}
var obj = {
  constructor: Fn,
  getName: function() {
    console.log(this.name)
    }
}
Fn.prototype = obj
var f = new Fn
var newObj = Object.create(f, {
  getAge:{
    value: function() {
        console.log(this.age)
    }
  }
})
newObj.__proto__ // f
newObj.__proto__.__proto__ // Fn.prototype --> obj
newObj.__proto__.__proto__.__proto__ // Object.prototype
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • 手写 Object.create(proObj)
function object(o) {
  function Fn() {}
  Fn.prototype = o
  Fn.prototype.constructor = Fn
  return new Fn;
}
var obj = {
  getX: function() {
    console.log('get x!')
  }
}
var newObj = object(obj)
1
2
3
4
5
6
7
8
9
10
11
12

Object.create (opens new window)

# 知识点七 原型链扩展

# 扩展第一步 在内置类的原型上扩展方法

基于内置类的原型扩展方法注意事项:

  • 自己扩展的方法最好加上特殊的前缀,方式把内置的方法覆盖掉
  • 链式写法的核心:执行完某一个方法,不仅得到了想要的结果,而且返回值还需要是当前这个类的实例,只有这样才能一直链下去
var ary = [12, 23, 34, 12]
// 原生的实现原理
Array.prototype.pop = function() {
  // this --> ary
  this.length--
}

// 扩展去重方法
Array.prototype.unique = function unique() {
  var obj = {}
  for(var i = 0; i < this.length; i++) {
    var cur = this[i]
    if(cur === obj[cur]) {
        this[i] = this[this.length - 1]
      this.length--
      i--
      continue
    }
    obj[cur] = cur
  }
  obj = null
  // 为了实现链式写法
  return this
}

ary.unique()

ary.pop()
console.log(ary) // [12, 23]

ary.__proto__.pop() // this --> ary.__proto__ --> Array.prototype, 方法不起作用
console.log(ary) // [12, 23]
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

image.png

# 扩展第二步 批量给原型上设置公有方法

注意

  • 如果之前原型上存在一些方法的话,我们现在新创建的这个对象会把之前写的那些方法都给覆盖掉,所以浏览器内置类禁止自己创建一个新对象来扩展原型的方法的,防止我们用 Fn.prototype = {} 把内置的方法覆盖掉
  • 自己创建的对象中不会自带 constructor 属性,所以导致了我们 f.constructor 的结果是 Object 而不是我们认为的 Fn
function Fn() {
  this.x = 100
}
Fn.prototype = {
  constructor: Fn,
  a: function a() {},
  b: function b() {},
  c: function c() {}
}

var f = new Fn
1
2
3
4
5
6
7
8
9
10
11

image.png

# 扩展第三步 函数也是对象

# 扩展第四步 基于call、apply的原型链深入

# 扩展第五步 更多的继承方式

# 扩展第六步 constructor深入

# 扩展第七步 组合应用

# 知识点八 需要总结的内容

  1. windowdocumentdiva 的原型链一级级的结构画出来,画出来后,每一级原型上都有哪些属性和方法以及每个方法的作用、哪些兼容哪些不兼容总结出来。

# 参考

es6中class类的全方面理解 (opens new window)

入门JavaScript中的this指向(ES6) (opens new window)

ES5和ES6继承的执行顺序和区别 (opens new window)