this、call、apply、bind

this

指向调用它的对象(多个对象时指向它上一级的对象)

  1. 全局window
  2. 单纯的函数调用 window(在严格模式中,则是undefined)
  3. 作为对象的方法:指向当前对象
  4. 作为构造函数:新创建的对象(实例)
  5. 内部函数:window,而不是当前对象(用that保存)
  6. 使用call、apply设置this:将某个函数绑定到某个对象上
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var o={
    a: 10,
    b: {
    fn: function(){
    console.log(this.a)
    }
    }
    }

    o.b.fn(); // undefined 此时的this应该是b

    const j=o.b.fn;
    j(); // window

赋值表达式的值是函数本身,this不能得到维持

1
2
3
4
5
6
7
8
9
function a() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(a()()())
// window

箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,因为调用 a 符合前面代码中的第一个情况,所以 this 是 window。并且 this 一旦绑定了上下文,就不会被任何代码改变。

this与闭包

this对象是在运行时基于函数的执行环境绑定的。但是匿名函数的执行环境具有全局性,因此this对象通常指向window。

1
2
3
4
5
6
7
8
9
10
11
12
var a={
b:40,
c: function(){
var that= this
return function(){
console.log(this) // window
console.log(that) // a
}
}
}

a.c()()

由于每个函数在调用的时候会自动取得两个特殊的变量:this、arguments,只会搜索到其活动对象为止,因此永远不可能访问外部函数中的这两个变量。可以把外部作用域中的this对象保存在一个闭包能够访问到的变量里

call、apply、bind

在JavaScript中,call、apply和bind是Function对象自带的三个方法,都是为了改变某个函数运行时的上下文(context)而存在的,就是改变函数体内部 this 的指向。

在特定的作用域中调用函数

  • apply 、 call 、bind 三者第一个参数都是 this 要指向的对象,也就是想指定的上下文
  • apply 、 call 、bind 三者都可以利用后续参数传参
  • bind 是返回对应 函数,便于稍后调用;apply 、call 则是立即调用
  1. call
1
objA.call(objB, arg1, arg2, arg3)
  1. apply
1
2
objA.apply(objB,[arg1, arg2, arg3])
// 第二个参数可以是数组/类数组 arguments对象
  • 第一个参数都是你想指向的上下文,第二个参数call 需要按顺序传递进去,而 apply 则是放在数组里
  • 若call、apply的第一个参数是null/空,则this指向window
  • call、apply是改变上下文this并立即执行改变的函数、bind可以让函数想什么时候调用就什么时候,可以再执行时再添加参数
  1. bind
    bind()方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    this.num = 9; 
    var mymodule = {
    num: 81,
    getNum: function() {
    console.log(this.num);
    }
    };

    mymodule.getNum(); // 81

    var getNum = mymodule.getNum;
    getNum();
    // 9 因为在这个例子中,"this"指向全局对象

    var boundGetNum = getNum.bind(mymodule);
    boundGetNum(); // 81

应用场景

其他对象可以利用数组/对象原型上面的方法

  1. 数组之间追加
1
2
3
var array1 = [12 , "foo" , {name:"Joe"} , -2458]; 
var array2 = ["Doe" , 555 , 100];
Array.prototype.push.apply(array1, array2);
  1. 获取数组中的最大值和最小值
1
2
3
var  numbers = [5, 458 , 120 , -215 ]; 
var maxInNumbers = Math.max.apply(Math, numbers), //458
maxInNumbers = Math.max.call(Math,5, 458 , 120 , -215);
  1. 定义一个 log 方法,让它可以代理 console.log 方法
1
2
3
4
5
function log(msg) {
console.log(msg);
}
log(1); //1
log(1,2); //1

可以考虑使用 apply 或者 call,注意这里传入多少个参数是不确定的,所以使用apply是最好的

1
2
3
4
5
function log(){
console.log.apply(console, arguments);
};
log(1); //1
log(1,2); //1 2

给每一个 log 消息添加一个”(app)”的前辍

  • arguments参数是个伪数组,通过 Array.prototype.slice.call 转化为标准数组,再使用数组方法unshift
1
2
3
4
5
6
function log(){
var args = Array.prototype.slice.call(arguments);
args.unshift('(app)');

console.log.apply(console, args);
};

模拟实现

call

  • 判断传入的对象是否是null/undefined,是的话对象转为window对象
  • 为传入的对象创建一个新方法指向调用call的函数this,执行函数传入参数获取返回值,删除创建的方法,返回返回值

    改变了 this 指向,让新的对象可以执行该函数。那么思路是否可以变成给新的对象添加一个函数,然后在执行完以后删除?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Function.prototype.myCall = function (context) {

    var context = context || window
    // 给 context 添加一个属性
    // getValue.call(a, 'yck', '24') => a.fn = getValue
    context.fn = this
    // 将 context 后面的参数取出来
    var args = [...arguments].slice(1)
    // getValue.call(a, 'yck', '24') => a.fn('yck', '24')
    var result = context.fn(...args)
    // 删除 fn
    delete context.fn
    return result

    }

apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Function.prototype.myApply = function (context) {
var context = context || window
context.fn = this

var result
// 需要判断是否存储第二个参数
// 如果存在,就将第二个参数展开
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}

delete context.fn
return result
}

bind

bind 和其他两个方法作用也是一致的,只是该方法会返回一个函数。并且我们可以通过 bind 实现柯里化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var _this = this
var args = [...arguments].slice(1)
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}

bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效(也不指向window),但传入的参数依然生效