博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaScript || this
阅读量:6684 次
发布时间:2019-06-25

本文共 5561 字,大约阅读时间需要 18 分钟。

关于this

this是JavaScript的一个关键字,自动定义在所有函数中,难点在于this的指向。

this的指向在函数调用时进行绑定,它的context取决于函数调用时的各种条件,与函数定义位置无关

1 this的作用

this可以使不同的context对象重复使用已经存在、声明的函数,无需针对每个对象编写不同版本的函数

function identify() {    return this.name.toUpperCase();}function speak() {    var greeting = "Hello, i'm " + identify.call( this );        console.log( greeting );}var me = {name: "Kyle"};var you = {name: "Reader"};identify.call( me );         // KYLEidentify.call( you );       // READERspeak.call( me );              // "Hello, i'm KYLE"speak.call( you );              // "Hello, i'm READER"

2 误解

  • this并不是指向函数本身

  • 在任何情况下,this都不指向函数的词法作用域

3 this是什么?

this是在函数被调用时发生绑定,其指向取决于函数调用的位置。

当一个函数被调用是,会创建一个执行上下文(context)。其中包含函数的调用位置(调用栈)、函数的调用方式和传入的参数等信息。thiscontext中的一个属性

4 this解析

4.1 函数调用位置

函数在程序代码中被调用的位置,清楚调用位置才能明确this的指向

确定函数的调用位置:最关键是分析调用栈(为达到当前指向位置所调用的所有函数)。分析调用栈,可以得出真正的调用位置

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

4.2 this的绑定规则

在分析清楚调用位置后,根据this绑定的四条规则决定绑定对象。四条规则分别对应四种不同的函数调用方式

总共有四条绑定规则,其优先级是:默认绑定 < 隐式绑定 < 显式绑定 < new绑定

  • 默认绑定:作为独立调用的函数

  • 隐式绑定:作为对象的方法调用的函数

  • 显式绑定(硬绑定):使用call()apply()bind()方法,强制将对象绑定到函数的this

  • new绑定:

4.2.1 默认绑定

默认绑定指将函数作为独立的函数来调用,默认绑定将this绑定到全局对象

分析隐式绑定时,一个对象内部包含一个指向函数的属性,并且通过对象的属性间接引用函数,将this间接绑定到该对象上

function foo() {    console.log(this.a);}var a = 2;foo();  // 2
  • a在全局作用域中声明,是全局对象的一个属性;

  • foo()使用不带任何修饰的函数进行调用,只能使用默认绑定规则;此时,非严格模式下this指向全局对象,所有this.a被解析为全局变量a

  • 在严格模式中,this不能绑定到全局对象,this只能绑定到undefined

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

4.2.2 隐式绑定

判断函数的调用位置是否有上下文对象,隐式绑定将this绑定到调用方法的上下文对象上。

function foo() {    console.log(this.a);}var obj = {    a: 2,    foo: foo};obj.foo();  // 2   foo()的调用位置包含上下文对象obj,this隐式绑定到obj对象
  • 对象属性引用链中,只有最后一层会影响调用位置

    function foo() {    console.log(this.a);}var obj = {    a: 2,    obj2: obj2};var obj2 = {    a: 42,    foo: fooobj.obj2.foo();  // 42   实际是通过obj对象的obj2属性对象来调用foo()函数,this指向obj2

隐式丢失

被隐式绑定的函数会丢失绑定对象,然后应用默认绑定,非严格模式下将this绑定到全局对象。

  • 隐式绑定丢失发生在将隐式绑定的函数赋值给另外的变量,通过改变了来调用函数

    function foo() {    console.log(this.a);}var obj = {    a: 2,    foo: foo};var bar = obj.foo;   //  函数别名,传递引用var  a = "global a";bar();  // "global a",函数的调用位置,bar()其实是一个不带任何修饰的函数调用,所以应用默认的绑定规则
  • 在函数中将回调函数作为参数传入时,参数传递是一种隐式赋值(传递引用),所以应用默认绑定规则

    function foo() {    console.log(this.a);}function doFoo(fn) {    // fn是obj.foo函数本身的一个引用    fn();   // fn的调用位置}var obj = {    a: 2,    foo: foo};var bar = obj.foo;   //  函数别名,传递引用var  a = "global a";doFoo(obj.foo);  // "global a",传入的函数被隐式赋值,应用默认绑定规则setTimeout(obj.foo, 100);   //"global a",使用语言本身的内置函数时道理相同

4.2.3 显式绑定

在JavaScript中,函数是对象,每个函数对象都定义有call()apply()bind()方法,bind()在ES5中。

call()apply()方法:

  • 第一个方法是一个对象,将该对象绑定到this

  • 可以直接指定绑定的对象,称为显示绑定

  • call()apply()区别在于其他参数

    function foo() {    console.log(this.a);}var obj = {    a: 2,    foo: foo};foo.call(obj);   // 2  通过foo.call(obj);,在调用foo()时强制将其this绑定到obj对象

显式绑定仍然会有绑定丢失问题:可以使用显示绑定的一个变形来解决这个问题;

硬绑定

  • 创建一个函数bar(),在内部调用foo.call(obj),强制将foothis绑定到obj对象上,无论怎样调用bar()函数,都会手动在obj对象上调用foo,因此foothis指向不会改变

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

硬绑定的应用场景

  • 创建一个包裹函数,传入所有的参数,并返回接收到的所有值

    function foo(sth) {    return this.a + sth;}var obj = {    a: 2};var bar = function() {    // 将arguments传入需要调用的函数    return foo.apply(obj, arguments);   }bar(3);   //  2 + 3 = 5
  • 创建一个可以重复使用的函数

    function foo(sth) {    return this.a + sth;}var obj = {    a: 2};// 简单的辅助绑定函数function bind(fn, obj) {    return function() {        return fn.apply(obj, arguments);    }}var bar = bind(foo, obj);bar(3);   //  2 + 3 = 5
  • 硬绑定是一种非常常见的模式,ES5提供内置Function.prototype.bind方法:返回一个硬绑定的新函数,bind(obj)将参数obj设置为this的上下文,并调用原始函数。

    function foo(sth) {    return this.a + sth;}var obj = {    a: 2};var bar = foo.bind(obj);bar(3);   //  2 + 3 = 5

4.2.4 new绑定

JavaScript中的new机制与传统面向对象语言不同。JavaScript中构造函数只是使用new操作符调用的函数,使用new操作符调用函数时:

  1. 创建一个全新对象;

  2. 新对象被执行__proto__链接

  3. 新创建的对象被绑定到函数调用时的this

  4. 如果函数没有返回其他对象,new表达式中的函数调用自动返回新创建的对象

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

4.3 优先级

判断this的指向:找到函数的调用位置,并根据优先级判断应用的规则,默认绑定的优先级最低

  • 显示绑定的优先级高于隐式绑定:在判断时优先考虑显示绑定

    function foo(a) {    this.a = a;}var obj1 = {    a: 2;    foo: foo};var objb = {    a: 4;    foo: foo};obj1.foo();  // 2obj2.foo();  // 4obj1.foo.call(obj2);   // 4obj2.foo.call(obj1);   // 2
  • new绑定的优先级高于隐式绑定:

  • new绑定的优先级的高于显示绑定:

    • bar被硬绑定到obj1对象上,但是new bar(3)并未将obj1.a修改为4;

    • new bar(3)修改了调用bar()中的this,得到一个新对象

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

4.4 根据优先级判断函数调用位置应用的规则

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

  2. 函数是否通过call()apply()显示绑定?或者bind()硬绑定?如果是,this指向绑定的对象。

  3. 函数是否在某个上下文对象中调用?如果是,this指向那个上下文对象

  4. 如果不是上述三种情况,使用默认绑定。严格模式下绑定到undefined,非严格模式下绑定到全局对象

4.5 绑定例外

4.5.1 被忽略的this

nullundefined作为this的绑定对象传入call()apply()bind()方法,在调用时被忽略,实际应用默认绑定规则。

使用null来忽略this绑定可能产生副作用:安全的做法是传入一个特殊对象,将this绑定到这个对象不会产生任何副作用。Object.create(null)

5 this词法

ES6中的箭头函数不能使用上述4种规则,而是根据外层(函数或全局)作用域来绝对this。箭头函数常用于回调函数中。

function foo() {    // 返回一个箭头函数    return (a) => {        // this继承自foo()        console.log(this.a);    }}var obj = {    a: 2};var obj2 = {    a: 42};var bar = foo.call(obj1);  bar.call(obj2);   // 2, 不是42

转载地址:http://isaao.baihongyu.com/

你可能感兴趣的文章
wordpres搭建
查看>>
css优先级详解
查看>>
小白第三天
查看>>
2016年linux运维人员必会开源运维工具体系
查看>>
理性分析Python和C#哪个更有前途?
查看>>
mysql主备复制I/O线程不能同步问题
查看>>
高速缓存dns
查看>>
安装lenovo SR860 7x69服务器遇到的坑
查看>>
MIT透过机器学习技术用胺基酸预测蛋白质结构
查看>>
python课堂笔记之django-day01(8)
查看>>
Hadoop之HDFS分布式文件系统具有哪些优点?
查看>>
小型企业公司路由器做DHCP服务器
查看>>
愿不负青春 历经风雨 归期我们仍是少年|六一大童节
查看>>
洞悉物联网发展1000问之物联网与大数据,人工智能到底是什么关系?
查看>>
JAVA数组和面向对象
查看>>
NVisionXR_iOS教程四 —— 在立方体上贴材质
查看>>
Microsoft Visual C++ Runtime library not enough space for thread data
查看>>
Centos 7 ntp时间服务器搭建
查看>>
电压电流采集模块,温湿度采集,称重模块,变送器,adc模数转换模块
查看>>
RAID和LVM
查看>>