JavaScript 函数调用和this指针

函数调用和this指针

1. 全局环境的this指针

浏览器全局环境下this指向window对象javascript

console.log(this);
//Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

nodejs环境下this指向global对象html

2. 函数中的this指针

2.1 全局环境下函数调用

非严格模式this指向window,严格模式除箭头函数外指向undefinejava

//a.js 非严格模式
function fun1(){console.log(this)}
fun1();//window

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

var fun2 = ()=>{console.log(this)}
fun2();//window

//b.js 严格模式
'use strict';
function fun1(){console.log(this)}
fun1();//undefined

(function(){console.log(this)})() //undefined

var fun2 = ()=>{console.log(this)}
fun2();//window

2.2 做为对象方法调用

function做为对象属性时,this指针指向对象的实例node

var obj1 = {
    name: 'a',
    func1:function(){console.log(this.name);}
}
obj1.func1();//a
//等同于
function func2(){console.log(this.name);}
var obj2 = {
    name: 'b',
    funRef:func2
}
obj2.funRef();

2.3 构造函数调用

构造函数调用时,this指针指向函数的实例(function也是对象的一种)浏览器

function func3(c){
    this.c = c;
    console.log(this.c);//c
    console.log(this);//func3 {c: "c"}
}
var finst3 = new func3('c');

2.4 同步回调函数

同步回调函数中的this指针服从上面1-3点的规则闭包

function callback()
{
    console.log(this.d);//undefined
    console.log(this);//window
}
function func4(cb){
    cb.d = 'd';    
    cb();
}
func4(callback);

2.5 异步回调函数

关于JavaScript是单线程执行就很少提了,这里详细分析一下对于异步操做的回调函数中this的指向问题。
提到异步操做就不得不提Event Loop,其详细介绍能够参见阮一峰大神的这篇app

简单来讲就是:异步

  • 全部异步函数的执行被放在“任务队列”中由另一个单独的线程执行。当异步操做有结果时,会通知主线程调用该异步操做的回调函数


注意:这里就有问题了,当主线程调用异步操做的回调函数时,是以什么环境和做用域来执行的呢?
async

答案是: 以全局环境和执行回调函数时的做用域链来执行回调函数。
函数

参考如下代码:

setTimeout(function(){
    console.log(this);//1秒后输出window
    }, 1000);
//再看
var asyncFun1 = {
    propA:"a",
    synFun:function() {        
        console.log(this.propA);
        console.log(this);        
    },
    asynFun: function(){setTimeout(this.synFun, 1000)}
}
asyncFun1.synFun();//输出依次是
//a
//{propA: "a", synFun: ƒ, asynFun: ƒ}
asyncFun1.asynFun();
//undefined
//window
//再再看
var asyncFun2={
  propB:"b",
  asynFun:function(){
      var c = 'string c';
      setTimeout(function(){
          console.log(c);
      },1000);
    }
}
asyncFun2.asynFun();//输出'string c'

注意为何会输出'string c': 虽然回调函数运行是在全局环境(this指向window),可是在其做用域链中是可以访问到变量c的。

常见的异步操做包括:

  • setTimeout/setInterval
  • XMLHTTPRequest
  • jQuery AJAX(这里的this被处理过并不指向window)
  • Fetch
  • Promise

关于如何在异步操做的回调函数中使用正确的this指针,往下看。

3. 在异步函数回调中正确使用this指针

经过上面的分析咱们知道在异步回调函数中this会指向window,那么咱们须要怎么作才能让this指向咱们指定的对象呢?

3.1 箭头函数与做用域链

3.1.1 箭头函数定义: (x,y)=>{return x+y;}

  • 当参数只有一个时,小括号()能够省略: x=>{return x+1;}
  • 当函数体只有一行语句而且该语句结果做为返回值时,{}花括号能够省略: x=>x+1;
var fun1 = (x)=>{return x + 1;}
var fun2 = x=>{return x+1;}
var fun3 = x=>x+1;
//返回值都是2
fun1(1);//2
fun2(1);//2
fun3(1);//2

3.1.2 箭头函数特性: 箭头函数体内没有本身的this指针

那么箭头函数体内的this指针将符合做用域链原则,指向做用域链上最近的this

function fun4() {
  setTimeout(() => {
    console.log(this.id);
  }, 1000);
}
var id = 1;
fun4.call({ id: 2 });//输出为2
fun4();//输出为1

分析以下:

  • 若是异步操做的回调函数是普通函数,则其this指向window对象。
  • 但箭头函数体内没有this指针,则按做用域链原则,回调函数中的this指针应该为fun4的this指针。
  • fun4做为普通函数,在全局环境调用时this指向window对象,因此 fun4();输出为1
  • function.call做用在调用函数时,将新的对象替换原有对象。则意味着fun4.call时fun4体内的this指针将指向{ id: 2 },因此输出为2

3.2 闭包和做用域链

接下来看看闭包中的this指针问题

//a.js
function debounce(fn, delay) {  
  var timer = null;
  return function() {    
    var context = this;
    var args = arguments;    
    //定义context和args,经过做用域链达到保存this指针和arguments的做用
    clearTimeout(timer);
    timer = setTimeout(function() {
       //用apply保证回调函数的this指针不会被异步函数重置为window 
       fn.apply(context, args);
    }, delay);
  }
}
//等同于
function debounce(fn, delay) {  
  var timer = null;
  return function() {    
    clearTimeout(timer);
    timer = setTimeout(()=>
      {fn.apply(this, arguments) }, delay);
  }
}

document.a1 = "2";
var obj1 = {
    a1:"1",    
    fun1:function(){
        console.log(this);
        console.log(this.a1);
    }
}

document.addEventListener('scroll', debounce(obj1.fun1, 2000));
//2秒后输出
//document
//2
//b.js
function debounce(fn, delay) {  
  var timer = null;
  return function() {    
    clearTimeout(timer);
    timer = setTimeout(()=> fn(), delay);
  }
}

window.a1 = "3";
document.a1 = "2";
var obj1 = {
    a1:"1",    
    fun1:function(){
        console.log(this);
        console.log(this.a1);
    }
}

document.addEventListener('scroll', debounce(obj1.fun1, 2000));
//2秒后输出
//window
//3

3.3 Call,Apply,Bind

关于这三个函数的对比和做用网上介绍不少,其主要做用为:
使用that替换函数内部this的指针指向为that。

function.call(that, arg1, arg2);
function.apply(that, [arg1, arg2]);
function.bind(that);
//以下
var obj1 = {
  a1:"1",
  fun1:function(){console.log(this.a1)}
}
var obj2 = {
  a1:"2"
}
obj1.fun1();//1
obj1.fun1.call(obj2);//2
var funBind = obj1.fun1.bind(obj2);
funBind();//2
相关文章
相关标签/搜索