this关键字全面剖析

目录

1.全局的this(浏览器window):

2.一般函数的this(浏览器window):

3.作为对象方法的函数的this(调用该函数的对象):

4.对象原型链上的this:

5.构造函数中的this(有return则指向return的对象,无则默认返回this):

6.bind方法和this

7.call/apply方法中的this

8.隐式绑定的函数会丢失绑定对象。

9.this优先级

10. this在箭头函数的作用:

11. 总结:this绑定对象的判断流程


在JavaScript中This总是指向调用它所在方法的对象。在 JavaScript 中,被称为 this 的事物,指的是“拥有”当前代码的对象。this 的值,在函数中使用时,是“拥有”该函数的对象。请注意 this 并不是变量。它属于关键词。您无法改变 this 的值。当不带拥有者对象调用对象时,this 的值成为全局对象。在 web 浏览器中,全局对象就是浏览器对象。

1.全局的this(浏览器window):

console.log(this.document === document) //true
console.log(this === window) //true
this.a = 37;
console.log(window.a); //37

2.一般函数的this(浏览器window):

一般函数
function f1(){
    return this;
}
f1()===window; //true,global object

3.作为对象方法的函数的this(调用该函数的对象):

var o = {
    prop:37,
    f:function(){
        return this.prop;
    }
};
console.log(o.f()); //37
f作为一个对象的方法,那么作为对象的方法去调用的时候,比如o.f调用的时候,这种情况,这个this,一般会指向对象

var o = {
    prop:37
};
function indepedent(){
    return this.prop;
}
o.f = indepedent;
console.log(o.f()); //37
这里并不是去看函数是再怎么样创建的,而是只要将这个函数作为对象的方法,这个o.f去调用的话,那么这个this就会指向这个o 

关于this,一般来说,谁调用了方法,该方法的this就指向谁,如:
function foo(){
  console.log(this.a)
}
var a = 3;
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 输出2,因为是obj调用的foo,所以foo的this指向了obj,而obj.a = 2
 
如果存在多次调用,对象属性引用链只有上一层或者说最后一层在调用位置中起作用,如:
function foo() {
  console.log( this.a )
}
var obj2 = {
  a: 42,
  foo: foo
}
var obj1 = {
  a: 2,
  obj2: obj2
}
obj1.obj2.foo(); // 42

4.对象原型链上的this:

var o = {
    f:function(){
        return this.a + this.b;
    }
}
var p = Object.create(o);
p.a = 2;
p.b = 6;
console.log(p.f()); //8
这里p的原型是o,那么p.f的时候调用的是对象o上面的函数属性f,this也指向p

5.构造函数中的this(有return则指向return的对象,无则默认返回this):

new一个MyClass空对象,如果没有返回值,默认会返回this,有返回值,那么o就指向返回的对象了
function MyClass(){
    this.a = 37;
}
var o = new MyClass();
console.log(o.a); //37
function C2(){
    this.a = 37;
    return{
        a:38
    }
}
o=new C2();
console.log(o.a); //38
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:

    创建(或者说构造)一个全新的对象
    这个新对象会被执行[[Prototype]]连接
    这个新对象会绑定到函数调用的this
    如果函数没有返回其他对象,那么new表达式中的函数会自动返回这个新对象
new是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定。

6.bind方法和this

function f(){
  return this.a;
}
var g = f.bind({
    a:'test'
});
console.log(g()); //test
var o = {
    a:37,
    f:f,    
    g:g
};
console.log(o.f(),o.g()); //37,test

7.call/apply方法中的this

function add(c,d){
    return this.a + this.b+c+d;
}
var o = {
    a:1,
    b:3
};
add.call(o,5,7); //1+3+5+7 = 16 add(5,7)
add.apply(o,[10,20]); //1+3+10+20 = 34
function bar(){
    console.log(Object.prototype.toString.call(this));
}
bar.call(7); //[Objedt Number]

// example 2
function foo( something ) {
    console.log( this.a, something)
    return this.a + something
}

var obj = {
    a: 2
}

var bar = function() {
    return foo.apply( obj, arguments)
}

var b = bar(3); // 2 3
console.log(b); // 5
在bar函数中,foo使用apply函数绑定了obj,也就是说foo中的this将指向obj,
与此同时,使用arguments(不限制传入参数的数量)作为参数传入foo函数中;
所以在运行bar(3)的时候,首先输出obj.a也就是2和传入的3,然后foo返回了两者的相加值,所以b的值为5
也可使用bind
function foo( something ) {
    console.log( this.a, something)
    return this.a + something
}
 
var obj = {
    a: 2
}
 
var bar = foo.bind(obj)
 
var b = bar(3); // 2 3
console.log(b); // 5

8.隐式绑定的函数会丢失绑定对象。

/*一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,
这个时候的应用默认绑定,从而把this绑定到全局对象或者undefined上,
取决于是否是严格模式。
example 1: 最常见的就是作为参数传递以及变量赋值。*/
var a = "oops, global";
function foo() {
    console.log( this.a )
}
var obj = {
    a: 2,
    foo: foo
}
function fn(param) {
    param();
};
var bar = obj.foo; // 变量赋值。虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身。
bar();  // 因此此时的bar()其实是一个普通的函数调用,因此应用了默认绑定。

fn(obj.foo); /* 参数传递, 单纯传递了一个函数而已,this并没有跟函数绑在一起,所以this丢失,
应用了默认绑定这里指向了window。*/

// example 2:隐式绑定丢失并不是都会指向全局对象
var name = 'tobey';
let obj = {
    name: 'jupy',
    fn: function () {
        console.log(this.name);
    }
};
let obj1 = {
    name: 'cindy'
}
obj1.fn = obj.fn;
obj1.fn(); //输出cindy
// 虽然丢失了 obj 的隐式绑定,但是在赋值的过程中,又建立了新的隐式绑定,这里this就指向了对象 obj1。

/*example 2:this指针只在age方法的函数内指向xiaoming,在函数内部定义的函数,
this又指向undefined了!(在非strict模式下,它重新指向全局对象window!)。*/
var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        function getAgeFromBirth() {
            var y = new Date().getFullYear();
            return y - this.birth;  // this处在函数里面的函数里面,那么指向的是默认的全局对象,由于使用strict模式所以是指向了undefined
        }
        return getAgeFromBirth();
    }
};
 
xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined
/*
修复的办法也不是没有,(1)我们用一个that变量首先捕获this:用var that = this;
就可以放心地在方法内部定义其他函数,而不是把所有语句都堆到一个方法中。*/
var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var that = this   // 捕捉到了this关键字所指的对象
        var fn = function () {
            return new Date().getFullYear() - that.birth; // that指向obj对象
        };
        return fn();  
    }
};
 
// (2)完全修复了this的指向问题,不再需要that。
var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
        return fn();
    }
};
obj.getAge(); // 25

9.this优先级

(1)隐式绑定:如果函数调用时,前面存在调用它的对象,那么this就会隐式绑定到这个对象上

# 隐式绑定
function fn() {
    console.log(this.name);
};
let obj = {
    name: 'tobey',
    func: fn
};
obj.func()   # tobey    

'''(1)如果函数调用前存在多个对象,this指向距离调用自己最近的对象,比如这样:'''
function fn() {
    console.log(this.name);
};
let obj = {
    name: 'tobey',
    func: fn,
};
let obj1 = {
    name: 'jupy',
    o: obj
};
obj1.o.func()  # tobey   最近的对象是o即obj

''' (2) 如果将obj对象的name删除,那么输出undefined,注意不要将作用域链和原型链弄混淆了,
obj对象虽然obj1的属性,但它两原型链并不相同,并不是父子关系,由于obj未提供name属性,
所以是undefined。'''
function fn() {
    console.log(this.name);
};
let obj = {
    func: fn,
};
let obj1 = {
    name: 'jupy',
    o: obj
};
obj1.o.func()   # 输出undefined

'''(3)原型链:虽然obj对象并没有name属性,但顺着原型链,找到了产生自己的构造函数Fn,
由于Fn原型链存在name属性,所以输出时间跳跃了。'''
function Fn() {};
Fn.prototype.name = 'tobey';

function fn() {
    console.log(this.name);
};

let obj = new Fn();
obj.func = fn;

let obj1 = {
    name: 'jupy',
    o: obj
};
obj1.o.func()   # 输出tobey

 作用域链与原型链的区别:

  • 当访问一个变量时,解释器会先在当前作用域查找标识符,如果没有找到就去父作用域找,作用域链顶端是全局对象window,如果window都没有这个变量则报错。
  • 当在对象上访问某属性时,首选i会查找当前对象,如果没有就顺着原型链往上找,原型链顶端是null,如果全程都没找到则返一个undefined,而不是报错。

(2)显式绑定:指我们通过call、apply以及bind方法改变this的行为,相比隐式绑定,我们能清楚的感知 this 指向变化过程。

let obj1 = {
    name: 'tobey'
};
let obj2 = {
    name: 'jupy'
};
let obj3 = {
    name: 'echo'
}
var name = 'cindy';

function fn() {
    console.log(this.name);
};
fn(); //cindy
// 分别通过call、apply、bind改变了函数fn的this指向
fn.call(obj1); // tobey
fn.apply(obj2); // jupy
fn.bind(obj3)(); // echo

 当我们调用一个函数时,我们习惯称之为函数调用函数处于一个被动的状态;而call与apply让函数从被动变主动,函数能主动选择自己的上下文,所以这种写法我们又称之为函数应用注意,如果在使用call之类的方法改变this指向时,指向参数提供的是null或者undefined,那么 this 将指向全局对象。

call、apply与bind有什么区别?

  • 1.call、apply与bind都用于改变this绑定,但call、apply在改变this指向的同时还会执行函数,而bind在改变this后是返回一个全新的boundFcuntion绑定函数,这也是为什么上方例子中bind后还加了一对括号 ()的原因。
  • 2.bind属于硬绑定,返回的 boundFunction 的 this 指向无法再次通过bind、apply或 call 修改;call与apply的绑定只适用当前调用,调用完就没了,下次要用还得再次绑。
  • 3.call与apply功能完全相同,唯一不同的是call方法传递函数调用形参是以散列形式,而apply方法的形参是一个数组。在传参的情况下,call的性能要高于apply,因为apply在执行时还要多一步解析数组。
  • let obj1 = {
        name: 'tobey'
    };
    let obj2 = {
        name: 'jupy'
    };
    var name = 'cindy';
    
    function fn() {
        console.log(this.name);
    };
    // 描述一:
    fn.call(undefined); //cindy
    fn.apply(null); //cindy
    fn.bind(undefined)(); //cindy
    // 描述二
    fn.call(obj1); //tobey
    fn(); //cindy
    fn.apply(obj2); //jupyt
    fn(); //cindy
    let boundFn = fn.bind(obj1);//tobey
    boundFn.call(obj2);//tobey
    boundFn.apply(obj2);//tobey
    boundFn.bind(obj2)();//tobey
    // 描述三
    let obj = {
        name: 'jupy'
    };
    
    function fn(age,describe) {
        console.log(`我是${this.name},我的年龄是${age},我非常${describe}!`);
    };
    fn.call(obj,'26','帅');//我是jupy,我的年龄是26,我非常帅!
    fn.apply(obj,['26','帅']);//我是jupy,我的年龄是26,我非常帅!

(3)new绑定:准确来说,js中的构造函数只是使用new 调用的普通函数,它并不是一个类,最终返回的对象也不是一个实例,只是为了便于理解习惯这么说罢了。

那么new一个函数究竟发生了什么呢,大致分为三步:

  • 1.以构造器的prototype属性为原型,创建新对象
  • 2.将this(可以理解为上句创建的新对象)和调用参数传给构造器,执行;
  • 3.如果构造器没有手动返回对象,则返回第一步创建的对象

这个过程我们称之为构造调用。

 this 绑定优先级:

显式绑定 > 隐式绑定 > 默认绑定

new绑定 > 隐式绑定 > 默认绑定

默认绑定的优先级是四条规则中最低的,所以可以先不考虑它。

  • (1)隐式绑定和显式绑定哪个优先级更高?显式绑定优先级更高,也就是说在判断时应当先考虑是否可以存在显式绑定。
  • (2)new绑定隐式绑定的优先级谁更高?new绑定隐式绑定优先级高。
  • (3)new绑定显式绑定谁的优先级更高呢?注意不存在这种绑定同时生效的情景,如果写这两种代码会直接报错。
// 比较一 显式>隐式
function foo(a){
    console.log(this.a)
}
 
var obj1 = {
    a: 2,
    foo: foo
}
var obj2 = {
    a: 3,
    foo: foo
}
// 隐式绑定
obj1.foo(); // 2
obj2.foo(); // 3
// 显式绑定
obj1.foo.call(obj2); // 3
obj2.foo.call(obj1); // 2

// 比较二  new>隐式
function foo(something){
    this.a = something
}
var obj1 = {
    foo: foo
}
var obj2 = {}
 // 隐式
obj1.foo(2); 
console.log(obj1.a); // 2
 // 显式
obj1.foo.call(obj2,3);
console.log(obj2.a); // 3
 // new绑定
var bar = new obj1.foo(4)
console.log(obj1.a); // 2
console.log(bar.a); // 4


// 比较三  new和显示同时出现会报错
function Fn(){
    this.name = '听风是风';
};
let obj = {
    name:'行星飞行'
}
let echo = new Fn().call(obj);//报错 Uncaught TypeError: (new Fn()).call is not a function

//比较三,如果不是同时出现,可以看到,new绑定修改了硬绑定中的this,所以new绑定的优先级比显式绑定更高。
function foo(something){
    this.a = something
}
var obj1 = {}
var bar = foo.bind(obj1); // 显式绑定,bind方法不执行函数,返回函数
bar(2);
console.log(obj1.a); // 2

var baz = new bar(3);  // new绑定
console.log(obj1.a); // 2
console.log(baz.a); // 3

10. this在箭头函数的作用:

准确来说,箭头函数中没有this,箭头函数的this指向取决于外层作用域中的this(意味着箭头函数的this无法主动修改),外层作用域或函数的this指向谁,箭头函数中的this便指向谁。

function foo() {
    // 返回一个箭头函数
    return (a) => {
        // this继承自foo()
        console.log(this.a)
    };
}
var obj1 = {
    a: 2
};
var obj2 = {
    a: 3
};
 
var bar = foo.call(obj1);
bar.call(obj2); // 2, 不是3!
// foo()内部创建的箭头函数会捕获调用时foo()的this。由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new也不行!)

11. 总结:this绑定对象的判断流程

如果要判断一个运行中的函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断this的绑定对象。

  1. 由new调用?绑定到新创建的对象。
  2. 由call或者apply(或者bind)调用?绑定到指定的对象。
  3. 由上下文对象调用?绑定到那个上下文对象。
  4. 默认:在严格模式下绑定到undefined,否则绑定到全局对象。

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值