乐趣区

关于javascript:彻底弄懂js中this指向包含js绑定优先级面试题详解

为什么要应用 this

在 javascript 中,this 堪称是无处不在,它能够用来指向某些元素、对象,在适合的中央应用 this,能让咱们缩小无用代码的编写

var user = {
  name: "aclie",
  sing: function () {console.log(user.name + '在唱歌')
  },
  dance: function () {console.log(user.name + '在跳舞')
  },
  study: function () {console.log(user.name + '在学习')
  },
}

以上这段代码中,每个办法都须要用到 user 对象中的 name 属性,如果当 user 对象名称发生变化,那么所有办法都要改变,这种状况下,应用 this 是个很好的抉择

var user = {
  name: "aclie",
  sing: function () {console.log(this.name + '在唱歌')
  },
  dance: function () {console.log(this.name + '在跳舞')
  },
  study: function () {console.log(this.name + '在学习')
  },
}

this 的指向

this 的指向和函数在哪里定义无关,和如何调用无关

以下 foo 函数调用形式不同,this 的值也不同

 function foo(){console.log(this)
}

foo()

var obj = {foo: foo}
obj.foo() 

obj.foo.apply("hello")    

执行后果如下图所示

this 四种绑定形式

一、默认绑定

当函数独立调用时,this 默认绑定 window

// 1、间接调用
function foo() {console.log(this)
}
foo()

// 2、对象中的函数
var obj1 = {foo: foo}
var fn1 = obj1.foo
fn1()

// 3、被全局变量援用
var obj2 = {bar: function () {console.log(this)
  }
}
var fn2 = obj2.bar
fn2()

// 4、函数嵌套调用
function foo1() {console.log('foo1', this)
}
function foo2() {console.log('foo2', this)
  foo1()}
function foo3() {console.log('foo3', this)
  foo2()}
foo3()

// 5、通过闭包调用
var obj2 = {bar: function () {return function () {console.log(this)
    }
  }
}
obj2.bar()()

执行后果如下

以上五种调用形式全都属于默认绑定,因为他们最终都是独自的对函数进行调用

二、隐式绑定

调用的对象外部有对函数的援用

function foo() {console.log(this)
}

var obj1 = {
  name: 'obj1',
  foo: foo
}
obj1.foo()

var obj2 = {
  name: 'obj2',
  bar: function () {console.log(this)
  }
}
obj2.bar()

var obj3 = {
  name: 'obj3',
  baz: obj2.bar
}
obj3.baz()

以上代码执行后果为

以上三种都属于隐式绑定,他们都是通过对象调用,this 就指向了该对象

三、显式绑定

不心愿在对象外部蕴含这个函数的援用,但又心愿通过对象强制调用,应用 call/apply/bind 进行显式绑定

function foo() {console.log(this)
}
var obj = {name: 'obj1',}

foo.call(obj)
foo.apply(obj)
foo.call("xxx")

以上代码的执行后果为

foo 函数间接调用 this 应该指向 window,这里通过 call/apply 来扭转了 this 的指向

四、new 绑定

通过 new 关键字来创立构造函数的实例,绑定 this

function Person(name, age) {
  this.name = name
  this.age = age
}
const p1 = new Person('alice', 20)
const p2 = new Person('mogan', 24)
console.log(p1)
console.log(p2)

以上代码的执行后果如下

此时 this 指向的是通过 new 创立的实例对象

this 绑定的优先级

一、隐式绑定高于默认绑定

function foo() {console.log(this)
}

var obj = {
  name: 'obj',
  foo: foo
}
obj.foo()

以上代码执行后果为

foo 函数默认绑定 window 对象,当同时存在隐式绑定和默认绑定时,隐式绑定优先级高于默认绑定

二、显示绑定高于隐式绑定

// 案例一
var user = {
  name: 'user',
  foo: function(){console.log(this)
  }
}
user.foo.call('kiki')

// 案例二
function foo() {console.log(this)
}
var obj = {
  name: "obj",
  foo: foo.bind("aclie")
}
obj.foo()

以上代码的执行后果为

如果隐式绑定优先级更高的话,this 的指向应该都为对象,但依据以上执行后果得悉 this 绑定为显示绑定的后果,所以当同时存在隐式绑定和显示绑定时,显示绑定的优先级高于隐式绑定

三、new 高于隐式绑定

var user = {
  name: 'lisa',
  foo: function () {console.log(this)
  }
}
new user.foo()

以上代码的执行后果如下

当同时存在于 new 关键字绑定和隐式绑定时,this 绑定了 foo 构造函数,所以 new 关键字的优先级高于隐式绑定

四、new 高于显示绑定

function bar(){console.log(this)
}
var fn = bar.bind('hello')
new fn()

以上代码的执行后果如下

当同时存在于 new 关键字绑定和显示绑定时,this 绑定了 bar 构造函数,所以 new 关键字的优先级高于显示绑定

综上,以上四种绑定的优先级程序为

new 关键字 > 显式绑定 > 隐式绑定 > 默认绑定

规定之外

还有几种非凡的绑定形式,不在上述四种绑定规定中

一、疏忽显示绑定

当显示绑定的值为 null/undefined 时,this 间接绑定 window

var user = {
  name: 'alice',
  foo: function () {console.log(this)
  }
}
user.foo()
user.foo.call(null)
user.foo.apply(undefined)

以上代码执行后果如下

二、间接函数援用

var obj1 = {
  name: 'obj1',
  foo: function () {console.log(this)
  }
}
var obj2 = {name: 'obj2'};
obj2.baz = obj1.foo;
obj2.baz();

(obj2.bar = obj1.foo)()

以上代码的执行后果为

两种形式所绑定的 this 不同,第二种形式进行了赋值调用,实际上是间接函数援用,(obj2.bar = obj1.foo)这里返回了赋值的后果,再加上一个小括号,就间接调用赋值的后果函数

三、箭头函数

箭头函数是不绑定 this 的,它的 this 来源于下级作用域

var user = {
  name: 'kiki',
  foo: () => {console.log('箭头函数中的 this',this)
  }
}
user.foo()

以上代码的执行后果如下

这里调用 foo 函数,因为箭头函数不绑定 this,所以去 foo 函数的下级查找 this,找到了全局对象 window

面试题

1、考查间接函数援用

var name = "window";
var person = {
  name: "person",
  sayName: function () {console.log(this.name);
  }
};
function sayName() {
  var sss = person.sayName;
  sss(); 
  person.sayName(); 
  (person.sayName)(); 
  (b = person.sayName)();}
sayName();

执行 sayName 函数

  • 变量 sss 被 person.sayName 办法赋值,执行 sss 函数,此时是独立函数调用,this 指向全局 window,全局中变量 name 被绑定到了 window 中,所以 this.name 为 ”window”
  • person.sayName() 为隐式绑定,this 指向 person 对象,所以 this.name 为 person.name,即 ”person”
  • (person.sayName)() 与前一个实质是一样的,隐式绑定,this 指向 person 对象,所以 this.name 为 person.name,即 ”person”
  • (b = person.sayName)() 是间接函数援用,person.sayName 赋值给 b 变量,而小括号括起来的代表赋值的后果,this 指向 window,this.name 为 window.name,即 ”window”

所以执行后果为

2、定义对象时是不产生作用域的

var name = 'window'
var person1 = {
  name: 'person1',
  foo1: function () {console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {return function () {console.log(this.name)
    }
  },
  foo4: function () {return () => {console.log(this.name)
    }
  }
}
var person2 = {name: 'person2'}

person1.foo1();
person1.foo1.call(person2);

person1.foo2();
person1.foo2.call(person2);

person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);

person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);

调用过程剖析

  1. foo1 函数

    • person1.foo1() 隐式绑定,this 指向 person1,this.name 为 person1.name,即 “person1”
    • person1.foo1.call(person2) 隐式绑定 + 显示绑定 person2,显示绑定优先级更高,所以 this 指向 person2,this.name 为 person2.name,即 “person2”
  2. foo2 函数

    • person1.foo2() 隐式绑定,箭头函数没有本人的 this,所以向下层作用域查找,找到了全局 window(person1 是对象,定义它的时候不产生作用域),全局变量 name 被绑定到了 window 中,this.name 为 window.name,即 “window”
    • person1.foo2.call(person) 隐式绑定 + 显示绑定,然而 箭头函数不绑定 this,这里的显示绑定有效,没有本人的 this,向下层作用域查找,找到全局 window,this.name 为 window.name,即 “window”
  3. foo3 函数

    • person1.foo3()() 这里相当于执行 person1.foo() 的返回函数,这里是独立函数调用,this 指向全局 window,this.name 为 window.name,即 “window”
    • person1.foo3.call(person2)() 这里通过 call 扭转的是 foo3 函数中 this 的指向,但最终执行的是 foo3 函数返回的闭包,闭包作为独立函数调用,this 依然指向全局 window,this.name 为 window.name,即 ’window”
    • person1.foo3().call(person2) 这里将 foo3 函数返回的闭包显示绑定了 person2 对象,this 指向 person2,this.name 为 person2.name,即 ”person2″
  4. foo4 函数

    • person1.foo4()() 执行 person1.foo() 的返回值,返回的闭包是箭头函数没有 this 的,向下层作用域查找,找到了 foo4 函数,foo4 的 this 指向 person1,所以闭包的 this 也指向 person1,thiss.name 为 person1.name,即 “person1”
    • person1.foo4.call(person2)() 返回的闭包没有 this,向下层作用域找到了 foo4 函数,foo4 函数的 this 通过显示绑定变成了 person2,所以闭包的 this 也指向 person2,this.name 为 person2.name,即 ”person2″
    • person1.foo4().call(person) 返回的闭包是箭头函数,无奈通过 call 进行显示绑定,间接向下级作用域查找,找到 foo4 函数,foo4 的 this 指向 person1,所以闭包的 this 指向 person1,this.name 为 person1.name,即 ”person1″

上述代码的执行后果如下

3、构造函数中定义函数,该函数的下级作用域是构造函数

var name = 'window'
function Person (name) {
  this.name = name
  this.foo1 = function () {console.log(this.name)
  },
  this.foo2 = () => console.log(this.name),
  this.foo3 = function () {return function () {console.log(this.name)
    }
  },
  this.foo4 = function () {return () => {console.log(this.name)
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1()
person1.foo1.call(person2)

person1.foo2() 
person1.foo2.call(person2)

person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)

person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2) 

调用剖析过程

  1. foo1 函数

    • person1.foo1() 隐式绑定,this 指向 person1,person1 创立实例时传入 name 为 person1,所以 this.name 为 person1
    • person1.foo1.call(person2) 隐式绑定 + 显示绑定,显示绑定优先级更高,绑定 person2,person2 创立实例时传入的 name 为 person2,所以 this.name 为 person2
  2. foo2 函数

    • person1.foo2() 隐式绑定,但 foo2 是箭头函数,没有本人的 this,向下层作用域查找,找到了 Person 构造函数,此时 this 是指向 person1 这个对象的,而 person1 实例化时传入的 name 为 person1,所以 this.name 为 person1
    • person1.foo2.call(person2) 隐式绑定 + 显式绑定,但 foo2 是箭头函数,不绑定 this,所以 this 依然须要向下层作用域查找,找到 Person 构造函数,this 指向 person1 对象,所以 this.name 为 person1
  3. foo3 函数

    • person1.foo3()() 执行 person1.foo3 的返回值,返回的函数是独立调用,this 指向 window,全局的 name 变量被绑定到 window 中,this.name 为 window.name,即 “window”
    • person1.foo3.call(person2)() 显式绑定更改的是 foo3 函数的 this,最终执行的是 foo3 函数的返回值,依然是函数的独立调用,所以 this 指向 window,this.name 为 window.name,即 “window”
    • person1.foo3().call(person2) foo3 函数的返回函数通过显示绑定将 this 绑定到了 person2 中,person2 创立实例时传入的 name 为 person2,所以 this.name 为 person2
  4. foo4 函数

    • person1.foo4()() 执行 foo4 函数的返回值,返回函数为箭头函数,没有 this,所以向下层作用域查找,找到 foo4 函数的 this 指向 person1,所以箭头函数的 this 也指向 person1,所以 this.name 为 person1
    • person1.foo4.call(person2)() foo4 通过显示绑定将 this 绑定成了 person2,返回的函数为箭头函数,this 与父级作用域 foo4 统一,所以箭头函数的 this 也指向 person2,所以 this.name 为 person2
    • person1.foo4().call(person2) foo4 函数的返回值为箭头函数,不绑定 this,这里显示绑定有效,向下级作用域查找 this,找到 foo4 函数,this 指向 person1

执行后果如下

4、辨别作用域

var name = 'window'
function Person (name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {return function () {console.log(this.name)
      }
    },
    foo2: function () {return () => {console.log(this.name)
      }
    }
  }
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()() 
person1.obj.foo1.call(person2)() 
person1.obj.foo1().call(person2) 
person1.obj.foo2()() 
person1.obj.foo2.call(person2)() 
person1.obj.foo2().call(person2) 
  1. foo1 函数

    • person1.obj.foo1()() 执行 foo1 函数的返回函数,此时该函数为独立函数调用,this 指向 window,全局变量 name 被增加到 window 中,这里的 this.name 指向 window.name,即 “window”
    • person1.obj.foo1.call(person2)() 这里显示绑定扭转 foo1 中 this 的指向,但最终执行的是 foo1 函数的返回值,返回函数作为独立函数调用,this 依然指向 window,所以 this.name 为 window.name,即 “window”
    • person1.obj.foo1().call(person2) 这里通过显示绑定更改 foo1 函数的返回函数中 this 的指向,所以该函数 this 指向 person2,而 person2 在实例化的时候传入 name 值为 person2,所以 this.name 为 person2
  2. foo2 函数

    • person1.obj.foo2()() 执行 foo2 的返回函数,此时该函数为独立函数调用,但它本人没有 this,要向下级作用域查找,找到 foo2 函数的 this 指向 obj,所以该函数的 this 也指向 obj,this.name 为 obj.name,即 “obj”
    • person1.obj.foo2.call(person2)() 执行 foo2 的返回函数,此时该函数为独立函数调用,但它本人没有 this,要向下级作用域查找,foo2 函数的 this 通过显示绑定变成 person2,所以该函数的 this 也为 person2,而 person2 在实例化的时候传入 name 值为 person2,所以 this.name 为 person2
    • person1.obj.foo2().call(person2) foo2 的返回函数为箭头函数,不绑定 this,显式绑定有效,也没有本人的 this,要向下级作用域查找,找到 foo2 函数的 this 指向 obj,所以该函数的 this 也指向 obj,this.name 为 obj.name,即 “obj”

所以执行后果为

以上就是对于 this 指向的了解,对于 js 高级,还有很多须要开发者把握的中央,能够看看我写的其余博文,继续更新中~

退出移动版