通过调用标识符确定this

218 阅读9分钟

! ! ! JS的函数this,只在执行时才确定,而非声明与赋值时

一. 纲

this的性质

  1. 作用:表示函数执行时的环境
  2. 值:一个对象
  3. 特点:动态性

确定this的难度

  • JS语言的动态性:

    函数的this在执行时才能确定

  • 函数为一级公民

    可作实参、返回值、数据赋值进行传递

解决方法

  • 书写函数时,要预想该函数会被如何调用
  • 以调用标识符确定this值
  • 以变量代替this、使用绑定

函数的调用标识符

  1. 函数是复杂数据类型
  2. 变量可获取函数值--引用地址
  3. 函数调用时,该调用名字是该函数此次调用时的标识符

以标识符确定this值

  1. 未使用绑定字:

    函数的调用标识符是否属于一个对象的成员

    是:this就指向该对 否:寬鬆模式是window;严格模式是未定义

  2. 使用绑定字 (执行时)

    把this绑定到指定对象上

开启严格模式

函数体内使用严格关键词

class方法与模块方法自动运行在严格模式中

不包括方法简写

僞代碼 以下變量fn均表示一個宽松環境的函數, obj表示一個對象

fn();	// window
obj1.fn();	// obj1
obj2.obj3.fn()	// obj3
fn.call(obj4)  // obj4
new fn()   // 实例

函数fn执行时,其调用标识符均是fn

每个标识符fn所属的对象都不同,其this就不同

  • fn() : 调用标识符fn不属于任何对象:thiswindow

  • obj1.fn() : 调用标识符fn属於对象obj1的成员

    thisobj1

  • obj2.obj3.fn(): 调用标识符fn属於对象obj2的成员

    thisobj3

  • 未使用绑定时,函数的调用标识符属于对象的一个成员时,其this就是该对象

二. 未使用绑定字

确定 this 值 :

只要查看该函数调用时的标识符

该标识符决定当前函数执行时的this值

1 函数自我调用

  1. 调用标识符是该函数本身的标识符

  2. 该标识符不属于任何对象的属性

  3. 此时this指向

    宽松模式:全局对象window、global(node环境)·

    严格模式: this为未定义  

  4. 立即执行函数也是函数自我调用,

    其调用标识符是anonymou

    其this,永远是window/未定义

2 一个函数的多种调用形式

  1. 函数的引用地址被赋值给一个对象的属性

  2. 函数调用时: 若调用标识符属于对象的一个成员

  this就指向该对象

  1. 例:

    1.函数作为数据进行赋值

    // 伪代码: fn表示宽松模式的下的函数 
    const A = {
      fn: fn
    };
    const B = {
      b1: A.fn,  
      b2: A    
    };
    var c = objA.fn;
    fn();      // window
    A.fn();    // A
    B.b1()     // B
    B.b2.fn(); // A
    c();       // window
    

函数的调用属于地址引用,可赋值给不同变量

调用函数时的标识符 ,决定了此函数执行时的其内部的this值

分析表

调用形式调用标识符标识符所属对象this
fn0()fn0window
A.fn()fn()AA
B.b1()b1()BB
B.b2.fn()fn()b2,b2的值是AA
c()c()window
2. 函数作为数据进行赋值
    var obj = {
      y: function fn1(x) {
        console.log(this);
        while (x) {
          fn1(x - 1);
          break;
        }
      },
      z: function fn2(x) {
        console.log(this);
        while (x) {
          obj.z(x - 1);
          break;
        }
      },
    };
obj.y(1); //obj 、 window"
obj.z(1); //obj 、window"
调用形式调用标识符标识符所属对象this
obj.y(1)y()objobj
fn1(x - 1)fn1()window
obj.z(1)z()objobj
obj.z(x-1)z()objobj
3.函数作为参数传入

1.参数传递是一种隐式赋值

2.传参:类似于: argument = A.fn ;

 参数cb获取了函数fn的引用
// 伪代码: fn表示宽松模式的下的函数 
let A = {
      fn,
      fn2: (function () {
        console.log(this);
      })()
    },
   B = {};
function b(cb) {
  console.log(this);  // window
  B.fn = cb;
  cb();   // window
  B.fn(); // B
}
A.fn();   //  A
b(fn);
调用形式调用标识符标识符所属对象this
A.fn()fn()AA
b(fn)b()window
cb()cb()window
B.fn()fn()BB

3.参数所属对象要看情况

	1.如在浏览器方法中的定时器

	其回调函数参数是属于`window`对象的成员

	回调函数`this``window`对象
var obj = {
  fo: function fo() {
    'use strict'
    console.log(this);
  }
};
obj.fo(); // obj
setTimeout(obj.fo, 100); // window
	2.如事件监听

		其回调参数所属对象是元素本身

4. 其他

  1. getter 与 setter函数同理

  2. 实例中继承的方法同理

  3. class语法的super调用的方法中的this

    1.super是静态的,指向当前类的基类

    2.但是super调用的方法时,进行了绑定

    this绑定到**使用`super·`的类**
    
    function fn() { };
    let obj = {};
    Function.prototype.f1 = function () {
      console.log(this);
      return function f2() {
        console.log(this);
      }
    }
    Function.prototype.x(); //  Function.prototype
    obj.f = fn.f1(); //fn
    obj.f();  // obj
    fn.f1()(); // window
    let f2 = fn.f1();
    f2();   // window
调用形式调用标识符标识符所属对象this
Fn.proto.x()x()Fn.protoFn.proto
fn.f1()f1()fnfn
**f2的this,同样只能在其被调用时决定**

	**不在于它被赋值给谁**
调用形式调用标识符标识符所属对象this
obj.f()f()objobj
fn.f1()()匿名函数()window
f2()f2()window

3.事件处理

  1. 内联事件:

    1. 事件触发函数执行,该函数的调用标识符决定this

    2. 传入this实参

      内联事件中的this实参是该事件的元素本身

<button onclick="fn(this)">AMTF</button>
<button onclick="obj.fn(this)">JLSJ</button>
function fn(v) {
   console.log(this, v); // window、 button-AMTF
}
let obj = { fn }; // obj、 button-JLSJ
document.getElementsByTagName("button")[0]
  .onclick = function () {
    console.log(this);  //  button-AMTF
  }
  1. 外联事件与事件监听

    1. 相当于把函数赋值给该元素的某个属性

    2. 触发该函数执行时,调用者是这个元素

      this指向调用该函数的元素-- e.currentTarget

    btn.addEventListener('click', fn);
    function fn(e) {
      if (e.target == this) {
        // 只在元素本身被觸發時執行
       // doSomething...
       console.log(e.target, this);
      }
    }
  1. IE的attachEvent的回调函数的this是window

    修改this:把函数赋值给对象,再执行这个对象的方法

function attachEvent (elm, event, cb) {
    elm.attachEvent(("on" + event), function () {
        elm.cb = cb;
        elm.cb(window.event);
        delete elm.cb;
    });
}
// 或者使用绑定
function attachEvent(elm, event, cb) {
    elm.attachEvent(("on" + event), cb.bing(elm, window.event));
}

三.使用绑定

确定this值: 绑定关键字指定对象即为this值

**同样只能在函数执行时确定**

四个绑定关键词: new、call、apply、bing

1.callapply

  1. 直接指定一个对象作为 this 的值

  2. 可使用apply来展开一个数组

    ES5:可传入类数组对象

2. 硬绑定bing

  1. 原理
function fakeBind(fn, obj) {
  return function () {
    return fn.apply(obj, arguments);
  };
}
  1. 返回一个新函数
  2. 新函数与原函数的函数体相同
  3. 但其this被绑定到指定的对象
  1. 在使用了bing的函数是尚未执行的,

    this是不确定的: 只能在执行时确定

    使用new调用:为new创建的对象

    没使用new调用:为bing绑定的对象

    只会被new改变

3. 构造器的new绑定

一定會用新创建的实例对象替换原來的this

无论该函数是否已被其他绑定字绑定过

4. 自带绑定

JS、库、宿主环境的内置函数,提供一个参数

用于绑定 this

例:数组的forEach的参数2

5. 绑定到空/原始值

  1. 不错的空对象: Object.create(null);
  2. 绑定到null / undefined
  • 宽松模式:绑定到全局对象
  • 严格模式:null / undefined
  1. 绑定到原始值(字符串类型、布尔类型或者数字类型)会封装成对应的对象

new String、new Boolean、new Number

    let o = {
      a: function () {
        console.log(this);
      }
    };

    o.a();		// o
    o.a.call(null);  // window
    o.a.call(1);	// Number(1)

四. 箭头函数

确定this值 :

箭头函数没有this值

通过引用其上属函数的this值

若该箭头函数没有上属函数,则其this值为未定义

  1. 无法使用绑定关键词修改箭头函数的this值

    因为他没有this值

  2. 唯一修改this值: 给他套上普通函数,再修改该普通函数的this

function x() {
  return (a) => {
    //this 取自 x()
    console.log(this.a);
  };
}
var oA = { a: 2 };
var oB = { a: 3 };
var y = x.call(oA);
y.call(oB); // 2

由于xthis 绑定到 oA

箭头函数的this指向 oA: y的this 也指向 oA

  1. 确定箭头函数的this:
    • 没有所属函数时,只能是未定义
    • 有所属函数,取决于所属函数执行时的this

五.例

1.闭包函数

在一个函数中返回另一个函数

    function f1() {
      console.log(this);
      return function f2() {
        console.log(this);
      }
    }
    f1()();

 函数f1返回的函数的this值,由调用时决定

   f1( )( )·:两次this都是指向了window

	f2执行时,如立即执行函数
    const obj = {};
    obj.y = f1();
    obj.y();
	let fn = fn1();
    fn();
把f1的返回值,赋值给一个变量

  f2的this:调用时决定--fn()、obj.y()

  调用标识符所属的对象不相同

其 f2的this也不相同

2. 反柯里化:

反柯里化:让对象去借用一个原本不属于它的方法

普通函數寫法
    Function.prototype.uncurrying = function () {
      var self = this;
      return function () {
        let thisArg = [].shift.call(arguments);
        return self.apply(thisArg, arguments);
      }
    }
箭頭函數寫法
    Function.prototype.uncurrying = function () {
      return (...args) => this.call( ...args)
    }
  1. 箭头函数的this是取值其外层函数的this,

  2. 外层函数uncurrying的this值,决定于调用时的

let push = Array.prototype.push.uncurrying();
  1. 此时uncurrying的调用者是push函数

则此时的this是指向push函数

3. bing的实现

  1. 工具函数
    objectCreate = Object.create || function (proto) {
      var F = function () { };
      F.prototype = proto;
      return new F();
    }
    let slice = Array.prototype.slice.uncurrying();
    let concat = Array.prototype.concat.uncurrying();
  1. 实现逻辑
    Function.prototype.bind = function (thisArg) {
      if (typeof this !== "function") {
        throw new TypeError("被绑定的不是函數");
      }

      var self = this;
      var baseArgs = slice(arguments, 1); 
   
      var bind = function () {
        return self.apply(
          this instanceof self ? this : thisArg,
          concat(baseArgs, slice(arguments))
        );
      }
      bind.prototype = objectCreate(self.prototype);
      return bing;
    };

4. 自定义默认绑定对象

实现:修改例3的bind函數的返回值的apply的参数1

默认绑定--只当this指向全局对象或者undefined

   把this绑定到默认的对象

        return self.apply(  
          ((!this || this === (window || global)) ?
            thisArg : this
          ),
          concat(baseArg, slice(arguments))
        ); 
`apply`参数1的定制,可实现多种绑定

5. 多重綁定

  const obj1 = {
    name: "obj1"
  };
  const obj2 = {
    name: "obj2"
  };

  function f1() {
    console.log(this);
  }
  function f2() {
    f1.call(this);
  }
  const f3 = () => f2.call(this);

  f2.call(obj1); // obj1
  f3.call(obj2); // window

f2 .call(obj1 )

f2的this被綁到了obj1,所以f1.call(this)的this是obj1;使得f1的this為obj1

f3.call(obj2)

f3是箭頭函數--this綁定無效,但其沒有所屬的函數,所以f2.call(this)的this值為未定義

而在寬鬆模式下的綁定到未定義會改爲绑定到全局对象

6. 立即执行函数

    let o = {
      a: (function () {
        console.log(this);  // window
        return function () {
          console.log(this); // 看情况:调用标识符决定
        }
      })()
    };
    (function (fn) {
      console.log(this); // o
      fn.call(this);    // window
      (() => console.log(this))(); // o
    }).call(o, () => console.log(this));

立即执行函数的this

  • 未使用绑定时,永远是window/未定义
  • 执行时使用绑定,是被绑定的对象
  • 箭头函数,其this如同继承的属性,取自其所属的函数