前言:面试的一个重要考察点——this 对于一些人来讲一直是个老大难。虽然面试题目变化万千,但只要真正掌握了原理,不少问题就能迎刃而解的。node
下面我整理了一下判断this指向的3个步骤:es6
第一准则是:this永远指向函数运行时所在的对象,而不是函数被建立时所在的对象(箭头函数则相反)。面试
既然this指向取决于函数调用时的位置,那咱们先看看如何确认调用位置。浏览器
首先介绍一下什么是调用栈和调用位置。bash
function a() {
// 调用栈: c->b->a
// 当前调用位置在b中
console.log(a);
}
function b() {
// 调用栈: c->b
// 当前调用位置在c中
a()
}
function c() {
// 调用栈: c
// 当前调用位置是全局做用域
b();
}
c(); // c的调用位置
复制代码
完成了第一步:肯定了函数的调用位置,下面看看第二步 this的四个绑定规则。app
能够这样理解:没法应用其余绑定规则时为默认规则,独立函数调用时通常是默认绑定。函数
function test(){
console.log(this.a);
}
var a = 'aaa'; // 或者window.a='aaa'
test(); // 输出aaa
复制代码
上例中,test()不在任何对象内,是独立调用,属于默认绑定。ui
(ps:默认绑定this指向的全局对象,在浏览器里面是window,在node中是global, 在严格模式下,若是 this 没有被执行上下文定义,this指向为 undefined。)this
隐式绑定存在于在调用位置有上下文对象或者说调用时被对象包含或拥有。es5
var obj1 = {
name: 'kelvin',
say: function(){
console.log(this.name);
}
}
obj1.say();// kelvin
// 或者
function say() {
console.log(this.name);
}
var obj2 = {
name: 'kelvin',
say: say
}
obj2.say(); // kelvin
复制代码
此时能够用一句简单的话归纳:谁去调用this就指向谁。不过有时这个规则会在不经意间失效,这种现象就叫隐式丢失。
// 第一种
function fun() {
console.log(this.a)
}
function doFun(fn) {
// 参数传递其实就是一个隐式的赋值
fn();
}
var obj = {
a: 'obj a',
fun: fun
}
var a = 'global a';
doFun(obj.fun); // 'global a'
复制代码
显式绑定里面有硬绑定和某些API调用的"上下文"控制this
1)硬绑定
硬绑定就是咱们常常看到的call、apply、bind(es5中)三个方法,此处省略实例。
这里我扩展一下三个方法的实现原理
1. call()
Function.prototype.myCall = function (context) {
var context = context || window
// 给 context 添加一个属性
// getValue.call(a, 'yck', '24') => a.fn = getValue
// this 指代调用call方法的当前函数
context.fn = this
// 将 context 后面的参数取出来
var args = [...arguments].slice(1)
// getValue.call(a, 'yck', '24') => a.fn('yck', '24')
var result = context.fn(...args)
// 删除 fn
delete context.fn
return result
}
2. apply()
Function.prototype.myApply = function (context) {
var context = context || window
context.fn = this
var result
// 须要判断是否存储第二个参数
// 若是存在,就将第二个参数展开
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
3. bind()
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var _this = this
var args = [...arguments].slice(1)
// 返回一个函数
return function F() {
// 由于返回了一个函数,咱们能够 new F(),因此须要判断
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
结论:能够看出call、apply是利用“隐式绑定规则”,经过将当前函数this放置到context对象中,为context所包含或拥有,从而达到this指向context的目的。bind的不一样之处是它返回一个新函数,且内部使用apply实现。
复制代码
2)API调用的"上下文"
JS中有几个内置函数,filter、forEach等都有一个可选参数,在执行 callback 时的用于指定 this 值。以forEach为例子:
var person = {
name: '小张'
}
function say(item) {
console.log(this.name + ' ' + item)
}
[1,2,3,4].forEach(say, person)
//小张 1
//小张 2
//小张 3
//小张 4
复制代码
首先看new作的4件事:
又new所作的第三件事情可知:this指向新对象
PS: 四种规则的优先级以下: new 绑定>显式绑定>隐式绑定>默认绑定。
凡事有例外,es6中引入的箭头函数却不适用于上面的四个规则。看代码以下:
function a(){
return ()=>{
console.log(this.name);
}
}
const obj1={
name: “张”,
}
const obj2={
name: “刘”,
}
var test = a.call(obj1);
test.call(obj2); // 张
复制代码
由输出结果:“张”,能够看出箭头函数是不适用于上面的四规则的。箭头函数的具体规则是:箭头函数this是在声明时就肯定了,其this就是声明时所在做用域的this肯定的。好比上面的例子,箭头函数是在a函数中声明的,因此箭头函数中所用的this就是a的this,而a中的this是根据调用位置和规则肯定是obj1,因此箭头函数中this也是指向obj1。
到此对this的掌握程度应该足以应对不少面试题了,谢谢你们的阅读!
Over,thanks!