【自检清单】JS基础-原型与原型链

打算以一名【合格】前端工程师的自检清单为纲整理本身的知识体系,因为是整理,风格偏简洁,勘误、疑惑、意见建议可前往小弟博客交流,后续的整理也会在博客及时更新,博客地址github.com/logan70/Blo…html

原型设计模式以及JavaScript中的原型规则

原型与原型链

原型前端

JavaScript中,一个对象从被建立开始就和另外一个对象关联,从另外一个对象上继承其属性,这个另外一个对象就是原型vue

获取原型的方法jquery

  1. 能够经过Object.getPrototypeOf(obj)来获取obj的原型。
  2. 固然对象都是经过构造函数new出来的(字面量对象也能够这么理解),也能够经过访问对应构造函数的prototype属性来获取其原型。
const obj = {}
expect(Object.getPrototypeOf(obj) === Object.prototype).toBe(true)
复制代码

原型链git

当访问一个对象的属性时,先在对象的自己找,找不到就去对象的原型上找,若是仍是找不到,就去对象的原型(原型也是对象,也有它本身的原型)的原型上找,如此继续,直到找到为止,或者查找到最顶层的原型对象中也没有找到,就结束查找,返回undefined。这条由对象及其原型组成的链就叫作原型链github

特殊原型规则

原型链顶层编程

普通对象能够理解为Object构造函数建立的,即普通对象的原型都指向Object.prototypeObject.prototype也是个对象,可是其原型比较特殊,为null,是原型链的顶层,切记!!!设计模式

expect(Object.getPrototypeOf({})).toBe(Object.prototype)
expect(Object.getPrototypeOf(Object.prototype)).toBe(null)
复制代码

构造函数的原型babel

函数,包括构造函数均可理解为由构造函数Function建立,Function自己也不例外。前端工程师

const getProto = Object.getPrototypeOf
const FuncProto = Function.prototype
expect(getProto(Function)).toBe(FuncProto)
expect(getProto(Object)).toBe(FuncProto)
expect(getProto(Number)).toBe(FuncProto)
expect(getProto(Symbol)).toBe(FuncProto)
expect(getProto(Array)).toBe(FuncProto)
复制代码

instanceof的底层实现原理及手动实现

做用

instanceof 用于检测右侧构造函数的原型是否存在于左侧对象的原型链上。

Symbol.hasInstance

ES6新增的内置Symbol,用做对象方法标识符,该方法用于检测任意对象是否为拥有该方法对象的实例。instanceof操做符优先使用该Symbol对应的属性。

这样一来instanceof右侧并不是必须为函数,对象也能够的。示例代码以下:

const MyArray = {
    [Symbol.hasInstance](obj) {
        return Array.isArray(obj)
    }
}

expect([] instanceof MyArray).toBe(true)
复制代码

手写实现

const isObj = obj => ((typeof obj === 'object') || (typeof obj === 'function')) && obj !== null
function myInstanceOf(instance, Ctor) {
    if (!isObj(Ctor)) // 右侧必须为对象
        throw new TypeError('Right-hand side of 'instanceof' is not an object')

    const instOfHandler = Ctor[Symbol.hasInstance]
    // 右侧有[Symbol.hasInstance]方法,则返回其执行结果
    if (typeof instOfHandler === 'function') return instOfHandler(instance)
        
    // 右侧无[Symbol.hasInstance]方法且不是函数的,返回false
    if (typeof Ctor !== 'function') return false
        
    // 左侧实例不是对象类型,返回false
    if (!isObj(instance)) return false
    
    // 右侧函数必须有原型
    const rightP = Ctor.prototype
    if (!isObj(rightP))
        throw new TypeError(`Function has non-object prototype '${String(rightP)}' in instanceof check`)
        
    // 在实例原型连上查找是否有Ctor原型,有则返回true
    // 知道找到原型链顶级尚未,则返回false
    while (instance !== null) {
        instance = Object.getPrototypeOf(instance)
        if (instance === null) return false
        
        if (instance === rightP) return true
    }
}
复制代码

ECMAScript定义

标准出处 -> ECMAScript#instanceof

InstanceofOperator ( V, target )

  1. If Type(target) is not Object, throw a TypeError exception.
  2. Let instOfHandler be ? GetMethod(target, @@hasInstance).
  3. If instOfHandler is not undefined, then
  4. Return ToBoolean(? Call(instOfHandler, target, « V »)).
  5. If IsCallable(target) is false, throw a TypeError exception.
  6. Return ? OrdinaryHasInstance(target, V).

OrdinaryHasInstance ( C, O )

  1. If IsCallable(C) is false, return false.
  2. If C has a [[BoundTargetFunction]] internal slot, then
    • Let BC be C.[[BoundTargetFunction]].
    • Return ? InstanceofOperator(O, BC).
  3. If Type(O) is not Object, return false.
  4. Let P be ? Get(C, "prototype").
  5. If Type(P) is not Object, throw a TypeError exception.
  6. Repeat,
    • Set O to ? O.[[GetPrototypeOf]]().
    • If O is null, return false.
    • If SameValue(P, O) is true, return true.

实现继承的方式及优缺点

面向对象编程

面向对象编程(Object Oriented Programming),简称OOP,是一种程序设计思想。OOP把对象做为程序的基本单元,一个对象包含了数据和操做数据的函数。

面向对象编程的三大基本特性:封装,继承,多态

封装

将一段逻辑/概念抽象出来作到“相对独立”。封装的概念并非OOP独有的,而是长久以来一直被普遍采用的方法。主要目的总结为两点:

  • 封装数据和实现细节。达到保护私有内容、使用者无需关心内部实现、且内部变化对使用者透明的目的。
  • 封装变化。将不变和可变部分隔离,提高程序稳定性、复用性和可扩展性。

JavaScript中典型的封装就是模块化,实现方法有闭包ES ModuleAMDCMDCommonJS等。

多态

多态的概念也不是OOP独有的。所谓多态就是同一操做做用于不一样的对象上面,能够产生不一样的解释和不一样的执行结果。

多态的目的就是用对象的多态性消除条件分支语句,提高程序的可拓展性。

JavaScript是一门弱类型多态语言,具备与生俱来的多态性。

继承

两大概念:

  • 类(Class):抽象的模板;
  • 实例(Instance):根据类建立的具体对象。

JavaScript中没有类的概念,使用构造函数做为对象模板,经过原型链来实现继承(ES6 中的Class只是语法糖)。

原型及原型链相关知识详见深刻JavaScript系列(六):原型与原型链

类式继承

将父类实例赋值给子类原型。缺点以下:

  • 父类实例过早建立,没法接受子类的动态参数;
  • 子类全部实例原型为同一父类实例,修改父类实例属性会影响全部子类实例。
function SupClass() {...}
function SubClass() {...}
SubClass.prototype = new SupClass()
复制代码

构造函数式继承

子类构造函数中执行父类构造函数。缺点以下:

  • 没法继承父类原型上的属性和方法。
function SupClass() {...}
function SubClass() {
  SupClass.call(this, arguments)
}
复制代码

组合式继承

类式继承+构造函数继承。缺点以下:

  • 父类构造函数需调用两次。
function SupClass() {...}
function SubClass() {
  SupClass.call(this, arguments)
}
SubClass.prototype = new SupClass()
复制代码

原型式继承

对类式继承的封装,功能相似Object.create,缺点以下:

  • 若每次传入同一个原型,仍是存在修改后影响其余子类实例的问题。
function createObj(o) {
  function F() {}
  F.prototype = o
  return new F()
}
复制代码

寄生式继承

拓展原型式继承建立的对象并返回。

function createObj(o) {
  const obj = Object.create(o)
  obj.name = 'Logan'
  return obj
}
复制代码

寄生组合式继承

寄生式继承+构造函数式继承。

function inherit(child, parent) {
  const p = Object.create(parent.prototype)
  child.prototype = p
  p.constructor = child
  return child
}

function SupClass() {...}
function SubClass() {
  SupClass.call(this)
}

SubClass = inherit(SubClass, SupClass)
复制代码

开源项目中应用原型继承的案例

jQuery

jQuery源码

var jQuery = function(selector, context) {
  return new jQuery.fn.init(selector, context)
}

jQuery.fn = jQuery.prototype = {
	constructor: jQuery,
  ... // 各类原型方法
}

jQuery.fn.init = function(selector, context, root) { ... }
jQuery.fn.init.prototype = jQuery.fn // 校订实例的原型
复制代码

Vue

Vue源码

function Vue(options) {
  this._init(options)
}

// initMixin
Vue.prototype._init = function (options) { ... }

// stateMixin
Object.defineProperty(Vue.prototype, '$data', {...})
Object.defineProperty(Vue.prototype, '$props', {...})
Vue.prototype.$set = function() {...}
Vue.prototype.$delete = function() {...}
Vue.prototype.$watch = function() {...}

// eventMixin
Vue.prototype.$on = function() {...}
Vue.prototype.$once = function() {...}
Vue.prototype.$off = function() {...}
Vue.prototype.$emit = function() {...}

// lifecycleMixin
Vue.prototype._update = function() {...}
Vue.prototype.$forceUpdate = function() {...}
Vue.prototype.$destory = function() {...}

// renderMixin
Vue.prototype.$nextTick = function() {...}
Vue.prototype._render = function() {...}
复制代码

new的详细过程及其模拟实现

new一个对象的详细过程

  1. 建立一个全新对象,并将该对象原型指向构造函数的原型对象;
  2. 将构造函数调用的this指向这个新对象,并执行构造函数;
  3. 若是构造函数执行结果为对象类型(包含Object,Functoin, Array, Date, RegExg, Error等),则返回执行结果,不然返回建立的新对象。

模拟实现new

function newOperator(Ctor, ...args) {
  if (typeof Ctor !== 'function') {
    throw new TypeError('First argument is not a constructor')
  }
  // 1. 建立一个全新对象,并将该对象原型指向构造函数的原型对象
  const obj = Object.create(Ctor.prototype)

  // 2. 将构造函数调用的this指向这个新对象,并执行构造函数;
  const result = Ctor.apply(obj, args)

  // 3. 若是构造函数执行结果为对象类型,则返回执行结果,不然返回建立的新对象
  return (result instanceof Object) ? result : obj
}
复制代码

ES6 Class的底层实现原理

ES6 中的类Class,仅仅只是基于现有的原型继承的一种语法糖,咱们一块儿来看一下Class的底层实现原理。

Class的底层实现要素

  1. 只能使用new操做符调用Class
  2. Class可定义实例属性方法和静态属性方法;
  3. Class的实例可继承父Class上的实例属性方法、子Class可继承父Class上的静态属性方法。

只能使用new操做符调用Class

实现思路:使用instanceof操做符检测实例是否为指定类的实例。

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError('Cannot call a class as a function')
  }
}
复制代码

定义实例属性方法和静态属性方法

实现思路

  • 在构造函数的原型上定义属性方法,即为实例属性方法;
  • 在构造函数自己定义属性方法,即为静态属性方法。
function _createClass(Constructor, protoProps = [], staticProps = []) {
  // 在构造函数的原型上定义实例属性方法
  _defineProperties(Constructor.prototype, protoProps)
  // 在构造函数自己定义静态属性方法
  _defineProperties(Constructor, staticProps)
}

// 实现公用的批量给对象添加属性方法的方法
function _defineProperties(target, props) {
  props.forEach(prop => {
    Object.defineProperty(target, prop.key, prop)
  })
}
复制代码

继承实例属性方法和静态属性方法

实现思路:借用原型链继承实现。

function _inherits(subClass, superClass) {
  // 子类实例继承父类的实例属性方法
  subClass.prototype = Object.create(superClass.prototype)
  // 修正constructor属性
  subClass.prototype.constructor = subClass

  // 子类继承父类的静态属性方法
  Object.setPrototypeOf(subClass, superClass)
}
复制代码

模拟编译

了解了Class的底层实现要素,咱们就来将Class模拟编译为使用原型继承实现的代码。

源代码

class Person {
  constructor(options) {
    this.name = options.name
    this.age = options.age
  }
  eat() {
    return 'eating'
  }
  static isPerson(instance) {
    return instance instanceof Person
  }
}

class Student extends Person {
  constructor(options) {
    super(options)
    this.grade = options.grade
  }
  study() {
    return 'studying'
  }
  static isStudent(instance) {
    return instance instanceof Student
  }
}
复制代码

编译后代码

var Person = (function() {
  function Person(options) {
    // 确保使用new调用
    _classCallCheck(this, Person)
    this.name = options.name
    this.age = options.age
  }

  _createClass(
    Person,
    // 实例属性方法
    [{
      key: 'eat',
      value: function eat() {
        return 'eating'
      }
    }],
    // 静态属性方法
    [{
      key: 'isPerson',
      value: function isPerson(instance) {
        return instance instanceof Person
      }
    }]
  )
  return Person
})();
var Student = (function (_Person) {
  // 继承父类实例属性方法和静态属性方法
  _inherits(Student, _Person)

  function Student(options) {
    // 确保使用new调用
    _classCallCheck(this, Student)

    // 执行父类构造函数
    _Person.call(this, options)

    this.grade = options.grade
  }

  _createClass(Student,
    // 实例属性方法
    [{
      key: 'study',
      value: function study() {
        return 'studying'
      }
    }],
    // 静态属性方法
    [{
      key: 'isStudent',
      value: function isStudent(instance) {
        return instance instanceof Student
      }
    }]
  )

  return Student
})(Person);
复制代码

测试代码

const person = new Person({ name: 'Logan', age: 18 })
const student = new Student({ name: 'Logan', age: 18, grade: 9 })

expect(person.eat()).toBe('eating')
expect(student.eat()).toBe('eating') // 继承实例方法
expect(student.study()).toBe('studying')

expect(Student.isStudent(student)).toBe(true)
expect(Person.isPerson(person)).toBe(true)
expect(Student.isStudent(person)).toBe(false)
expect(Student.isPerson(student)).toBe(true) // 继承静态方法
复制代码

公有字段和私有字段(提案中)

公有(public)和私有(private)字段声明目前在JavaScript标准委员会TC39的 试验性功能 (第3阶段),下面进行模拟实现。

静态公有字段

使用

class ClassWithStaticField {
  static staticField1 = 'static field' // 设定初始值
  static staticField2 // 不设定初始值
}
复制代码

polyfill

function ClassWithStaticField() {}
// @babel/plugin-proposal-class-properties 中使用Object.defineProperty实现,此处简便起见直接赋值
ClassWithStaticField.staticField1 = 'static field' // 设定初始值
ClassWithStaticField.staticField2 = undefined // 不设定初始值
复制代码

公有实例字段

使用

class ClassWithInstanceField {
  instanceField1 = 'instance field' // 设定初始值
  instanceField2 // 不设定初始值
}
复制代码

polyfill

function ClassWithInstanceField() {
  // @babel/plugin-proposal-class-properties 中使用Object.defineProperty实现,此处简便起见直接赋值
  this.instanceField1 = 'instance field' // 设定初始值
  this.instanceField2 = undefined // 不设定初始值
}
复制代码

静态私有字段

静态私有字段只能在静态方法内访问,且只能经过类的属性进行访问,不能经过this进行访问。

使用

class ClassWithPrivateStaticField {
  static #PRIVATE_STATIC_FIELD

  static publicStaticMethod() {
    ClassWithPrivateStaticField.#PRIVATE_STATIC_FIELD = 42
    return ClassWithPrivateStaticField.#PRIVATE_STATIC_FIELD
  }
}
复制代码

polyfill

经过闭包实现静态私有字段

var ClassWithPrivateStaticField = (function() {
  var _PRIVATE_STATIC_FIELD
  function ClassWithPrivateStaticField() {}

  ClassWithPrivateStaticField.publicStaticMethod = function() {
    _PRIVATE_STATIC_FIELD = 42
    return _PRIVATE_STATIC_FIELD
  }
  return ClassWithPrivateStaticField
})();
复制代码

私有实例字段

使用

class ClassWithPrivateField {
  #privateField;
  
  constructor() {
    this.#privateField = 42;
    console.log(this.$privateField)
  }
}
复制代码

polyfill

经过WeakMap结合实例自己为key实现

var ClassWithPrivateField = (function() {
  var _privateField = new WeakMap()
  function ClassWithPrivateField() {
    _privateField.set(this, undefined)
    _privateField.set(this, 42)
    console.log(_privateField.get(this))
  }
})();
复制代码
相关文章
相关标签/搜索