JavaScript中的this指向绑定规则(超全)
1.1 为什么需要this?
为什么需要this?
在常见的编程语言中,几乎都有this这个关键字(Objective-C中使用的是self),但是在JavaScript中的this和常见的面向对象语言中的this不太一样
- 常见面向对象的编程语言中,比如Java,C++,Swift、Dart等等一系列语言中,this通常只会出现在类的方法中
- 也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象
但是JavaScript中的this更加灵活无论是它出现的位置还是它代表的含义
我们来看一下编写一个obj的对象,有this和没有this的区别
从上面我们可以看出:this的作用就是提高的代码的复用性,不用随着对象名字的改变而改变,直接this指向当前对象即可
1.2 目前掌握两个this的判断方法
1.2.1 以默认的方式调用一个函数,this指向window
- 代码示例:
function foo(name,age) {
console.log(arguments)
console.log(this)
}
foo("123","hahah","xiix")//默认调用,没有对象引用
function sayHello(name) {
console.log(this)
}
- 结果分析
- 思考题:下述代码fn调用的this指向什么?
var obj = {
name:"why",
running:function() {
console.log(this)
console.log(this===obj)
},
eating:function() {
console.log("eaintg~",this.name)
},
eaing:function() {
console.log("studying~",this.name)
}
}
//题目一
obj.running()
var fn = obj.running
fn()
答案:默认调用,指向window对象
function bar() {
console.log(this)
}
var obj = {
name:"why",
"bar":bar
}
obj.bar()//谁调用它,对象就会指向哪个
答案:指向obj对象,被obj对象调用
1.2.2 通过对象调用,this指向调用的对象
- 代码案例
var obj = {
name:"why",
running:function() {
console.log(this)//指向的就是obj
console.log(this===obj)
},
eating:function() {
console.log(this)
},
eaing:function() {
console.log(this)
}
}
obj.running()
结果分析:被obj对象调用,所以指向obj这个对象
- 思考题:下述代码调用的this指向什么?
function bar() {
console.log(this)
}
var obj = {
name:"why",
"bar":bar
}
obj.bar()//谁调用它,对象就会指向哪个
答案:指向obj对象,被obj对象调用
1.3 this到底指向什么呢?
我们先来看一个令人困惑的问题:
- 定义一个函数,我们采用三种不同的方式对它就行调用,它产生了三中不同的效果
这样的案例可以给我们什么样的启示呢?
- 函数在调用时,JavaScript会默认给this绑定一个值
- this的绑定和定义的位置(编写的位置)没有关系
- this绑定和调用的方式以及调用的位置有关系
- this是在运行时被绑定的
1.4 this的绑定规则
- 绑定规则一:默认绑定;
- 绑定规则二:隐式绑定;
- 绑定规则三:显示绑定
- 绑定规则四:new绑定
1.4.1 规则一:默认绑定
什么情况下使用默认绑定呢?独立函数调用
独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用
//1.定义函数
function foo() {
console.log("foo:",this)
}
foo()//默认调用,this指向window
//2.函数定义在对象中,但是独立调用
var obj = {
name:"why",
bar:function() {
console.log("bar:",this)
}
}
var baz = obj.bar
baz()//独立函数调用
//3.高阶函数
function test(fn) {
fn()
}
test(obj.bar)
严格模式下,独立函数调用的函数中的this指向的是undefined
"use strict"
//1.定义函数
function foo() {
console.log("foo:",this)
}
foo()//默认调用,this指向window
//2.函数定义在对象中,但是独立调用
var obj = {
name:"why",
bar:function() {
console.log("bar:",this)
}
}
var baz = obj.bar
baz()//独立函数调用
1.4.2 隐式绑定
另外一种比较常见的调用方式就是通过某个对象进行调用的
- 也就是它的调用位置中,是通过某个对象发起的函数调用
//你不知道的JavaScript(上中下)
function foo() {
console.log("foo函数:",this)
}
var message = "Hello World"
var obj = {
bar:foo
}
obj.bar()
偷偷的把这个this绑定到这个对象上了
1.4.3 new绑定
JavaScript中函数可以当做一个类的构造函数来使用,也就是new关键字
使用new关键字来调用函数时,会执行如下的操作
- 创建一个全新的对象
- 这个对象会被执行prototype连接
- 这个新对象会绑定到函数调用的这个this上(this绑定在这个步骤完成
- 如果函数没有返回其他对象,表达式辉返回这个新对象
/*
1. 创建新的空对象
2.将this指向这个空对象
3.执行函数体中的代码
4.没有显示返回这个非空对象时,默认返回这个对象
*/
function foo() {
console.log("foo函数:",this)
this.name = "why"
}
new foo()
1.4.4 this的绑定规则四-显示绑定
隐式绑定有一个前提条件
- 必须在调用的对象内部有一个对函数的引用(比如一个属性)
- 如果没有这样的引用,在进行调用时,会报找不到该函数的错误
- 正是通过这个引用,间接将this绑定到了这个对象上
如果我们不希望在对象内部包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?
-
JavaScript所有的函数都可以使用call和apply方法
- 第一个参数是相同的,要求传入一个对象
- 这个对象的作用是什么呢?就是给this准备的
- 在调用这个函数时,会将this绑定到这个传入的对象中
- 后面的参数,apply为数组,call为参数列表
- 第一个参数是相同的,要求传入一个对象
-
因为上面的过程,我们明确的绑定了this指定的对象,所以称之为显示绑定
var obj = {
name:"why"
}
function foo() {
console.log("foo函数:",this)
}
//执行函数,并且函数中的this指向obj这个对象
obj.foo = foo//先把这个函数放到obj里面
obj.foo()
//这个绑定方法比较麻烦,需要在对象里面添加一个属性,然后在把属性赋值给当前对象,在调用,太麻烦了
//直接执行函数,并且强制this就是obj对象
foo.call(obj)
foo.call(123)
foo.call("abc")
foo.call(undefined)//指向window
//call/apply
function foo(name,age,height) {
console.log("foo函数被调用:",this)
}
//()调用
foo("why",18,1.88)//window
//apply
//第一个参数:绑定this
//第二个参数:传入额外的实参,以数组的形式
foo.apply("apply",["kobe",30,1.98])//String
//call
//第一个参数:绑定this
//参数列表:后续的参数是以我们多参数来传递的,会作为我们的实参
foo.call("call","james",25,2,2.05)
如果我们希望this总是绑定到一个对象上,可以怎么做呢?
- 使用bind方法,bind()方法创建一个新的绑定函数(bound function,BF);
1.4.5 call、apply、bind
- 通过call或者apply绑定this对象
显示绑定后,this就会明确的指向绑定的对象
function foo() {
console.log(this);
}
foo.call(window);//window
foo.call({name:"why"});
foo.call(123); //Number对象,存放时123
如果我们希望一个函数总是显示的绑定到一个对象上,可以怎么做呢
- 使用bind方法,bind()方法创建一个新的绑定函数(boud function,BF)
- 绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015中的术语)
- 在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用
function foo() {
console.log("foo:",this)
}
var obj = {name:"why"}
//需求:调用foo时,总是绑定到obj对象身上(不希望obj对象身上有我们的函数)
var bar = foo.bind(obj)//其实在执行这行foo.bind(obj)时,生成了一个新的函数,原有的foo函数还是指向window,bar函数指向这个被绑定的对象
bar()//this指向的是obj
bar()
function foo() {
console.log("foo:",this)
}
var obj = {name:"why"}
//需求:调用foo时,总是绑定到obj对象身上(不希望obj对象身上有我们的函数)
var bar = foo.bind(obj)//其实在执行这行foo.bind(obj)时,生成了一个新的函数,原有的foo函数还是指向window,bar函数指向这个被绑定的对象
bar()//this指向的是obj
bar()
//2.bind函数的其他参数(了解)
var bar = foo.bind(obj,"kobe",18,1.88)
bar("james")
1.5 内置函数的调用
有些时候,我们辉调用一些JavaScript的内置函数,或者一些第三方库中的内置函数
- 这些内置函数要求我们传入内外一个函数
- 我们自己并不会显示的调用这些函数,而且JavaScript内部或者第三方库会帮助我们执行
- 这些函数中的this是如何绑定的呢?
setTimeout\数组中的forEach、div的点击
//1.定时器
setTimeout(function() {
console.log("定时器函数:",this) //默认指向Window
},10000)
自动调用,this指向window
内置函数(第三方库):根据一些经验,Vue传入生命周期函数
//2.按钮的点击监听
var btnE1 = document.querySelector("button")
btnE1.onclick = function() {
console.log("btn的点击:",this)
}
btnE1.addEventListener("click:",function(){
console.log("btn的点击:",this)
})
//forEach
var names = ["abc","cba","nba"]
names.forEach(function(item){
console.log("foeEach:",this)
})
//forEach
var names = ["abc","cba","nba"]
names.forEach(function(item){
console.log("foeEach:",this)
},"aaaaaa") //显示绑定
1.6 this绑定的优先级比较
学了四条规则,接下来开发中我们只需要去查找函数的调用应用了哪条规则即可,但是如果一个函数调用的位置应用了多条规则,优先级谁更高?
- 默认规则的优先级是最低的
毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this
- 显示绑定优先级高于隐式绑定绑定
代码测试
- new绑定的优先级高于隐式绑定
代码测试
- new绑定的优先级高于bind
- new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
- new绑定可以和bind一起使用,new绑定优先级更高
- 代码测试
优先级顺序
new
bind
/apply/call
隐式绑定
默认绑定
bind的优先级高于apply
1.7 this规则之外-忽略显示绑定
上述的规则已经足以应付平时的开发,但是总有一些语法,超出了我们的规则之外。(神话故事和动漫中总是有类似这样的人物)
- 情况一:如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则:
function foo() {
console.log("foo:",this)
}
foo.apply("abc")
foo.apply(null)
foo.apply(undefined)
在严格模式下,绑定的使我们基本数据类型,就不是一个对象
"use strict"
function foo() {
console.log("foo:",this)
}
foo.apply("abc")
foo.apply(null)
foo.apply(undefined)
1.7.1 间接函数的引用
创建一个函数的间接引用,这种情况使用默认绑定规则
- 赋值(obj2.foo = obj1.foo()的结果是foo函数
- foo函数被直接调用,那么是默认绑定