JavaScript中this指向的问题总结(五种绑定)
1.默认绑定
- 什么情况下使用默认绑定呢?独立函数调用。区分严格模式 严格模式下就是undefined,非严格模式下就是window
- 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用;
- 我们通过几个案例来看一下,常见的默认绑定
1-1.普通调用
function fn() {
console.log(this);
}
fn();
普通函数被独立调用的时候,this指向window
1-2.函数定义在对象中
1-2-1.对象直接调用
var obj = {
name: "小明",
// 我们并不知道f函数中的this指向谁
// 只有确定了f函数是怎么调用的,我们才知道f函数中的this指的是谁
fn: function() {
console.log(this);
}
};
obj.fn(); //{name: '小明', fn: ƒ}
因为函数fn是被obj调用的,所以这里的this就指向obj这个对象
1-2-2.将函数fn作为一个具体的值赋值给变量
var obj = {
name: "小明",
fn: function() {
console.log(this);
}
};
var fn1 = obj.fn;
fn1(); // thsi 指向 window
其实这里就是将obj.fn中的属性值全部取出,然后赋值给新的fn1变量
这样的话fn1变量中保存的就是一个函数 是函数就可以调用
不过这中方法已经脱离了对象,从而变成了普通调用
1-3. “use strict” 严格模式下的this
1-3-1.普通函数被独立调用
"use strict"
function fn() {
console.log(this);
}
fn(); //this指向undefined
this指向undefined
1-3-2.函数定义在对象中
1-3-2-1.对象直接调用
"use strict"
var obj = {
name: "张三",
fn: function() {
console.log(this);
}
}
obj.fn(); //this指向obj对象
this指向obj对象
1-3-2-2.将函数fn作为一个具体的值赋值给变量
"use strict"
var obj = {
name: "张三",
bar: function() {
console.log(this);
}
}
// obj.bar();
var fn = obj.bar;
fn(); //严格模式 this依然指向undefined
严格模式下,只要是独立调用函数,它的this只会指向undefined
1-3-2-3.函数嵌套 独立调用
"use strict"
function t1() {
console.log(this);
t2();
}
function t2() {
console.log(this);
t3();
}
function t3() {
console.log(this);
}
t1();
//undefined
//undefined
//undefined
嵌套函数在严格模式下, 独立调用的函数this依然指向undefined,而非严格模式下,this指向window
1-4.高阶函数
- 一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数
var obj = {
fn: function() {
console.log(this);
}
}
function test(box) {
box();
}
test(obj.fn) //this指向window
分析一下,test可以看作是一个
高阶函数,test在调用的时候,将obj.fn对应的属性值传递给了变量box,而现在box变量就是一个函数,因此我们可以调用它,由于box函数是独立调用,所以输出是window
2.隐式绑定
- 另外一种比较常见的调用方式是通过某个对象进行调用的:
- 也就是它的调用位置中,是通过某个对象发起的函数调用,this指向这个对象
- 我们通过几个案例来看一下,常见的隐式绑定
function fn() {
console.log(this);
}
var obj = {
fn1: fn,
}
console.log(obj.fn1);
obj.fn1(); //{fn1: ƒ}
这个函数中,console.log(obj.fn1);的结果为:
ƒ fn() {
console.log(this);
}
也就是说通过obj.fn1招到了fn这个函数,obj.fn1函数调用可以间接的调用fn这个函数,fn在执行的这一刻,是由obj发起的,所以js引擎就将obj对象自动绑定到fn函数中作为this
function fn() {
console.log(this);
}
var obj1 = {
name: "刘备",
fn: fn
}
var obj2 = {
name: "关羽",
fn1: obj1
}
obj2.fn1.fn(); //this指向obj1
这里的this就是obj1,因为这个fn函数调用是obj1这个对象发起的
3.new绑定 构造函数绑定
- JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。
- 使用new关键字来调用函数是,会执行如下的操作:
1.创建一个全新的对象;
2.这个this就会指向创建的空对象
3.给这个空对象身上追加属性;
4.如果函数没有返回其他对象,表达式会返回这个新对象;
function Person(name, height, weight, addr) {
this.name = name;
this.height = height;
this.weight = weight;
this.addr = addr;
console.log(this); //Person {name: '张三', height: 1.88, weight: 60, addr: '北京'}
console.log(this.name); //张三
}
var p1 = new Person("张三", 1.88, 60, "北京")
console.log(p1);//Person {name: '张三', height: 1.88, weight: 60, addr: '北京'}
构造函数中,this指向我们创建出来的实例对象
4.显式绑定
- 隐式绑定有一个前提条件:
- 必须在调用的对象内部有一个对函数的引用(比如一个属性);
- 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
- 正是通过这个引用,间接的将this绑定到了这个对象上;
- 如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?
- JavaScript所有的函数都可以使用call和apply方法
- 第一个参数是相同的,要求传入一个对象;
- 这个对象的作用是什么呢?就是给this准备的
- 在调用这个函数时,会将this绑定到这个传入的对象上
- 后面的参数,apply为数组,call为参数列表;
function.apply(thisArg,[argsArray])
function.call(thisArg,arg1,arg2,arg3,……)
- 第一个参数是相同的,要求传入一个对象;
- 因为上面的过程,我们明确的绑定了this指向的对象,所以称之为 显式绑定
//由隐式绑定推敲出显式绑定
var obj ={
name:"张三",
}
function fn(){
console.log(10+20);//30
console.log(this);//{name: '张三', bar: ƒ}
}
obj.bar=fn;
obj.bar();
需求: 用户调用fn函数,并且将this指向我们的obj对象
隐式绑定是可以做的,就是将fn函数作为属性值给obj追加属性
但是会比较麻烦,使obj对象多了一个bar属性
// 由隐式绑定推敲出显式绑定
var obj = {
name: "张三",
}
function foo() {
console.log(this);
};
foo.call(obj);//{name: '张三'}
foo.call(123456);//Number {123456}
foo.call("hello");//String {'hello'}
需求: 用户调用foo函数,并且将this指向我们的obj对象
显式绑定比较简单,还是调用foo函数,不过是通过call或者apply函数
即调用了函数foo,也将foo中this指向了obj,也没有给obj增加属性
甚至在foo函数调用的时候,给this指定任意的值
自己显式的指定this指向,这种this绑定成为显式绑定
function foo() {
console.log(this);
}
foo(); //window
foo.apply(); //window
foo.call(); //window
apply和call方法既可以调用函数,也可以在调用函数时改变函数中this的指向,apply和call方法不写参数表示this值默认指向
function foo() {
console.log(this);
};
foo(); //window
foo.apply("hello"); //包装类型对象 String {'hello'}
foo.call("world") //包装类型对象 String {'world'}
function foo(name, sex, addr) {
console.log(this);
console.log(name, sex, addr);
};
foo("张三", "男", "北京");
foo.apply("hello", ["李四", "女", "上海"]);
foo.call("word", "王五", "男", "广州");
函数可以用来设置参数 传入参数 打印参数
apply和call方法不同在于:传递实参时方式不一样
apply方法的第一个参数设置this指向 之后的参数全部放置在一个数组中
call方法的第一个参数设置this指向 之后的参数以数列的顺序依次传入
-
apply和call
- 相同点:1.都可以调用函数 2.都可以绑定this对象
- 不同点:传入实参的方式不一样
-
如果我们希望一个函数总是显示的绑定到一个对象上,可以怎么做呢?
- 使用bind方法,bind() 方法创建一个新的绑定函数(bound function,BF);
- 绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语)
- 在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
function.bind(thisArg[,arg[,arg2[,···]]])
function foo(name, sex, addr) {
console.log(this);
console.log(name, sex, addr);
}
var obj = {
name: "张三"
}
var bar = foo.bind(obj, "李四", "女", "上海");
bar(); // this指向obj
虽然bar是独立调用,但是因为bind改变了this的指向,所以现在this不再指向window,而是指向指定的对象
5.内置函数的绑定
- 有些时候,我们会调用一些JavaScript的内置函数,或者一些第三方库中的内置函数。
- 这些内置函数会要求我们传入另外一个函数;
- 我们自己并不会显式的调用这些函数,而且JavaScript内部或者第三方库内部会帮助我们执行;
- 这些函数中的this又是如何绑定的呢?
- setTimeout、数组的forEach、div的点击 一般都是根据经验来做出结论
setTimeout(function() {
console.log(this); //this指向window
}, 1000)
var arr = ['一', '二', '三', '四', '五'];
arr.forEach(function() {
console.log(this);
}, {
name: "张三"
})
var btn = document.querySelector('button');
btn.onclick = function() {
console.log(this);
}
// 触发点击事件的按钮本身 <button>按钮</button>
btn.addEventListener("click", function() {
console.log(this);
})
// 触发点击事件的按钮本身 <button>按钮</button>
以上两个不同写法的点击事件,this都指向它的时间源码,也就是这个按钮本身
6.ES6箭头函数this
- 箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this
- 箭头函数中是没有this的存在的,如果在箭函数中调用this,它在箭头函数本体内是无法找到的,于是就会向父级查找
- 因为箭头函数并不绑定this对象,那么this引用就会从上层作用于中找到对应的this
var obj = {
name: "张三",
fn: () => {
console.log(this);
}
}
obj.fn(); //指向window
可以理解为它的this是继承它父级的this