在-JavaScript-中轻松处理-this

作者:Dmitri Pavlutin翻译:疯狂的技术宅 原文:https://dmitripavlutin.com/fi... 未经允许严禁转载 我喜欢 JavaScript 中能够更改函数执行上下文(也称为 this)的特性。 例如,你可以在类似数组的对象上使用数组方法: const reduce = Array.prototype.reduce;function sumArgs() { return reduce.call(arguments, (sum, value) => { return sum += value; });}sumArgs(1, 2, 3); // => 6但是从另一方面来说,this 关键字很难掌握。 你可能会经常去检查 this 的值不正确的原因。以下各节将会教给你一些把 this绑定到所需的值简单的方法。 在开始之前,我需要一个辅助函数 execute(func)。它只是用来执行作为参数的函数: function execute(func) { return func();}execute(function() { return 10 }); // => 10现在,让我们继续了解围绕 this 的错误的本质:方法分离。 1. 方法分离问题Person 类包含字段 firstName 和 lastName。另外,它还有 getFullName()方法,返回全名。 Person 的一种可能的实现方式是: function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; this.getFullName = function() { this === agent; // => true return `${this.firstName} ${this.lastName}`; }}const agent = new Person('John', 'Smith');agent.getFullName(); // => 'John Smith'你会看到 Person 函数作为构造函数被调用:new Person('John','Smith')。在 Person 函数内部创建新的实例。 ...

October 15, 2019 · 3 min · jiezi

解决这两种this混乱的场景让它的指向明明白白

this永远指向当前函数的主人,在构造函数中this指向新创建的对象,说明构造函数的主人是新创建的对象。但是有以下两种场景会引起this指向的混乱:加了定时器new去创建对象的时候,this指向新创建的对象也就是a1,当我们调用a1.show()的时候输出10。 function Aaa(){ this.a = 10; } Aaa.prototype.show = function(){ alert(this.a); } var a1 = new Aaa(); a1.show();//10但是当我们在构造函数中添加一个定时器,让它4秒后调用this.show,输出undefined。 function Aaa(){ this.a = 10; setInterval(this.show,4000); } Aaa.prototype.show = function(){ alert(this.a); } var a1 = new Aaa(); a1.show();我们在原型方法上打印this,发现4秒之后,this指向的是当前窗口[object Window],this.show本来是a1的方法,但是把this.show这个函数当参数,传给定时器后this指向了window,函数的主人变成了window。 function Aaa(){ this.a = 10; setInterval(this.show,4000); } Aaa.prototype.show = function(){ alert(this); } var a1 = new Aaa(); a1.show();为了避免这种情况,我们在给定时器传参之前,将this赋值给一个新的变量_this,将this存储一下。现在我们再执行a1.show()这个方法,4秒之后打印的还是10,这样就不会引起指向混乱的问题。 function Aaa(){ this.a = 10; //起一个变量,将this存储一下 var _this = this; /* 把this.show这个函数,当参数传给定时器。 */ setInterval(function(){ _this.show(); },4000); } Aaa.prototype.show = function(){ alert(this.a);//10 } var a1 = new Aaa(); a1.show();//10事件绑定现在创建一个Bbb的构造函数,通过new生成一个b1对象,调用b1.show()方法输出20。 ...

September 10, 2019 · 1 min · jiezi

js深入四万脸懵圈的this指向

作为一个js菜鸡的我而言,在之前讲到过那么多的js链式查找机制,比如说原型链,作用域链等等,想当然的把这个机制带入到了this指向上边,结果就是这个this指向指的我万脸懵逼(标题换字了,担心被河蟹),在经过漫长的通(gou)俗(pi)易(bu)懂(tong)的 ECMAScript规范阅读之后,分享一下我所认知的this指向简而言之,js中this的指向不是在函数定义的时候确定的,而是在调用的时候创建阶段确定的,也就是说this指向谁,完全取决于函数的调用方式 常见的几种调用方式直接调用, 比如说function a() {console.log(this); } a(); 这个例子里边this指向的是全局对象,在客户端的全局对象是window对象,在node 中的全局对象是global对象 (function a() { function b() { console.log(this); } b() })()直接调用指的是直接用函数名称后边加()执行调用的函数,无论是否在全局作用域间接调用 const obj ={ name:'obj对象',a(){ console.log(this)}}obj.a() 如图 在图中我们可以看到我们在对象里边调用对象里边的方法的时候,this指向的是obj对象, 或者说外边有一个函数 然后给一个obj对象的属性赋值 const obj ={ name:'obj对象', a(){ console.log(this) } } obj.a() obj.b=function(){ console.log(this,'b') } obj.b()打印的结果都是obj对象 new调用当我们他用过new 创建一个新的对象的时候,new会调用这个构造函数来创建一个对象,那么这个对象里边的this是这个被new的函数调用的,那么自然 new调用的时候,this就是指向这个新对象的 function A(data) { this.data = data; } class B{ constructor(data){ this.data = data } } let a = new A("A"); let b = new B("B"); console.log(a.data); console.log(b.data); 如图 ...

July 10, 2019 · 1 min · jiezi

JavaScript深入浅出第1课箭头函数中的this究竟是什么鬼

摘要: 箭头函数极大地简化了this的取值规则。 普通函数与箭头函数普通函数指的是用function定义的函数: var hello = function () { console.log("Hello, Fundebug!");}箭头函数指的是用=>定义的函数: var hello = () => { console.log("Hello, Fundebug!");}JavaScript箭头函数与普通函数不只是写法上的区别,它们还有一些微妙的不同点,其中一个不同点就是this。 箭头函数没有自己的this值,箭头函数中所使用的this来自于函数作用域链。这句话很简单,不过听着稍微有点莫名其妙,得从头说起。 this到底是什么?关于this的文章也够多了,有时候越描越黑,我就不再添乱了,我只负责搬运一下MDN文档:this,感兴趣的可以仔细阅读一下,我摘录一些最重要的话就好了。 A function's this keyword behaves a little differently in JavaScript compared to other languages. It also has some differences between strict mode and non-strict mode.JavaScript是一门比较奇特的语言,它的this与其他语言不一样,并且它的取值还取决于代码是否为严格模式("use strict")。 this的值是什么? The JavaScript context object in which the current code is executing.this就是代码执行时当前的context object。 Global context In the global execution context (outside of any function), this refers to the global object whether in strict mode or not.代码没有在任何函数中执行,而是在全局作用域中执行时,this的值就是global对象,对于浏览器来说,this就是window。 ...

June 18, 2019 · 2 min · jiezi

Javascript深入理解this作用域问题以及newletvarconst对this作用域的影响

理解this作用域《javascript高级程序设计》中有说到: this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象调用时,this等于那个对象。不过,匿名函数具有全局性,因此this对象同常指向window针对于匿名函数this具有全局性的观点仍是有争议的,可参考 https://www.zhihu.com/questio... 关于闭包经常会看到这么一道题: var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } };console.log(object.getNameFunc()());//result:The Window<font color="green">在这里,getNameFunc return了1个匿名函数,可能你会认为这就是输出值为<font color="blue">The Window</font>的原因 </font> 但是,我们再来尝试写1个匿名函数 var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return this.funAA; }, funAA:function(){ return this.name } }; console.log(object.getNameFunc()(),object.funAA())可以发现,同样是匿名函数,却输出了<font color="blue">The Window, My Object</font> 在作用域链中,执行函数时会创建一个称为“运行期上下文(execution context)”的内部对象,运行期上下文定义了函数执行时的环境。个人认为是因为<font color="red">函数执行在window作用域</font>下,getNameFunc的作用域链被初始化为<font color="red">window的[[Scope]]</font>所包含的对象,导致输出结果为window.name 对作用域链不是很了解的同学,可以查看这边文章【Javascript】深入理解javascript作用域与作用域链 实践是检验真理的唯一标准,让我们用代码测试一下 var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return this.funAA(); }, funAA:function(){ return this.name } };console.log(object.getNameFunc(),object.funAA())可以发现,输出了 <font color="blue">My Object, My Object</font> getNameFunc仍为匿名函数,但是return的是this.funAA(),此时,this.funAA变成了在getNameFunc作用域中执行,而不是在window下,验证了我们之前的猜想: ...

April 30, 2019 · 1 min · jiezi

深入理解JavaScript中的this指向

与其他语言相比,js中的this有所不同,也是比较头疼的问题。在参考了一些资料后,今天,就来深入解析一下this指向问题,有不对的地方望大家指出。为什么要用this对于前端开发者来说,this是比较复杂的机制,那么为什么要花大量时间来学习呢,先来看一段代码。如果不使用this,要给identify( )和speak( )显式传入一个对象:function identify(context) { return context.name.toUpperCase();}function speak(context) { var greeting = “Hello, I’m” + identify(context); console.log(greeting);}identify(you);speak(me);可以看到,speak( )里面直接写了identify( )的函数名,然而,随着使用模式越来越复杂,显式传递的上下文会让代码变得混乱,尤其体现在面向对象中。显然,this提供了一种方式来隐式“传递”一个对象的引用,更加简洁,易于复用。this的误解1. this指向函数本身记录函数foo被调用的次数:function foo(num) { console.log(“foo:” + num); // 记录次数 this.count++;}foo.count = 0;var i;for (i = 0; i < 10; i++) { if (i > 5) { foo(i); }}// foo: 6// foo: 7// foo: 8// foo: 9//foo被调用了多少次?console.log(foo.count); // 0从前两次的console.log( )可以看出,foo确实被调用了4次,但是foo.count仍然为0,显然this指向函数本身的理解是错误的。2. this指向函数作用域要明确的是,this在任何情况下都不指向函数的词法作用域。因为,作用域“对象”无法通过JavaScript代码访问,它存在于JavaScript引擎内部。下面的代码试图使用this来隐式引用函数的词法作用域,没有成功:function foo() { var a = 2; this.bar();}function bar() { console.log(this.a);}foo(); // ReferenceError: a is not defined直接报出了访问不到foo( )中的a。ReferenceError和作用域判别失败相关,而TypeError代表作用域判别成功,但是对结果的操作是非法的、不合理的。this是什么排除了以上两个误解之后,来看一下this到底是什么。this是运行时绑定的,它和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(执行上下文),这个记录包含函数在哪里被调用,函数的调用方式、传入的参数等,this就是这个记录的一个属性,在函数执行的过程中用到。即,this总是指向调用它所在方法的对象1. 在浏览器中,调用方法没有明确对象时,this指向windowfunction foo() { console.log(this.a);}var a = 2;foo(); // 2在全局中声明变量a = 2,然后在全局中直接调用foo( ),this指向了全局对象,得到a的值。要注意的是,在严格模式(strict mood)下,如果this没有被执行环境定义,那它将绑定为undefined。function foo() { “use strict”; console.log(this.a);}var a = 2;foo(); // TypeError: this is undefined在严格模式下,调用foo( )不影响this绑定。function foo() { console.log(this.a);}var a = 2;(function() { “use strict”; foo(); // 2})();2. 在浏览器中,setTimeout、setInterval和匿名函数执行时的当前对象是全局对象windowfunction foo() { console.log(this.a);}var obj = { a: 2, foo: foo};var a = “global”;setTimeout(obj.foo, 100); // “global"JavaScript中的setTimeout( )的实现和下面伪代码相似:function setTimeout(fn, delay) { // 等待delay毫秒 fn(); // 调用函数}3. apply / call / bind可以强制改变this指向function foo() { console.log(this.a);}var obj = { a: 2};foo.call(obj); // 2foo.apply(obj); // 2foo.bind(obj); // 2call和apply的区别在于第二个参数,call是把args全部列出来,用“,”分隔,而apply是一个类数组。call、apply是硬绑定,通过硬绑定的函数不能再修改它的this。function foo() { console.log(this.a);}var obj = { a: 2};var bar = function() { foo.call(obj);}bar(); // 2setTimeout(bar, 100); // 2bar.call(window); // 2函数foo( )内部手动调用了foo.call(obj),把foo的this强制绑定到了obj,所以后面即使又把bar( )绑定到了window,还是无法改变this指向。4. new操作符改变this指向在传统的面向对象语言中,会使用new初始化类,然而在JavaScript中new的机制和面向对象语言完全不同。在js中,构造函数只是使用new操作符时被调用的函数,它们并不属于一个类,也不会实例化一个类。也就是说,js中,不存在所谓的“构造函数”,只有对函数的“构造调用”。function foo(a) { this.a = a;}var bar = new foo(2);console.log(bar.a); // 2使用new调用foo( ),会构造一个新对象并把它绑定到foo( )调用中的this上。优先级既然有那么多可以改变this的指向,那么它们的优先级是怎么样的呢,记住这句话:范围越小,优先级越高。可以按照下面的顺序来判断:判断函数是否在new中调用过:var bar = new foo();判断函数是否通过call、apply、bind绑定过:var bar = foo.call(obj);判断函数是否在某个上下文对象中调用过:var bar = obj.foo();如果以上情况均不存在,那么在严格模式下,绑定到undefined,否则绑定到全局对象:var bar = foo(); ...

March 27, 2019 · 1 min · jiezi

「前端面试题系列4」this的原理以及用法

这是前端面试题系列的第 4 篇,你可能错过了前面的篇章,可以在这里找到:伪类与伪元素的区别及实战如何实现一个圣杯布局?今日头条 面试题和思路解析在前端的面试中,经常会问到有关 this 的指向问题。最近,朋友Z 向我求助说,他一看到 this 的题目就犯难,搞不清楚 this 究竟指向了谁。我为他做了解答,并整理成了这篇文章,希望能帮到有需要的同学。一道面试题朋友Z 给我看了这样一道题:var length = 10;function fn () { console.log(this.length);} var obj = { length: 5, method: function (fn) { fn(); arguments0; }}; obj.method(fn, 1);问:浏览器的输出结果是什么?它的答案是:先输出一个 10,然后输出一个 2。让我们来解析一下原因:在我们这道题中,虽然 fn 作为 method 的参数传了进来,但它的调用者并不受影响,任然是 window,所以输出了 10。arguments0;这条语句并不常见,可能大家有疑惑的点在这里。 其实,arguments 是一种特殊的对象。在函数中,我们无需指出参数名,就能访问。可以认为它是一种,隐式的传参形式。当执行 arguments0; 时,其实调用了 fn()。而这时,fn 函数中 this 就指向了 arguments,这个特殊的对象。obj.method 方法接收了 2 个参数,所以 arguments 的 length,很显然就是 2 了。改造一下再来,不少同学对 this 的指向感到疑惑,是因为 this 并没有指向我们预期的那个对象。就像这道题,从语义上来看,我们期望 fn() 输出的是 obj 自己的 length,也就是 5,而不是 10。那么如果要得到 5 的结果,我们该如何修改这段代码呢?其实只要多做一步处理就好。就是让 this 指向 obj 自己。这里,我们可以用 call 来改变 this 的指向,像下面这样:var length = 10;function fn () { console.log(this.length);}var obj = { length: 5, method: function (fn) { // 在这里用call 将 this 指向 obj 自己 fn.call(this); }}; obj.method(fn);输出的结果就是 5 了,搞定。看吧,this 也没那么复杂吧,我们只需要一些简单的操作,就能控制 this 的指向了。那么,问题来了,为什么有时候 this 会失控呢?其实,这与 this 机制背后的原理有关。不过别急,让我们从理解 this 的基本概念开始,先来看看 this 到底是什么?this 是什么?this 是 JavaScript 中的一个关键字。它通常被运用于函数体内,依赖于函数调用的上下文条件,与函数被调用的方式有关。它指向谁,则完全是由函数被调用的调用点来决定的。所以,this,是在运行时绑定的,而与编写时的绑定无关。随着函数使用场合的不同,this 的值也会发生变化。但是有一个总的原则:那就是this 总会指向,调用函数的那个对象。为什么要用this?从概念上理解起来,似乎有点费劲。那我们为什么还要使用 this 呢?用了 this 会带来什么好处?让我们先看下面这个例子:function identify() { return this.name.toUpperCase();}var me = { name: “Kyle”};var you = { name: “Reader”};identify.call( me ); // KYLEidentify.call( you ); // READER一开始我们可能太不明白为何这样输出。那不如先换个思路,与使用 this 相反,我们可以明确地将环境对象,传递给 identify()。像这样:function identify(context) { return context.name.toUpperCase();}identify( you ); // READER在这个简单的例子中,结果是一样的。我们可以把环境对象直接传入函数,这样看来比较直观。但是,当模式越发复杂时,将执行环境作为一个明确的参数传递给函数,就会显得非常混乱了。而 this 机制,可以提供一种更优雅的方式,来隐含地“传递”一个对象的引用,这会使得 API 的设计更加地干净,复用也会变得容易。this 的原理明白了 this 的概念之后,不经让我好奇,为何 this 指向的就是函数运的执行环境呢?之前,看到了 阮老师 的一篇文章,十分透彻地分析了 this 的原理。我根据自己的理解,整理如下。很多教科书会告诉你,this 指的是函数运行时所在的环境。但是,为什么会这样?也就是说,函数的运行环境到底是怎么决定的?理解 this 的原理,有助于帮我们更好地理解它的用法。JavaScript 语言之所以有 this 的设计,跟内存里面的数据结构有关系。来看一个简单的示例:var obj = { foo: 5 };上面的代码将一个对象赋值给变量 obj。JavaScript 引擎会先在内存里面,生成一个对象 { foo: 5 },然后把这个对象的内存地址赋值给变量 obj。也就是说,变量 obj 其实只是一个地址。后面如果要读取 obj.foo,引擎先从 obj 拿到内存地址,然后再从该地址读出原始的对象,返回它的 foo 属性。这样的结构很清晰,但如果属性的值是一个函数,又会怎么样呢?比如这样:var obj = { foo: function () {} };这时,JavaScript 引擎会将函数单独保存在内存中,然后再将函数的地址赋值给 foo 属性的 value 属性。可以看到,函数是一个单独的值(以地址形式赋值),所以才可以在不同的环境中执行。又因为,JavaScript 允许在函数体内部,引用当前环境的其他变量。所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。this 的用法在理解了 this 的原理之后,我们用下面的 5 种情况,来讨论 this 的用法。1、纯粹的函数调用这是函数的最通常用法,属于全局性调用,因此 this 就代表全局对象 window。function test(){ this.x = 1; console.log(this.x);}test(); // 12、作为对象方法的调用函数作为某个对象的方法调用,这时 this 就指这个上级对象。function test(){ console.log(this.x);}var o = {};o.x = 1;o.m = test;o.m(); // 13、作为构造函数调用所谓构造函数,就是通过这个函数生成一个新对象(object)。这时,this 就指这个新对象。function test(){ this.x = 1;}var o = new test();console.log(o.x); // 14、apply调用apply() 是函数对象的一个方法,它的作用是改变函数的调用对象,它的第一个参数就表示改变后的调用这个函数的对象。因此,this 指的就是这第一个参数。var x = 0;function test() { console.log(this.x);}var o = {};o.x = 1;o.m = test;o.m.apply(); //0apply() 的参数为空时,默认调用全局对象。因此,这时的运行结果为0,证明this指的是全局对象。它与上文中提到的 call 的作用是一样的,只是写法上略有区别。由于篇幅原因,我会另启一篇,来详述它们的用法。5、箭头函数ES6 中的箭头函数,在大部分情况下,使得 this 的指向,变得符合我们的预期。但有些时候,它也不是万能的,一不小心的话,this 同样会失控。因为篇幅内容较多,我会另写一篇文章来介绍。另一道面试题最后,让我们来巩固一下 this 的概念和用法。来看一道面试题:window.val = 1; var obj = { val: 2, dbl: function () { this.val *= 2; val *= 2; console.log(‘val:’, val); console.log(’this.val:’, this.val); }}; // 说出下面的输出结果 obj.dbl(); var func = obj.dbl; func();答案是输出:2 、 4 、 8 、 8 。解析:执行 obj.dbl(); 时, this.val 的 this 指向 obj,而下一行的 val 指向 window。所以,由 window.val 输出 2,obj.val 输出 4 。最后一行 func(); 的调用者是 window。 所以,现在的 this.val 的 this 指向 window。别忘了刚才的运算结果,window.val 已经是 2 了,所以现在 this.val *= 2; 的执行结果就是 4。val *= 2; 的执行结果,就是 8 了。 所以,最终的结果就是输出 8 和 8 。总结this 指代了函数当前的运行环境,依赖于函数调用的上下文条件,在运行时才会进行绑定。请牢记总原则:this 总会指向,调用函数的那个对象。参考文献this 与对象原型JavaScript 的 this 原理PS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。 ...

January 20, 2019 · 2 min · jiezi

Javascript 很全的this的用法

前言: 名字取得可能有点大,this 关键字是 JavaScript 中最复杂的机制之一。笔者也困扰已久,但自从阅读了《你不知道的Javascript》以后豁然开朗,整理成文。如需更更详细的解释,请阅读《你不知道的Javascript》第二部分第1章第2章。绑定规则默认绑定最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用 其他规则时的默认规则。function foo() { console.log( this.a ); } var a = 2; foo(); // 2foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用 默认绑定,无法应用其他规则。ps:不带任何修饰的函数的描述如果有疑惑的话请不要纠结,继续看下去就就会明白。需要说明的一点是:如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定 到 undefined: function foo() { “use strict”; console.log( this.a ); } var a = 2; foo(); // TypeError: this is undefined隐式绑定另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包 含,不过这种说法可能会造成一些误导。function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2 此处foo里的this就是指的.前面的对象ps:啰嗦两句,为什么obj.foo()可以执行,这是一种属性的引用链的写法,因为obj 和foo 都挂在全局作用域上。如果还不明白再举一个例子。function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.foo(); // 42对象属性引用链中只有最顶层或者说最后一层会影响调用位置.此处也就是obj2这里说一个比较容易出错的地方:function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var a = “oops, global”; // a 是全局对象的属性 setTimeout( obj.foo, 100 ); // “oops, globalJavaScript 环境中内置的 setTimeout() 函数实现和下面的伪代码类似:function setTimeout(fn,delay) { // 等待 delay 毫秒 fn(); // <– 调用位置! }setTimeout的参数fn可以看做调用时候传入的函数复制给fn,这个是挂在全局作用域上的(此处的说法不严谨,很多时候框架的不同此处的this被绑定到哪儿很不确定).所以,回调函数丢失 this 绑定是非常常见的。es6 的=>很好的解决了这个问题。显式绑定JavaScript 提供的绝大多数函数以及你自 己创建的所有函数都可以使用 call(..) 和 apply(..) 方法。它们会把这个对象绑定到 this,接着在调用函数时指定这个 this。因为你可以直接指定 this 的绑定对象,因此我 们称之为显式绑定。function foo() { console.log( this.a ); } var obj = { a:2 }; foo.call( obj ); // 2通过 foo.call(..),我们可以在调用 foo 时强制把它的 this 绑定到 obj 上。如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对 象,这个原始值会被转换成它的对象形式(也就是 new String(..)、new Boolean(..) 或者 new Number(..))。这通常被称为“装箱”。ps:硬绑定只能执行一次,之后再绑别的都是绑不上去的。由于硬绑定是一种非常常用的模式,所以在 ES5 中提供了内置的方法 Function.prototype. bind,它的用法如下: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 ); // 5bind(..) 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数。new绑定对于js的new 初学者可能会把它和JAVA类的语言混淆首先我们重新定义一下 JavaScript 中的“构造函数” 。在 JavaScript 中,构造函数只是一些 使用 new 操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上, 它们甚至都不能说是一种特殊的函数类型,它们只是被 new 操作符调用的普通函数而已。创建(或者说构造)一个全新的对象。这个新对象会被执行 [[ 原型 ]] 连接。这个新对象会绑定到函数调用的 this。如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。2涉及到原型链的知识,本篇可忽视。function foo(a) { this.a = a; } var bar = new foo(2); console.log( bar.a ); // 2使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定。ps:此处可能看得比较费解,请阅读《你不知道的js》第二部的关于类与原型链的章节,还是比较复杂的,在此就不展开了。此处你可以理解为用函数new 了一个新对象,然后绑定到 函数的this上使用。在 new 中使用硬绑定函数的一般用处主要目的是预先设置函数的一些参数,这样在使用 new 进行初始化时就可以只传入其余的参数。bind(..) 的功能之一就是可以把除了第一个 参数(第一个参数用于绑定 this)之外的其他参数都传给下层的函数(这种技术称为“部 分应用”,是“柯里化”的一种)。举例来说:function foo(p1,p2) { this.val = p1 + p2; } // 之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么 // 反正使用 new 时 this 会被修改 var bar = foo.bind( null, “p1” ); var baz = new bar( “p2” ); baz.val; // p1p2优先级我们可以根据优先级来判断函数在某个调用位置应用的是哪条规则。可以按照下面的 顺序来进行判断:(数值越小优先级越高)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()绑定例外在某些场景下 this 的绑定行为会出乎意料,你认为应当应用其他绑定规则时,实际上应用 的可能是默认绑定规则。被忽略的this如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值 在调用时会被忽略,实际应用的是默认绑定规则.间接引用另一个需要注意的是,你有可能(有意或者无意地)创建一个函数的“间接引用”,在这 种情况下,调用这个函数会应用默认绑定规则。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赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 foo() 而不是 p.foo() 或者 o.foo()。1 软绑定用硬绑定之后就无法使用隐式绑定或者显式绑定来修改 this。如果可以给默认绑定指定一个全局对象和 undefined 以外的值,那就可以实现和硬绑定相 同的效果,同时保留隐式绑定或者显式绑定修改 this 的能力。if (!Function.prototype.softBind) { Function.prototype.softBind = function(obj) { var fn = this; // 捕获所有 curried 参数 var curried = [].slice.call( arguments, 1 ); var bound = function() { return fn.apply( (!this || this === (window || global)) ?obj : this curried.concat.apply( curried, arguments ) ); }; bound.prototype = Object.create( fn.prototype ); return bound; }; }关于=>有话说箭头函数并不是使用 function 关键字定义的,而是使用被称为“胖箭头”的操作符 => 定 义的。箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决 定 this。之前我们有说过setTimeout的this绑定丢失的问题es6和es6以前的解决办法如下,可以比较着看es6:function foo() { setTimeout(() => { // 这里的 this 在此法上继承自 foo() console.log( this.a ); },100); } var obj = { a:2 }; foo.call( obj ); // 2es5function foo() { var self = this; // lexical capture of this setTimeout( function(){ console.log( self.a ); }, 100 ); } var obj = { a: 2 }; foo.call( obj ); // 2ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这 其实和 ES6 之前代码中的 self = this 机制一样。 ...

November 12, 2018 · 3 min · jiezi