约定以下只讨论浏览器下的this
函数的this是在调用时绑定的,this的指向取决于函数调用的位置。
调用位置:函数在代码中被调用的位置,而不是声明的位置。难点在于编程模式可能会隐藏真正的调用位置。
绑定规则
默认绑定
- 非严格模式下,在全局作用域中,即所有函数体的外部,this指向Window
<script type="text/javascript" charset="utf-8">
console.log(this) //Window
</script>
- 非严格模式下,在函数作用域中,this指向Window
<script type="text/javascript" charset="utf-8">
function foo(){
console.log(this) // Window
}
foo()
</script>
- 严格模式下,在全局作用域中,即所有函数体的外部,this指向Window或者undefined
//在js脚本中
<script type="text/javascript" charset="utf-8">
"use strict"
console.log(this) //Window
</script>
//在模块中,自动开启严格模式
<script type="module" charset="utf-8">
console.log(this) //undefined
</script>
- 严格模式下,在函数作用域中,this的值为undefined
<script type="text/javascript" charset="utf-8">
function foo(){
"use strict"
console.log(this) // Window
}
foo()
</script>
隐式绑定
调用位置上存在上下文
- 实例
//普通版
function foo (){
console.log(this.a)
}
var obj = {
a:2,
foo:foo
}
obj.foo() //2
//加强版:对象属性引用链中只有最后一层会影响调用位置
function foo (){
console.log(this.a)
}
var obj2 = {
a : 42
foo:foo
}
var obj1 = {
a : 2,
obj2 : obj2
}
obj1.obj2.foo() //42
- 方式二 绑定丢失
function foo(){
console.log(this.a)
}
var obj = {
a : 2,
foo : foo
}
var a = 'oops,global'
obj.foo() // '2'
var bar = obj.foo //注意:函数作为参数传递给函数的时候也相当于一次赋值。
bar() // 'oops,global'
显示绑定
- call、apply、bind进行绑定
funciton foo(){
console.log(this)
}
var obj = {
a : 2
}
foo.call(obj) // 2
foo.apply(obj) // 2
foo.bind(obj)() //2
- 硬绑定:将显示绑定包裹在函数中
function foo (){
console.log(this.a)
}
var obj = {
a : 2
}
var bar = function(){
foo.call(obj)
}
bar() // 2
setTimeout(bar, 100) // 2
bar.call(window) //2 硬绑定的this一旦绑定了就不能修改了
- 如何实现一个bind函数
//ES5
Function.prototype.bind = function(){
var self = this
//获取调用bind时候传递的参数
var rest1 = Array.prototype.slice.call(arguments)
var context = rest1.shift()
return function(){
var rest2 = Array.prototype.slice.call(arguments)
return self.apply(context,rest1.concat(rest2))
}
}
//ES6
Function.prototype.bind = function(...rest1)
{
const context = rest1.shit()
return (...rest2)=>{
return self.apply(context,[...rest1,...rest2])
}
}
new绑定
使用new创建一个对象的时候发生了什么
function myNew = function(func){
var o = Object.create(func.prototype) //对应下面第一条 和 第二条
var k = func.call(o) //对应第三条 第四条
//对应第5条
if(typeof k ==='object'){
return k
}else{
return o
}
}
- 创建一个全新的空对象
- 这个新对象会被执行[[Prototype]]连接(构造函数的prototype被赋值给这个新对象的_proto_)
- 构造函数的this指向这个对象
- 执行构造方法,属性和方法被添加到这个对象中
- 如果函数没有返回其他对象,this指向这个新对象。如果有返回值则返回构造函数中返回的对象
function Foo (a){
this.a = a
}
var bar = new Foo(2)
bar.a // 2
- 如果函数的返回值是fuction或object,this指向返回的对象。
function Foo (a){
this.a = a
return {
a:2
}
}
var bar = new Foo(4)
bar.a // 2
绑定的优先级
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
- 显式绑定和隐式绑定的比较
function foo (){
console.log(this.a)
}
var obj1 = {
a : 1,
foo : foo
}
var obj2 = {
a : 2,
foo : foo
}
obj1.foo() //1
obj2.foo() //2
obj1.foo.call(obj2) //2
obj2.foo.call(obj1) //1
说明:显示绑定 > 隐式绑定
- new绑定和隐式绑定
function foo (){
console.log(this.a)
}
var obj1 = {
a : 1,
foo : foo
}
var obj2 = {
a : 2,
foo : foo
}
obj1.foo() //1
obj1.foo.call(obj2) //2
var bar = new obj1.foo()
例外
- 如果把null、undefine作为this的绑定对象传入call、apply、bind,非严格模式下,实际应用的是默认绑定
function foo (){
console.log(this.a)
}
var a = 1
foo.call(null) //1
箭头函数
待补充
练习题
var a = 20
var obj = {
a: 40,
foo:() => {
console.log(this.a)
function func() {
this.a = 60
console.log(this.a)
}
func.prototype.a = 50
return func
}
}
var bar = obj.foo() // 20
bar() // 60
new bar() // 60
var a = 20
var obj = {
a: 40,
foo: function() {
console.log(this.a)
function func() {
this.a = 60
console.log(this.a)
}
func.prototype.a = 50
return func
}
}
var bar = obj.foo() // 40
bar() // 60
new bar() // 60