基础手写

94 阅读3分钟

手写new

构造函数做了什么?

写之前,需要先了解下new做了什么?

  1. 由于new之后返回的是一个实例对象
  2. 该实例有隐式原型指向构造函数的显示原型
  3. 该实例对象有构造函数的所有属性
  4. 返回这个对象
// 前提,构造函数Fn
function Fn(){}

function myNew () {
    const obj = {}
    obj.__proto__ = Fn.prototype
    const result = Fn.call(obj)
    return result instanceof Object ? result : obj
}

return判断?

最后一步,为什么有判断?

构造函数内容扩展---- return

function Person (name, age) {
  this.name = name
  this.age = age
  // 没写return ,相当于返回了undefied,默认返回了this
  
  return ''  //{ name: 'lucy', age: 18 }
  return false //{ name: 'lucy', age: 18 }
  return 1 //{ name: 'lucy', age: 18 } 基本数据类型 Number Boolean String Undefined Null
  
  return {} // {}
  return [] // []
  return function print(){ console.log('print') } // function print(){ console.log('print') } 
}

const p = new Person('lucy', 18)

我们通过以上5种,构造函数的return,发现:

  • 构造函数,没有写return,其实实际我们理解为返回了this对象
  • 字面看起来,没写return,相当于return undefied ,进一步我们发现,如果return 了基本数据类型,那么实例p没被影响,还是 { name: 'lucy', age: 18 }
  • 如果return了引用类型,那么实例就是返回的引用类型的值

因此,myNew第三步骤执行完,获得的result保存的是构造函数返回return的值,而不同的类型将会影响构造函数的作用。

因此,当 res 为引用数据类型时 应返回 res 本身,而当 res 为基本数据类型时 应返回创建的新对象。

手写new基础实现

// 前提,构造函数Fn
function Fn(){}

function myNew (Fn) {
    const obj = {}
    obj.__proto__ = Fn.prototype
    const result = Fn.call(obj)
    return result instanceof Object ? result : obj
}

手写new函数的进阶(优化和变动)

了解了构造函数的基础操作之后,我们可以通过以往知识,减少代码行数。

Object.create

Object.create(base) 创建一个基于base的新对象,且base是新对象的原型

const base = {}
const newObj = Object.create(base)
newObj.__proto__ === base  // true

因此,对于基础写法的第一二行,可以合并

function myNew(Fn){
    // const obj = {}
    // obj.__proto__ = Fn.prototype
    const obj = Object.create(Fn.prototype)
    const result = Fn.call(obj)
    return result instanceof Object ? result : obj 
}

call, apply, bind

其实这里第三步除了用 apply 绑定this并执行构造函数中的语句,还可以用另外两种显示绑定。

function myNew(Fn,...args){
    // const obj = {}
    // obj.__proto__ = Fn.prototype
    const obj = Object.create(Fn.prototype)
    const result = Fn.call(obj, )
    return result instanceof Object ? result : obj 
}

let res = fn.call(obj, ...args)
// 使用 call 时,参数是逐个传参,因此要使用数组的解构
let res = fn.bind(obj, ...args)()
// 使用 bind 时,参数也是逐个传参,因此也要使用数组的解构
// 此外, bind 是不会自行执行的,必须手动调用

3. 使用 typeof 判断对象实例类型

有些文章里最后一步可能会使用 typeof 来判断类型,但我们必须注意 typeof null === "object" 这个bug的存在,以及 typeof 函数 === “function” 这种情况。

因此不能简单的写作下面这种方式:

function myNew(fn, ...args) {
  let obj = Object.create(fn.prototype);
  let res = fn.apply(obj, args)
  console.log(res,typeof res)
  return (typeof res === "object" && res !== null) || (typeof res === "function") ? res : obj // 直接使用 typeof 来判断
}

当构造函数的 return 值为 null 时,控制台打印实例为 null 。

手写instanceof

语法

obj instanceof Constructor.  //true/false

作用

左侧是右侧的实例吗 =》 右侧构造函数的原型对象在左侧对象的原型上吗=》 Constructor.prototype === obj.proto

思路

  1. 基本数据类型,instanceof 都是false;null 也是基本数据类型
  2. 左侧对象的原型对象是null,返回false
  3. 左侧隐式原型对象是右侧构造函数的显示原型,返回true
  4. 如果前两者都不是,持续顺着左侧原型的原型上走
function myInstaceof(left, right){
    if (type left !== 'object') return false
    if (left === null) return false
    let proto = Object.getPrototypeOf(left)
    while(true){
        if (proto === null) return false
        if (proto === right.prototype) return true
        proto = Object.getPrptotypeOf(proto)
    }
}

通用的获取数据类型的方法

function getType (obj) {
  // 基本数据类型,直接获取typeof
  if (typeof obj !== 'object') {
    return typeof obj
  }
  // 非基本数据类型,通过ES6方式获取 '[object Array]'截取
  return Object.prototype.toString.call(obj).slice(8, -1)
}