zoukankan      html  css  js  c++  java
  • Javascript中的 this

    什么是 this? 为什么要用 this?

    this不是编写时绑定的,而是运行时绑定。它依赖于函数调用的上下文条件。this绑定与函数声明的位置没有任何关系,而与函数被调用的方式紧密相连。

    当一个函数被调用时,会建立一个称为执行环节的活动记录。这个记录包含函数是从何处(调用栈---call-stack)被调用的,函数是如何被调用的,被传递了什么参数等信息。这个记录的属性之一,就是在函数执行期间将被使用的this引用。

    this 机制提供了更优雅的方式来隐含地“传递”一个对象引用,导致更加干净的API设计和更容易的复用。

    你的使用模式越复杂,你就会越清晰地看到:将执行环境作为一个明确参数传递,通常比传递 this 执行环境要乱.

    对this的困惑

    想当初,对"this"这个名字用太过于字面的方式考虑而产生了困惑,没有真正的了解this是如何实际工作的。接下来首先要摒弃一些误解。

    一:this 指向函数自己

    看一个例子:

    function fn(num) {
    	console.log( "fn: " + num );
    
    	// 追踪 `fn` 被调用了多少次
    	this.count++;
    }
    fn.count = 0;
    for (let i=0; i<5; i++) {
    	fn( i );
    }
    // fn: 0
    // fn: 1
    // fn: 2
    // fn: 3
    // fn: 4
    
    console.log(fn.count); // 0
    

    复制代码到控制台打印一下,what??? fn.count为什么是0,console.log( "fn: " + num )明明告诉我们实际调用五次的,初学时的就是这种状态 --- N脸懵逼。这种错误的认为就是我们对于this(在 this.count++ 中)的含义进行了过于字面化的解释。

    为什么会出现这种情况??
    记住,this的指向与所在方法的调用位置有关,而与方法的声明位置无关。 this.count++这个语句,不小心创建了一个全局变量,此时的this是指向的全局对象。在非严格模式下,全局作用域中函数被独立调用时,它的this默认指向(绑定)window或者global。在严格模式中,它的this为undefined。

    这个问题的解决方法有多种,这里给出其中一种-强迫thi指向fn函数对象

    function fn(num) {
    	console.log( "fn: " + num );
    
    	// 追踪 `fn` 被调用了多少次
    	this.count++;
    }
    fn.count = 0;
    for (let i=0; i<5; i++) {
    	fn.call(fn, i );
    }
    // fn: 0
    // fn: 1
    // fn: 2
    // fn: 3
    // fn: 4
    
    console.log(fn.count); // 5
    

    二:this 函数的作用域

    this不会以任何方式指向函数的词法作用域。作用域好像是一个将所有可用标识符作为属性的对象,这从内部来说是对的。但是 JavasScript 代码不能访问作用域“对象”。它是 引擎 的内部实现。

    看一个例子:

    function fn() {
    	var a = 2;
    	this.bar(); // 这里的this指向全局对象window
    }
    
    function bar() {
    	console.log( this.a ); // 这里的this指向全局对象window
    }
    
    fn(); // undefined
    
    

    没想到吧,最后输出咋不是2呢?瞎搞了吧哈哈
    如果你想用 this 在 fn() 和 bar() 的词法作用域间建立一座桥,使得bar() 可以访问 fn()内部作用域的变量 a。这是不可能的,你不能使用 this 引用在词法作用域中查找东西。如果你想要函数bar访问函数fn里的a 变量,可以用闭包实现:

    function fn() {
    	var a = 2;
      (function b() {
        console.log( a );
      })()
    }
    
    fn(); // 2
    

    调用点

    先来了解一下调用点,调用点决定函数执行期间 this 指向哪里。

    用代码来展示一下调用栈和调用点:

    function baz() {
        // 调用栈是: `baz`
        // 我们的调用点是 global scope(全局作用域)
    
        console.log( "baz" );
        bar(); // <-- `bar` 的调用点
    }
    
    function bar() {
        // 调用栈是: `baz` -> `bar`
        // 我们的调用点位于 `baz`
    
        console.log( "bar" );
        foo(); // <-- `foo` 的 call-site
    }
    
    function foo() {
        // 调用栈是: `baz` -> `bar` -> `foo`
        // 我们的调用点位于 `bar`
    
        console.log( "foo" );
    }
    
    baz(); // <-- `baz` 的调用点
    

    绑定规则

    先来了解一下4种绑定规则,用于考察调用点并判定4种规则中的哪一种适用。

    默认绑定

    1. 默认绑定常见的情况是独立函数调用,也就是 fn()。这时 this 指向了全局对象 window

    看一个例子:

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

    再看例子,给上文误解的例子加深理解:

    var count = 0;
    function fn(num) {
    	console.log( "fn: " + num );
    
    	// 追踪 `fn` 被调用了多少次
    	this.count++;
    }
    fn.count = 0;
    for (let i=0; i<5; i++) {
    	fn( i );
    }
    // fn: 0
    // fn: 1
    // fn: 2
    // fn: 3
    // fn: 4
    
    console.log(fn.count); // 0
    console.log(count); // 5
    
    1. 严格模式 下,对于 默认绑定 来说全局对象是不合法的,this 将被设置为 undefined

    看一个例子:

    function fn() {
    	"use strict";
    
    	console.log( this.a );
    }
    
    var a = 2;
    
    fn(); // TypeError: `this` is `undefined`
    
    1. 显示使用 window 调用 fn() 则不会报错。

    看一个例子:

    function fn() {
    	"use strict";
    
    	console.log( this.a );
    }
    
    var a = 2;
    
    window.fn(); // 2
    
    1. 即便所有的 this 绑定规则都是完全基于调用点的,但如果 fn() 的内容没有在 严格模式 下执行,对于默认绑定来说全局对象是唯一合法的。fn() 调用点的 严格模式 状态与此无关。

    看一个例子:

    function fn() {
    	console.log( this.a );
    }
    
    var a = 2;
    
    (function(){
    	"use strict";
    
    	fn(); // 2
    })();
    

    注意: 应尽量避免 严格模式非严格模式混淆使用。

    隐式绑定

    这个规则是: 调用点是否有一个环境对象。当一个方法引用存在一个环境对象时, 这个对象应当被用于这个函数调用的 this 绑定。

    1. 函数如果被某个对象包含,且作为这个对象的方法调用时,函数里面的 this 就是这个对象。

    看一个例子:

    function fn() {
      console.log(this.a);
    }
    var obj = {
      a: 2,
      foo: foo
    }
    obj.fn(); // 2
    
    1. 只有对象属性引用链的最后一层是影响调用点的。

    看一个例子:

    function fn() {
      console.log(this.a);
    }
    var obj1 = {
      a: 42,
      foo: foo
    }
    var obj2 = {
      a: 2,
      obj1: obj1
    }
    obj2.obj1.foo(); // 42
    
    1. 当一个隐式绑定丢失了它的绑定,意味着它可能会退回到默认绑定。

    看一个例子:

    function fn() {
      console.log(this.a);
    }
    var obj = {
      a: 2,
      fn: fn
    };
    var bar = obj.fn; // 函数引用!
    var a = 'oops, global'; // `a` 也是一个全局对象的属性
    bar();  // "oops, global"
    
    
    1. 当传递一个回调函数式,参数传递仅仅是一种隐式的赋值,而且因为是传递一个函数,它是一个隐含的引用赋值,所以最终结果也是指向了全局对象,触发了默认绑定,this 绑定到 window中了。

    看一个例子:

    function fn() {
    	console.log( this.a );
    }
    
    function dofn(fn) {
    	// `fn` 只不过 `fn` 的另一个引用
    
    	fn(); // <-- 调用点!
    }
    
    var obj = {
    	a: 2,
    	fn: fn
    };
    
    var a = "oops, global"; // `a` 也是一个全局对象的属性
    
    dofn( obj.fn ); // "oops, global"
    
    

    或者

    function fn() {
    	console.log( this.a );
    }
    
    var obj = {
    	a: 2,
    	fn: fn
    };
    
    var a = "oops, global"; // `a` 也是一个全局对象的属性
    
    setTimeout( obj.fn, 100 ); // "oops, global"
    

    显式绑定

    利用 JavaScript 提供的的 call, apply, bind方法强制一个函数使用某个特定的对象作为this的绑定。

    1. 通过 call或者 apply 强制函数的 this 指向 fn .

    看一个例子:

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

    注意: 如果你传递一个简单基本类型值(string,boolean,或 number 类型)作为 this 绑定,那么这个基本类型值会被包装在它的对象类型中(分别是 new String(..),new Boolean(..),或 new Number(..))。这通常称为“封箱(boxing)”。

    说明: 就 this 绑定的角度讲,call(..) 和 apply(..) 是完全一样的。它们在处理其他参数上的方式不同,call的参数列表要一个一个列在后面,如fn.call(obj, '参数一', '参数二'), 而apply参数列表使用的是数组,如fn.apply(obj, ['参数一', '参数二'])

    1. 硬绑定 bind
      ECMAScript 5 引入了 Function.prototype.bind。 bind(..)会创建一个与原本函数具有相同函数体和作用域的函数,但是在这个心函数中,this 将永久被绑定到了 bind的第一个参数,无论这个函数如何被调用的。

    看一个例子:

    function fn(something) {
    	console.log( this.a, something );
    	return this.a + something;
    }
    
    var obj = {
    	a: 2
    };
    
    var bar = fn.bind( obj );
    
    var b = bar( 3 ); // 2 3
    console.log( b ); // 5
    

    注意: 在 ES6 中,bind(..) 生成的硬绑定函数有一个名为 .name 的属性,它源自于原始的 目标函数(target function)。举例来说:bar = fn.bind(..) 应该会有一个 bar.name 属性,它的值为 "bound foo",这个值应当会显示在调用栈轨迹的函数调用名称中。

    new 绑定

    当函数使用 new 调用时,即构造器调用时,会自动执行下面的操作:

    1. 一个全新的对象会凭空创建(就是被构建)
    2. 这个新构建的对象会被接入原形链([[Prototype]]-linked)
    3. 这个新构建的对象被设置为函数调用的 this 绑定
    4. 除非函数返回一个它自己的其他对象,否则这个被 new 调用的函数将自动返回这个新构建的对象。

    看个例子:

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

    new 的创建过程:

    var new2 = function(func) {
      var o = Object.create(func.prototype);
      var k = func.call(o);
      if(typeof k === 'object') {
        return k;
      } else {
        return o;
      }
    }
    
    

    四种绑定规则的顺序

    1. 函数是通过 new 被调用的吗(new 绑定)?如果是,this 就是新构建的对象。
    var bar = new foo()
    
    1. 函数是通过 callapply 被调用(明确绑定),甚至是隐藏在 bind 硬绑定 之中吗?如果是,this 就是那个被明确指定的对象。
    var bar = foo.call( obj2 )
    
    1. 函数是通过环境对象(也称为拥有者或容器对象)被调用的吗(隐含绑定)?如果是,this 就是那个环境对象。
    var bar = obj1.foo()
    
    1. 否则,使用默认的 this(默认绑定)。如果在 strict mode 下,就是 undefined,否则是 global 对象。
    var bar = foo()
    

    箭头函数里的 this

    箭头函数不是通过 function 关键字声明的,而是通过所谓的“大箭头”操作符:=>。
    箭头函数是从封闭它的(函数或全局)作用域采用 this 绑定。

    看一个例子:

    function fn() {
      // 返回一个箭头函数
    	return (a) => {
        // 这里的 `this` 是词法上从 `fn()` 采用的
    		console.log( this.a );
    	};
    }
    
    var obj1 = {
    	a: 2
    };
    
    var obj2 = {
    	a: 3
    };
    
    var bar = fn.call( obj1 );
    bar.call( obj2 ); // 2, 不是3!
    

    最常见的用法是用于回调,比如setTimeout

    function fn() {
    	setTimeout(() => {
    		// 这里的 `this` 是词法上从 `fn()` 采用
    		console.log( this.a );
    	},100);
    }
    
    var obj = {
    	a: 2
    };
    
    fn.call( obj ); // 2
    

    总结

    • this 总是指向调用它所在方法的对象
    • this 的指向与所在方法的调用位置有关,而与方法的声明位置无关
    • 在浏览器中,调用方法时没有明确对象的, this 指向 windowNode 中,这种情况指向 global,但是在 Node cli 下,与浏览器的行为保持一致。严格模式下,thisundefined
    • 在浏览器中 setTimeoutsetInterval 和匿名函数执行时的当前对象是全局对象 window
    • applycall 能够强制改变函数执行时的当前对象,让 this 指向其他对象
    • es6开始,出现箭头函数(lamda表达式), 是在声明时候绑定this的
    • [绑定特例],自行查看书籍(https://github.com/CuiFi/You-Dont-Know-JS-CN/blob/master/this %26 object prototypes/ch2.md#绑定的特例)

    学习链接
    你不懂JS: this 是什么?
    你不懂JS: this 豁然开朗!
    this - JavaScript | MDN 官方中文版
    JavaScript 的 this 原理

  • 相关阅读:
    LeetCode Array Easy 414. Third Maximum Number
    LeetCode Linked List Medium 2. Add Two Numbers
    LeetCode Array Easy 283. Move Zeroes
    LeetCode Array Easy 268. Missing Number
    LeetCode Array Easy 219. Contains Duplicate II
    LeetCode Array Easy 217. Contains Duplicate
    LeetCode Array Easy 189. Rotate Array
    LeetCode Array Easy169. Majority Element
    LeetCode Array Medium 11. Container With Most Water
    LeetCode Array Easy 167. Two Sum II
  • 原文地址:https://www.cnblogs.com/arissy/p/10173002.html
Copyright © 2011-2022 走看看