JavaScript之你不知道的this

栏目: JavaScript · 发布时间: 6年前

内容简介:只有深谙了this,你才有可能用 JavaScript 创建类似谷歌地图这样大型的复杂应用提供了一种更优雅的方式来隐式”传递”一个对象引用, 让API设计更加简洁和清晰首先来看一段代码, 此处不使用this, 需要给identify()和speak()显示的传入一个上下文对象:

只有深谙了this,你才有可能用 JavaScript 创建类似谷歌地图这样大型的复杂应用

一、这篇文章出现的背景

1. this在我们开发过程中的重要性(开发场景) -- 通过一段代码简单了解this

提供了一种更优雅的方式来隐式”传递”一个对象引用, 让API设计更加简洁和清晰

首先来看一段代码, 此处不使用this, 需要给identify()和speak()显示的传入一个上下文对象:

// 定义 you & me对象
var me = {
    name: "Kyle"
};
var you = {
    name: "Reader"
};

function identify(context) {
    return context.name.toUpperCase();
}
function speak(context) {
    var greeting = "Hello, I'm " + identify( context );
    console.log( greeting );
}

identify( you ); // READER
speak( me ); //hello, 我是 KYLE
复制代码

使用this解决: 可以在不同的上下文对象(me 和 you)中重复使用函数identify()和speak()

function identify() {
    return this.name.toUpperCase();
}
function speak() {
    var greeting = "Hello, I'm " + identify.call( this );
    console.log( greeting );
}

identify.call( me ); // KYLE
identify.call( you ); // READER

speak.call( me ); // Hello, 我是 KYLE
speak.call( you ); // Hello, 我是 READER
复制代码

显然, 随着你的使用模式越来越复杂, 显式传递上下文对象会让代码变得越来越混乱, this可以让你的代码变得更优雅。特别是当你使用对象(关联)和原型时, 利用this使得函数可以自动引用合适的上下文对象显的尤为重要

2.两种错误的理解

  • this指向函数自身
  • this指向函数的作用域, 这个在某些情况下是正确的, 但是在其他情况下确实错误的

事实上, 一部分人认为"this既不指向函数自身也不指向函数的词法作用域", 但是也是不对的, 在某种情况下, this就指向函数自身, 也可能指向词法作用域

3.本质

this是在运行(函数被调用)时发生绑定的,并不是在编写时绑定, 它的上下文取决于函数调用时的各种条件,它指向什么完全取决于函数在哪里被调用

二、this 绑定规则 & 优先级

简单来说, 有这大致四种

  1. 由new调用(new绑定)
  2. 函数是否通过call、apply(显式绑定)或者硬绑定调用
  3. 函数是否在某个上下文对象中调用(隐式绑定)
  4. 默认绑定

1. 默认绑定

无法应用其他规则时的默认规则, 严格模式下绑定到undefined, 否则绑定到全局对象

最常用的函数调用类型:独立函数调用

function foo() {
    console.log( this.a );
}

var a = 2;
foo(); // 2
复制代码

代码中, foo()是直接使用不带任何修饰的函数引用进行调用的,只能适用于this的默认绑定,无法应用其他规则,因此this指向全局对象

// 严格模式下
function foo() {
    "use strict";
    console.log( this.a );
}

var a = 2;
foo(); // TypeError: this is undefined
复制代码

所以, 不推荐这种写法。

2. 隐式绑定

考虑调用位置是否有上下文对象,或者说是否被某个对象或者包含

当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象

必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把 this 间接(隐式)绑定到这个对象上

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

obj.foo(); // 2
复制代码

对象属性引用链中只有最后一层会影响调用位置, 即调用栈的末端

function foo() {
    console.log( this.a );
}
var obj2 = {
    a: 42,
    foo: foo
};
var obj1 = {
    a: 2,
    obj2: obj2
};

obj1.obj2.foo(); // 42
复制代码

3. 显示绑定

强制指定某些对象对函数进行调用,this则强制指向调用函数的对象

  • call(thisObj, arg1, arg2, arg3...)
  • apply(thisObj, argArr)
  • ES5 中提供了内置的方法 硬绑定bind(thisObj)

显示绑定场景

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

foo.call( obj ); // 2
复制代码

硬绑定常用场景

function foo(something) {
    console.log( this.a, something );
    return this.a + something;
}
var obj = {
    a:2
};

var bar = foo.bind( obj );
var b = bar( 3 ); // 2 3
console.log( b ); // 5
复制代码

4. new绑定

new方式优先级最高,只要是使用new方式来调用一个构造函数,this一定会指向new调用函数新创建的对象

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

console.log( bar.a ); // 2
复制代码

三、绑定例外

1. 箭头函数

,实际原因是箭头函数根本没有自己的this

this指向的固定化,并不是因为箭头函数内部有绑定this的机制, 实际原因箭头函数没有自己的this,它的this是继承而来,默认指向在定义它时所处的对象(宿主对象)。 捕获其所在(即定义的位置)上下文的this值,作为自己的this值, 如果在当前的箭头函数作用域中找不到变量,就像上一级作用域里去找, 导致内部的this就是外层代码块的this

// demo 1
function foo() {
	 setTimeout(() => {
	    console.log('id:', this.id);
	  }, 100);
}
var id = 21;
foo.call({ id: 42 }) // id: 42

//demo 2
function Person() {
    this.name = 'dog';
    this.age = '18';
    setTimeout( () => {
        console.log(this);
        console.log('my name:' + this.name + '& my age:' + this.age)
    }, 1000)
}
var p = Person();

复制代码

2. 被忽略的this

当被绑定的是null,则使用的是默认绑定规则

// 如果你把 null 或者 undefined 作为 this 的绑定对象传入 call 、 apply 或者 bind ,这些值在调用时会被忽略,
实际应用的是默认绑定规则
function foo() {
	console.log( this.a );
}
var a = 2222;
foo.call( null ); // 2222
复制代码

四、(隐式)绑定丢失

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

1. 引用赋值丢失

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 函数本身,这就相当于:var bar = foo, obj对象只是一个中间桥梁, obj.foo只起到传递函数的作用,所以bar跟obj对象没有任何关系,此时的 bar() 其实是一个不带任何修饰的函数调用. 而bar本身又不带a属性,因此应用了默认绑定,最后a只能指向window.

2. 传参丢失

function foo() {
    console.log( this.a );
}
function doFoo(fn) {
    // fn其实引用的是foo
    fn(); // <-- 调用位置!
}
var obj = {
    a: 2,
    foo: foo
};
var a = "oops, global"; // a是全局对象的属性

doFoo( obj.foo ); // "oops, global"
复制代码

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一 个例子一样

3. 回调函数丢失

function thisTo(){
   console.log(this.a);
}
var data={
    a:2,
    foo:thisTo //通过属性引用this所在函数
};
var a=3;//全局属性

setTimeout(data.foo,100);// 3
复制代码

所谓传参丢失,就是在将包含this的函数作为参数在函数中传递时,this指向改变

setTimeout 函数的本来写法应该是 setTimeout(function(){......},100); 100ms后执行的函数都在“......”中, 可以将要执行函数定义成 var fun = function(){......}, 即:setTimeout(fun,100),100ms后就有:fun();所以此时此刻是data.foo作为一个参数,是这样的:setTimeout(thisTo,100);100ms过后执行thisTo(), 实际道理还跟1.1差不多,没有调用thisTo的对象,this只能指向window 实际上你没办法控制回调函数的执行方式,没有办法控制会影响绑定的调用位置. 因此, 回调函数丢失this绑定是非常常见的,甚至更加出乎意料的是,调用回调函数的函数可能会修改this,特别 是在一些流行的JavaScript库中时间处理器会把回调函数的this强制绑定到触发事件的DOM元素上


以上所述就是小编给大家介绍的《JavaScript之你不知道的this》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python深度学习

Python深度学习

[美] 弗朗索瓦•肖莱 / 张亮 / 人民邮电出版社 / 2018-8 / 119.00元

本书由Keras之父、现任Google人工智能研究员的弗朗索瓦•肖莱(François Chollet)执笔,详尽介绍了用Python和Keras进行深度学习的探索实践,涉及计算机视觉、自然语言处理、生成式模型等应用。书中包含30多个代码示例,步骤讲解详细透彻。由于本书立足于人工智能的可达性和大众化,读者无须具备机器学习相关背景知识即可展开阅读。在学习完本书后,读者将具备搭建自己的深度学习环境、建......一起来看看 《Python深度学习》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

html转js在线工具
html转js在线工具

html转js在线工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具