认识this

90 阅读3分钟

this到底是什么东西

599584-391af3aad043c028.webp

执行上下文的创建阶段,会分别生成变量对象,建立作用域链,确定this指向。

函数的this在调用时绑定的,完全取决于函数的调用位置(也就是函数的调用方法)。为了搞清楚this的指向是什么,必须知道相关函数是如何调用的。

默认绑定

使用不带任何修饰符的的函数引用调用 非严格模式默认指向全局对象

var name = 'window';
var doSth = function(){
    console.log(this.name); 
} 
doSth(); // 'window'

你可能会误以为window.doSth()是调用的,所以是指向window。虽然本例中window.doSth确实等于doSthname等于window.name。上面代码中这是因为在ES5中,全局变量是挂载在顶层对象(浏览器是window)中

严格模式中 this指向undefined

注意ES6的let

let name2 = 'window2'; 
let doSth2 = function(){ 
console.log(this === window); 
console.log(this.name2); 
} 
doSth2() // true, undefined

因为let没有给全局添加属性

隐式绑定

是否由上下文对象调用?绑定到那个上下文对象。

var name = 'window'; 
var showName = function(){ 
    console.log(this.name); 
} 
var student = {
    name: '咸鱼', 
    showName: showName, 
    other: { name: '咸鱼2', showName: showName, } 
} 
student.showName(); // '咸鱼' 
student.other.showName(); // '咸鱼2' 
// 用call类比则为: 
student.showName.call(student); 
// 用call类比则为: 
student.other.showName.call(student.other);

隐式丢失

被隐式绑定的函数会丢失绑定对象

function foo(){
 console.log(this.a)
}
var obj = {
    a:2,
    foo:foo
}
var bar = obj.foo
var a = 'window'
bar(); //window

虽然bar是obj.foo的一个引用,但他引用的是函数本身,因此bar()是不带任何修饰符的的函数调用,使用了默认绑定。

显式绑定

使用### call、apply、bind方法

call apply bind都是改变函数中的this

  • 区别
    • 第一个参数都是控制this指向
    • call 第二个参数是arg1,arg2 :参数列表
    • apply 第二个参数是数组(或者类数组)
    • bindcall类似不过它的返回值是一个函数
var doSth = function(name){ 
    console.log(this); 
    console.log(name); 
} 
doSth.call(2, '咸鱼'); // Number{2}, '咸鱼' 
doSth.apply(2, ['咸鱼2']); // Number{2}, '咸鱼2'
doSth.bind(2, '咸鱼3')(); // Number{2}, '咸鱼3'

实现一个 call /bind 函数

  • this 参数可以传 null,当为 null 的时候,视为指向 window
  • 将函数设为对象的属性
  • 执行该函数
  • 删除该函数
function called(context) {
    const args = Array.prototype.slice.call(arguments, 1);
    context.fn = this;
    if(context) {
        const result = context.fn(...args);
        delete context.fn;
        return result;
    } else {
        return this(...args);
    }
}

function bound(context) {
    const args = Array.prototype.slice.call(arguments, 1);
    const fn = this;
    return function(...innerArgs) {
        const allArgs = [...args, ...innerArgs];
        return fn.apply(fn, allArgs);
    }
}

new绑定

new到底做了什么?

  • 创建了一个对象;
  • 该对象的原型,指向了这个 Function(构造函数)的 prototype;
  • 该对象实现了这个构造函数的方法;
  • 根据一些特定情况返回对象
    • 如果没有返回值,则返回创建的对象;
    • 如果有返回值,是一个对象,则返回该对象;
    • 如果有返回值,不是个对象,则返回创建的对象;

实现一个 new 函数

function newObj(Father) {
    if(typeof Father !== "function") {
        throw new Error("new operator function the frist param must be a function!")
    }
    var obj = Object.create(Father.prototype);
    var result = Father.apply(obj, Array.prototype.slice.call(arguments, 1));
    
    
    return result && typeof result === "object" && result !== null ? result : obj;
}

总结

this 的指向,是根据上下文,动态决定的。

  • 在简单调用时,this 默认指向的是 window / global / undefined (浏览器/node/严格模式)
  • 对象调用时,绑定在对象上;
  • 使用 call . apply . bind 时,绑定在指定的参数上;
  • 使用 new 关键字是,绑定到新创建的对象上; (以上三条优先级:new > apply/call/bind > 对象调用)
  • 使用箭头函数,根据外层的规则决定。