【JS】this关键字的相关问题
我是目录
- 引言
- this
- 为什么要用this
- 对this的误解
- this并不指向自身
- this 并不指向普通函数的作用域
- this的绑定方式
- 默认绑定(Default Binding)
- 隐式绑定(Implicit Binding)
- 隐式绑定丢失
- 面试实战
- 显式绑定(Explicit Binding)
- 硬绑定
- 软绑定
- call apply bind的区别
- call函数实现
- apply函数实现
- new绑定原理
- 实现new关键字的方法
- 四种绑定方式的优先级
- 显式绑定的优先级高于隐式绑定。
- new绑定的优先级高于隐式绑定。
- 箭头函数中的this
- 箭头函数的绑定无法被修改
- 不适合箭头函数的场景
引言
JS中this关键字,面试时候一般不太会直接提问,一般会问:new的实现;call/apply的区别等等这种比较初级的知识。这篇主要总结一下this的相关知识,主要包括this的指向、绑定方式、call/apply、new的实现等等。
this
this
提供了一种优雅的方式来隐式传递一个对象引用,API可以设计的更加简洁且易于复用。如果不用this的话,就必须给一些函数显式传入上下文对象。this
使得函数可以自动引用合适的上下文对象。
this
的指向,是在调用函数时根据执行上下文所动态确定的,它指向调用它的对象。
this
是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this
的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
在之前的【JS】作用域、执行上下文与闭包中提到,当一个函数被调用时, 会创建一个执行上下文。这个执行上下文中会包含函数传入的参数、变量对象、作用域链等信息。this 的指向就是在执行上文中被确定的。
为什么要用this
this
可以隐式传递一个对象引用,使代码更加简洁。
function identify(context) {return context.name.toUpperCase();
}
function speak(context) {var greeting = "Hello, I'm " + identify( context );console.log( greeting );
}
identify( you ); // READER
speak( me ); //hello, 我是 KYLE
上面的一段函数就是显式传入了上下文对象,可以用this
进行改写:
function identify() {return this.name.toUpperCase();
}
function speak() {var greeting = "Hello, I'm " + identify.call( this );console.log( greeting );
}
var me = {name: "Kyle"
};
var you = {name: "Reader"
};
identify.call( me ); // KYLE
identify.call( you ); // READER
speak.call( me ); // Hello, 我是 KYLE
speak.call( you ); // Hello, 我是 READER
为什么需要从函数内部引用函数自身?
常见的原因是递归( 从函数内部调用这个函数) 或者可以写一个在第一次被调用后自己解除绑定的事件处理器。
function foo() {foo.count = 4; // foo 指向它自身
}
setTimeout(function() {// 匿名( 没有名字的) 函数无法指向自身
}, 10);
对this的误解
- this并不指向自身
- this 并不指向普通函数的作用域
this并不指向自身
在函数内部的this,很容易让人误认为它指向函数自身。
function foo(num) {console.log( "foo: " + num );// 记录 foo 被调用的次数this.count++;
}
foo.count = 0;
var i;
for (i = 0; i < 10; i++) {if (i > 5) {foo( i );}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo 被调用了多少次?
console.log( foo.count ); // 0
foo函数确实被调用了4次,但是foo.count其实还是0。在foo函数中看似累加的count其实每次都是创建了一个全局变量,它的值是NaN。
想解决的话只要强制改变this指向就可以
function foo(num) {console.log( "foo: " + num );// 记录 foo 被调用的次数// 注意, 在当前的调用方式下, this 确实指向foothis.count++;
}
foo.count = 0;
var i;
for (i=0; i < 10; i++) {if (i > 5) {// 使用 call(..) 可以确保 this 指向函数对象 foo 本身foo.call( foo, i );}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo 被调用了多少次?
console.log( foo.count ); // 4
this 并不指向普通函数的作用域
this 在任何情况下都不指向函数的词法作用域。 在 JavaScript 内部, 作用域确实和对象类似,可见的标识符都是它的属性。但是作用域“ 对象” 无法通过 JavaScript代码访问, 它存在于 JavaScript 引擎内部。
function foo() {var a = 2;this.bar();
}
function bar() {console.log( this.a );
}
foo(); // ReferenceError: a is not defined
这段代码试图通过 this.bar()
来引用 bar() 函数。这是绝对不可能成功的。调用 bar()
最自然的方法是省略前面的 this
,直接使用词法引用标识符。
此外,编写这段代码的开发者还试图使用 this
联通 foo()
和 bar()
的词法作用域,从而让bar()
可以访问 foo()
作用域里的变量 a
。这是不可能实现的,不能使用 this
来引用一个词法作用域内部的东西。
每当想要把 this
和词法作用域的查找混合使用时,一定要提醒自己,这是无法实现的。
this的绑定方式
在了解this的绑定方式之前,需要知道什么是调用位置(call site)。
调用位置是指函数在代码中被调用的位置( 而不是声明的位置),需要分析调用栈。我们关心的调用位置就在当前正在执行的函数的前一个调用中。
function baz() {// 当前调用栈是: baz// 因此, 当前调用位置是全局作用域console.log( "baz" );bar(); // <-- bar 的调用位置
}