关于this

  与静态词法作用域不用,this的指向动态绑定,在函数执行期间才能确定。感觉有点像C++的多态?

  1. var a = 1;
  2. var obj = {
  3. a: 2,
  4. fn: function() {
  5. console.log(this.a);
  6. }
  7. };
  8. obj2 = {
  9. a: 3,
  10. fn: obj.fn
  11. };
  12. //通过对象调用 this指向obj
  13. obj.fn(); //
  14. //通过函数调用 this指向window
  15. setTimeout(obj.fn, 0); //
  16. obj2.fn(); //

  这个例子很好理解,谁调用的函数,this就指向谁。

  当一个函数被调用时,会创建一个活动记录(上下文)。这个记录会包含函数在哪里被调用,函数的调用方法,传入的参数等信息。this就是记录的其中一个信息。

  开始详解this了!

  理解this绑定,首先要理解调用位置:调用位置就是函数在代码中被调用的位置。

  分析位置最重要的是分析调用栈,我们关心的调用位置就在当前正在执行的函数的前一个调用中。

  1. //调用栈是f1 即全局作用域
  2. function f1() {
  3. console.log('f1');
  4. f2(); //f2调用位置
  5. }
  6. //调用栈是f1 => f2
  7. function f2() {
  8. console.log('f2');
  9. f3(); //f3调用位置
  10. }
  11. //调用栈是f1 => f2 => f3
  12. function f3() {
  13. console.log('f3');
  14. }
  15. f1(); //f1调用位置

   

  通过找到调用位置,判断需要应用哪一条规则。this绑定的四条规则:

默认绑定

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

  1. var a = 1;
  2. // 全局函数
  3. function fn() {
  4. // 'use strict' error
  5. console.log(this.a); //
  6. }
  7. //默认this绑定到window
  8. fn();

  在这里,fn是直接进行调用,没有任何修饰,相当于window.fn(),只能适用默认绑定,指向了window。

隐式绑定

  这种绑定需要考虑调用位置是否有上下文对象。

  1. function fn() {
  2. console.log(this.a);
  3. }
  4. var obj = {
  5. a: 2,
  6. fn: fn
  7. };
  8. obj.fn(); //

  当fn被调用时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因为调用fn时this被绑定到obj,因此this.a和obj.a是一样的。

  另外,链式调用时,只有最近的调用对象会影响this。

  1. var a = 1;
  2. var obj = {
  3. a: 2,
  4. fn: function() {
  5. console.log(this.a);
  6. }
  7. };
  8. obj2 = {
  9. a: 3,
  10. fn: obj
  11. };
  12. //this.a相当于obj.a
  13. obj2.fn.fn(); //

隐式丢失

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

  1. function fn() {
  2. console.log(this.a);
  3. }
  4. var a = 1;
  5. var obj = {
  6. a: 2,
  7. fn: fn
  8. }
  9. var f = obj.fn;
  10. obj.fn(); //
  11. f(); //

  被作为函数调用时,会被默认绑定到window对象。

  另外一种情况是传入回调函数时:

  1. function fn() {
  2. console.log(this.a);
  3. }
  4.  
  5. function fn2(fn) {
  6. fn();
  7. }
  8. var a = 1;
  9. var obj = {
  10. a: 2,
  11. fn: fn
  12. }
  13. //还是作为函数调用
  14. fn2(obj.fn);

显式绑定

  就是用apply和call方法强制绑定this。一个例子说明一切:

  1. var a = 1;
  2. var obj = {
  3. a: 2,
  4. fn: function() {
  5. console.log(this.a);
  6. }
  7. };
  8. obj2 = {
  9. a: 3,
  10. fn: obj.fn
  11. };
  12. obj.fn.call(this); //
  13. obj.fn.call(obj); //
  14. obj.fn.call(obj2); //

硬绑定

  直接用vue源码来举例子吧。

  1. function bind(fn, ctx) {
  2. return function(a) {
  3. var len = arguments.length;
  4. return len ? len > 1 ? fn.apply(ctx, arguments) : fn.call(ctx, a) : fn.call(ctx);
  5. }
  6. }

  简单暴力,都会背了。

new绑定

  JS中new的机制实际上和面向类的语言完全不同。

  在JS中,构造函数只是一些使用new操作符时被调用的函数,它们不属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被new操作符调用的普通函数而已。

  Number作为构造函数时,ES5.1中这样描述:当Number在new表达式中被调用时,它是一个构造函数,会初始化新创建的对象。

  因为,包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。实际上不存在所谓的构造函数,只有对于函数的构造调用。

  使用new来调用函数,会自动执行下面的操作:

  1、创建一个全新的对象。

  2、这个新对象会被执行【原型】连接。

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

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

  1. function fn(a, b) {
  2. this.a = a;
  3. this.b = b;
  4. }
  5. var f = new fn();
  6. console.log(f); //{a:undefined,b:undefined}

优先级

  如果某个调用位置应用多条规则,需要为这些规则设定优先级。(默认绑定、隐式绑定、显示绑定、new绑定)

  首先默认绑定优先级最低。

  隐式 VS 显示?

  1. function fn() {
  2. console.log(this.a);
  3. }
  4. var obj1 = {
  5. a: 2,
  6. fn: fn
  7. };
  8. var obj2 = {
  9. a: 3,
  10. fn: fn
  11. }
  12. obj1.fn(); //
  13. obj2.fn(); //
  14. obj1.fn.call(obj2); //
  15. obj2.fn.call(obj1); //

  显式比较厉害。

  再来比较一下new和显示绑定优先级:

  1. function fn(a) {
  2. this.a = a;
  3. }
  4. var obj = {};
  5. //this强制绑定到obj对象上
  6. var f1 = fn.bind(obj);
  7. f1(2);
  8. //现在obj有了一个a属性
  9. console.log(obj.a); //
  10. //new一个对象将this.a改成3
  11. var f2 = new f1(3);
  12. //这里依然没有变
  13. console.log(obj.a); //
  14. //将this指向了new出来的a
  15. console.log(f2.a); //

  可以看出,通过new的绑定,覆盖了bind强制绑定的a,所以new绑定是优先于显示绑定的。

  顺便贴一下MDN提供的bind()实现:

  1. if (!Function.prototype.bind) {
  2. Function.prototype.bind = function(oThis) {
  3. if (typeof this !== 'function') {
  4. throw new TypeError('Function.prototype.bind - what is trying ' +
  5. 'to be bound is not callable');
  6. }
  7. var aArgs = Array.prototype.slice.call(arguments, 1),
  8. fToBind = this,
  9. fNOP = function() {},
  10. fBound = function() {
  11. return fToBind.apply(
  12. this instanceof fNoP && oThis ? this : oThis,
  13. aArgs.concat(Array.prototype.slice.call(arguments))
  14. );
  15. };
  16. fNOP.prototype = this.prototype;
  17. fBound.prototype = new fNOP();
  18. return fBound;
  19. };
  20. }

  

总结

优先级为:

  1、如果使用了new,this绑定新创建的对象。

  2、通过call、apply显式绑定。

  3、是否使用隐式绑定?即对象调用

  4、其余情况默认绑定在window上

例外情况

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

  如果用apply展开数组可能用得上,比如fn.apply(null,arr),其中arr为数组,里面的值作为参数传给fn。

  但是这个参数十分不安全,可能会不经意的修改全局变量,可以通过纯空对象来创建一个安全的this。

  1. var a = 1,
  2. b = 1;
  3. console.log(a); //
  4.  
  5. function fn(a) {
  6. this.a = a;
  7. }
  8.  
  9. function fn2(b) {
  10. this.b = b;
  11. }
  12. //原本可能只想利用apply
  13. fn.apply(null, [2]);
  14. //不小心改了全局变量!
  15. console.log(a);
  16. //创建一个纯空对象
  17. var obj = Object.create(null);
  18. console.log(b); //
  19. //安全的this
  20. fn2.call(obj, 3);
  21. // 现在安全了!
  22. console.log(b); //

  

  

  但是引入了箭头函数,问题就有点奇怪了。

  1. var a = 1;
  2. var obj = {
  3. a: 2,
  4. fn: () => {
  5. console.log(this.a);
  6. }
  7. };
  8. obj2 = {
  9. a: 3,
  10. fn: obj.fn
  11. };
  12. obj.fn(); //1
  13. setTimeout(obj.fn, 0); //1
  14. obj2.fn(); //1

  全是1,这是怎么回事?换arguments的例子来看:

  1. function fn() {
  2. setTimeout(function() {
  3. console.log(arguments);
  4. }, 0)
  5. }
  6. fn(1, 2); //[] 确实未传入参数
  7. function fn2() {
  8. setTimeout(() => {
  9. console.log(arguments);
  10. }, 0)
  11. }
  12. fn2(1, 2); //[1,2] 把外部的参数吸进来了!

  简单来讲,箭头函数使得this查询回到了词法作用域规则,它本身不具有this、arguments等属性,根据外层作用域来决定this。

  1. function fn() {
  2. return (a) => console.log(this.a);
  3. }
  4. var obj = {
  5. a: 1
  6. };
  7. var obj2 = {
  8. a: 2
  9. }
  10. var f = fn.call(obj);
  11. f(); //
  12. f.call(obj2); //

  当箭头函数外部的fn被绑定到obj时,无论怎么调用和绑定,this指向永远是上一层作用域(window),所以会输出1。

真·总结

  如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面4条规则来判断this的绑定对象。

  1、由new调用?绑定到新创建的对象。

  2、由call、apply或bind调用?绑定到指定对象。

  3、由上下文调用?绑定到那个上下文调用。

  4、默认:严格模式绑定到undefined,否则绑定到全局对象。

读书笔记-你不知道的JS上-this的更多相关文章

  1. 读书笔记-你不知道的JS上-对象

    好想要对象··· 函数的调用位置不同会造成this绑定对象不同.但是对象到底是什么,为什么要绑定他们呢?(可以可以,我也不太懂) 语法 对象声明有两个形式: 1.字面量 => var obj = ...

  2. 读书笔记-你不知道的JS上-函数作用域与块作用域

    函数作用域 Javascript具有基于函数的作用域,每声明一个函数,都会产生一个对应的作用域. //全局作用域包含f1 function f1(a) { var b = 1; //f1作用域包含a, ...

  3. 读书笔记-你不知道的JS上-词法作用域

    JS引擎 编译与执行 Javascript引擎会在词法分析和代码生成阶段对运行性能进行优化,包含对冗余元素进行优化(例如对语句在不影响结果的情况下进行重新组合). 对于Javascript来说,大部分 ...

  4. 读书笔记-你不知道的JS上-混入与原型

    继承 mixin混合继承 function mixin(obj1, obj2) { for (var key in obj2) { //重复不复制 if (!(key in obj1)) { obj1 ...

  5. 读书笔记-你不知道的JS上-闭包与模块

    闭包定义 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行. 看一段最简单的闭包代码: function foo() { var a = 2; //闭包 fun ...

  6. 读书笔记-你不知道的JS上-声明提升

    变量声明提升 Javascript代码一般情况下是由上往下执行的,但是有些情况下不成立. a = 2; //变量声明被提升在当前作用域顶部 var a; console.log(a); console ...

  7. 读书笔记-你不知道的JS中-promise

    之前的笔记没保存没掉了,好气,重新写! 填坑-- 现在与将来 在单个JS文件中,程序由许多块组成,这些块有的现在执行,有的将来执行,最常见的块单位是函数. 程序中'将来'执行的部分并不一定在'现在'运 ...

  8. 读书笔记-你不知道的JavaScript(上)

    本文首发在我的个人博客:http://muyunyun.cn/ <你不知道的JavaScript>系列丛书给出了很多颠覆以往对JavaScript认知的点, 读完上卷,受益匪浅,于是对其精 ...

  9. 读书笔记-你不知道的JS中-函数生成器

    这个坑比较深 可能写完我也看不懂(逃 ES6提供了一个新的函数特性,名字叫Generator,一开始看到,第一反应是函数指针?然而并不是,只是一个新的语法. 入门 简单来说,用法如下: functio ...

随机推荐

  1. 小甲鱼:Python学习笔记002_数组_元组_字符串

    创建普通数组 >>> member=["山东黄金","九阳股份"] >>> member ['山东黄金', '九阳股份'] ...

  2. Spring-hibernate-BaseDao

    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w ...

  3. RobotFramework自动化测试框架-移动手机自动化测试Clear Text关键字的使用

    Clear Text关键字用来清除输入框的数据,该关键字接收一个参数[ locator ],这里的locator指的就是界面元素的定位方式. 示例1:Clear Text清除输入框数据时,采用reso ...

  4. MVC查询数据接收及校验

    本来想写一篇aspx的TreeView控件绑值的文章的,在写案例的时候,写了一半,发现有些地方还得考虑以下,就留待下次了. 这一篇的话,是最近在开发一个项目的时候,有大量的页面和数据表,需要花式查询, ...

  5. 走进AngularJS

      前  言 xiaoq AngularJS 通过新的属性和表达式扩展了 HTML. 使用起来非常方便. 1. AngularJS的指令与表达式 AngularJS 通过 指令 扩展了 HTML,且通 ...

  6. Spring中的Service/DAO/DTO

  7. Hive内置数据类型

    Hive的内置数据类型可以分为两大类:(1).基础数据类型:(2).复杂数据类型.其中,基础数据类型包括:TINYINT,SMALLINT,INT,BIGINT,BOOLEAN,FLOAT,DOUBL ...

  8. PHP通过URL获取文件大小

    function getFileSize($url){ $url = parse_url($url); if($fp = @fsockopen($url['host'],empty($url['por ...

  9. jP61 2.15

    import java.util.Scanner; public class Distance { public static void main(String[] args) {    Scanne ...

  10. django celery的分布式异步之路(二) 高并发

    当你跑通了前面一个demo,博客地址:http://www.cnblogs.com/kangoroo/p/7299920.html,那么你的分布式异步之旅已经起步了. 性能和稳定性是web服务的核心评 ...