乐趣区

关于javascript:前端面试必问javascript原型和继承

如题,筹备面试一次就看一次,索性本人好好总结一下吧,一劳永逸。

本文从以下几个方面着手

  • 0 怎么了解面向对象
  • 1 创建对象的形式
  • 2 记住原型链的小窍门
  • 3instanceof 模仿实现
  • 4new 关键字 模仿实现
  • 5 继承的实现(逐渐实现)

0 怎么了解面向对象

其实我也不晓得咋答复这问题,我只晓得,面试官问这个后,就示意他要问一堆继承的问题了。上面是援用周老师的一段说辞。

“ 面向对象是一种编程思维 与面向过程是对应的 个别的语言都是面向对象的 js 自身也是基于面向对象构建进去的,例如 js 自身就有很多内置类,Promise 就是,能够 new Promise 来创立一个实例,来治理异步编程。还有 vue 也是,平时都是创立 vue 的实例啊。”

1 创建对象的形式

1. 对象字面量

var o1 = {name: 'o1'}
var o2 = new Object({name: 'o2'})

2. 通过构造函数

var M = function(name){this.name = name}
var o3 = new M('o3')

3.Object.create

var o4 = Object.create(p)

2 记住原型链的小窍门

记忆总是有法则的,如高中期间学的三角函数,须要背公式很多,强行去背全副的公式是容易凌乱的。不过如果把外围的几点背牢,其余的公式只须要稍加推导即可。对于原型链也是一样,有几点在最开始就记住的话,前面就不会乱了。原型链中要害概念:构造函数 实例constructor protoprototype, 首先要记住他们的关系

  • 实例(对象)有__proto__ , 实例(对象)没有 prototype
  • 构造函数有 prototype,同时 prototype 又是对象,那么 prototype 即满足下面一条,除了领有__proto__外,还含有 constructor
  • 构造函数的 prototype 的 constructor 就是指向构造函数自身,即上例子中 M.prototype.constructor === M

下面 3 点请先牢记,前面所总结的残缺继承和这有严密的关联

其实 构造函数 实例constructor protoprototype 的关系曾经在下面的例子和 3 点介绍中介绍完了。无妨再回顾一下

  1. 构造函数即一般函数,只不过前边有 new 关键字
  2. 通过 new构造函数,生成的对象即为实例。
  3. 以下面生成 o3 实例为例子

     o3.__proto__ === M.prototype  //true
    
     o3.prototype   //undefined
    
     o3.__proto__ === M.prototype //true
  4. o3 实例自身并无 constructor,不过会借助原型链向上查找,即,

     o3.constructor === M.prototype.constructor  // true
    
     o3.constructor === M //true

小结 理清这几个关键词的关系后,原型链就清朗很多了

3 instanceof 模仿实现

instanceof 的原理是什么呢?先来看一下应用

[] instanceof Array  // true

即右边是对象,左边是类型,instanceof 就是要判断左边类型的 prototype,是否在右边实例的原型链上, 如下例子所示

[].__proto__ === Array.prototype //true
Array.prototype.__proto__ === Object.prototype //true
Object.prototype__proto__ //null

那么根据这个思维来实现一下 instanceof 吧,肯定会印象更加粗浅

function myInstanceof2(left, right){if(left === null || left === undefined){return false}
    if(right.prototype === left.__proto__) {return true}

    left = left.__proto__
    return myInstanceof2(left, right)
}

console.log(myInstanceof2([], Array))

4 new 模仿实现(简要版)

new 的过程产生了什么?

  1. 生成空对象
  2. 这个空对象的__proto__赋值为构造函数的 prototype
  3. 绑定 this 指向
  4. 返回这个对象

     // 构造函数
     function M(name){this.name = name}
     // 原生 new
     var obj = new M('123')
    
     // 模仿实现
     function create() {
       // 生成空对象
       let obj = {}
       // 拿到传进来参数的第一项,并扭转参数类数组
       let Con = [].shift.call(arguments)
       // 对空对象的原型指向赋值
       obj.__proto__ = Con.prototype
       // 绑定 this 
       //(对应上面应用来阐明:Con 是参数第一项 M,// arguments 是参数['123'],
       // 就是 M 办法执行,参数是 '123',执行这个函数的 this 是 obj)let result = Con.apply(obj, arguments)
       return result instanceof Object ? result : obj
     }
    
     var testObj = create(M, '123')
     console.log('testObj', testObj)

5 继承的实现(逐渐实现)

一步一步来,从简到繁,更能直观发现继承的原理与毛病

  1. 构造方法形式 外围 Parent1.call(this)

     // 构造方法形式
     function Parent1(){this.name = 'Parent1'}
     Parent1.prototype.say = function () {alert('say')
     }
     function Child1(){Parent1.call(this)
         this.type = 'type'
     }
    
     var c1 = new Child1()
     c1.say() // 报错

毛病:只能继承父类构造函数外部属性,无奈继承父类构造函数原型对象上属性

思考:为什么 call 实现了继承,call 实质是什么?

  1. 只借助原型继承 外围 Child2.prototype = new Parent2()

     // 原型
     function Parent2(){
         this.name = 'Parent2'
         this.arr = [1,2]
     }
     Parent2.prototype.say = function () {alert('say')
     }
     function Child2(){// Parent2.call(this)
         this.type = 'type'
     }
     Child2.prototype = new Parent2()
    
     var c21 = new Child2()
     var c22 = new Child2()
    
     c21.say()
     c21.arr.push('9')
     console.log('c21.arr :', c21.arr)
     console.log('c22.arr :', c22.arr)

毛病:c21.arr 与 c22.arr 对应的是同一个援用

思考:为什么这么写是同一个援用?

  1. 组合继承 1

把下面两个继承形式的长处合并起来,毛病都摈弃掉

 function Parent3(){
    this.name = 'Parent3'
    this.arr = [1,2]
}
Parent3.prototype.say = function () {alert('say')
}
function Child3(){Parent3.call(this)
    this.type = 'type'
}
Child3.prototype = new Parent3()

var c31 = new Child3()
var c32 = new Child3()

c31.say()
c31.arr.push('9')
console.log('c31.arr :', c31.arr)
console.log('c31.arr :', c32.arr)

思考:这么写就没有问题了吗?

答:生成一个实例要执行 Parent3.call(this),new Child3(),也就是 Parent3 执行了两遍。

  1. 组合继承 2

扭转上例子 的

  Child3.prototype = new Parent3()

  Child3.prototype = Parent3.prototype

毛病:很显著,无奈定义子类构造函数原型公有的办法

  1. 组合继承优化 3 再次扭转上例子 的

    Child3.prototype = Parent3.prototype

   Child3.prototype = Object.create(Parent3.prototype)

问题就都解决了。因为 Object.create 的原理是:生成一个对象,这个对象的__proto__, 指向所传的参数。

思考:是否还有疏漏?一时想不起来的话,能够看下这几个后果

console.log(c31 instanceof Child3) // true
console.log(c31 instanceof Parent3) // true
console.log(c31.constructor === Child3) // false
console.log(c31.constructor === Parent3) // true

所以回想起文章结尾所说的那几个须要牢记的点,就须要从新赋值一下子类构造函数的 constructor:Child3.prototype.constructor = Child3,完整版如下

function Parent3(){
    this.name = 'Parent3'
    this.arr = [1,2]
}
Parent3.prototype.say = function () {alert('say')
}
function Child3(){Parent3.call(this)
    this.type = 'type'
}

Child3.prototype = Object.create(Parent3.prototype)
Child3.prototype.constructor = Child3

var c31 = new Child3()
var c32 = new Child3()

c31.say()
c31.arr.push('9')
console.log('c31.arr :', c31.arr)
console.log('c31.arr :', c32.arr)

console.log('c31 instanceof Child3 :', c31 instanceof Child3)
console.log('c31 instanceof Parent3 :', c31 instanceof Parent3)
console.log('c31.constructor === Child3 :', c31.constructor === Child3)
console.log('c31.constructor === Parent3 :', c31.constructor === Parent3)

5 es6 的继承

class Parent{constructor(name) {this.name = name}
  getName(){return this.name}
}

class Child{constructor(age) {this.age = age}
  getAge(){return this.age}
}

es6 继承记住几个注意事项吧

  • 1 构造函数不能当一般函数一样执行 Parent() 是会报错的
  • 2 不容许重定向原型 Child.prototype = Object.create(Parent.prototype) 无用
  • 3 继承写法如下,下面的 Child 类想继承父类,改成如下写法就好

<figcaption></figcaption>

留神写了 extends 关键字,constructor 中就必须写 super(), 打印后果如下:

最初

平时始终有整顿面试题的习惯,有随时跳出舒服圈的筹备,人不知; 鬼不觉整顿了 229 页了,在这里分享给大家,有须要的 点击这里收费支付题目 + 解析 PDF


退出移动版