JavaScript高级用法 —— call \ apply \ bind 和 Arguments 对象的解析(三)

目标:

  • 掌握 call 和 apply 的原理及手写 call 和 apply
  • 掌握 bind 的原理及手写 bind

1、call

在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

var value = 1
var obj = {
	value: 2
}
function foo(name) {
	console.log(this.value)
	console.log(name)
}
foo.call(null, 'relex') // 输出1, relex
foo.call(obj, 'relex') // 输出2, relex

由上述代码,可以知道call有以下几个特点:

  • thisnull 的时候,视为指向 window
  • 改变this的指向
  • 可以指定参数

上述的foo.call(obj, 'relex')等价于下面这段代码的obj.foo()

var obj = {
	value: 2,
	foo: function(name) {
		console.log(this.value)
		console.log(name)
	}
}
obj.foo()

所以实现 call 的思路就是:在需要指向的对象上,加上需要执行的函数,执行完之后删除掉,最后返回结果

Function.prototype.call2 = function(context) {
	var context = context || window
	context.fn = this
	
	const args = [...arguments].slice(1)
	let result = context.fn(...args)
	
	delete context.fn
	return result
}

2、apply

applycall唯一的区别就是传参的形式不同,apply 是以数组的形式传入参数。

var obj = {
	value: 1
}
function fun(name, age) {
	console.log(this.value, name, age)
}
fun.apply(obj, ['relex', 24])

所以 apply 的实现与 call 的区别就是函数形参上的不同了

Function.prototype.apply2 = function(context, arr) {
	context = context || window
	context.fn = this
	
	var result
	if(arr) {
		result = context.fn(...arr)
	} else {
		result = context.fn()
	}
	
	delete context.fn
	return result
}

3、bind

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

var foo = {
    value: 1
};

function bar(name, age) {
    console.log(this.value, name, age);
}

// 返回了一个函数
var bindFoo = bar.bind(foo, name); 

bindFoo(24); // 输出1, relex, 24

bind 的特点:

  • 第一个参数作为运行时的 this
  • 返回一个新函数
  • 可以传入参数

手写实现 bind

Function.prototype.bind2 = function(context) {
	var self = this
	var bindArgs = [...arguments].slice(1) // 通过bind时传入的参数
	return function() {
		var newFunArgs = [...arguments] // 调用新函数时传入的参数
		self.apply(context, bindArgs .concat(newFunArgs)) // 改变this指向
	}
}

当对 bind 返回的新函数使用 new 操作符创建新对象时,提供的this值会被忽略,同时调用时的参数被提供给模拟函数。这种行为就像把原函数当成构造器。

Function.prototype.bind2 = function(context) {
	var self = this
	var bindArgs = [...arguments].slice(1)
	var fBound =  function() {
		var newFunArgs = [...argumens]
		self.apply(this instanceof fBound ? this : context, bindArgs.concat(newFunArgs))
	}
	fBound.prototype = this.prototype
	return fBound
}

对返回的新 bind 创建的函数进行 new 操作符构造新对象时,相当于 new fBound() ,所以此时的 this instanceof fBoundtruethis 指向实例

4、Arguments对象

4.1、类数组对象

类数组对象是拥有一个 length 属性和若干索引属性的对象,不具备数组的方法

类数组对象如何调用数组方法?

通过 Function.call 间接调用

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 } // 类数组对象

Array.prototype.join.call(arrayLike, '&'); // name&age&sex

Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] 
// slice可以做到类数组转数组

Array.prototype.map.call(arrayLike, function(item){
    return item.toUpperCase();
}); 
// ["NAME", "AGE", "SEX"]

那类数组如何转数组呢?

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
// 1. slice
Array.prototype.slice.call(arrayLike); // ["name", "age", "sex"] 
// 2. splice
Array.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"] 
// 3. ES6 Array.from
Array.from(arrayLike); // ["name", "age", "sex"] 
// 4. apply
Array.prototype.concat.apply([], arrayLike)

4.2、Arguments

Arguments 对象是类数组对象

Arguments 对象只定义在函数体中,包括了函数的参数和其他属性。在函数体中,arguments 指代该函数的 Arguments 对象。

此外,它还有一个 callee 属性,通过它可以调用函数自身

// 闭包经典面试题使用 callee
var data = [];

for (var i = 0; i < 3; i++) {
    (data[i] = function () {
       console.log(arguments.callee.i) 
    }).i = i;
}

data[0]();
data[1]();
data[2]();

// 0
// 1
// 2

arguments 还有个特点:传入的参数,实参和 arguments 的值会共享,当没有传入时,实参与 arguments 值不会共享

function foo(name, age, sex, hobbit) {

    console.log(name, arguments[0]); // name name
    // 改变形参
    name = 'new name';
    console.log(name, arguments[0]); // new name new name

    // 改变arguments
    arguments[1] = 'new age';
    console.log(age, arguments[1]); // new age new age
    
    // 测试未传入的是否会绑定
    console.log(sex); // undefined
    sex = 'new sex';
    console.log(sex, arguments[2]); // new sex undefined

    arguments[3] = 'new hobbit';
    console.log(hobbit, arguments[3]); // undefined new hobbit
}

foo('name', 'age')

4.3、实际用途:

  • 将参数从一个函数传递到另一个函数
    // 使用 apply 将 foo 的参数传递给 bar
    function foo() {
        bar.apply(this, arguments);
    }
    function bar(a, b, c) {
       console.log(a, b, c);
    }
    
    foo(1, 2, 3)
    
  • 方法参数不定长
  • 函数柯里化
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值