本文目录

[[toc]]

range()

测试用例:

for (let num of range(1, 4)) {
  console.log(num)
}
// 1
// 2
// 3
// 4

普通循环

/**
 * @param {number} from
 * @param {number} to
 */
function range(from, to) {
  const list = []

  for (let i = from; i <= to; ++i) {
    list.push(i)
  }

  return list
}

迭代器 interator

/**
 * 手动实现 iterator
 *
 * @param {number} from
 * @param {number} to
 */
function range(from, to) {
  let i = from
  return {
    [Symbol.interator]() {
      const isDone = i > to
      return {
        done: isDone,
        value: isDone ? undefined : i,
      }
    }
  }
}

生成器 generator

/**
 * 生成器的结果能够被 for of 迭代出来,其本身就实现了 iterator
 *
 * @param {number} from
 * @param {number} to
 */
function range(from, to) {
  return (function * () {
    while (from <= to) {
      yield from++
    }
  })()
}

迭代器 + 生成器 interator + generator

/**
 * 多包一层,纯凑方案数,好处是不需要 IIFE 了
 *
 * @param {number} from
 * @param {number} to
 */
function range(from, to) {
  return {
    [Symbol.iterator]: function * () {
      while (from <= to) {
        yield from++
      }
    },
  }
}

new

function _new(fn, ...args) {
  const target = Object.create(fn.prototype)
  const result = fn.call(target, ...args)
  const isObjectOrFunction = (result !== null && typeof result === 'object') || typeof result === 'function'
  return isObjectOrFunction ? result : target
}

instanceof

function myInstanceof(left, right) {
  // 这里先用typeof来判断基础数据类型,如果是,直接返回false
  if (typeof left !== 'object' || left === null) {
    return false
  }
  // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
  let proto = Object.getPrototypeOf(left)
  while (true) {
    if (proto === null) {
      return false
    }
    if (proto === right.prototype) {
      // 找到相同原型对象,返回true
      return true
    }
    proto = Object.getPrototypeof(proto)
  }
}

call

function myCall(thisArg, ...args) {
  if (thisArg == null) {
    return fn(...args)
  }

  // 使用 Symbol 防止属性冲突
  const prop = Symbol('temporary property for fn')
  thisArg[prop] = fn

  const result = thisArg[prop](...args)
  // 移除临时属性
  Reflect.deleteProperty(thisArg, prop)

  return result
}

function printName() {
  console.log(this.name)
}

console.log(printName.myCall({ name: 'ly' })) // => ly

apply

function myApply(thisArg, args) {
  // 这里的 this 其实就是 func.myCall(thisArg, ...args) 中的 func,因为 myCall 是通过 func 调用的嘛
  const func = this

  if (thisArg === undefined || thisArg === null) {
    // 如果 thisArg 是 undefined 或则 null,this 指向全局对象,直接调用就可以达到指向全局对象的目的了
    return func(args)
  }

  const tempFunc = Symbol('Temp property')
  // 在 thisArg 上临时绑定 func
  thisArg[tempFunc] = func

  // 通过 thisArg 调用 func 来达到改变 this 指向的作用
  const result = thisArg[tempFunc](args)

  // 删除临时属性
  delete thisArg[tempFunc]
  return result
}

function printName() {
  console.log(this.name)
}

console.log(printName.myCall({ name: 'ly' })) // => ly

bind

普通实现,不支持 new 创建对象

function myBind(func, thisArg, ...args) {
  // bind 返回的是一个新函数
  return function (...otherArgs) {
    // 执行函数时 this 始终为外层函数中的 thisArg,前面的调用参数也被绑定为 args
    return func.call(thisArg, ...args, ...otherArgs)
  }
}

function printThisAndAndArgs(...args) {
  console.log(`This is ${JSON.stringify(this)}, arguments is ${[...args].join(', ')}`)
}

const boundFunc = myBind(printThisAndAndArgs, { name: 'Lily' }, 1, 2, 3)
boundFunc(4, 5, 6) // => This is {"name":"Lily"}, arguments is 1, 2, 3, 4, 5, 6

支持 new 创建的 bind 版本

function myBind(func, thisArg, ...args) {
  // bind 返回的是一个新函数,如果使用 new 调用了被绑定后的函数,其中的 this 即是 new 最后返回的实例对象,也就是 target
  return function (...otherArgs) {
    // 当 new.target 为 func,不为空时,绑定 this,而不是 thisArg
    return func.call(new.target ? this : thisArg, ...args, ...otherArgs)
  }
}

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

const BoundStudent1 = Student.bind({ name: 'Taylor' }, 'ly')
console.log(new BoundStudent1(22)) // => Student { name: 'ly', age: 22 }

const BoundStudent2 = myBind(Student, { name: 'Taylor' }, 'ly')
console.log(new BoundStudent2(22)) // => { name: 'ly', age: 22 }

最终完美版

/**
 * 模拟实现 Function.prototype.bind
 *
 * @param {Function} fn 被绑定 this 的函数
 * @param {*} thisArg
 * @param  {...any} boundArgs 被绑定的参数
 * @returns {Function} 绑定 this 后的新函数
 */
function myBind(fn, thisArg, ...boundArgs) {
  function boundFn(...otherArgs) {
    // 被使用 new 调用时 this 应该就是被指定的 this
    const ctx = new.target ? this : thisArg
    return fn.call(ctx, ...boundArgs, ...otherArgs)
  }

  // 这一步是为了 boundFn 被当作构造函数使用时,其实例能正常访问 fn 原型链上的属性
  boundFn.prototype = Object.create(fn.prototype)
  boundFn.prototype.constructor = boundFn

  // 默认就是 writeable: false,所以 name 和 length 都被设置为不可写了
  Object.defineProperties(boundFn, {
    name: {
      // 被绑定 this 的新函数的 name 都是原函数前面加 bound
      value: `bound ${fn.name}`,
    },
    length: {
      // boundFn 的 length 是剩余需要传递的参数
      value: Math.max(fn.length - boundArgs.length, 0),
    },
  })

  return boundFn
}

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

Student.prototype.type = 'student'
const BoundStudent2 = Student.myBind({ name: 'Taylor' }, 'ly')

console.log(new BoundStudent2(22).type) // => student
console.log(BoundStudent2.name) // => bound Student
console.log(BoundStudent2.length) // => 2