this

调用位置:顾名思义,调用位置就是寻找函数被调用的位置,换句话说就是找到当前正在执行的函数的前一个调用,调用位置决定了this的绑定。
调用栈:为了达到当前执行位置所调用的所有函数,调用位置就在当前正在执行函数的前一个调用中。

this的绑定规则

  1. 默认绑定:

    • 通过调用位置来决定默认绑定的对象是谁;

    • 函数运行(也可以理解为函数体存在与)在严格模式下无法将this绑定在全局对象上,它绑定的是undefined,但是函数调用在严格模式下时则不影响;

      在严格模式下调用函数不会影响this的默认绑定:

      function fn () {console.log('--->', this.a)
      }
      var a = '我是a'"use strict"
      fn() // ---> 我是a
      

      严格模式下运行,则会限制默认绑定:

      "use strict"function fn () {console.log('--->', this.a)
      }
      var a = '我是a'
      fn() // TypeError: Cannot read properties of undefined (reading 'a')
      
  2. 隐式绑定

    • 是否被某个上下文对象所“拥有”或“包含”
    • 对象的属性引用链的生效范围只在最后一层起作用,换句话说就是this只绑定在上一层对象的作用域中。
    const obj1 = {a: 'obj1---a',fn: fn
    }const obj2 = {a: 'obj2---b',obj: obj1
    }function fn () {console.log(this.a)
    }
    // 只查找了上一层
    console.log(obj2.obj.fn()) // obj1---a
    
    • 被隐式绑定的函数会丢失绑定对象从而应用默认绑定(函数传参也会导致this丢失)
    const obj1 = {a: 'obj1---a',fn: fn
    }const obj2 = {a: 'obj2---b',obj: obj1
    }function fn () {console.log(this.a)
    }var a = '全局a'
    const copyFn = obj2.obj.fn // 实际引用的是函数本身
    console.log(copyFn()) // 全局a
    
  3. 显示绑定

  • 通过callapplybind指定要绑定的this对象。
    如果第一个参数传入的是一个基本类型,会将其转换成它的对象形式;

    // 传入基本类型
    String.prototype.num = 2
    function handelNumber (val) {console.log(this) // String {''}return this.num += val // 4
    }
    console.log(handelNumber.call('', 2)) // 4;
    
  • 硬绑定:属于显示绑定的一个变种方法,与之相对的还有软绑定。实现方式是在函数内部通过bind或另外两种显示绑定的方式强制绑定到指定对象上,后续不管如何调用,都在内部执行这一步骤;
    创建一个可以复用的辅助函数:

    const obj = {num: 0
    }function handelNumber (val) {return this.num += val
    }function costomBind (fn, obj) {return (...args) => {return fn.apply(obj, args);}
    }const next = costomBind(handelNumber, obj)
    const a = next(1)
    const b = next(2)
    const c = next(3)
    console.log(a, b, c); // 1 3 6
    
  1. new 绑定
  • 要知道new绑定的原理,首先要清楚new操作符做了什么:

    • 创建一个新的对象;
    • 将构造函数的protptype指向这个新对象;
    • 新对象会绑定到函数调用的this,换句话说就是构造函数的this会指向这个新对象;
    • 返回这个新对象。
    function customNew (fn, ...args) {// 创建一个新对象const obj = {};// 将构造函数的protptype指向这个新对象obj.__proto__ = fn.prototype;// 绑定this到这个新对象上fn.call(obj, ...args);// 返回新对象return obj
    };function fn1() {this.a = '111'
    };const obj1 = customNew(fn1);
    const obj2 = new fn1();
    console.log(obj1);
    console.log(obj2);
    
  • new是能够影响到this绑定行为的最后一种方式。

  1. 优先级

    1. new 绑定(new操作符的底层实现上,对硬绑定函数做了判断,如果被new调用则会用新的this替换硬绑定函数的this);
    2. 显示绑定;
    3. 隐式绑定;
    4. 默认绑定。
  2. 绑定例外

    • 如果在使用callapplybind时,第一个参数传入null或undefined,那么this会被忽略,实际上应用的是默认绑定。
    • 如果函数不关心this,这种方式是一个很好的选择。
    function foo (a, b) {console.log(a, b);
    }
    const bar = foo.bind(null, 2)
    console.log(bar(3)) // 2 3;
    
    • 这种方式的弊端在于某个函数确实使用了this,但传入null默认绑定的是全局对象,会造成难以追踪的bug。为了解决这个问题,可以使用另一种更安全的this,空对象委托,这在《你不知道的JavaScript 上》中称之为DMZ对象。将this绑定到这个对象上不会产生任何副作用,因为所有的this都会限定在这个对象中:

      • 创建一个真正空对象,最简单的方式是Object.create(null),看起来跟{}很像,但Object.create(null)并不会创建prototype,所以它比{}更空。
      const obj1 = Object.create(null) // 无属性
      const onbj2 = {} // [[Prototype]]: Object
      
  3. 间接引用

    • 在函数赋值时,容易造成间接引用。

       function foo () {console.log(this);console.log(this.a);
      }var a = 3;
      const obj1 = { a: 1, foo: foo };
      const obj2 = { a: 2 };(obj2.foo = obj1.foo)() // 调用位置是foo
      
  4. 软绑定

    • 相对于硬绑定无法再次修改this,软绑定可以同时支持隐式绑定和显示绑定再次修改this;
    • 下面是《你不知道的JavaScript 上》中软绑定的代码实现:
    if (!Function.prototype.softBind) {Function.prototype.softBind = function (obj) {var fn = this;console.log('fn--', fn);var curried = [].slice.call(arguments, 1); // 获取第一个参数之后的所有参数。var bound = function () {return fn.apply((!this || this === (window || global)) // 判断是否传入的是可用this(不能是null或者undefined),或不能是全局对象。? obj // 如果是null或全局对象,默认绑定在初始化传入的对象上。: this, // 允许后续隐式或显示的修改this的绑定。curried.concat.apply(curried, arguments) // 柯里化);}/*** 1. 这里是为了将绑定对象上的prototype拷贝到新制定的对象上* 2. 注意是拷贝不是引用,新对象上修改prototype上的属性不会影响到其他对象*/bound.prototype = Object.create(fn.prototype);return bound;}
    }
    
    • 测试一下是否达到上面说的效果:
    function foo () {console.log('name:', this.name);
    };var obj = { name: 'obj' };
    var obj2 = { name: 'obj2' };
    var obj3 = { name: 'obj3' };var fooObj = foo.softBind(obj)
    fooObj() // obj
    fooObj.call(obj2, '111', 2, [1]) // obj2
    fooObj.call(obj3, '111', 2, [1]) // obj3
    
  5. this的词法

    • 无法适用this规则:箭头函数;
    • 箭头函数不能使用this的四种规则,而是根据外层(函数或者全局)作用域来决定this;
    function foo () {return () => {console.log(this.a);}
    }
    var a = 0
    const obj = { a: 1 }
    const obj2 = { a: 2 }const bar = foo.call(obj)
    bar.call(obj2) // 1
    
    • 上面例子可以看到箭头函数的 this 永远指向其上下文的this,任何方法都改变不了其指向,如 call() , bind() , apply() ,new也不行!

总结

  • 判断this绑定,需要找到函数的调用位置,并根据this的四种绑定规则:

    • new调用,绑定到新对象;
    • 显示绑定调用,绑定到指定对象;
    • 上下文调用,绑定到上下文对象;
    • 默认:函数体(注意是函数体而不是函数调用位置)处于严格模式下时绑定到undefined,否则绑定到全局对象。
  • 箭头函数不应用与this的绑定规则,而是继承外层函数的this绑定。跟ES6之前的self = this一样。

学习笔记(三):JavaScript中的this相关推荐

  1. html5学习笔记---05.JavaScript 中的面向对象,继承和封装

    05.JavaScript 中的面向对象 a.创梦技术qq交流群:CreDream:251572072 a.JavaScript 是一种基于对象的语言   类:JavaScript 对象很抽象,所以下 ...

  2. 【theano-windows】学习笔记三——theano中的导数

    前言 就一个NN而言,包含梯度.偏置.参数更新,而前面第一篇博客学习了theano中符号变量的定义, 第二篇博客学习了变量的随机初始化, 变量之间的互相操作(类似于sigmoid(w∗x+b)sigm ...

  3. docker学习笔记(三)docker中的网络

    目录 Linux中的网卡 Network Namespace Docker中的Bridge网络 使用自定义Bridge网络创建容器 Container中的其他网络 Host网络 None网络 Linu ...

  4. 【theano-windows】学习笔记十七——梯度中的consider_constant

    前言 主要是在写玻尔兹曼机相关的theano时, 在计算梯度grad的时候发现一个参数名字叫做consider_constant,来看看这个到底做了什么事情 参考博客: using consider_ ...

  5. Mr.J-- jQuery学习笔记(三十二)--jQuery属性操作源码封装

    扫码看专栏 jQuery的优点 jquery是JavaScript库,能够极大地简化JavaScript编程,能够更方便的处理DOM操作和进行Ajax交互 1.轻量级 JQuery非常轻巧 2.强大的 ...

  6. 【AngularJs学习笔记三】Grunt任务管理器

    为什么80%的码农都做不了架构师?>>>    #0 系列目录# AngularJs学习笔记 [AngularJs学习笔记一]Bower解决js的依赖管理 [AngularJs学习笔 ...

  7. J2EE学习笔记三:EJB基础概念和知识 收藏

    J2EE学习笔记三:EJB基础概念和知识 收藏 EJB正是J2EE的旗舰技术,因此俺直接跳到这一章来了,前面的几章都是讲Servlet和JSP以及JDBC的,俺都懂一些.那么EJB和通常我们所说的Ja ...

  8. tensorflow学习笔记(三十二):conv2d_transpose (解卷积)

    tensorflow学习笔记(三十二):conv2d_transpose ("解卷积") deconv解卷积,实际是叫做conv_transpose, conv_transpose ...

  9. Ethernet/IP 学习笔记三

    Ethernet/IP 学习笔记三 原文为硕士论文: 工业以太网Ethernet/IP扫描器的研发 知网网址: http://kns.cnki.net/KCMS/detail/detail.aspx? ...

  10. Spring学习笔记(三) AOP_annotation,AOP_XML

    在学习课程以前,听说AOP有种很神秘的感觉,好像很好深的技术.其实原理很简单,使用动态代理的方式给程序增加逻辑.与此相似的有struts2中的filter拦截器. 再讲AOP之前先把需求说一下: 同S ...

最新文章

  1. 五分钟了解机器学习十大算法
  2. Android LruCache 压缩图片 有效避免程序OOM
  3. hdu 3863 No Gambling (不会证明,但是是对的,,)
  4. 启动Cognos时报0106错误
  5. 会员连锁配置以及金额走向
  6. mysql安装主从配置_MySQL安装与主从配置
  7. 基于交换机的PC端网络通信
  8. django mongodb mysql,Django MongoDB Django NoSQL方案
  9. 软考设计师15-数据结构01
  10. 详解:MySQL数据库的权限管理和运维实操
  11. 94G的kindle电子书btsync分享
  12. 九、Kali Linux 2 社会工程学工具
  13. 医疗行业源代码该如何保密
  14. 国开大学c语言程序设计形考任务1,国开电大《数控编程技术》形考任务1-4答案...
  15. php max file uploads,php上传多文件max_file_uploads限制问题
  16. Python_计算加速度
  17. 拇指玩安装器提示“存储卡空间不足”的解决办法
  18. 使用电脑麦克风的时候发现有杂音怎么办
  19. android电话本导入iphone,换手机之后安卓通讯录怎么导入iphone手机
  20. matlab-频响函数

热门文章

  1. c语言编程cos怎么用,cos x的taylor公式用c语言如何编程
  2. 本人每天懒床(起不来)问题的分析及相关的想法
  3. linux文本编辑的几种退出方法
  4. matlab阵风仿真,基于matlab风力发电系统的建模与仿真毕业设计论文
  5. 模具的分类,模具的等级,你知道吗,你知道什么是软模和硬模吗?
  6. 快速幂算法及其在动态规划中的应用(矩阵幂)
  7. Windows下安装cab文件
  8. C/C++经典项目:用C++制作在线考试系统(附源码)
  9. NPC中点箝位三电平 SVPWM控制 T型三电平 羊角波调制
  10. 1,Linux命令行设置中文显示