js的this指向问题
this关键字是javascript中最复杂的知识点之一,是一个特别的关键字,被自动定义在所有函数的作用域中。在写javascript的时候经常用到,但是你真的懂this吗?作为一个合格的码农,绝对不能只会背口诀,还需要懂实现逻辑。在好久之前学习this的时候,我自己做了一些小总结,也照猫画虎地写了一些代码不断地去测试。希望这些学习笔记能帮助到大家。
一 this指向分类
1.1 默认绑定
默认状态就是无绑定状态,经常在独立函数中用到。
function test(){
console.log(this.a);
}
var a = 1111;
test();
//1111
分析输出结果:
test()前面没有调用它的对象,这种写法就是应用了this的默认绑定,this指向全局对象(非严格模式下),所以输出了1111
扩展
在非严格模式下:test()函数会挂载在window上所有函数中的this指向的是全局对象window。
在严格模式下:禁止了this关键字指向全局对象,this指向undifined,undifined没有全局对象,抛出错误
1.2 隐式绑定
函数的调用是在某个对象上触发的,调用位置上存在上下文对象。典型的的形式为XXX.fun();
function test(){
console.log(this.a);
}
var obj = {
a:1111,
test:test
}
obj.test();
//1111
分析输出结果
test()函数是在外部声明,严格来说并不属于obj,但是在调用test()时,调用位置会使用obj的上下文来引用函数,隐式绑定会把函数调用中的this即test()中的this绑定到这个上下文对象(obj)中。
- 永远记住一句话:this永远指向最后调用它的那个对象。对象属性链中只有最后一层会影响调用的位置
function test(){
console.log(this.a);
}
var obj1 = {
a:1111,
test:test
}
var obj2 = {
a:2222,
obj1:obj1
}
obj2.obj1.test();
//1111,this指向最后绑定它的obj1
注意的是,在隐式绑定经常会出现隐式丢失的问题
Q:什么是隐式丢失?
A:被绑定的函数丢失绑定对象。
attention:参数传递其实是一种隐性赋值
function fn(){
console.log(this.a);
}
var a = 'hello world';
var obj = {
a:'obj',
fn:fn
}
var demo = obj.fn;
demo();
//控制台最终打印hello world
分析输出结果
demo直接指向了fn的引用,调用的时候跟obj没有关系。demo绑定了fn函数的引用,因此demo只是一个函数的调用,应用了默认绑定绑定了全局对象。所以打印出来hello world。
- 混淆知识点:XXX.fn();fn()前如果什么都没有,就不是隐式绑定。
除了上述这种丢失之外,隐式绑定丢失还经常发生在回调函数中(事件回调也是其中一种)
function test(){
console.log(this.a);
}
function gn(fn){
fn();
}
var obj = {
a:1111,
test:test
}
var a = '我是全局对象'
gn(obj.test);
//我是全局对象
再一次说明了参数传递也是一种隐形赋值
- 特殊情况:定时器setTimeout
function test(){
console.log(this.a);
}
var a = 'hello world';
var obj = {
a:'obj',
test:test
}
setTimeout(obj.test,0);
//hello world
输出结果分析
setTimeout第一个参数是传入回调函数,obj.test被当做一个函数进行绑定,相当于
function setTimeout(test,delay){//delay毫秒后执行
test();//obj.test
}
1.3 显式绑定
借助apply,call,bind等方法直接指定this绑定对象,call、apply、bind的第一个参数,就是对应函数的this所指向的对象。
Q:apply,call.bind有什么区别?
A:apply,call作用一样,传递参数的方式不一样,都会执行对应的函数,但是bind不一样,它不会执行,需要手动去调用。
//apply绑定
function test(){
console.log(this.a);
}
var obj = {
a:1111,
}
test.apply(obj);//hello world
//call绑定
function test(){
console.log(this.a);
}
var obj = {
a:1111,
}
test.call(obj);//hello world
使用显式绑定是不是不会出现绑定丢失呢?当然不是
- 在显式绑定也会面临绑定丢失的问题
function sayHi(){
console.log('hello',this.name);
}
var obj = {
name:'Gabrielle',
sayHi:sayHi
}
var name = 'Gaby';
var Hi = function(fn){
fn();
}
Hi.call(obj,obj.sayHi);
//hello,Gaby
输出结果分析
在执行fn的时候,相当于直接调用了sayHi方法(obj.sayHi已经被赋值给fn了,隐式绑定也丢失了),没有指定this的值,对应的是默认绑定。所以输出结果hello Gaby。
Q:如何解决绑定丢失问题?
A:给fn硬绑定this。
function sayHi(){
console.log('hello',this.name);
}
var obj = {
name:'Gabrielle',
sayHi:sayHi
}
var name = 'Gaby';
var Hi = function(fn){
fn.call(this);
}
Hi.call(obj,obj.sayHi);
//hello,Gabrielle
输出结果分析
因为obj被绑定到Hi函数中的this上,fn又将这个函数绑定给sayHi的函数。
也可以用bind改写,bind被归类为硬绑定。
function sayHi(){
console.log('hello',this.name);
}
var obj = {
name:'Gabrielle',
sayHi:sayHi
}
var name = 'Gaby';
var Hi = sayHi.bind(obj);
Hi.call(obj,obj.sayHi);
//hello,Gabrielle
- 绑定例外
应用绑定规则:把null,undifined作为绑定对象传入call,apply,bind,这些值往往被忽略
var obj = {
name:'Gabrielle'
}
var name = 'Gaby';
function Hi() {
console.log(this.name);
}
Hi.call(null);
//Gaby
1.4 new绑定
js没有类,通过构造函数来模拟类,使用new来调用函数,会自动执行下面的操作
创建一个对象,这个对象被执行[[Prototype]]连接,这个新对象会绑定到函数调用的this,如果函数没有返回其他对象,那么返回这个新对象,否则返回构造函数返回的对象。
- 手写new
//1、创建一个空对象
function _new(fn,...args){
//2、这个对象的 __proto__ 指向 fn 这个构造函数的原型对象
var obj = Object.create(fn.prototype);
//3、改变this指向
var res = fn.apply(obj,args);
// 4. 如果构造函数返回的结果是引用数据类型,则返回运行后的结果,否则返回新创建的 obj
if ((res!==null && typeof res == "object") || typeof res == "function") {
return res;
}
return obj;
}
- new绑定this
function person(name){
this.name = name;
}
var per = new person('Gaby');
console.log(per.name);
//Gaby
//per已经被绑定到person调用的this上
插播一个小片段
在写上述代码的时候,我忘记给person()传入参数了,导致控制台打印出来了undefined。然后我就在思考一个问题,为什么给person()传入参数才可以有正常的输出。这背后的逻辑是什么?在此给大家提个醒,function person()是一个构造函数,this.name要拿到name,但是我的name没有传入进去,new的person对象中没有得到我想要传入构造函数里面的name,因为没有传入参数,所以根本无法赋值。
1.5 箭头函数this绑定
ES6箭头函数需要单独讨论,它不适用上面的四种绑定规则。箭头函数中没有this
箭头函数的this指向取决于外层作用域中的this,外层作用域或函数的this指向谁,箭头函数中的this便指向谁。
function fn(){
return () =>{
console.log(this.name);
}
}
let obj1 = {
name='Gabrielle'
};
let obj1 = {
name='Gaby'
}
let bar = fn.call(obj1);//fn this指向obj1
bar.call(obj2);
//Gabrielle
Q1:为什么再一次绑定后没有修改?
A2:cuz箭头函数的特性:一旦箭头函数的this绑定成功,也无法被再次修改。
Q2:真的无法被修改吗?
A2:其实也不是没有解决办法。箭头函数的this会像作用域继承一样从上层作用域找,因此可以修改外层函数this指向达到间接修改箭头函数this的目的。
function fn(){
return () =>{
console.log(this.name);
}
}
let obj1 = {
name='Gabrielle'
};
let obj1 = {
name='Gaby'
}
fn.call(obj1)();//fn this指向obj1,箭头函数this也指向obj1
fn.call(obj2)();//fn this指向obj2,箭头函数this也指向obj2
二 keep going
JS深入理解要整理的知识点又多又杂,学好JS一定要理解背后的逻辑和原理,而不是简简单单的知道怎么写代码去运用而已。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/16173.html