​   在工作中经常看到有使用 callapply,ibnd 这3个方法,每次看到后都会去网上搜一下用法,然而过一段时间就又不会了,归根到底是自己没有理解他的原理,这次准备彻底弄懂,并把自己的理解总结下来。

作用主要有两点:

  • 1、允许为不同的对象分配和调用属于另一个对象的函数/方法,也就是改变(或者说是扩大)函数运行的作用域,优点:对象不需要跟方法有任何耦合关系;
  • 2、提供新的 this 值给当前调用的函数/方法。

# 1、fun.call(thisArg, arg1, arg2, ...)

参数 描述
thisArg 在fun函数运行时指定的this值。
arg1,arg2 指定的参数列表,必须一个一个的列出来

下面来看几个例子:

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.sayHi = function(a,b) {
    console.log("this的值:" + this)
    console.log("sayhi:" + this.name + ":" + this.age + ",参数的个数:" + arguments.length);
}
var p1 = new Person("小白", 2);
p1.sayHi.call();
p1.sayHi.call(null);
p1.sayHi.call(undefined);
p1.sayHi.call("1");
p1.sayHi.call(true);
var obj = {
    name: "张三",
    age: 21
}
p1.sayHi.call(obj);
function Teacher(name, age) {
    this.name = name;
    this.age = age;
}
var t1 = new Teacher("张三", 18);
p1.sayHi.call(t1, 1, 2, 3); 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

输出结果如下: 暂无图片

结果分析:当给 fun.call() 中的 thisObj 赋值为空、nullundefined 时,函数 fun 中的 this 值指向 window 下面call()函数的源码的大概实现:

Function.prototype.Call = function(context,...args) {
    //注意 1 context不传或者为null时,context为window
    var context = context || window;
    //注意2 要保证context是引用数据类型的值
    !/^(object|function)$/.test(typeof context) ? context = Object(context) : null
    //注意3 创建唯一值,避免跟原对象的属性值相同
    let key=Symbol("key"),result;
     //改变this的指向,使得在调用fn时,fn中的this指向context
    context[key] = this;
   
    result = context[key](...args);
    //用完删除,不改变原始的数据结构
    delete context[key];
    return result;
}
function foo(){
    console.log(this);
}
var obj={c:4};
var arr=[1,2,3];
//Array
foo.Call(arr,1,2,3);
foo.call(arr,1,2,3);
//object
foo.Call(obj,1,2,3);
foo.call(obj,1,2,3);
//Number
foo.Call(1,1,2,3)
foo.call(1,1,2,3);
//Boolean
foo.Call(true,1,2,3)
foo.call(true,1,2,3);
//null
foo.es6Call(null,1,2,3)
foo.call(null,1,2,3);
//undefined
foo.Call(undefined,1,2,3)
foo.call(undefined,1,2,3);
//String
foo.Call("123",1,2,3)
foo.call("123",1,2,3);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

调用结果如下所示: 暂无图片 上面代码的不足之处在于:只考虑到 BooleanStringNumbernullundefinedObject 以及 Array 这几种数据类型的功能实现;

下面是一道经典的call的面试题

var name = '珠峰培训';
function A (x, y) {
    var res = x + y;
    console.log(res, this.name);
}
function B (x, y) {
    var res = x - y;
    console.log(res, this.name);
}
B.call(A, 40, 30);//10,A
//B.call.call=>call
B.call.call.call(A, 20, 10);//NAN undefined
Function.prototype.call(A, 60, 50);// 没有输出 
//Function.prototype.call.call=>call
Function.prototype.call.call.call(A, 80, 70);// NAN undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 2、fun.apply(thisArg, [arguments])

参数 描述
thisArg 在fun函数运行时指定的this值。
arguments 指定的参数数组

​  apply()call() 这两个方法的用途是一样的,都是在特定的作用域中调用函数,实际上就是设置函数 fun 内的 this 对象的值。call()apply() 的区别在于 call() 里传递的参数必须一个一个的列出来,apply 里传递的参数可以是数组也可以是 arguments 对象。

区别可见如下代码:

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.sayHi = function(a,b) {
    console.log("a的值:"+a);
    console.log("b的值:"+b);
    console.log("this的值:" + this)
    console.log("sayhi:" + this.name + ":" + this.age + ",参数的个数:" + arguments.length);
}	
var obj={
    name:"李四",
    age:25
}
var p1=new Person("张三",30);
p1.sayHi.call(obj, 1, 2, 3);
p1.sayHi.call(obj, [1,2,3]);
p1.sayHi.apply(obj, [1,2,3]); 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

输出结果如下: 暂无图片 下面 apply() 函数的源码的大概实现(缺点跟 call() 的实现是一样的。):

Function.prototype.es6Apply = function(context, arr) {
    /**
     * this=>fn 要执行的函数
     * context=>obj 最后要改变的this
     * args=>最后要传递的参数
     */
    var context = context || window;
    !/^(object|function)$/.test(typeOf context)?context=Object(context):null
    let key=Symbol("key"),result;
    context[key] = this;
    //判断是否传参
    if(!arr) {
        result = context[key]();
    } else {
        if(!(Object.prototype.toString.call(arr) == "[object Array]")) {
            throw new Error('CreateListFromArrayLike called on non-object');
        }
        result = context[key](...arr);
    }
    delete context[key];
    return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 3、call()apply 的常见用法

apply 常常被用于数组操作。

1、如合并两个数组,且改变原数组。

var array = ['a', 'b'];     
var elements = [0, 1, 2];   
array.push.apply(array, elements);  
console.log(array); // ["a", "b", 0, 1, 2]  
1
2
3
4

2、如求取数组中的最大最小值。

var numbers = [5, 6, 2, 3, 7];  
var max = Math.max.apply(null, numbers);    
/* 基本等同于 Math.max(5, 6, 2, 3, 7)  */   
var min = Math.min.apply(null, numbers);  
/* 基本等同于 Math.min(5, 6, 2, 3, 7)  */ 
     
1
2
3
4
5
6

3、实现继承

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.showName = function() {
        console.log(this.name);
    };
    this.showAge = function() {
        console.log(this.age);
    }
}
function Teacher(name, age) {
    Person.apply(this, [age,name]);
}
var p1=new Person("李四",25);
var t1 = new Teacher("张三", 30);
t1.showName();//"张三"
t1.showAge();//30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

​  调用 var p1=new Person("李四",25) 时返回的是如下对象。Person 构造函数中的 this 指的是 Person暂无图片

​ 调用 var 11=new Teacher("张三",30) 时,因为调用了 Person.apply(this, [age,name])Person 构造函数中的 this 指的是 Teacher。所有返回的是如下对象。 暂无图片

Person.apply(this, [age,name])中的thisnew Teacher() 时指的是 Teacher ,所以 Person.apply(this, [age,name]) 这句代码的返回的是一个Teacher 实例对象。

# 4、bind()

  该方法会创建一个函数的实例,其 this 值会绑定到传给 bind() 函数的值。 例子如下:

window.color="red";
var o={color:"blue"};
function sayColor(){
    console.log(this.color);
}
var objSayColor=sayClor.bind(o);
objSayColor();//"blue"
sayColor();//"red"
1
2
3
4
5
6
7
8

​  上面的例子相当于改变了 sayColor()函数中 this 的引用。bind()函数的源码的实现如下:

//基础版
Function.prototype.bind=function(oThis,...args){
    const fToBind=this;
    return function(){
        return fToBind.apply(oThis,[...args,...arguments])
    }
}
//完整版    
Function.prototype.bind = function(oThis,...args) {
    if(typeof this !== 'function') {
        // closest thing possible to the ECMAScript 5
        // internal IsCallable function
        throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }
    var  fToBind = this,
        // fNOP = function() {},
        fBound = function() {
            // this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
            return fToBind.apply(this instanceof fBound ?
                                this :oThis,
                                 // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
                                [...args,...arguments]);
        };

    // 维护原型关系
    // if(this.prototype) {
    //     // Function.prototype doesn't have a prototype property
    //     fNOP.prototype = this.prototype;

    // }
    //继承原型
    this.prototype&&(fBound.prototype=Object.create(this.prototype)
    // 下行的代码使fBound.prototype是fNOP的实例,因此
    // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
    // fBound.prototype = new fNOP();   
    return fBound;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

评 论:

Last Updated: 6/11/2024, 11:35:27 AM