kooboo

执行期上下文

函数执行时(准确来说,是在函数发生预编译的前一刻),会创建一个执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境。

每调用一次函数,就会创建一个新的上下文对象,他们之间是相互独立且独一无二的。当函数执行完毕,它所产生的执行期上下文会被销毁。

参考链接:https://www.cnblogs.com/chenyingjie1207/p/9966036.html

this 指向

解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是 this,this 指向的是一个对象,这个对象我们称为函数执行的 上下文对象。

ES5 函数内 this 的指向【非常重要】

我们在《JavaScript 基础/函数.md》这篇文章讲过,函数的调用有六种形式。

在ES5语法中,根据函数的调用方式的不同,this 会指向不同的对象:

1、以函数的形式(包括普通函数、定时器函数、立即执行函数)调用时,this 的指向永远都是 window。比如fun();相当于window.fun();

2、以方法的形式调用时,this 指向调用方法的那个对象

3、以构造函数的形式调用时,this 指向实例对象

4、以事件绑定函数的形式调用时,this 指向绑定事件的对象

5、使用 call 和 apply 调用时,this 指向指定的那个对象

第 1 条的举例

function fun() {
    console.log(this);
    console.log(this.name);
}

var obj1 = {
    name: 'smyh',
    sayName: fun,
};

var obj2 = {
    name: 'vae',
    sayName: fun,
};

var name = '全局的name属性';

//以函数形式调用,this是window
fun(); //可以理解成 window.fun()

打印结果:

    Window
    全局的name属性

上面的举例可以看出,this 指向的是 window 对象,所以 this.name 指的是全局的 name。

第 2 条的举例

function fun() {
    console.log(this);
    console.log(this.name);
}

var obj1 = {
    name: 'smyh',
    sayName: fun,
};

var obj2 = {
    name: 'vae',
    sayName: fun,
};

var name = '全局的name属性';

//以方法的形式调用,this是调用方法的对象
obj2.sayName();

打印结果:

    Object
    vae

上面的举例可以看出,this 指向的是 对象 obj2 ,所以 this.name 指的是 obj2.name。

ES6 箭头函数中 this 的指向

ES6 中的箭头函数并不使用上面的准则,而是会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。

改变函数内部的 this 指向

JS 专门为我们提供了一些方法来改变函数内部的 this 指向。常见的方法有 call()、apply()、bind() 方法。继续往下看。

call()

call() 方法的作用

call() 方法的作用:可以调用一个函数,与此同时,它还可以改变这个函数内部的 this 指向。

call() 方法的另一个应用:可以实现继承。之所以能实现继承,其实是利用了上面的作用。

语法:

fn1.call(想要将this指向哪里, 函数实参1, 函数实参2);

备注:第一个参数中,如果不需要改变 this 指向,则传 null。

call() 方法举例

举例 1、通过 call() 调用函数:

const obj1 = {
    nickName: 'qianguyihao',
    age: 28,
};
function fn1() {
    console.log(this);
    console.log(this.nickName);
}
fn1.call(this); // this的指向并没有被改变,此时相当于 fn1();

上方代码的打印结果:

window
undefined

上面的代码,跟普通的函数调用 fn1() 没有区别。

举例 2、通过 call() 改变 this 指向:

var obj1 = {
    nickName: 'qianguyihao',
    age: 28,
};

function fn1(a, b) {
    console.log(this);
    console.log(this.nickName);
    console.log(a + b);
}

fn1.call(obj1, 2, 4); // 先将 this 指向 obj1,然后执行 fn1() 函数

上方代码的打印结果:

obj1
qianguyihao
6

举例 3、通过 call() 实现继承:

// 给 Father 增加 name 和 age 属性
function Father(myName, myAge) {
    this.name = myName;
    this.age = myAge;
}

function Son(myName, myAge) {
    // 【下面这一行,重要代码】
    // 通过这一步,将 father 里面的 this 修改为 Son 里面的 this;另外,给 Son 加上相应的参数,让 Son 自动拥有 Father 里的属性。最终实现继承
    Father.call(this, myName, myAge);
}

const son1 = new Son('千古壹号', 28);
console.log(JSON.stringify(son1));

上方代码中,通过 call() 方法,让 Son 继承了 Father 里面的 name 和 age 属性。

打印结果:

{"myName":"千古壹号","myAge":28}

apply() 方法

apply() 方法的作用

apply() 方法的作用:可以调用一个函数,与此同时,它还可以改变这个函数内部的 this 指向。这一点,和 call()类似。

apply() 方法的应用: 由于 apply()需要传递数组,所以它有一些巧妙应用,稍后看接下来的应用举例就知道了。

语法:

fn1.apply(想要将this指向哪里, [函数实参1, 函数实参2]);

备注:第一个参数中,如果不需要改变 this 指向,则传 null。

到这里可以看出, call() 和 apply() 方法的作用是相同的。唯一的区别在于,apply() 里面传入的实参,必须是数组(或者伪数组)

apply() 方法举例

举例、通过 apply() 改变 this 指向:

var obj1 = {
    nickName: 'qianguyihao',
    age: 28,
};

function fn1(a) {
    console.log(this);
    console.log(this.nickName);
    console.log(a);
}

fn1.apply(obj1, ['hello']); // 先将 this 指向 obj1,然后执行 fn1() 函数

注意,上方代码中,apply() 里面传实参时,需要以数组的形式。即便是传一个实参,也需要传数组。

打印结果:

obj1
qianguyihao
hello

apply() 方法的巧妙应用:求数组的最大值

我们知道,如果想要求数组中元素的最大值,数组本身是没有自带方法的。那怎么办呢?

虽然数组里没有获取最大值的方法,但是数值里有 Math.max(数字1,数字2,数字3) 方法,可以获取多个数值中的最大值。 另外,由于 apply() 方法在传递实参时,传的刚好是数组,所以我们可以 通过 Math.max() 和 apply() 曲线救国。

举例:求数组中多个元素的最大值:

const arr1 = [3, 7, 10, 8];

// 下面这一行代码的目的,无需改变 this 指向,所以:第一个参数填 null,或者填 Math,或者填 this 都可以。严格模式中,不让填null。
const maxValue = Math.max.apply(Math, arr1); // 求数组 arr1 中元素的最大值
console.log(maxValue);

const minValue = Math.min.apply(Math, arr1); // 求数组 arr1 中元素的最小值
console.log(minValue);

打印结果:

10
3

bind() 方法

bind() 方法的作用

bind() 方法不会调用函数,但是可以改变函数内部的 this 指向。

把call()、apply()、bind()这三个方法做一下对比,你会发现:实际开发中, bind() 方法使用得最为频繁。如果有些函数,我们不需要立即调用,但是又想改变这个函数内部的this指向,此时用 bind() 是最为合适的。

语法:

新函数 = fn1.bind(想要将this指向哪里, 函数实参1, 函数实参2);

参数:

  • 第一个参数:在 fn1 函数运行时,指定 fn1 函数的this 指向。如果不需要改变 this 指向,则传 null。

  • 其他参数:fn1 函数的实参。

解释:它不会调用 fn1 函数,但会返回 由指定this 和指定实参的原函数拷贝。可以看出, bind() 方法是有返回值的。