call apply bind
语法:
fun.call(thisArg, arg1, arg2, ...)
thisArg
第一个参数thisArg是函数fun运行时指定的this值,这个值在非严格模式下,null和undefined都指向window,在严格模式下,是谁就指向谁自己,例子:
var name = 'leah'
var obj = {
name: 'lihhh'
}
function test1 () {
console.log(this.name);
}
test1.call(obj) //lihhh this指向obj
test1.call(null) //leah this指向window
arg1, arg2, ...
指定的参数列表
返回值
返回值是你调用的方法的返回值,若该方法没有返回值,则返回undefined
。
func.apply(thisArg, [argsArray])
第一个参数thisArg是函数fun运行时指定的this值,这个值在非严格模式下,null和undefined都指向window
第二个参数argsArray是可选参数,可以是数组或者类数组,将作为单独的参数传给fun。
相同点:
第一个参数都是函数运行时指定的this,在非严格模式下,null和undefiend都指向window。都可以只传一个参数。
区别:
1、调用方式不同
function fun(a, b) {
alert(this.name);
}
var obj = {
name: "obj",
sayName: function () {
alert(this.name);
}
};
var obj2 = {
name: "obj2"
};
fun.call(obj2)
alert的答案是obj2,此时this指向了obj2
对于apply可以这样:
fun.apply(obj2)
对于bind需要这样:
fun.bind(obj)()
注意:使用call和apply会直接执行这个函数,而bind是将绑定好的this重新返回一个新函数,什么时候调用由你自己决定。需要自己手动调用。
var objName = {name:'tom'};
var obj = {
name:'hhhh',
sayHello:function(){
console.log(this.name);
}.bind(objName)
};
obj.sayHello();
这个输出tom
var objName = {name:'tom'};
var obj = {
name:'hhhh',
sayHello:function(){
console.log(this.name);
}.call(objName)
};
obj.sayHello();
如果是call会报错obj.sayHello is not a function,这是因为call会立即执行,执行完内存就会释放这个方法,没有这个方法了,而bind不会。
2、传参
call就是挨个传值,apply传一个数组,bind也是挨个传值
call()方法第一个参数传的是一个你要借用的对象,第二个是实参,可以在对象之后依次传递
fun.call(obj,2,3);
fun.apply(obj,[2,3]);
fun.bind(obj)(2, 3)
深入理解:
想要知道call是怎样被执行的,涉及到了原型链查找机制。
fun.call(obj)
其实是首先通过fun的原型链,找到Function.ptototype中的call方法,call方法中的this指向的就是fun,然后在执行call方法的时候,改变了this的指向,成了obj
call、apply在数组中的巧妙用法
//利用Math.max方法求数组中的最大值
var arr = [2,20,30,34,50]
console.log(Math.max.apply(null, arr)); // 50
//让伪数组调用数组的方法
function fn () {
[].push.call(arguments,3)
console.log(arguments);
}
fn(1,2,3) // [1,2,3,3]
//让数组使用字符串的方法
var arrs = ['abcabcasa']
console.log(''.indexOf.call(arrs, 's'));// 7
call源码实现
先从例子开始解析call的实现
var name = 'leah'
var obj = {
name: 'lihhh'
}
function test1 () {
console.log(this.name);
}
test1.call(obj) //lihhh this指向obj
test1.call(null) //leah this指向window
在这个过程中,call修改 了this指向,并且执行了test1函数,那么接下来我们看看他是如何修改了this指向的
在这个例子当中,将函数test2变成了obj里面的一个方法,然后再执行这个函数,这个函数里的this就指向了obj对象。所以我们只要在 thisArg里添加这个函数,然后在调用这个函数,执行后再删除这个函数即可。
第一步:改变this指向
Function.prototype.mycall = function (obj) {
obj.fn = this //此时this就是fn
obj.fn() //执行fn
delete obj.fn //删除fn
}
通过在Function的原型上绑定mycall方法,这样所有函数就都可以访问到这个方法。
将fn设置成obj对象的一个属性,并让它等于this。
接着调用,此时this指向了obj。
最后删除。不然会导致obj上的属性越来越多。
例子:
var name = 'leah'
var obj = {
name: 'lihhh'
}
function test1 () {
console.log(this.name);
}
test1.mycall(obj) //lihhh this指向obj
第二部:传参
上面我们实现了一个简单的call函数,但是还不能传参,接着我们处理一下参数问题。
我们知道有一个arguments参数表示传入的所有参数,不妨打印出来看看
可以看到一共有四个,第一个是我们传入的对象,后面三个才是我们想要的参数。那怎么取出后面这三个呢,arguments是伪数组,不能直接使用数组方法,之前我们都是通过Array.prototype.splice.call(arguments),这样来使用,但现在我们正在是实现call方法,所以call还不能使用,那最简单的就是通过for循环来截取了,如下:
Function.prototype.mycall = function (obj) {
obj.fn = this //此时this就是fn
obj.fn() //执行fn
delete obj.fn //删除fn
console.log(arguments)
var args = []
for(var i = 1; i < arguments.length; i++){
args.push(arguments[i])
}
console.log(args) //[1,2,3]
}
var name = 'leah'
var obj = {
name: 'lihhh'
}
function test1 () {
console.log(this.name);
}
test1.mycall(obj,1,2,3)
此时截取到的是一个数组,但是数组不能作为参数传给函数,而应该是将数组里的元素分别传给函数,此时有个evel方法应该了解一下。
eval可计算某个字符串,并执行其中的的 JavaScript 代码
。
例如:
eval("x=10;y=20;document.write(x*y)")
document.write(eval("2+2"))
var x=10
document.write(eval(x+17))
因此我们就可以利用eval来处理这个问题了,接着来改写我们的模拟函数
Function.prototype.mycall = function (obj) {
console.log(arguments)
var args = []
for(var i = 1; i < arguments.length; i++){
// args.push(arguments[i])
args.push("arguments[" + i + "]");
}
console.log(args) //[1,2,3]
obj.fn = this //此时this就是fn
eval("obj.fn(" + args + ")"); //执行fn
delete obj.fn //删除fn
}
var name = 'leah'
var obj = {
name: 'lihhh'
}
function test1 (a,b,c) {
console.log(a + b + c + this.name);
}
test1.mycall(obj,"我的", "名字", "是")
一共改了两个地方
第一个是将 args.push(arguments[i])改成了args.push("arguments[" + i + "]");若是不这么写,得到的args数组就是[我的,名字,是]这样的形式,那eval执行的时候就会是表达式变成了eval("obj.fn(我的,名字,是)"),没有引号的话就相当于是一个变量,而不是字符串,那找不到这样的变量肯定就会报错。所以改成了args.push("arguments[" + i + "]"),args最终就是这个样子
["arguments[1]","arguments[2]","arguments[3]"]
,当执行eval
时,arguments[1]
此时确实是作为一个变量存在不会报错,于是被eval
解析成了一个真正的字符传递给了函数。
还有一种方法就是不使用eval,使用new Function()方法来执行。只知道有这么一种方法,具体的还不知道怎么实现,以后补充
newFunction ([arg1[, arg2[, ...argN]],] functionBody)
简单例子:
var sum = newFunction('a', 'b', 'return a + b');
console.log(sum(2, 6));
也可以用扩展运算符。[...arguments]替换了es5
中的Array.prototype.slice.call(arguments)
写法,改写后如下
Function.prototype.mycall = function (obj) {var args = []
for(var i = 1; i < arguments.length; i++){
args.push(arguments[i])
// args.push("arguments[" + i + "]");
}
console.log(args) //[1,2,3]
obj.fn = this //此时this就是fn
obj.fn(...args); //执行fn
delete obj.fn //删除fn
}
var name = 'leah'
var obj = {
name: 'lihhh'
}
function test1 (a,b,c) {
console.log(a + b + c + this.name);
}
test1.mycall(obj,"我的", "名字", "是") //lihhh this指向obj
test1.mycall(null,"我的", "名字", "是") //leah this指向window
test1.mycall(undefined,"我的", "名字", "是") //leah this指向window
这种方法比eval简单
第三步:判断this特殊指向
如果第一个参数传的null或者undefined,在非严格模式下,null和undefined都指向window。所以要判断。
Function.prototype.mycall = function (obj) {
//判断是否为null或者undefined
var object = obj || window;
var args = []
for(var i = 1; i < arguments.length; i++){
args.push(arguments[i])
}
console.log(args) //[1,2,3]
object.fn = this //此时this就是fn
object.fn(...args); //执行fn
delete object.fn //删除fn
}
var name = 'leah'
var obj = {
name: 'lihhh'
}
function test1 (a,b,c) {
console.log(a + b + c + this.name);
}
test1.mycall(obj,"我的", "名字", "是") //lihhh this指向obj
test1.mycall(null,"我的", "名字", "是") //leah this指向window
test1.mycall(undefined,"我的", "名字", "是") //leah this指向window
apply源码实现
Function.prototype.myapply = function (obj,arr) { var object = obj || window; object.fn = this if(!arr){ object.fn() }else{ let args = arr object.fn(...args); //执行fn } delete object.fn }
test1.myapply(obj,["我的", "名字", "是"]) //lihhh this指向obj
test1.myapply(null,["我的", "名字", "是"]) //leah this指向window
test1.myapply(undefined,["我的", "名字", "是"]) //leah this指向window