this、call、apply、bind

this

在JavaScript中,this的指向是调用时决定的,而不是创建时决定的,this永远指向最后调用它的那个对象。

全局上下文

在全局上下文中,this指全局对象

  • this等价于window对象
  • var === this. === window.
    在浏览器里面this等价于window对象,如果你声明一些全局变量,这些变量都会作为this的属性。

函数上下文

在函数内部,this的值取决于函数被调用的方式

  • 直接调用
    this指向window,严格模式下是undefined

  • 作为对象的一个方法
    当this所在的函数被以obj.fn()形式调用时,指向obj

  • call()、apply,(不同点:前者传的是若干个参数列表,后者是包含多个参数的数组)
    this指向绑定的对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let Person = {
    name: 'YoLin',
    age: 25
    }
    function say(job) {
    console.log(`我是${this.name},今天{this.age},职业是${job}`)
    }
    say.call(Person, "FE")
    say.apply(Person, ["FE"])
  • bind()
    this将永久地被绑定到了bind的第一个参数。
    与call、apply相似,接受若干个参数列表并返回一个新的函数,不同的是需要我们手动调用。

  • 箭头函数
    所有的箭头函数都没有自己的this,都指向外层。
    箭头函数会捕获其所在上下文的this值,作为自己的this值。
    箭头函数的 this 始终指向函数定义时的 this,而非执行时
    箭头函数需要记着这句话:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”。

  • 作为一个构造函数(new实例化一个对象)
    this被绑定到正在构造的新对象。
    通过构造函数创建一个对象其实执行这样几个步骤:
    1、创建新对象
    2、将this指向这个对象
    3、给对象赋值(属性、方法)
    4、返回this

    new的过程

    1
    2
    3
    4
    5
    6
    7
    8
    var a = new myFunction("Li","Cherry");

    new myFunction{
    var obj = {};
    obj.__proto__ = myFunction.prototype;
    var result = myFunction.call(obj,"Li","Cherry");
    return typeof result === 'obj'? result : obj;
    }

    1、创建一个空对象 obj;
    2、将新创建的空对象的隐式原型指向其构造函数的显示原型。
    3、使用 call 改变 this 的指向
    4、如果无返回值或者返回一个非对象值,则将 obj 返回作为新对象;如果返回值是一个新对象的话那么直接直接返回该对象。

总结

如果要判断一个函数的this绑定,就需要找到这个函数的直接调用位置。然后可以顺序按照下面四条规则来判断this的绑定对象:

  • 由new调用:绑定到新创建的对象
  • 由call或apply、bind调用:绑定到指定的对象
  • 由上下文对象调用:绑定到上下文对象
  • 默认:全局对象
    注意:箭头函数不使用上面的绑定规则,根据外层作用域来决定this,继承外层函数调用的this绑定。

call

手写call的思路:

  • 1、将函数设置为对象的一个属性
  • 2、执行该函数
  • 3、删除该函数
    通过Arguments 对象中取值,然后放入一个数组里
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 以上个例子为例,此时的arguments为:
    // arguments = {
    // 0: foo,
    // 1: 'kevin',
    // 2: 18,
    // length: 3
    // }
    // 因为arguments是类数组对象,所以可以用for循环
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
    }

    // 执行后 args为 [foo, 'kevin', 18]
    把这个参数数组放到要执行的函数的参数里面去

eval() 函数会将传入的字符串当做 JavaScript 代码进行执行

1
eval('context.fn(' + args +')')

这里 args 会自动调用 Array.toString() 这个方法。
最终代码

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

var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}

var result = eval('context.fn(' + args +')');

delete context.fn
return result;
}

apply与call类似,只是传入的参数是一个数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Function.prototype.apply = function (context, arr) {
var context = Object(context) || window;
context.fn = this;

var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}

delete context.fn
return result;
}

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

  • 返回一个函数
  • 可以传入参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Function.prototype.myBind = function(context) {
    if (typeof this !== 'function') {
    return new Error('Function.prototype.bind - what is trying to be bound is not callable')
    }
    var args = Array.prototype.slice.call(arguments,1)
    var self = this
    var fNOP = function() {}
    var fBound = function() {
    var bingArgs = Array.prototype.slice.call(arguments)
    self.apply(this instanceof fNOP.prototype ? this : context, args.concat(bindArgs))
    }
    fNOP.prototype = this.prototype
    fBound.prototype = new fNOP()
    return fBound
    }

类数组

具备与数组特征类似的对象

1
2
3
4
5
6
let arrayLike = {
0: 1,
1: 2,
2: 3,
length: 3
};

获取 DOM 节点的方法
方法中的 arguments 都是类数组
可以通过for循环进行遍历,类数组无法使用 forEach、splice、push 等数组原型链上的方法

call的使用场景:

  • 对象的继承
    1
    2
    3
    4
    function Parent() {}
    function Child() {
    Parent.call(this)
    }
  • 借用方法
    类数组想用使用数组的方法
    1
    let domNodes = Array.prototype.slice.call(arr)
    apply的一些妙用:
    1、获取数组中数字最大最小值
    1
    2
    Math.max.apply(null, arr)
    Math.min.apply(null, arr)
    2、合并两个数组
    1
    Array.prototype.push.apply(arr1,arr2)

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!