javascript之模拟call以及apply实现

163 阅读2分钟

call

使用一个指定的上下文对象和若干个指定的参数值的前提下调用某个函数或方法。如果传入null, 则this指向window

  • 注意两点:

  1. 改变方法内部的this指向
  2. 执行方法
  • 实现步骤一

  1. 将函数设为对象的方法
  2. 执行该方法
  3. 删除该方法
Function.prototype.myCall = function(context) {
    context.fn = this // this代表sayValue方法
    context.fn() // 执行sayValue方法
    delete context.fn // 删除对象上的方法
}

var obj = {
    value: 'hello javascript'
}

function sayValue() {
    console.log(this.value)
}

sayValue.myCall(obj)

  • 实现步骤二 (传入参数)

  1. 通过函数的arguments对象,来获取不定参数
Function.prototype.myCall = function (ctx) {
    ctx.fn = this
    // 将arguments转化成数组,通过slice()方法截取数组除第一项以外的所有项,并返回截取的数组
    var newArr = Array.prototype.slice.call(arguments, 1)
    ctx.fn(...newArr) // ES6中的 '...' 展开运算符,可以展开数组
    delete ctx.fn
}
var obj = {
    value: 'hello, obj'
}
function saySth(name, age) {
    console.log('name: ', name)
    console.log('age: ', age)
    console.log('obj.value: ', this.value)
}

saySth.myCall(obj, 'alex.cheng', 18)

// 运行试试 !
  • 实现步骤三

  1. 函数的返回值,看以下栗子:
var obj = {}
obj.value = 'alex.cheng'
function bar() {
	return {
		value: this.value
	}
}
bar.call(obj)

>>> {value: "alex.cheng"}
  1. 最终实现
Function.prototype.myCall = function (ctx) {
    var ctx = ctx || window // 如果传入的ctx是null的话,就将ctx指向window
    ctx.fn = this
    // 将arguments转化成数组,通过slice()方法截取数组除第一项以外的所有项,并返回截取的数组
    var newArr = Array.prototype.slice.call(arguments, 1)
    var result = ctx.fn(...newArr) // ES6中的 '...' 展开运算符,可以展开数组
    delete ctx.fn
    return result // 返回ctx.fn的返回值
}

var value = 'hey, this is a global_value .' // 定义一个全局变量

var obj = {
    value: 'hello, obj.value'
}
function saySth(name, age) {
    return {
        name,
        age,
        value: this.value
    }
}

saySth.myCall(obj, 'alex.cheng', 18) // 传入obj,saySth函数的this指向obj
>>> {name: "alex.cheng", age: 18, value: "hello, obj.value"}

saySth.myCall(null, 'alex.cheng', 18) // 传入null,saySth函数的this指向window
{name: "haolun", age: 18, value: "hey, this is a global_value ."}


// 自己动手试一试 @.@

apply

apply的用法和call的用法一样,唯一的区别,就是传入的参数不同,apply需要传入数组,例如 fn.apply( null, [ ] )

  1. 模拟实现也和call 相似,实现如下 :
Function.prototype.myApply = function (ctx) {
	var ctx = ctx || window
	ctx.fn = this
	var result
	var args = arguments[1] // 获取传入的数组
	if (!args) { // 如果没有传入数组参数,则直接调用ctx.fn方法
		result = ctx.fn()
	} else {
		result = ctx.fn(...args) // 如果传入了数组参数,将数组参数采用ES6的'...'扩展运算符将其展开并传入ctx.fn方法
	}
	delete ctx.fn
	return result
}

// 亲自试一试 :)
Math.max.myApply(null, [1,2,3,4,55,44,33,22,11])