• JavaScript中的this—你不知道的JavaScript上卷读书笔记(三)


    this是什么?

    this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到。

    调用位置与调用栈:

    调用位置就是函数在代码中被调用的位置(而不是声明的位置)。

    下面我们来看看到底什么是调用栈和调用位置:

    function baz() {
    	// 当前调用栈是:baz
    	// 因此,当前调用位置是全局作用域
    	console.log( "baz" );
    	bar(); // <-- bar 的调用位置
    }
    
    function bar() {
    	// 当前调用栈是baz -> bar
    	// 因此,当前调用位置在baz 中
    	console.log( "bar" );
    	foo(); // <-- foo 的调用位置
    }
    
    function foo() {
    	// 当前调用栈是baz -> bar -> foo
    	// 因此,当前调用位置在bar 中
    	console.log( "foo" );
    }
    
    baz(); // <-- baz 的调用位置
    

    this绑定规则

    1. 默认绑定:
    function foo() {
    	console.log( this.a );
    }
    var a = 2;
    foo(); // 2
    

    上述代码中,函数调用时应用了this 的默认绑定,因此this 指向全局对象(非严格模式下)。

    那么我们怎么知道这里应用了默认绑定呢?可以通过分析调用位置来看看foo() 是如何调用的。在代码中,foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。

    如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此this 会绑定到undefined:

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

    这里有一个微妙但是非常重要的细节,虽然this 的绑定规则完全取决于调用位置,但是只有foo() 运行在非strict mode 下时,默认绑定才能绑定到全局对象;严格模式下与foo()的调用位置无关:

    function foo() {
    	console.log( this.a );
    }
    var a = 2;
    
    (function(){
    	"use strict";
    	foo(); // 2
    })();
    
    2. 隐式绑定:
    function foo() {
    	console.log( this.a );
    }
    var obj = {
    	a: 2,
    	foo: foo
    };
    
    obj.foo(); // 2
    

    当foo() 被调用时,它的落脚点指向obj 对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this 绑定到这个上下文对象。因为调用foo() 时this 被绑定到obj,因此this.a 和obj.a 是一样的。

    对象属性引用链中只有最顶层或者说最后一层会影响调用位置

    #######隐式丢失

    一个最常见的this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this 绑定到全局对象或者undefined 上,取决于是否是严格模式。

    function foo() {
    	console.log( this.a );
    }
    var obj = {
    	a: 2,
    	foo: foo
    };
    var bar = obj.foo; // 函数别名!
    var a = "oops, global"; // a 是全局对象的属性
    bar(); // "oops, global"
    

    虽然bar 是obj.foo 的一个引用,但是实际上,它引用的是foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

    3. 显式绑定:

    就像我们刚才看到的那样,在分析隐式绑定时,我们必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this 间接(隐式)绑定到这个对象上。那么如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么做呢?

    JavaScript 中的“所有”函数都有一些有用的特性(这和它们的[[ 原型]] 有关——之后我们会详细介绍原型),可以用来解决这个问题。具体点说,可以使用函数的call(..) 和apply(..) 方法。严格来说,JavaScript 的宿主环境有时会提供一些非常特殊的函数,它们并没有这两个方法。但是这样的函数非常罕见,JavaScript 提供的绝大多数函数以及你自己创建的所有函数都可以使用call(..) 和apply(..) 方法。

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

    通过foo.call(..),我们可以在调用foo 时强制把它的this 绑定到obj 上。

    显式绑定仍然无法解决我们之前提出的丢失绑定问题,通过以下方法可以解决:

    硬绑定

    	function foo() {
    		console.log( this.a );
    	}
    	var obj = {
    		a:2
    	};
    	var bar = function() {
    		foo.call( obj );
    	};
    	bar(); // 2
    	setTimeout( bar, 100 ); // 2
    	// 硬绑定的bar 不可能再修改它的this
    	bar.call( window ); // 2
    
    4. new绑定:

    使用new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

    1. 创建(或者说构造)一个全新的对象。

    2. 这个新对象会被执行[[ 原型]] 连接。

    3. 这个新对象会绑定到函数调用的this。

    4. 如果函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象。

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

    优先级: new绑定>显式绑定>隐式绑定

    判断this:

    1. 函数是否在new 中调用(new 绑定)?如果是的话this 绑定的是新创建的对象。

      var bar = new foo()

    2. 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象。

      var bar = foo.call(obj2)

    3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。

      var bar = obj1.foo()

    4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

      var bar = foo()

    绑定例外

    1. 被忽略的this

    把null 或者undefined 作为this 的绑定对象传入call、apply 或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。

    function foo() {
    	console.log( this.a );
    }
    var a = 2;
    foo.call( null ); // 2
    
    更安全的ths

    一种“更安全”的做法是传入一个特殊的对象,把this 绑定到这个对象不会对你的程序产生任何副作用。。就像网络(以及军队)一样,我们可以创建一个“DMZ”(demilitarized zone,非军事区)对象——它就是一个空的非委托的对象。

    function foo(a,b) {
    	console.log( "a:" + a + ", b:" + b );
    }
    // 我们的DMZ 空对象
    var ø = Object.create( null ); //此处的∅不会创建Object.prototype 这个委托,所以它比{}“更空”
    // 把数组展开成参数
    foo.apply( ø, [2, 3] ); // a:2, b:3
    // 使用bind(..) 进行柯里化
    var bar = foo.bind( ø, 2 );
    bar( 3 ); // a:2, b:3
    

    2. 间接引用

    另一个需要注意的是,你有可能(有意或者无意地)创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。

    function foo() {
    	console.log( this.a );
    }
    var a = 2;
    var o = { a: 3, foo: foo };
    var p = { a: 4 };
    o.foo(); // 3
    (p.foo = o.foo)(); // 2
    

    3. 软绑定(此处不讨论)

    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 也不行!)

  • 相关阅读:
    MySQL表之间的关系概述
    网路通信简介
    多态与鸭子类型
    组合与类继承
    类与对象的属性与方法以及封装
    对象与类的初始
    2018.12.12
    2018.12.9浮动布局,盒子显隐,定位,z-index,流式布局,小米开头
    2018.12.8浮动布局,display总结,overflow,清浮动
    2018.12.7边界圆角redius,背景图设置,平铺,精灵图,盒子伪类索引
  • 原文地址:https://www.cnblogs.com/ylweb/p/8001637.html
Copyright © 2020-2023  润新知