手写系列

代码不是背出来的。

JavaScript原生

new 操作符

当代码 new Foo(...) 执行时,会发生以下事情:

  1. 一个继承自 Foo.prototype 的新对象被创建。
  2. 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
  3. 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)

初级版

function _new(fn, ...args) {
  // 1. 创建一个空的简单JavaScript对象(即`{}`);
  let obj = new Object()
  // 2. 链接该对象(即设置该对象的__proto__)到fn的原型对象 ;
  obj.__proto__ = fn.prototype
  // 3. 将步骤1新创建的对象作为`this`的上下文 ;
  //    使用apply,改变构造函数的this指向,使其
  //    指向新对象,这样,obj就可以访问到构造函数中的属性了。
  fn.apply(obj, args)
  // 4. 返回`this`。
  return obj
}

function Person(name, age) {
  this.name = name
  this.age = age
}

let asuna = _new(Person, 'Asuna', 20)
let kirito = new Person('kirito', 21)
console.log('自己的new', asuna)
console.log('原生的new', kirito)

由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)

初级版代码会产生一个问题

// 函数返回对象
function Animal(name, sex) {
  this.type = 'animal'
  return { name, sex }
}

function _new(fn, ...args) {...}

let cat = new Animal('cat', 'male')
let dog = _new(Animal, 'dog', 'male')
console.log(cat)
console.log(dog)
// 控制台输出
// {name: "cat", sex: "male"}
// Animal {type: "animal"}

修复

function _new(fn, ...args) {
  let obj = new Object()
  obj.__proto__ = fn.prototype
  let ret = fn.apply(obj, args)
  // 如果该函数没有返回对象,则返回this。
  return typeof ret === 'object' ? ret : obj
}

// {name: "cat", sex: "male"}
// {name: "dog", sex: "male"}

JSON.stringify

文档

  • Boolean | Number| String 类型会自动转换成对应的原始值
  • undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)
  • 不可枚举的属性会被忽略
  • 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略
function jsonStringify(obj) {
  const type = typeof obj
  if (type !== 'object' || obj === null) {
    if (/string|undefined|function/.test(type)) {
      obj = `"${obj}"`
    }
    return String(obj)
  } else {
    const json = []
    const isArr = Array.isArray(obj)
    for (let key in obj) {
      let _value = obj[key]
      let _type = typeof _value
      if (/string|undefined|function/.test(_type)) {
        _value = `"${_value}"`
      } else if (_type === 'object') {
        _value = jsonStringify(_value)
      }
      json.push(`${isArr ? '' : '"' + key + '":'}${_value.toString()}`)
    }
    return `${isArr ? '[' : '{'}${json.join(',')}${isArr ? ']' : '}'}`
  }
}

console.log(JSON.stringify([1, 'false', false]))
console.log(jsonStringify([1, 'false', false]))
console.log(JSON.stringify(null))
console.log(jsonStringify(null))

JSON.parse

手写 call / apply

1.Function.call 按套路出牌

call 的核心:

  • 将函数设为对象的属性
  • 执行并删除这个函数
  • 指定 this 到函数并传入给定阐述执行函数
  • 如果不传入函数,默认指向为 window

JavaScript实现

实现(10).add(10).add(10)

链式调用

Number.prototype.add = function(num) {
  if (
    Object.prototype.toString.call(this) !== '[object Number]' ||
    Object.prototype.toString.call(num) !== '[object Number]'
  ) {
    throw new TypeError('类型不是 Number')
  }
  return this + num
}

let ret = (10).add(10).add(10) // 30

参考资料

MDN-new运算符

手写源码系列(三):new操作符的实现