搞懂 JS this、call、apply、bind

搞懂 JS this、call、apply、bind

javascript 的 this

ECMAScript 规范中这样写:
this 关键字执行为当前执行环境的 ThisBinding。
MDN 上这样写:
In most cases, the value of this is determined by how a function is called.
在绝大多数情况下,函数的调用方式决定了 this 的值。
可以这样理解,在 JavaScript 中,this 的指向是调用时决定的,而不是创建时决定的,这就会导致 this 的指向会让人迷惑,简单来说,this 具有运行期绑定的特性。

首先需要理解调用位置,调用位置就是函数在代码中被调用的位置,而不是声明的位置。

通过分析调用栈(到达当前执行位置所调用的所有函数)可以找到调用位置。

全局上下文
在全局执行上下文中 this 都指代全局对象。

this等价于window对象
var === this. === winodw.
console.log(window === this); // true
var a = 1;
this.b = 2;
window.c = 3;
console.log(a + b + c); // 6

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

函数上下文
在函数内部,this 的值取决于函数被调用的方式。
直接调用
this 指向全局变量。

function foo(){
  return this;
}
console.log(foo() === window); // true

如何改变 this 指向

call()、apply()

this 指向绑定的对象上。

var person = {
  name: "test",
  age: 25
};
function say(job){
  console.log(this.name+":"+this.age+" "+job);
}
say.call(person,"FE"); // test:25
say.apply(person,["FE"]); // test:25

可以看到,定义了一个 say 函数是用来输出 name、age 和 job,其中本身没有 name 和 age 属性,我们将这个函数绑定到 person 这个对象上,输出了本属于 person 的属性,说明此时 this 是指向对象 person 的。

如果传入一个原始值(字符串、布尔或数字类型)来当做 this 的绑定对象, 这个原始值会被转换成它的对象形式(new String()),这通常被称为“装箱”。

call 和 apply 从 this 的绑定角度上来说是一样的,唯一不同的是它们的第二个参数。

bind()

this 将永久地被绑定到了 bind 的第一个参数。

bind 和 call、apply 有些相似。

var person = {
  name: "test",
  age: 25
};
function say(){
  console.log(this.name+":"+this.age);
}
var f = say.bind(person);
console.log(f());

call 和 apply 有什么区别

call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

当调用一个函数时,可以赋值一个不同的 this 对象。this 引用当前对象,即 call 方法的第一个参数。

通过 call 方法,你可以在一个对象上借用另一个对象上的方法,比如 Object.prototype.toString.call([]),就是一个 Array 对象借用了 Object 对象上的方法。

语法 fun.call(thisArg[, arg1[, arg2[, …]]])

thisArg

在 fun 函数运行时指定的 this 值。需要注意的是下面几种情况

(1)不传,或者传 null,undefined, 函数中的 this 指向 window 对象
(2)传递另一个函数的函数名,函数中的 this 指向这个函数的引用,并不一定是该函数执行时真正的 this 值 (3)值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象,如 String、Number、Boolean
(4)传递一个对象,函数中的 this 指向这个对象

arg1, arg2, …

指定的参数列表。

语法与 call() 方法的语法几乎完全相同,唯一的区别在于,apply 的第二个参数必须是一个包含多个参数的数组(或类数组对象)。apply 的这个特性很重要,

在调用一个存在的函数时,你可以为其指定一个 this 对象。 this 指当前对象,也就是正在调用这个函数的对象。 使用 apply, 你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。

语法:fun.apply(thisArg[, argsArray])

bind() 函数会创建一个新函数(称为绑定函数)

bind 是 ES5 新增的一个方法
传参和 call 或 apply 类似
不会执行对应的函数,call 或 apply 会自动执行对应的函数
返回对函数的引用
语法 fun.bind(thisArg[, arg1[, arg2[, …]]])

下面例子:当点击网页时,EventClick 被触发执行,输出 JSLite.io p1 p2, 说明 EventClick 中的 this 被 bind 改变成了 obj 对象。如果你将 EventClick.bind(obj,‘p1’,‘p2’) 变成 EventClick.call(obj,‘p1’,‘p2’) 的话,页面会直接输出 JSLite.io p1 p2

var obj = {name:'JSLite.io'};
/**
 * 给document添加click事件监听,并绑定EventClick函数
 * 通过bind方法设置EventClick的this为obj,并传递参数p1,p2
 */
document.addEventListener('click',EventClick.bind(obj,'p1','p2'),false);
//当点击网页时触发并执行
function EventClick(a,b){
    console.log(
            this.name, //JSLite.io
            a, //p1
            b  //p2
    )
}

模拟实现 call

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;
}

// 测试一下
var value = 2;

var obj = {
    value: 1
}

function bar(name, age) {
    console.log(this.value);
    return {
        value: this.value,
        name: name,
        age: age
    }
}

bar.call(null); // 2

console.log(bar.call2(obj, 'kevin', 18));
// 1
// Object {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }

模拟实现 apply

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;
}

你可能感兴趣的:(前端,javascript,前端,vue.js)