this 执向

144 阅读5分钟

this 执向规则

  • 默认指向
// 适用场合:默认情况下 
// 指向规则:非严格模式下 this 指向全局对象,严格模式下 this 指向 undefined
var a = "a"; 
function foo() { 
    console.log(this.a); 
} 
foo(); // "a"(非严格模式下)
在浏览器中 this 指向浏览器(window
  • 隐式绑定
// 适用场合:调用函数时,有上下文对象
// 指向规则:this 指向调用链中的最后一层,即函数的直接调用者
var a = "a",
  obj1 = { a: "a1", foo },
  obj2 = { a: "a2", obj1, foo };
function foo() {
  console.log(this.a);
}
obj1.foo(); // 'a1'
obj2.foo(); // 'a2'
obj2.obj1.foo(); // 'a1'

// 在下面的例子中,虽然 bar 指向 obj1.obj2.foo
// 但在实际调用 bar 时,未带任何上下文对象
// 所以会导致隐式丢失,并应用默认绑定
var bar = obj2.obj1.foo;
bar(); // 'a'(非严格模式下)

简单来说, 普通函数调用, 谁调用他, 他的 this 就指向谁, 在对象中, 如果是对象调用他, 那么会指向对象, 这是因为给他隐式绑定了这个对象的上下文

  • 显示绑定
// 适用场合:通过 call、bind、bind 手动改变了 this 指向时
// 指向规则:this 指向 call、apply、bind 的第一个参数
var a = "global_a",
  b = "global_b",
  obj = {
    a: "obj_a",
    b: "obj_b",
  };
function foo(a, b) {
  console.log(this.a, this.b, a, b);
}
foo(a, b); // global_a, global_b, global_a, global_b
foo.call(obj, a, b); // obj_a, obj_b, global_a, global_b
foo.apply(obj, [a, b]); // obj_a, obj_b, global_a, global_b
const fooBind = foo.bind(obj);
fooBind(a, b); // obj_a, obj_b, global_a, global_b

通过 apply、 bind、 call 可以显式绑定函数的 this 执向

  • new 关键字绑定
// 适用场合:使用 new 和构造函数创建实例时
// 指向规则:构造函数内的 this 指向被创建实例
function foo(a) {
  this.a = a;
}
var obj = new foo("a"),
console.log(obj.a); // 'a'

构造函数内部的 this 指向实例对象
  • 箭头函数
// 适用场合:使用箭头函数时
// 指向规则:箭头函数内的 this 指向定义箭头函数所处环境中的 this,即箭头函数外层的 this
// 注意,箭头函数内的 this 指向不能被 call、apply、bind 等改变, 箭头函数的 this 不能被显示的改变, 在声明的时候, 箭头函数的 this 指向就已经被指定了
var a = "a",
  obj = {
    a: "obj",
    foo: function () {
      console.log(this.a);
      (() => {
        console.log(this.a);
      })();
    },
    bar: () => {
      console.log(this.a);
    },
  };
obj.foo(); // 'obj', 'obj'
obj.bar(); // 'a'(非严格模式下)

从原理来说, 箭头函数的 this 指向就是他 outter (外部环境记录器)指向的地方

对象中的普通函数

指向调用链的最后一层,即函数的直接调用者,这样调用的函数有对象上下文, 会隐式绑定直接调用者为 this 的指向,如果采用变量存储, 会丢失隐式上下文, 会采用默认的 this 指向 window

普通函数

简述: 谁调用, 就指向谁

普通函数 this 指向直接调用者, 即谁调用他, 他就指向谁, 默认情况下他指向的是 window

自执行函数

(function () {
   console.log("this", this) // window
 })()

箭头函数中的 this 指向

简述:调用者 指向谁,则指向谁。

var age = 10;  
var person={  
   age:20,  
   getAge:()=>{  
       var age = 30;  
       return this.age;  
    },  
};  
person.getAge(); // 10

上述调用者是 person, person 指向的是 window, 从原理来说: 在声明的时候就已经确定了, 箭头函数的 this 指向。

call、 apply、 bind

简述: 显示改变函数 this 的指向, 我说指向谁, 就指向谁

  • call 实现原理

描述: 改变函数 this 指向, 第一参数为要指向的 this, 后面为函数的参数, 多个参数以逗号分隔, 会立即执行这个函数

// 实现一个 call 
Function.prototype.myCall (context, ...bindData) {
    if (typeof this !== 'function') {
       throw new Error('type Error')
    }
    const self = this // 指向调用的函数实例, 函数本质也是一个对象, Function.__proto__ === Object.prototype
    context = context === null || context === undefined ? globalThis : Object(context)
    // 最终的目的是要将函数的 this 指向传入的对象
    const key = Symbol()
    context[key] = self // 将函数作为对象中的成员
    const res = context[key](...bindData) // 需要执行这个函数,并将数据传入函数
    delete context[key]
    return res // 返回执行结果
}
  • apply 实现原理

简述: 改变函数 this 指向, 第一参数为要指向的 this, 后面为函数的参数, 多个参数以数组的形式传递, 会立即执行这个函数

// 实现一个 apply
Functiion.prototype.myApply(context, paramsArr) {
    if (typeof this !== 'function') {
       throw new Error('type Error')
    }
    const self = this
    context = context = null || undefined ? globalThis : Object(context)
    const key = Symbol()
    context[key] = self
    const res = context[key](...paramsArr)
    delete context[key]
    return res
}

// call 和 apply 实现基本相同

  • bind 实现原理

描述: 接收多个参数, 第一个是要指向的 this, 后面为函数参数, 多个参数以逗号分隔, 返回一个新的函数。

// 实现一个 bind
Function.prototype.myBind (context, ...bindData) {
    if (typeof this !== 'function') {
        throw new Error('type Error')
    }
    const self = this // 这个 this 指向的实例对象, 是一个函数
    return function (...args) { // 返回的函数也可以接受参数
        const paramsArr = bindData.concat(args)
        return self.apply(context, paramsArr)
    }
}

定时器中的 this

简述: 指向 window; 原因:会在独立的执行环境中执行

根据红宝书的描述: 定时器的函数会在全局作用域中的一个匿名函数中执行, 所以他始终指向 window, 如果是箭头函数则不是

当你向 setTimeout() 传递一个函数时,该函数中的 this 指向跟你的期望可能不同,这个问题在 JavaScript 参考中进行了详细解释。

由 setTimeout() 执行的代码是从一个独立于调用 setTimeout 的函数的执行环境中调用的。为被调用的函数设置 this 关键字的通常规则适用,如果你没有在调用中或用 bind 设置 this,它将默认为 window(或 global)对象。它将与调用 setTimeout 的函数的 this 值不一样。

在使用定时器时, 可以借用箭头函数的特性(不会改变 this 指向)

<form action="" class="example-form">
    <div>
        <label for="name">
            名称
        </label>
        <input class="input-ele" type="text" name="name" id="name" placeholder="please input your name"
            autocomplete="off">
    </div>
    <div style="margin-top:50px;">
        <label for="res">
            输入
        </label>
        <textarea class="input-ele" type="multipart" name="res" id="res" readonly
            placeholder="这里是每一次输入的结果"></textarea>
    </div>
</form>

<script>
    window.onload = function () {
        const resEle = document.querySelector("#res");
        function changeOutputVal() {
            resEle.value += `\n${ this.value }`;
        }
        function throttle(fun, delay) {
            let last, deferTimer
            return function () {
                let now = Date.now();
                if (last && now < last + delay) {
                    clearTimeout(deferTimer);
                    deferTimer = setTimeout( () =〉 {
                        last = now;
                        fun.apply(this); // 这里的 this 会指向 inputEle
                    }, delay)
                } else {
                    last = now;
                    fun.apply(this);
                }
            }
        }
        const inputEle = document.querySelector("#name");
        inputEle.addEventListener("input", throttle(changeOutputVal, 1000));
    }
</script>

也可以记录 this 值


window.onload = function () {
    // some code here
    
    const that = this;
    deferTimer = setTimeout(function () {
        last = now;
        fun.apply(that);
    }, delay)
    
    // some code here
}