this绑定
默认绑定
默认绑定,在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用。
function sayHi(){console.log('Hello,', this.name);}var name = 'YvetteLau';sayHi(); //Hello, YvetteLau
在调用Hi()时,应用了默认绑定,this指向全局对象(非严格模式下),严格模式下,this指向undefined,undefined上没有this对象,会抛出错误。
隐式绑定
函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。
function sayHi(){console.log('Hello,', this.name);}var person = {name: 'YvetteLau',sayHi: sayHi}var name = 'Wiliam';person.sayHi(); //Hello,YvetteLau
sayHi函数声明在外部,严格来说并不属于person,但是在调用sayHi时,调用位置会使用person的上下文来引用函数,隐式绑定会把函数调用中的this(即此例sayHi函数中的this)绑定到这个上下文对象(即此例中的person)
需要注意的是:对象属性链中只有最后一层会影响到调用位置。
隐式绑定有一个大陷阱,绑定很容易丢失(或者说容易给我们造成误导,我们以为this指向的是什么,但是实际上并非如此).
function sayHi(){console.log('Hello,', this.name);}var person = {name: 'YvetteLau',sayHi: sayHi}var name = 'Wiliam';var Hi = person.sayHi;Hi(); //Hello,Wiliam
Hi直接指向了sayHi的引用,在调用的时候,跟person没有关系。可以理解为赋值给了Hi变量。
以下代码可以加深隐式绑定丢失的理解
function sayHi(){console.log('Hello,', this.name);}var person1 = {name: 'YvetteLau',sayHi: function(){setTimeout(function(){console.log('Hello,',this.name);})}}var person2 = {name: 'Christina',sayHi: sayHi}var name='Wiliam';person1.sayHi();setTimeout(person2.sayHi,100);setTimeout(function(){person2.sayHi();},200);// Hello, Wiliam// Hello, Wiliam// Hello, Christina
- 第一条输出很容易理解,setTimeout的回调函数中,this使用的是默认绑定,非严格模式下,执行的是全局对象
- 第二条相当于是将person2.sayHi赋值给了一个变量,最后执行了变量,这个时候,sayHi中的this显然和person2就没有关系了。
- 第三条虽然也是在setTimeout的回调中,但是我们可以看出,这是执行的是person2.sayHi()使用的是隐式绑定,因此这是this指向的是person2,跟当前的作用域没有任何关系。
显式绑定
通过call,apply,bind的方式,显式的指定this所指向的对象注意: 如果将null或者是undefined作为this的绑定对象传入call、apply或者是bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
function sayHi(){console.log('Hello,', this.name);}var person = {name: 'YvetteLau',sayHi: sayHi}var name = 'Wiliam';var Hi = person.sayHi;Hi();//Hello, WiliamHi.call(person); || Hi.apply(person) //Hello, YvetteLau
显示绑定丢失
function sayHi(){console.log('Hello,', this.name);}var person = {name: 'YvetteLau',sayHi: sayHi}var name = 'Wiliam';var Hi = function(fn) {fn();}Hi.call(person, person.sayHi);//上述代码执行结果可以理解为var Hi = function(fn) {//这里this被call改为了person//但是sayHi函数被window调用 所有this指向window//如果sayHi是个箭头函数 则往外找到this是指向person(function sayHi(){console.log('Hello,', this.name);})()}
输出的结果是 Hello, Wiliam. 原因很简单,Hi.call(person, person.sayHi)的确是将this绑定到Hi中的this了。但是在执行fn的时候,相当于直接调用了sayHi方法(记住: person.sayHi已经被赋值给fn了,隐式绑定也丢了),没有指定this的值,对应的是默认绑定。
如何修改为不不丢失? :
fn.call(this);强制把this指向回去
new绑定
在javaScript中,构造函数只是使用new操作符时被调用的函数,这些函数和普通的函数并没有什么不同,它不属于某个类,也不可能实例化出一个类。任何一个函数都可以使用new来调用,因此其实并不存在构造函数,而只有对于函数的“构造调用”。
使用new来调用函数,会自动执行下面的操作:
- 创建一个空对象,构造函数中的this指向这个空对象
- 这个新对象被执行 [[原型]] 连接
- 执行构造函数方法,属性和方法被添加到this引用的对象中
- 如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
因此,我们使用new来调用函数的时候,就会新对象绑定到这个函数的this上。 ```javascript function sayHi(name){ this.name = name;function _new() {let target = {}; //创建的新对象//第一个参数是构造函数let [constructor, ...args] = [...arguments];//执行[[原型]]连接;target 是 constructor 的实例target.__proto__ = constructor.prototype;//执行构造函数,将属性或方法添加到创建的空对象上let result = constructor.apply(target, args);if (result && (typeof (result) == "object" || typeof (result) == "function")) {//如果构造函数执行的结构返回的是一个对象,那么返回这个对象return result;}//如果构造函数返回的不是一个对象,返回创建的新对象return target;}
} var Hi = new sayHi(‘Yevtte’); console.log(‘Hello,’, Hi.name);
> 输出结果为 Hello, Yevtte, 原因是因为在var Hi = new sayHi('Yevtte');这一步,会将sayHi中的this绑定到Hi对象上。<a name="uqpcC"></a>## 箭头函数箭头函数没有自己的this,它的this继承于外层代码库中的this。箭头函数在使用时,需要注意以下几点:1. **函数体内的this对象,继承的是外层代码块的this。**1. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。1. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。1. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。1. 箭头函数没有自己的this,所以不能用call()、apply()、bind()这些方法去改变this的指向.```javascriptvar obj = {hi: function(){console.log(this);return ()=>{console.log(this);}},sayHi: function(){return function() {console.log(this);return ()=>{console.log(this);}}},say: ()=>{console.log(this);}}let hi = obj.hi(); //输出obj对象hi(); //输出obj对象let sayHi = obj.sayHi();let fun1 = sayHi(); //输出windowfun1(); //输出windowobj.say(); //输出window
- obj.hi(); 对应了this的隐式绑定规则,this绑定在obj上,所以输出obj,很好理解。
- hi(); 这一步执行的就是箭头函数,箭头函数继承上一个代码库的this,刚刚我们得出上一层的this是obj
- 执行sayHi();这一步也很好理解,我们前面说过这种隐式绑定丢失的情况,这个时候this执行的是默认绑定,this指向的是全局对象window.
- fun1(); 按照箭头函数的this是继承于外层代码库的this就很好理解了。外层代码库我们刚刚分析了,this指向的是window
- obj.say(); 执行的是箭头函数,当前的代码块obj不能产生作用域不存在this,只能往上找,就找到了全局的this,指向的是window.
测试题
var number = 5; var obj = { number: 3, fn: (function () { var number; this.number *= 2; number = number * 2; number = 3; return function () { var num = this.number; this.number *= 2; console.log(num); number *= 3; console.log(number); } })() } var myFun = obj.fn; myFun.call(null); obj.fn(); console.log(window.number);结果: 10 9 3 27 20
解析:
var number = 5;
var obj = {
number: 3,
fn: (function () {
//1.自执行函数,被调用前已经执行,此时this指向window
//且只有一次形成闭包return中函数一直在访问修改闭包中的number
var number; //undefined
this.number *= 2; //window.number*2=10
number = number * 2; //undefined*2=NaN
number = 3;//修改当前变量number=3
return function () {
//2.第一次: this指向window
//3.第二次: this指向obj
var num = this.number; //window.number=10,obj.number=3
this.number *= 2;//window.number=20,obj.number=6
console.log(num);//10 3
number *= 3;//obj.number=9,obj.number=27这个number对应的闭包中的number;闭包中的number的此时是9
console.log(number);
}
})()
}
var myFun = obj.fn;//
myFun.call(null);//传入null默认绑定为window
obj.fn();//让this指向obj
console.log(window.number);//20
案例
普通函数this指向
// 1.普通函数 - window
function fn(){};
fn()
fn.call(person);//person对象
//普通函数定义在可产生作用域的方法中且自调用 - undefined
class Person {
constructor() {
function fn() {
console.log(this); //undefined
}
fn()
this.fn1 = function () {
console.log(this); //Preson 指向对象
}
this.fn1();
}
}
// 2.对象的方法(谁调用我,我指向谁)
var person ={
name:'张三',
run:function(){
console.log(this.name);
}
}
person.run(); //张三
person.run.call({name:'李四'},30) //李四
// 3.构造函数 -指向构造函数本身
function Star(){
console.log(this) // Star {}
}
var ldh = new Star()
var zxy = new Star()
// 4.绑定事件函数 - btn
btn.onclick = function(){}
// 5.定时函数 - window
//window.setTimeout 被window的所以放在哪都指向window
setTimeout(function(){},1000)
// 6.立即执行函数 参考上边第1点 - window
(function(){})()
箭头函数thsi指向
//箭头函数没有this,不能通过apply等修改this指向
//this 是静态的. this 始终指向函数声明时所在作用域下的 this 的值 (this会指向定义位置)
//1
var name='李四';
var all ={
name:'张三',
run1:function(){
console.log(this.name);
},
run2:()=>{//all对象不能产生作用域, 此时this指向window
console.log(this.name);
}
}
all.run1(); //张三
all.run2(); //李四
//2
function fn (){
console.log(this); //call改变this指向了obj ->{name: '张三'}
return ()=>{
// 没有this 往外找到obj ->{name: '张三'}
console.log(this);
// 如果没有call改变this指向 则会找到window, 因为fn所在作用域就是window
// 在没有call条件下 往fn添加this.name 输出指向都会是该值, 因为就是往window添加的,
// 此时输出window.name也是该值, 因为fn所在作用域就是window
}
}
const obj ={name:'张三'}
const resFn = fn.call(obj); //call会指向函数 此时resFn拿到的是fn函数的返回值
resFn(); //调用fn函数的返回值, 此时this被call改变为obj
//3
div.addEventListener("click", function(){
//此时this指向div 因为div的click事件是普通函数
//如果div的click函数也改为箭头函数 则this都往外找到window并指向window
console.log(this);
//定时器
setTimeout(() => {
//setTimeout原本this指向window 但箭头函数没有this,所有此处this往外找到div并指向它
//如果div的click函数也改为箭头函数 则this都往外找到window并指向window
console.log(this);//不使用箭头函数->window
}, 2000);
});
