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
}