su37josephxia/frontend-interview

Day18 - 介绍一下this指向4种形式

su37josephxia opened this issue · 25 comments

四种形式

bind, apply, call, new

bind

指定函数执行时的 this

apply

指定函数执行时的 this 并调用函数

call

指定函数执行时的 this 并调用函数,跟 apply 的区别在于,从第二位起是函数的参数,而 apply 会把第二个数组参数的所有元素作为函数参数

new

在调用构造函数的时候将 this 指向创建的空对象

  1. 如果是一般函数,this指向全局对象window;
  2. 在严格模式下"use strict",为undefined.
  3. 对象的方法里调用,this指向调用该方法的对象.
  4. 构造函数里的this,指向创建出来的实例.

第一、如果一个函数中有this,但是它没有以对象方法的形式调用,而是以函数名的形式执行,那么this指向的就是全局对象;
第二、如果一个函数中有this,并且这个函数是以对象方法的形式调用,那么this指向的就是调用该方法的对象;
第三、如果一个函数中有this,并且包含该函数的对象也同时被另一个对象所包含,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,

如:

var  obj = {
test;{
fun:function(){
console.log(this);
      }
   }
}

obj.test.fun();

第四、如果一个构造函数或以类方法中有this,那么它指向该构造函数或类创建出来的实例对象。

默认绑定
如果是一般函数,this指向全局对象window,在严格模式下"use strict",为undefined.
隐式绑定
对象的方法里调用,this指向调用该方法的对象.
显式绑定
对于call、apply、bind的这三种调用方式都是属于显式绑定,作用是通过显示传入一个对象,改变this的上下文为此对象。call和apply是直接改变上下文对象直接调用,而bind是返回一个已经显示绑定的上下文的函数
new绑定
构造函数里的this,指向创建出来的实例.
一般情况下:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

_Rainbow_Z:
⾸先,在默认情况下,this是指向全局对象的,⽐如在浏览器就是指向window。
其次,如果函数被调⽤的位置存在上下⽂对象时,那么函数是被隐式绑定的。
再次,显示改变this指向,常见的⽅法就是call、apply、bind,由于bind将obj绑定到f函数上后返回⼀个新函数,因此需要再在后⾯加上括号进⾏执⾏,这是bind与apply和call的 区别

_Rainbow_Z:
最后,也是优先级最⾼的绑定 new 绑定。 ⽤ new 调⽤⼀个构造函数,会创建⼀个新对象, 在创造这个新对象的过程中,新对象会⾃动绑定到Person对象的this上, 那么 this ⾃然就指向这个新对象。

_Rainbow_Z:
绑定优先级: new绑定 > 显式绑定 >隐式绑定 >默认绑定

  1. 一般的函数调用情况下,this指向全局。这是因为直接从内存中找到这个函数然后拿出来的时候,没有绑定所处的环境,所以采用默认的全局对象作为 this。

  2. 对象方法调用,obj.foo() ,这时候根据在对象属性里得到的函数的地址值来访问这个函数,这个函数拿出来的时候环境是处在这个对象内,所以 this 指向这个对象。

  3. new 创建实例的时候,this 指向的是这个实例。

  4. call,apply,bind 绑定 this,这时候 this 指向提供的对象。

a.如果是一般函数,this指向全局对象window;

b.在严格模式下"use strict",为undefined.

c.对象的方法里调用,this指向调用该方法的对象.

d.构造函数里的this,指向创建出来的实例.

  • 默认情况this指向window,严格严格模式下指向undefined
  • 对象调用时,只想当前对象
  • 被call、apply、bind改变时,只想改变的对象
  • 通过new创建新对象时默认指向新创建的对象

结论

  • 如果是一般函数,this指向全局对象window;
  • 在严格模式下"use strict",为undefined.
  • 对象的方法里调用,this指向调用该方法的对象.
  • 构造函数里的this,指向创建出来的实例

改变this指向的方法

改变this的指向并且执行调用函数
.call(), call(thisScope, arg1, arg2, arg3...)
.apply(), apply(thisScope, [arg1, arg2, arg3...]);两个参数

而bind 改变this的指向,返回的是函数
.bind() bind(thisScope, arg1, arg2, arg3...)

this指向的四种情况:

  • new,通过new构造函数里的this,指向新创建的实例
  • call、apply、bind绑定this,this指向传递的对象;
  • 默认情况下,全局上下文中this指向window,如果在严格模式时,this为undefined
  • 方法执行时,通过.调用方法,则方法中的this指向该对象

一,判断this指向原则

函数中的this,指向函数运行时所在的对象(当前对象,而不是上层对象)。如果函数运行时没有依赖的当前对象,则this指向最上层对象(eg: window)。

二,this指向的几种情况

1. obj.fun()中的this,指向当前运行的对象obj

fun 中的 this->obj ,自动指向.前的对象

var obj ={
   foo: function () {
     console.log(this);
   }
 };
 
 obj.foo() // obj

2. new Fun()中的this,指向当前运行的对象(new出来的新实例对象)

Fun 中的 this->正在创建的新对象,new 改变了函数内部的 this 指向,导致 this 指向实例化的对象

function Person(name) {
  this.name = name;
  console.log(this); // Person {name: "song"}
};

var person = new Person('song');
console.log(person.name);// "song" 

3. fun()中的this,指向当前运行的对象window

this->最上层对象(eg: window),直接执行函数,this指向默认的最上层对象(eg: window)

function f() {
  console.log(this);
}
f() // window

4. 函数中的函数的this,没有依赖的当前对象,默认指向window

this->最上层对象(eg: window),因为函数内函数,没有调用它的对象,所以只能指向默认的额window。

  • 例1,函数中的赋值型函数
 var o = {
   f1: function () {
     console.log(this);
     var f2 = function () {
       console.log(this);
     }();
   }
 }
 
 o.f1()
 // Object
 // Window (没有调用的它的对象了,所以只能指向默认的额window)

this指向的4种形式按优先级来说分别为

  1. new 关键字 this指向新创建的对象
  2. call,apply,bind绑定时,this指向绑定的对象
  3. 通过对象调用方法时,this指向调用方法的对象
  4. 没有调用对象时,指向全局
  1. 独立函数调用,例如getUserInfo(),此时this指向全局对象window
  2. 对象调用,例如stu.getStudentName(),此时this指向调用的对象stu
  3. call()、apply()和bind()改变上下文的方法,this指向取决于这些方法的第一个参数,当第一个参数为null时,this指向全局对象window
  4. 箭头函数没有this,箭头函数里面的this只取决于包裹箭头函数的第一个普通函数的this
  5. new构造函数调用,this永远指向构造函数返回的实例上,优先级最高。

当默认情况 this 指向 window 但严格模式下指向 undefined
当对象调用时指向当前对象
被call、apply、bind改变指向改变的对象
通过new创建新对象时新创建的对象
new -> call、apply、bind -> 对象调用 -> 默认情况

● web全局环境下的this指向window,nodejs环境为global对象
● 非严格模式下,函数内部的this指向window,严格模式下,为undefined
● 对象的方法里调用,this指向调用该方法的对象 。被嵌套的函数独立调用时,this默认指向window
● 构造函数里的this,指向创建出来的实例
● 此外,bind/call/apply 可以更改绑定this的指向

this 绑定有四条规则

默认绑定,隐式绑定,显示绑定,new绑定

this 是在函数被调用时才会绑定的,所以要确定函数中this绑定,我们首先需要找到函数的调用位置。
然后再根据以上四条规则,按照其优先级 new绑定>显示>隐式>默认 ,就能确定this 的绑定对象。

默认绑定

函数被直接调用,不符合其他三条绑定规则则会默认绑定,非严格模式下this指向全局对象,严格模式下指向undefined

function foo( ){
  console.log( this.a )
}
var a =1 ;
foo( );//1
function foo( ){
"use strict"
  console.log( this.a )
}
var a =1 ;
foo( );// TypeError: this is undefined

隐式绑定

调用位置有上下文对象,或者说调用函数被某个对象拥有或者包裹。引用到了对象的属性上,然后通过对象调用。
this会指向这个对象

function foo( ){
  console.log( this.a )
}
var obj={
   a:1,
  foo:foo
} ;
obj.foo( );//1

obj的foo属性,指向了foo函数,通过obj.foo()调用,this指向了obj。

  • 隐式丢失

但这里要注意,隐式丢失的情况,就是因为obj.foo其实也指向foo函数的引用,这个引用如果被赋值给另一个变量,再通过该变量调用,那就相当于跳过了obj,直接调用了,所以会造成隐式丢失。

function foo( ){
  console.log( this.a )
}
var obj={
   a:1,
  foo:foo
} ;
var b=obj.foo;
b( ); // TypeError: this is undefined

以上,相当于全局直接调用foo函数,所以是默认绑定。
注意,函数作为参数传参给别的函数,也是一种隐式赋值,一样会发生隐式丢失的情况。

显示绑定

通过,apply(...),call(...) ,bind( )硬绑定,修改this指向

function foo( ){
  console.log( this.a )
}
var obj={
   a:1
} ;
foo.call(obj); //1
foo.apply(obj); //1

var bar=foo.bind(obj);
bar(3);//1

bind其实就是基于包裹了一层函数,内部再通过apply实现的硬绑定,解决上面说的this丢失问题,这章暂不细纠。

以上代码都将,foo中this指向了obj。

new

通过new的方式来调用函数,会创建一个新的实例对象,构造函数中的this也会指向这个新创建的对象。
new的方式改变this指向的优先级式最高的。

function foo( a){
  this.a=a;
}
var b= new foo(2);
console.log(b.a);//2

总结

以上,我们知道了确认this绑定,需要知道调用位置,再按优先级从高到低,ew绑定>显示>隐式>默认,看匹配哪条规则,就能明确this指向。

  • 方法调用模式,当一个函数作为一个对象的属性被调用,this指向该对象
var myObject = {
	name: 'myobj',
  myFunc: function() {
  	console.log(this.name) // myobj
  }
}
myObject.myFunc()
  • 函数调用模式,当一个函数并非一个对象的属性时,那么它就是被当做一个函数来调用的。当这个函数被调用时,this绑定到全局对象
var name = 'window';
var myObject = {
	name: 'myobj',
  myFunc: function () {
  	console.log(this.name) // myobj 方法调用模式
  	var func = function() {
    	console.log(this.name) // window, 函数调用模式
    }  
    func()
  }
}

myObject.myFunc()
  • 构造器调用模式,当一个函数使用new新建实例时,会将新创建的实例对象指向构造器函数的prototype,并且this指向该实例对象
var myObject = function(name) {
	this.name = name
}
myObject.prototype.myFunc = function(){
	console.log(this.name)
}

var obj = new myObject('objname');
obj.myFunc()  // objname
  • apply, call, bind调用模式,第一个参数会传递this指向的对象
var name = 'window'
var obj = {
	name : 'obj'
}
var myFunc = function(){
	console.log(this.name)
}
myFunc()   // window
myFunc.bind(obj)() // obj
myFunc.apply(obj) // obj
myFunc.call(obj) // obj

全局上下文的this

严格/非严格模式全局上下文的this都指向window

函数上下文的this

场景 this指向
普通函数调用 严格模式默认指向undefined/非严格模式默认指向window
对象中的函数调用模式 默认指向调用的对象
call/apply/bind 调用模式 默认指向第一个参数
构造函数调用模式(new) 如果构造函数没有返回对象,就指向新对象,否则指向构造函数返回的对象
箭头函数调用模式 this指向上层非箭头函数的this

不同调用this的优先级

new 调用 > call/apply/bind > 对象上的函数调用 > 普通函数调用

1.如果是一般函数,this指向全局对象window;
2.在严格模式下"use strict",为undefined.
3.对象的方法里调用,this指向调用该方法的对象.
4.构造函数里的this,指向创建出来的实例.

this 对象是在运行时基于函数的执行环境绑定的
在全局函数中,this 指向 window,而当函数被作为某个对象的方法调用时,this 指向那个对象。
匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。
箭头函数中没有this,this指向其所处的词法作用域
普通函数,在严格模式下为undefined

this指向,也即this绑定的4种形式如下:

  1. 默认绑定,指向window。在全局环境下的this指向window;普通函数定义后,执行时内部的this也指向window;回调函数内部,this也指向window;箭头函数例外,其内部this是定义它的环境下的this指向。
console.log(this); // window;

function foo(){
    console.log(this);//  window;
}
foo()

setTimeout(function(){
    console.log(this); // window
},1000)
  1. 隐式绑定,指向调用函数的对象,如,对象内有函数属性,调用该函数,则this指向该对象。
let obj = {
    foo:function(){
        console.log(this); // obj
    }
}
obj.foo()
  1. 显示绑定,通过call、apply或bind去修改this的指向,如下
function foo(){
    console.log(this.a)
}
let obj = {
    a:1
}

foo.call(obj);// 1
  1. new 构造函数绑定,通过new 构造函数创建一个新的实例,将构造函数内的this指向该实例,如下
function Dog(options){
    this.name = options.name;
    this.age = options.age;
}

dog1 = new Dog({name:'dog1',age:'1'})
console.log(dog1.name); // dog1
console.log(dog1.age); // 1

dog2 = new Dog({name:'dog2',age:'2'})
console.log(dog2.name); // dog2
console.log(dog2.age); // 2

结论

  • 如果是一般函数,this 指向全局对象 window
  • 在严格模式下(use strict),为 undefined
  • 对象的方法里调用,this 指向调用该方法的对象
  • 构造函数里的 this,指向创建出来的实例

改变 this 指向的方法

call

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

func.call(thisScope, arg1, arg2, arg3...)

apply

apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

func.apply(thisScope, [arg1, arg2, arg3...])

bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

func.bind(thisScope, arg1, arg2, arg3...)

  1. 如果是普通函数,this 指向全局对象,在浏览器是 window,在 node 是 global
  2. 如果是箭头函数,this 指向定义时外部环境的 this
  3. 如果是对象调用,则 this 指向该对象
  4. 如果是在构造函数或者Class中, this 指向构造出来的实例
  5. 如果是使用 bind、call、apply,this 指向函数的第一个值对应的对象
  6. 如果是在严格模式下,this 在函数外部指向 全局,在函数内部指向 undefined

this 这四种指向性问题,其实说的是一个函数被调用的四种方式:

  • 一个函数可以作为函数被调用
  • 可以作为方法被调用
  • 可以作为构造函数被调用
  • 也可以用call apply bind的方式去调用。

这四种调用方式会造成的是会有不同的this指向。如果你是用函数的方式去调用,那么this在严格模式下就是undefined,在普通模式下就是window。如果你是用方法的方式去调用,那么 this 指向的就是这个方法所属的那个对象。如果你是用构造函数去调用,那么this就是一个空对象。如果你是用call apply bind去调用,那么就是你指定this的值了。