自检:前端知识清单——原型和原型链

前言

题目来自ConardLi的blog
写的是自己的题解,水平有限,所以仅供参考
代码会整合在github,觉得有帮助就给个star吧~

正文

一、Javascript基础

原型和原型链

1、理解原型设计模式以及JavaScript中的原型规则

每创建一个函数,函数上都会有一个prototype属性,访问一个对象的属性时,如果对象内不存在这个属性,那么就会访问__proto__属性,如果还是找不到,就再继访问__proto__属性继续找,直到null为止。

2、instanceof的底层实现原理,手动实现一个instanceof

const instanceOf = function (target, origin) {
    const proto = target.__proto__
    if (proto) {
        if (proto === origin.prototype) {
            return true
        }else{
            return instanceOf(proto,origin)
        }
    }else{
        return false
    }
}

3、实现继承的几种方式以及他们的优缺点

  • 原型链继承
    prototype
// 依靠原型链的继承
function Father() {
    this.name = 'father'
}
Father.prototype.getFatherName = function () {
    console.log(this.sayName)
}

function Child() {
    this.name = 'child'
}
Child.prototype = new Father()
Child.prototype.getChildName = function(){
    console.log(this.name)
}

const instance = new Child()

缺点:包含引用类型的原型属性会被所有的实例共享

function SuperType(){
  this.colors = ['red', 'blue']
}

function SubType(){
}

SubType.prototype = new SuperType()

var instance1 = new SubType()
var instance2 = new SubType()

instance1.colors.push('black')

console.log(instance1.colors)        //["red", "blue", "black"]
console.log(instance2.colors)        //["red", "blue", "black"]
  • 经典继承
    this

为了解决上面引用类型的原型属性共享的问题,我们借用构造函数,在子函数的构造函数里面调用父元素的构造函数

借用构造函数的问题:
方法都在构造函数里面定义,函数复用无从谈起

function Father(){
    this.color = ['blue','red']
}
function Child(){
    Father.call(this)
}

const instance1 = new Father()
const instance2 = new Child()

instance1.color.push('yellow')
instance2.color.push('black')

console.log(instance1.color)
console.log(instance2.color)

输出:
  • 组合继承
    this+prototype
function SuperType(name) {
  this.name = name
  this.colors = ['red','blue','yellow']
}

SuperType.prototype.sayName = function() {
  console.log(this.name)
}

function SubType(name,age) {
  SuperType.call(this,name)
  this.age = age
}

SubType.prototype = new SuperType()

SubType.prototype.sayAge = function() {
  console.log(this.age)
}

var instance1 = new SubType('Nicholas', 29)
instance1.colors.push('black')
console.log(instance1.colors)                 // ["red", "blue", "yellow", "black"]
instance1.sayName()                          //Nicholas
instance1.sayAge()                          //29

var instance2 = new SubType('Greg', 27)
console.log(instance2.colors)              //["red", "blue", "yellow"]
instance2.sayName()                        //Greg
instance2.sayAge()                        //27
  • 原型式继承
    这种继承模式封装了原型链继承,是原型链继承的升级版,原型链继承+工厂模式。
    基于已有的对象创建新对象,同时还不必因此创建自定义类型,让继承的逻辑更好地分离出来。
    首先我们得创建一个object函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。

缺点:会调用两次父类构造函数,第一次是创建子类型原型的时候

也就是ES5的Object.create()简陋版的原理

const object = function(o){
  function F(){}
  F.prototype = o
  return new F()
}
const person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
}
const anotherPerson = object(person)
anotherPerson.name = 'Greg'
anotherPerson.friends.push('Rob')

const yetAnotherPerson = object(person)
yetAnotherPerson.name = 'Linda'
yetAnotherPerson.friends.push('Barbie')

console.log(person.friends)
  • 寄生式继承
    增强原型式继承的功能的一种模式
    缺点:使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。
const createAnother = function(original){
  const clone = object(original)
  clone.sayHi = function(){
    console.log('hi')
  }
  return clone
}
const person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
}
const anotherPerson = createAnother(person)
anotherPerson.sayHi() //hi
  • 寄生组合式继承
    所谓寄生组合式继承,就是借用构造函数来继承属性
const object = function(o){
  function F(){}
  F.prototype = o
  return new F()
}
const inheritPrototype = function(Child, Parent){
  const prototype = object(Parent.prototype)
  prototype.constructor = Child
  Child.prototype = prototype
  Child.__proto__ = Parent
}
function Parent(name) {
  this.name = name
}

Parent.sayHello = function (){
    console.log('hello')
}

Parent.prototype.sayName = function() {
    console.log('my name is ' + this.name)
    return this.name
}


function Child(name, age) {
    Parent.call(this, name)
    this.age = age
}

inheritPrototype(Child, Parent)

Child.prototype.sayAge = function () {
    console.log('my age is ' + this.age)
    return this.age
}

5、至少说出一种开源项目(如Node)中应用原型继承的案例

jQuery

6、可以描述new一个对象的详细过程,手动实现一个new操作符

把大象放进冰箱有三步:
1、通过Object.create绑定原型并返回一个对象
2、绑定this
3、原构造函数如果返回值为对象,那么这个返回值会被正常使用,否则返回新对象。


提问:

  • Object.create返回的数据结构是怎么样的?
    一个新对象,带着指定的原型对象和属性。


7、理解es6 class构造以及继承的底层实现原理

class Person(){
  constructor(){
    
  }
}

class构造:

  • constructor就是原来构造函数的写法,就算不定义,也会有一个空的constructor

class中super使用场景:
super关键字用于访问和调用一个对象的父对象上的函数。

  • 第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
    并且必须在使用this关键字之前使用。
class A {}

class B extends A {
  constructor() {
    super()
  }
}
  • 调用父类上的静态方法
class Rectangle {
  constructor() {}
  static logNbSides() {
    return 'I have 4 sides';
  }
}

class Square extends Rectangle {
  constructor() {}
  static logDescription() {
    return super.logNbSides() + ' which are all equal';
  }
}
Square.logDescription(); // 'I have 4 sides which are all equal'

class底层实现原理:

const inherit = function (Child, Father) {
    Child.prototype = Object.create(Father.prototype, {
        constructor: {
            enumerable: false,
            configurable: true,
            writable: true,
            value: Father
        }
    })
    Object.setPrototypeOf(Child,Father)
}

1、ES6 的 class 内部是基于寄生组合式继承,它是目前最理想的继承方式,通过 Object.create 方法创造一个空对象,并将这个空对象继承 Object.create 方法的参数,再让子类(subType)的原型对象等于这个空对象,就可以实现子类实例的原型等于这个空对象,而这个空对象的原型又等于父类原型对象(superType.prototype)的继承关系。

2、Object.create 支持第二个参数,即给生成的空对象定义属性和属性描述符/访问器描述符,我们可以给这个空对象定义一个 constructor 属性更加符合默认的继承行为,同时它是不可枚举的内部属性。(enumerable:false)

3、ES6 的 class 允许子类继承父类的静态方法和静态属性,而普通的寄生组合式继承只能做到实例与实例之间的继承,对于类与类之间的继承需要额外定义方法,这里使用 Object.setPrototypeOf() 将 superType 设置为 subType 的原型,从而能够从父类中继承静态方法和静态属性。

你可能感兴趣的:(自检:前端知识清单——原型和原型链)