JavaScript之call, apply, bind, new的實現

lenlch發表於2018-11-26

call

  1. call 的實現, 一個一個傳參
var foo = {
  val: 1
}

function bar() {
  console.log(this.val)
}

bar.call(foo) // 1

// 思路
var foo = {
  var: 1,
  bar: function() {
    console.log(this.val)
  }
}

foo.bar() // 1


// 第一版
Function.prototype.call = function(ctx) {
  const context = ctx ? ctx : window
  context.fn = this // foo.bar
  context.fn()
  delete context.fn
}

// 第二版,加引數

Function.prototype.call = function(ctx) {
  const context = ctx ? ctx : window
  context.fn = this
  let args = []
  for (var i = 1; i < arguments.length; i++) {
    // es6
    // args.push(arguments[i])
    // eval
    args.push('arguments[' + i + ']')
  }

  // es6
  // context.fn(...args)
  // eval
  const res = eval('context.fn( ' + args + ')')

  delete context.fn

  return res
}

複製程式碼

apply

  1. apply 的實現 陣列傳參 思路,和apply雷同,只是處理引數的方式不一樣而已
Function.prototype.apply = function(ctx, arr) {
  const context = ctx ? ctx : window
  context.fn = this
  let res
  if (!arr) {
    return context.fn()
  } else {
    let args = []
    for (var i = 0; i < arr.length; i++) {
      // 這裡同樣有兩種寫法,就不按個貼了
      args.push(arr[i])
    }

    res = context.fn(...args)
  }

  delete context.fn
  return res
}
複製程式碼

bind

  1. bind 的實現 說明: bing的返回函式是一個函式 例外: 1) 返回的函式也可以進行傳參 2) 當例項是通過返回函式new出來的時候,this會失效,但是引數還是照樣生效
// 第一版

Function.prototype.bind = function(ctx) {
  const self = this

  return function() {
    return self.call(ctx)
  }
}

// 新增引數處理,先不管返回函式傳參的情況
Function.prototype.bind = function(ctx) {
  const self = this
  // 從第二個開始取
  const args = Array.prototype.slice.call(arguments, 1) // typeof Array
  return function() {
    return self.apply(ctx, args)
  }
}

// 處理返回函式傳參
Function.prototype.bind = function(ctx) {
  const self = this
  // 從第二個開始取
  const args1 = Array.prototype.slice.call(arguments, 1)
  return function() {
    const args2 = Array.prototype.slice.call(arguments)
    return self.apply(ctx, args1.contact(args2))
  }
}

// 處理new的情況,同時保留引數的引用
Function.prototype.bind = function(ctx) {
  const self = this
  // 從第二個開始取
  const args1 = Array.prototype.slice.call(arguments, 1)
  const resFn = function() {
    const args2 = Array.prototype.slice.call(arguments)
    return self.apply(this instanceof resFn ? this : cxt, args1.contact(args2))
  }
  resFn.prototype = this.prototype

  return resFn
}

// 優化
Function.prototype.bind = function(ctx) {
  if (typeof this !== 'function') {
    throw new Error('The caller shou be a function!!!')
  }
  const self = this
  // 從第二個開始取
  const args1 = Array.prototype.slice.call(arguments, 1)
  const resFn = function() {
    const args2 = Array.prototype.slice.call(arguments)
    return self.apply(this instanceof resFn ? this : cxt, args1.contact(args2))
  }

  return resFn
}
複製程式碼

new

  1. new 關鍵字的實現 思路: new出來的物件,可以訪問建構函式中的變數、方法和例項原型上的變數、方法。 可以新建一個物件,該物件的_proto_ 指向例項原型, 這個就可以訪問原型上的變數、方法 該變物件的this指向,使其能訪問建構函式的變數和物件
function createNew() {
  let obj = new Object()

  // constructor 是建構函式
  let constructor = [].shift.call(arguments)
  // constructor.prototype 是例項原型
  obj.__proto__ = constructor.prototype
  // 改變this指向
  constructor.apply(obj, arguments)

  return obj
}

// 問題: 當有返回值的時候
  1) 返回值為字串
  2) 返回值為物件

第一種情況:
function test(name, age) {
  this.name = name
  this.age = age

  return 'test'
}

const obj = new test('len', 23)

console.log(obj.name) // undefined
console.log(obj.age) // undefined

第二種情況:

function test(name, age) {
  this.name = name
  this.age = age

  return {
    name,
    age
  }
}

const obj = new test('len', 23)

console.log(obj.name) // len
console.log(obj.age) // 23

改進:
function createNew() {
  let obj = new Object()
  
  let constructor = [].shift.call(arguments)
  
  obj.__proto__ = constructor.prototype

  const res = constructor.apply(obj, arguments)

  return typeof res === 'object' ? res : obj
}
複製程式碼

相關文章