关于this:js-之-this-指向问题-函数执行上下文中的-this

函数执行上下文中的 this 在下面咱们晓得,个别的调用办法,是调用 window 上的办法。 那怎么获取以后函数的 this 呢? 1 通过 call/bind/apply 扭转 this this.myName = 'jszhang';let foo = function() { this.myName = 'zhangsan';}foo();console.log(window.myName); // 输入啥?console.log(foo.myName); // 输入啥?这时候的 this 指向 window,所以输入后果为; zhangsan undefined 通过 call 绑定后: this.myName = 'jszhang';let foo = function() { this.myName = 'zhazhazhang';}foo.call(foo);console.log(window.myName); // 输入啥?console.log(foo.myName); // 输入啥?输入后果为: jszhang zhazhazhang 当然你也能够换成 apply 和 bind,这里不做累述。 2 通过对象调用办法设置应用对象来调用其外部的一个办法,该办法的 this 是指向对象自身的。 案例 1 let myObj = { name: 'jszhang', showThis: function() { console.log(this.name); },};myObj.showThis(); // 输入啥?答案:输入 jszhang。 ...

February 23, 2022 · 3 min · jiezi

在-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关键字面试会问啊!总有一些面试官喜欢问你一段不可能这么写的代码。看一道经典且古老的面试题(学完本文后,末尾会有一道更复杂的面试题等着你哦!) 代码如下:```javascriptlet a = 5;let obj = { a : 10, foo: function(){ console.log(this.a) }}let bar = obj.fooobj.foo() bar()```我在读 Events 的 lib/events 源码的时候发现多次用到call关键字,看来有必要搞懂 this 与 call 相关的所有内容。 其中几句代码是这样写的 // 场景1:function EventEmitter() { EventEmitter.init.call(this);}// 场景2:return this.listener.call(this.target);// 场景3:return listenerCount.call(emitter, type);3.箭头函数使用不当报错,在封装 Node.js 的一个 ORM 映射框架 Sequelize 时,封装表关联关系,由于使用箭头函数造成了读到的上下文发生变化,不是想要的 model 信息,而是指向了全局 。 call 关键字在写代码过程中还是比较常用的,有时候我们常常会使用 call 关键字来指定某个函数运行时的上下文,有时候还使用 call 关键字实现继承。代码例子如下: var person = { "name": "koala"};function changeJob(company, work) { this.company = company; this.work = work;};changeJob.call(person, '百度', '程序员');console.log(person.work); // '程序员'文章概览图 ...

September 10, 2019 · 4 min · jiezi

JS篇JavaScript-执行上下文和提升

我们通常将 JavaScript 归类为动态或解释执行语言,但实际上它也是一门编译语言,它有自己的编译器形式,运行在 JavaScript 引擎中。 每个 Web 浏览器都有自己的 JavaScript 引擎形式:Chrome 有 V8,Mozilla 有 SpiderMonkey 等。这些 JavaScript 引擎的共同点都是将 JavaScript 代码转换为编译器可以理解的语言,然后执行它。 执行上下文 Execution Context当 JavaScript 代码运行的时候,运行 JavaScript 代码的环境形成了执行上下文 ,执行上下文决定代码可以访问哪些变量、函数、对象等。 我们将执行上下文简单视为运行当前代码的 environment / scope,我们知道作用域分为 global scope 和 local scope。 类似的,执行上下文也分为不同的类型: 全局执行上下文 - 代码首次执行时候的默认环境,在代码的整个执行过程中,只用一个全局执行上下文。 函数执行上下文 - 每当执行流程进入到一个函数体内部的时候,就会创建一个函数执行上下文,可以有任意数量的函数执行上下文。 执行栈/调用栈JavaScript 是单线程的,浏览器只分配给 JavaScript 一个主线程,一次只能执行一个任务(函数),因此它在执行栈中对其他操作(事件和函数执行)形成一个任务队列,排队等候执行。 每当在浏览器中加载脚本时,栈 stack 中的第一个元素就是全局执行上下文。当有函数执行时,将创建一个函数执行上下文,并将其置于全局执行上下文之上。一旦函数执行完成,它就会从执行堆栈中弹出,并将控制权交给它下面的上下文中。结合上面说到的,我们看一个例子: var name = "global variable";console.log(name)function func1() { console.log("func1 被调用了。") func2();}function func2() { console.log("func2 被调用了。");}func1(); ...

July 13, 2019 · 2 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

ES5-callapplybind方法总结包括理解this的指向问题

总结call,apply,bind方法的理解使用和区别。call,apply,bind这三个方法在JavaScript中是用来改变函数调用的this指向。那么改变函数this指向有什么用呢?我们先来看一段代码 var a= { name:'harden', fn:function () { console.log(this.name); }}var b = a.fn;a.fn();//hardenb();//undefined调用a.fn方法后得到了harden,但在b方法中我想得到harden,为什么却是undefined呢?原因是方法在执行的时候才能确定this到底指向谁,实际上this指向的是最终调用函数的对象。这里当b执行时,实际是window调用了fn函数,那么fn中的this就指向window。在开始讲call,apply,bind方法前,一起来总结一下this的指向问题。 理解JavaScript中的this指向问题。总体来说this指向可以概括为一句话:this指向在函数的定义时是不确定的,只有函数执行时才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象。但是这个说法在函数被很多对象包裹的时候并不成立,请看下面例子。简单来说就是:谁(哪个对象)调用的这个函数,那么这个函数中的this就指向这个对象。 例一 function a(){ var name= "harden"; console.log(this.name); //undefined console.log(this); //Window } a();因为this最终指向调用他的对象,在上述代码中其实是widow触发的这个方法,那么this就指向window,window中并没有定义a,那么就打印出undefined。例二: var a = { name:'harden', fn:function() { console.log(this.name);//harden console.log(this);//指向a(可以自己跑一下) }}a.fn()这里的this指向a,因为这里的fn函数是通过a.fn()执行的,那么this自然指向a。说到这我就有疑问了,如果我用 window.a.fn()执行函数,this不就指向window了吗?然后并不是这样的,请看下一个例子。补充一点:window是js的全局对象。例三: var a = { name:'harden', b:{ name:'james', fn:function() { console.log(this.name);//james console.log(this);//指向b } }}a.b.fn()我们看到最终是a调用的方法,那为什么this会指向b呢?现在总结三句话,来完全理解this的指向问题: 情况一:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window(除去严格模式外)。情况二:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。情况三:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,例子3可以证明。 构造函数中的this: function Fn(){ this.name = "harden"; } var a = new Fn(); console.log(a.name); //harden这里的a可以点出name,因为new关键字会改变this的指向。为什么new关键字会改变this呢,我自己有两种看法:1.在new的过程中会创建一个实例对象,通过apply等方法 通过 Fn.apply({}) 使this指向这个空对象,最后把fn方法中的材料加工完后返回给a。 ...

June 21, 2019 · 1 min · jiezi

JS核心知识点梳理上下文作用域闭包this中

引言满满的干货,面试必bei系列,参考大量资料,并集合自己的理解以及相关的面试题,对JS核心知识点中的作用域、闭包、this、上下文进行了梳理。上一篇介绍了作用域和上下文。因为感觉这两个概念互相纠缠,上下文的生成会依赖作用域规则。本篇重点介绍闭包和this。 this先介绍this,因为我觉得this最简单了,掌握住分析的方法,依照方法去分析,毫无难度。 为什么引入this因为我们解耦,为什么要解耦?因为我们要复用!举个例子:我费了九牛二虎之力写了一个方法,可以对某个数组a进行复杂的操作 var a = [xxx,xxx,....]function foo (){ a xxx // do something to a xxx a // do something to a}这个方法只能a用 耦合性太强。当其他数组b想用这个方法的时候由于方法里面的操作对象固定是a导致失败。当然我也不能写成b,因为如果数组c要用难道我们再改成c?怎么办,显然这个操作的对象不能是固定的,应该说最好是一个变量,谁调用,这个变量就是谁。this就这么产生了!所以说this的可变是业务的需要,我们要的就是它的可变。当然你要是掌握不了它的变化规则,那么对你来说引入this就是一场灾难了。 this的规则总原则: 函数中的this,指的是当前函数的执行主体;谁让函数执行的,那么this就指向谁 在全局作用域下,this指向window;函数体中的this,看函数执行前有没有".",如果有,那么点前面是谁,this就指向谁;如果没有“.”,那么会指向window;如果给元素的事件行为绑定方法,那么方法中的this,就会指向当前被绑定的那个元素;回调函数中的this指向window;自执行函数中的this永远指向window;改变thisapply call bind可以改变this 问题来了,为什么要改变this? 还记得我之前说的引入this是为了进行更好的复用吗?js里面没有类,但是通过原型、继承js在努力模仿类。同一个类当然能通过继承复用代码,不同类不继承的情况下怎么复用呢?通过改变this。举个例子,Array类有reverse方法。只要是数组,都继承了Array的reverse方法,可以直接调用。 [1,2,3].reverse() //[3,2,1]现在有个类数组arguments,由于不属于Array类,没有办法继承reverse方法,但是我就是想用,咋办?改变this Array.prototype.reverse.call(argumengts)闭包在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。 wiki闭包这个东西咋说呢,不同的程序员,不同的资料都有不同的理解。你把它理解为一个函数,也可以把它理解为函数+执行环境。 我们这里不纠结闭包的定义。而是关注闭包的现象,应用,再结合相关面试题去攻克它,最后谈一下我对闭包的思考。 现象话说了一箩筐,你倒是给我上代码呀..... 各位看官息怒,小的再多说几句 之前我们说过了,函数执行,生成执行环境。函数执行完,销毁执行环境。嗯,听上去很正常,不用的东西就销毁嘛。 但是如果函数执行完,该函数上下文还用用怎么办,有用的东西肯定不敢销毁了。这个就是闭包的现象,那么能引起这个现象的鄙人就把它理解为闭包! function foo () { var a = 1 return function bar () { a++ console.log(a) }}var b = foo() //函数执行完,我就问你foo的上下文你敢销毁吗?b() // 2b() // 3大家看foo执行完的结果赋值给了b,也就是函数bar赋值给了b,未来我可能让b执行的,也就是让bar执行,所以我需要保证bar能访问上下文不被销毁。bar能访问的上下文实际上是foo执行时候的上下文。所以foo执行完以后上下文不会被销毁,会继续存在。 ...

May 18, 2019 · 1 min · jiezi

JS篇你不知道的-JS-知识点总结一

typeof null 为 ”object” 解释不同的对象在底层都表示为二进制,在JavaScript中二进制前三位都为0的话会被判断为object类型,null 的二进制表示都是0,自然前三位都是0,所以执行 typeof null 时,会返回 ”object”。传统编程语言编译的三个步骤分词/词法分析解析/语法分析代码生成变量的赋值操作编译阶段 -- 首次编译器会在当前作用域中声明一个变量(变量提升),如果之前没用声明过。运行阶段 -- 运行时引擎会在作用中查找该变量,如果能够找到就会对它赋值(LHS查询。作用域作用域是一套规则,用于确定在何处以及如何查找变量。如果查找的目的是对变量赋值,那么就会使用 LHS 查询。如果查找的目的是获取变量的值,那么就会使用 RHS 查询。无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。闭包当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。thisthis 在任何情况下都不指向函数的词法作用域。在 JavaScript 中作用域确实和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过 JavaScript 代码访问,它存在 JavaScript 引擎的内部。this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调 用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(有时也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈),函数的调用方法,传入的参数等信息。this 就是记录其中的一个属性,会在函数执行的过程中用到。this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。调用栈 — 就是为了到达当前执行位置所调用到的所用函数。bind()会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数。new 操作符使用 new 来调用函数时,会自动执行下面的操作: 创建一个新的空对象;这个对象会被执行[[原型]]连接;这个新对象会绑定到函数调用的 this;如果函数没用返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。判断 this可以根据下面的顺序来进行判断: 函数是否在 new 中调用(new绑定)如果是的话,this 是绑定的是新创建的对象。 var bar = new foo();函数是否是通过 call apply (显示绑定)或者硬绑定调用,如果是的话,this绑定的是指定的对象。 var bar = foo.call(obj2);函数是否在某个上下文对象中调用(隐式调用),如果是的话,this绑定的是那个上下文对象。 var bar = obj.foo();若果都不是的话,使用默认绑定,如果在严格模式下,就绑定到 undefined,否则绑定到全局对象上。 var bar = foo();ES6 新增可计算属性名var prefix = "foo";var myObject = { [prefix + "bar"]: "hello", [prefix + "baz"]: "world" }myObject["foobar"]; // hellomyObject["foobaz"]; // worldin / hasOwnProperty() -- 判断某个对象是否存在某个属性in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中。hasOwnProperty() 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 链。forEach() / every() / sone() -- 历数组的值forEach() 会遍历数组中的所有值并忽略回调函数的返回值(忽略返回值)。every() 方法测试数组的所有元素是否都通过了指定函数的测试(返回值是false终止)。some() 方法测试是否至少有一个元素通过由提供的函数实现的测试(返回值是true终止)。for...in -- 遍历数组下标/对象可枚举属性不保证 key 顺序。for...of -- 遍历可迭代对象的值在可迭代对象上(包括 Array,Map,Set,String,TypedArray,arguments 对象等)上创建一个迭代循环,调用自定义迭代钩子自定义的 @@iterator 对象 ,并为每个不同属性的值执行语句。var randoms = {
[Symbol.iterator]: function() { return {
next: function() { return { value: Math.random() }; } }; } }; var randoms_pool = []; for (var n of randoms) { randoms_pool.push( n ); // 防止无限运行! if (randoms_pool.length === 100) break; } 类的继承和多态多态并不表示子类和父类有关联,子类得到的只是父类的一份副本,类的继承其实就是复制。属性的设置和屏蔽var myObject = {}; myObject.foo = "bar";如果 myObject 对象中包含名为 foo 的普通数据访问属性,这条赋值语句只会修改已有的属性值。如果 foo 不是直接存在于 myObject 中,[[Prototype]] 链就会被遍历,类似 [[Get]] 操作。如果原型链上找不到 foo,foo 就会被直接添加到 myObject 上。然而,如果 foo 存在于原型链上层,赋值语句 myObject.foo = "bar" 的行为就会有些不同 (而且可能很出人意料)。如果属性名 foo 既出现在 myObject 中也出现在 myObject 的 [[Prototype]] 链上层,那 么就会发生屏蔽。myObject 中包含的 foo 属性会屏蔽原型链上层的所有 foo 属性,因为 myObject.foo 总是会选择原型链中最底层的 foo 属性。转载请注明出处,如果想要了解更多,请搜索微信公众号:webinfoq。 ...

April 26, 2019 · 1 min · jiezi

译javascript的this关键词理解

一直以来,javascript里边的this都是一个很难理解的东西,之前看的最多的就是阮一峰老师关于this的理解: http://www.ruanyifeng.com/blo... http://www.ruanyifeng.com/blo... 今天在留言区发现了一国外大神关于this的理解,借助翻译工具读了一下原文,相对来说是最好的关于理解this的文章,就翻译了一下,也算是记录一下。 JavaScript的一个常用特性是“this”关键字,但它也常常是该语言中最令人困惑和误解的特性之一。“this”到底是什么意思?它是如何决定的? 本文试图澄清这种困惑,并以一种清晰的方式解释这个问题的答案。 “this”关键字对于那些用其他语言编程的人来说并不新鲜,而且它通常引用在通过类的构造函数实例化类时创建的新对象。例如,如果我有一个类Boat(),它有一个方法moveBoat(),当在moveBoat()方法中引用“this”时,我们实际上是在访问新创建的Boat()对象。 在JavaScript中,当使用“new”关键字调用函数构造函数时,函数构造函数中也有这个概念,但是它不是惟一的规则,而且“this”常常可以引用来自不同执行上下文的不同对象。如果您不熟悉JavaScript的执行上下文,我建议您阅读我关于这个主题的另一篇文章(本人注:文章找不到了)。谈得够多了,让我们来看一些JavaScript例子: // 全局作用域foo = 'abc';alert(foo); // abcthis.foo = 'def';alert(foo); // def无论何时在全局上下文中使用关键字“this”(而不是在函数中),它总是指向全局对象。现在让我们看看函数中“this”的值: var boat = { size: 'normal', boatInfo: function() { alert(this === boat); alert(this.size); }};boat.boatInfo(); // true, 'normal'var bigBoat = { size: 'big'};bigBoat.boatInfo = boat.boatInfo;bigBoat.boatInfo(); // false, 'big'那么上面的“this”是如何确定的呢?我们可以看到一个对象boat,它有一个属性size和一个方法boatInfo()。在boatInfo()中,如果该对象的值是实际的boat对象,它将发出警报,并警告该对象的size属性。因此,我们使用boat.boatInfo()调用函数,可以看到这是boat对象,并且boat的size属性是正常的。 然后我们创建另一个对象bigBoat,它的size属性为big。然而,bigBoat对象没有一个boatInfo()方法,因此我们使用bigBoat从boat复制该方法。boatInfo = boat.boatInfo。现在,当我们调用bigBoat.boatInfo()并输入函数时,我们看到它不等于boat, size属性现在是big。为什么会这样?这个值在boatInfo()中是如何变化的? 您必须意识到的第一件事是,任何函数中的这个值都不是静态的,它总是在每次调用函数时确定的,但是在函数实际执行之前,它是代码。函数内部的值实际上是由父作用域提供的,在父作用域中调用函数,更重要的是,函数语法是如何编写的。 每当调用一个函数时,我们必须查看方括号/圆括号“()”的左边。如果在括号的左边我们可以看到一个引用,那么传递给函数调用的“this”的值就是该对象所属的值,否则它就是全局对象。让我们来看一些例子: function bar() { alert(this);}bar(); // global - 因为方法bar()在调用时属于全局对象var foo = { baz: function() { alert(this); }}foo.baz(); // foo - 因为方法baz()在调用时属于对象foo如果到目前为止一切都很清楚,那么上面的代码显然是有意义的。通过用两种不同的方式编写call / invoke语法,我们可以在同一个函数中更改“this”的值,从而使事情变得更加复杂: ...

April 24, 2019 · 1 min · jiezi

大话javascript 6期:this深度解析

一、引言在执行上下文的创建阶段,会分别生成变量对象,建立作用域链,确定this指向。this的指向,是在函数被调用的时候确定的。也就是执行上下文被创建时确定的。因此,一个函数中的this指向,可以是非常灵活的二、this对象的定义this对象代表函数运行时,自动生成的一个内部对象,只能在函数内部使用在全局执行环境中(在任何函数体外部)this 都指向全局对象。在函数内部,this的值取决于函数被调用的方式。关键点:this永远指向一个对象,并且拥有着个对象的值在严格模式下,在全局作用域中和匿名函数中,this指向undefined当this在一个函数内出现的时候,this指向调用这个函数的对象三、this指向全局环境无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象。// 在浏览器中, window 对象同时也是全局对象:console.log(this === window); // truea = 37;console.log(window.a); // 37this.b = “MDN”;console.log(window.b) // “MDN"console.log(b) // “MDN"函数(运行)环境在函数内部,this的值取决于函数被调用的方式。1.对象方法调用模式当函数作为对象里的方法被调用时, this 指向调用该方法的对象如果函数作为一个对象的属性方法,并且被调用的时候,那么这个属性方法中的this 就指向这个对象下面的例子中,当 o.f()被调用时,函数内的this将绑定到o对象。var o = { prop: 37, f: function() { return this.prop; }};console.log(o.f()); // logs 37请注意,这样的行为,根本不受函数定义方式或位置的影响。在前面的例子中,我们在定义对象o的同时,将函数内联定义为成员 f 。但是,我们也可以先定义函数,然后再将其附属到o.f。这样做会导致相同的行为:var o = {prop: 37};function independent() { return this.prop;}o.f = independent;console.log(o.f()); // logs 37这表明函数是从o的f成员调用的才是重点。同样,this 的绑定只受最靠近的成员引用的影响。在下面的这个例子中,我们把一个方法g当作对象o.b的函数调用。在这次执行期间,函数中的this将指向o.b。事实证明,这与他是对象 o 的成员没有多大关系,最靠近的引用才是最重要的。o.b = {g: independent, prop: 42};console.log(o.b.g()); // 422.函数调用模式如果是普通函数调用方式。非严格模式下,this指向window,严格模式下,this是undefined;非严格模式下,this 的值默认指向全局对象。在浏览器中,全局对象是windowfunction f1(){ return this;}//在浏览器中:f1() === window; //在浏览器中,全局对象是window//在Node中:f1() === global;在严格模式下,this将保持他进入执行环境时的值,所以下面的this将会默认为undefined。function f2(){ “use strict”; // 这里是严格模式 return this;}f2() === undefined; // true所以,在严格模式下,如果 this 没有被执行环境(execution context)定义,那它将保持为 undefined。3.构造函数调用模式如果是构造函数调用方式,this指向实例化出来的新对象/* * 构造函数这样工作: * * function MyConstructor(){ * // 函数实体写在这里 * // 根据需要在this上创建属性,然后赋值给它们,比如: * this.fum = “nom”; * // 等等… * * // 如果函数具有返回对象的return语句, * // 则该对象将是 new 表达式的结果。 * // 否则,表达式的结果是当前绑定到 this 的对象。 * //(即通常看到的常见情况)。 * } /function C(){ this.a = 37;}var o = new C();console.log(o.a); // logs 37虽然构造器返回的默认值是this所指的那个对象,但它仍可以手动返回其他的对象(如果返回值不是一个对象,则返回this对象)。function C2(){ this.a = 37; return {a:38};}o = new C2();console.log(o.a); // logs 38在刚刚的例子中(C2),因为在调用构造函数的过程中,手动的设置了返回对象,与this绑定的默认对象被丢弃了。(这基本上使得语句 “this.a = 37;”成了“僵尸”代码,实际上并不是真正的“僵尸”,这条语句执行了,但是对于外部没有任何影响,因此完全可以忽略它)。4.call / apply 调用模式call()、apply()方式调用,this指向被绑定的对象;如果要想把 this 的值从一个环境传到另一个,就要用 call 或者apply 方法。// 将一个对象作为call和apply的第一个参数,this会被绑定到这个对象。var obj = {a: ‘Custom’};// 这个属性是在global对象定义的。var a = ‘Global’;function whatsThis(arg) { return this.a; // this的值取决于函数的调用方式}whatsThis(); // ‘Global’whatsThis.call(obj); // ‘Custom’whatsThis.apply(obj); // ‘Custom’当一个函数在其主体中使用 this 关键字时,可以通过使用函数继承自Function.prototype 的 call 或 apply 方法将 this 值绑定到调用中的特定对象。function add(c, d) { return this.a + this.b + c + d;}var o = {a: 1, b: 3};// 第一个参数是作为‘this’使用的对象// 后续参数作为参数传递给函数调用add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16// 第一个参数也是作为‘this’使用的对象// 第二个参数是一个数组,数组里的元素用作函数调用中的参数add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34使用 call 和 apply 函数的时候要注意,如果传递给 this 的值不是一个对象,JavaScript 会尝试使用内部 ToObject 操作将其转换为对象。因此,如果传递的值是一个原始值比如 7 或 ‘foo’,那么就会使用相关构造函数将它转换为对象,所以原始值 7 会被转换为对象,像 new Number(7) 这样,而字符串 ‘foo’ 转化成 new String(‘foo’) 这样,例如:function bar() { console.log(Object.prototype.toString.call(this));}//原始值 7 被隐式转换为对象bar.call(7); // [object Number]5.bind方法调用bind()方式调用,this指向被绑定的对象;ECMAScript 5 引入了 Function.prototype.bind。调用f.bind(someObject)会创建一个与f具有相同函数体和作用域的函数,但是在这个新函数中,this将永久地被绑定到了bind的第一个参数,无论这个函数是如何被调用的。function f(){ return this.a;}var g = f.bind({a:“azerty”});console.log(g()); // azertyvar h = g.bind({a:‘yoo’}); // bind只生效一次!console.log(h()); // azertyvar o = {a:37, f:f, g:g, h:h};console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty6.箭头函数在箭头函数中,this与封闭词法环境的this保持一致。在全局代码中,它将被设置为全局对象:如果是箭头函数,是根据当前的词法作用域来决定this, 具体来说,箭头函数会继承外层函数调用的this绑定。var globalObject = this;var foo = (() => this);console.log(foo() === globalObject); // true注意:如果将this传递给call、bind、或者apply,它将被忽略。不过你仍然可以为调用添加参数,不过第一个参数(thisArg)应该设置为null。// 接着上面的代码// 作为对象的一个方法调用var obj = {foo: foo};console.log(obj.foo() === globalObject); // true// 尝试使用call来设定thisconsole.log(foo.call(obj) === globalObject); // true// 尝试使用bind来设定thisfoo = foo.bind(obj);console.log(foo() === globalObject); // true无论如何,foo 的 this 被设置为他被创建时的环境(在上面的例子中,就是全局对象)。这同样适用于在其他函数内创建的箭头函数:这些箭头函数的this被设置为封闭的词法环境的。// 创建一个含有bar方法的obj对象,// bar返回一个函数,// 这个函数返回this,// 这个返回的函数是以箭头函数创建的,// 所以它的this被永久绑定到了它外层函数的this。// bar的值可以在调用中设置,这反过来又设置了返回函数的值。var obj = { bar: function() { var x = (() => this); return x; }};// 作为obj对象的一个方法来调用bar,把它的this绑定到obj。// 将返回的函数的引用赋值给fn。var fn = obj.bar();// 直接调用fn而不设置this,// 通常(即不使用箭头函数的情况)默认为全局对象// 若在严格模式则为undefinedconsole.log(fn() === obj); // true// 但是注意,如果你只是引用obj的方法,// 而没有调用它var fn2 = obj.bar;// 那么调用箭头函数后,this指向window,因为它从 bar 继承了this。console.log(fn2()() == window); // true在上面的例子中,一个赋值给了 obj.bar的函数(称为匿名函数 A),返回了另一个箭头函数(称为匿名函数 B)。因此,在 A 调用时,函数B的this被永久设置为obj.bar(函数A)的this。当返回的函数(函数B)被调用时,它this始终是最初设置的。在上面的代码示例中,函数B的this被设置为函数A的this,即obj,所以即使被调用的方式通常将其设置为 undefined 或全局对象(或者如前面示例中的其他全局执行环境中的方法),它的 this 也仍然是 obj 。7.原型链的this在对象原型链上某处定义的方法,this指向的是调用这个方法的对象对于在对象原型链上某处定义的方法,同样的概念也适用。如果该方法存在于一个对象的原型链上,那么this指向的是调用这个方法的对象,就像该方法在对象上一样。var o = { f: function() { return this.a + this.b; }};var p = Object.create(o);p.a = 1;p.b = 4;console.log(p.f()); // 5在这个例子中,对象p没有属于它自己的f属性,它的f属性继承自它的原型。虽然在对 f 的查找过程中,最终是在 o 中找到 f 属性的,这并没有关系;查找过程首先从 p.f的引用开始,所以函数中的 this 指向p。也就是说,因为f是作为p的方法调用的,所以它的this指向了p。这是 JavaScript 的原型继承中的一个有趣的特性。8.getter 与 setter 中的 this当函数在一个 getter 或者 setter 中被调用。用作 getter 或 setter 的函数都会把 this绑定到设置或获取属性的对象。function sum() { return this.a + this.b + this.c;}var o = { a: 1, b: 2, c: 3, get average() { return (this.a + this.b + this.c) / 3; }};Object.defineProperty(o, ‘sum’, { get: sum, enumerable: true, configurable: true});console.log(o.average, o.sum); // logs 2, 69.作为一个DOM事件处理函数当函数被用作事件处理函数时,它的this指向触发事件的元素(一些浏览器在使用非addEventListener的函数动态添加监听函数时不遵守这个约定)。// 被调用时,将关联的元素变成蓝色function bluify(e){ console.log(this === e.currentTarget); // 总是 true // 当 currentTarget 和 target 是同一个对象时为 true console.log(this === e.target); this.style.backgroundColor = ‘#A5D9F3’;}// 获取文档中的所有元素的列表var elements = document.getElementsByTagName(’’);// 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色for(var i=0 ; i<elements.length ; i++){ elements[i].addEventListener(‘click’, bluify, false);}10.作为一个内联事件处理函数当代码被内联on-event 处理函数调用时,它的this指向监听器所在的DOM元素:<button onclick=“alert(this.tagName.toLowerCase());"> Show this</button>上面的 alert 会显示button。注意只有外层代码中的this是这样设置的:<button onclick=“alert((function(){return this})());"> Show inner this</button>在这种情况下,没有设置内部函数的this,所以它指向 global/window 对象(即非严格模式下调用的函数未设置this时指向的默认对象)。11.作为定时器的参数作为定时器的参数时, this 指向 windowsetInterval(function() { console.log(this);}, 1000); ...

April 10, 2019 · 3 min · jiezi

Brief introduction of how to 'Call, Apply and Bind'

关于 this在绝大多数情况下,函数的调用方式决定了this的值。this不能在执行期间被赋值,并且在每次函数被调用时this的值也可能会不同。全局 thiswindow.something = ‘I love JavaScript’console.log(this.something) // ‘I love JavaScript’console.log(window === this) // true调用全局 functionvar a = 1function test() { console.log(this.a) }test() // 1 - still remains the window reference调用对象中的 functionthis.a = ‘I am in the global scope’function Test() { this.a = ‘I am in the test scope’ this.show = function() { console.log(this.a) }}Test.prototype.display = function () { console.log(this.a) }var test = new Test() // updated the scope of thistest.show() // I am in the test scopetest.display() // I am in the test scope 关于 call / applyJavaScript 内部提供了一种机制,让我们可以自行手动设置 this 的指向。它们就是 call 与 apply。所有的函数都具有着两个方法。它们除了参数略有不同,其功能完全一样。它们的第一个参数都为 this 将要指向的对象。一个最简单的继承function Laptop(name, storage) { this.name = name this.storage = storage}function Dell(name, storage, company) { Laptop.call(this, ‘Dell’, 1024) this.company = company}console.log(new Dell(‘Dell’, 1024, ‘Dell Inc’).storage)改变 thisvar obj = { entry: ‘mammals-banana-tower’, duration: 0}function breed(name) { console.log(‘Show this breeding info’, name, this.entry, this.duration) console.log(this === obj)}breed() // this => windowbreed.call(obj, ‘Frank’) // this => obj注:当没有传递任何参数作为 call() 的第一个参数时,在非严格模式下,this 会指向 window。实现一个简单的 callvar _call = function (that) { that = that ? Object(that) : window that.func = this function formatArgs(oArgs, sign) { var _args for (var i = 1, len = oArgs.length; i < len; i++) { _args.push(sign ? (’param’ + i) : oArgs[i]) } return _args } var args = formatArgs(arguments) var newFunc = (new Function(‘args’, ‘return that.func(’ + formatArgs(args, true).toString() + ‘)’))(args) that.func = null return newFunc}关于 bind() => {} 和 bind this用过 React 的同学都知道,当使用 class component 时,需要在 constructor 绑定当前的成员函数,或者针对事件委托的情况下,也需要进行绑定;ES6 箭头函数可以让我们更专注于具体的实现逻辑,简化了 this 操作// ES5// <a onclick={this.handleClick.bind(this)}></a>// constructor() { this.handleClick = this.handleClick.bind(this) }// ES6// <a onclick={() => handleClick()}></a>// handleClick = () => {}无效的 re-boundvar f = function() { console.log(this.text) }f = f.bind({ text: ‘I was bound’ }).bind({ text: ‘I won’t be bound’ })f() // I was bound很容易发现,f.bind() 返回的绑定函数对象仅在创建是保留当前的上下文(或者传入的参数),因此无法在第二次进行重绑定。一个相对完善的 bindvar _bind = function (that) { var fBound, target = this, slice = Array.prototype.slice, toStr = Object.prototype.toString, args = slice.call(arguments, 1); // except that if (typeof target !== ‘function’ || toStr.call(target) !== ‘[object Function]’) { throw new TypeError(‘Function.prototype.bind - what is trying to be bound is not callable’); } var binder = function () { var oArgs = args.concat(slice.call(arguments)) if (this instanceof fBound) { var result = target.apply(this, oArgs); return Object(result) === result ? result : this; } else { return target.apply(that, oArgs); } }; var i = 0, params = [], paramLength = Math.max(0, target.length - args.length); for (; i < paramLength; i++) { params.push(’param’ + i); } fBound = (new Function( ‘binder’, ‘return function(’ + params.join(’,’) + ‘) { return binder.apply(this,arguments); }’ ))(binder); // maintain the reference of prototype if (target.prototype) { var fNOP = function () { }; fNOP.prototype = target.prototype; fBound.prototype = new fNOP(); fNOP.prototype = null; } return fBound;};参考https://developer.mozilla.org…https://developer.mozilla.org...https://developer.mozilla.org…https://developer.mozilla.org...https://developer.mozilla.org…https://developer.mozilla.org...https://www.ecma-internationa…https://javascript.info/bindhttps://juejin.im/post/5c0605...https://github.com/mqyqingfen… ...

February 22, 2019 · 3 min · jiezi

Node.js this指针指向module.exports、global、实例,指针显式、隐式传递与绑定与优先级

一、this指针指向module.exportsconsole.log(“全局中的this指向的是module.exports”);console.log(this); //{}this.obj = “Hello World”;console.log(this.obj); //Hello Worldconsole.log(global.obj); //undefinedconsole.log(module.exports.obj); //Hello Worldconsole.log("——————————————————-" + “\n\n”);二、this指针指向global对象console.log(“在函数中this指向的是global对象,和全局中的this不是同一个对象”);function fn() { this.obj = “good good study! day day up!”;}fn();console.log(this);//{ obj: ‘Hello World’ }console.log(this.obj);//Hello Worldconsole.log(global.obj);//“good good study! day day up!“console.log(”——————————————————-” + “\n\n”);console.log(“在函数中this指向的是global对象,和全局中的this不是同一个对象”);function fn1() { function fn2() { this.msg = “I love you”; }fn2();console.log(this); //globalconsole.log(this.msg); //“I love you"console.log(global.msg); //“I love you”}fn1();console.log(”——————————————————-" + “\n\n”);三、在构造函数中this指向的是它的实例,而不是globalfunction Fn3(){ this.year = 1998;}let fn3 = new Fn3();console.log(this); //{ obj: ‘Hello World’ }console.log(fn3.year); //1998console.log(global.year); //undefinedconsole.log("——————————————————-" + “\n\n”);四、this指针显式、隐式传递与绑定console.log(“显式传递this”);let Kirito = {};function person(name, sex, age, addr, salary) { this.name = name; this.sex = sex; this.age = age; this.addr = addr; this.salary = salary;}//这里的传入Kirito为this指针所指向的对象//使用.call()进行显式传递person.call(Kirito, “桐人”, “男”, 18, “SAO”, 999999999);console.log(Kirito);console.log("——————————————————-" + “\n\n”);console.log(“隐式传递this”);let Ausua = { name: “亚丝娜”, sex: “女”, age: 18, addr: “SAO”, salary: 999999999, func() { console.log(this); }, func_bind: function () { console.log(this); }.bind(“绑定”)};Ausua.func();console.log("——————————————————-" + “\n\n”);console.log(“强制绑定this指针”);let func = function () { console.log(this);}.bind(Kirito);func();console.log("——————————————————-" + “\n\n”);console.log(“注意:\n\t这里的func是在原来的对象基础上,使用bind绑定了this指针,产生了新的函数对象!”);func = function () { console.log(this);};//注意:func此时绑定对象后,func变量的对象引用指针 指向绑定前func.bind(Kirito);func();//注意:func此时绑定对象后,func变量的对象引用指针 指向绑定后func = func.bind(Kirito);func();console.log("——————————————————-" + “\n\n”);五、this指针显式、隐式传递与绑定的优先级let priority = function () { console.log(this);};console.log(“绑定优先于隐式”);Ausua.func_bind();console.log("——————————————————-" + “\n\n”);console.log(“绑定优先于显式”);priority = priority.bind(“绑定”);priority.call(“显式”);priority();console.log("——————————————————-" + “\n\n”);console.log(“显式优先于隐式”);Ausua.func.call(“显式”);console.log("——————————————————-" + “\n\n”);console.log(“结论:优先级:bind>显示>隐式”); ...

January 17, 2019 · 1 min · jiezi

JS 总结之关于 this 应该知道的几个点

this 对每个 Jser 都不陌生,经常看到对象这里 this 那里 this,那什么是 this?答案就是上下文对象,即被调用函数所处的环境,也就是说,this 在函数内部指向了调用它(它指函数函数)的对象,通俗的讲,就是谁调用了函数。???? 情况 1this 指向 windowvar name = ‘xiaoming’ // 思考,为什么不能用 let 或者 const ?function foo () { console.log(this.name)}foo() // xiaoming谁调用了这个函数,答案就是 window。这好理解,因为这里的变量和函数都是直接挂在 window 上的,等同于 window.foo()。需注意,严格模式下,this 为 undefined???? 情况 2this 指向一个对象var name = ‘xiaoming’var foo = { name: ‘Jon’, getName () { console.log(this.name) }}foo.getName() // Jon谁调用了这个函数,答案是 foo 对象,所以打印了 Jon 而不是 xiaomingvar bar = foo.getNamebar() // xiaoming如果赋值到另一个变量,就变成 window 调用,所以打印了 xiaoming???? 情况 3this 指向了一个用 new 新生成的对象function Person (name) { this.name = name this.getName = function () { console.log(this.name) }}var jser = new Person(‘Jon’)jser.getName() // Jon这种方式成为 new 绑定,也叫做构造调用。JavaScript 中,new 的机制实际上和面向类的语言完全不同,构造函数只是一些使用 new 操作符时被调用的函数,它们并不会属于某个类,也不会实例化一个类。实际上,除 ES6 的 Symbol()外,所有函数都可以用 new 来调用,所以并不存在所谓的构造函数,只有对于函数进行构造调用。使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:创建(或者说构造)一个全新的对象这个新对象会被执行原型链接这个新对象会绑定到函数调用的 this如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象这个例子中,使用 new 来调用 Person(..) 时,我们会构造一个新对象(jser)并把它绑定到 Person(..) 调用中的 this 上。或者可以这么想,谁调用了 getName ?就是 jser 调用了,所以 this 指向了 jser???? 情况 4用 call,apply 和 bind 来修改 this 指向call / applycall 和 apply 是以不同对象作为上下文对象来调用某个函数,举个例子:var bar = { name: ‘bar’, getName () { console.log(this.name) }}var foo = { name: ‘foo’}bar.getName.call(foo) // foo看起来像是借用函数,对象 foo 借用了 bar 的函数 getName,所以我们判断一个对象类型,经常这么搞:let foo = [1,2,3,4,5]Object.prototype.toString.call(foo) // “[object Array]“apply 和 call 的用法一样,不同点在于 call 用参数表给调用函数传参,而 apply 使用了数组bindbind 可以永久性的修改函数中的 this 的指向,无论谁调用,this 指向都一样,并返回了完成绑定的函数,看例子:var bar = { name: ‘bar’, getName () { console.log(this.name) }}var foo = { name: ‘foo’}foo.func = bar.getName.bind(bar)foo.func() // bar这里的 func 不受 foo 影响,this 还是指向了 barvar bar = { name: ‘bar’, getName () { console.log(this.name) }}func = bar.getName.bind(bar)func() // bar这里的 func 也不受 window 影响,this 还是指向了 bar综合上述,bind 强制修改了 this,谁调用了函数 this 都不能被修改???? 忽略 this如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值再调用时会被忽略,实际应用的是默认绑定。???? 箭头函数ES6 中介绍了一种无法使用这些规则的特殊函数类型:箭头函数,根据外层(函数或者全局)作用域来决定 this,箭头函数常用于回调函数???? 情况 1 中,为什么不能用 let 声明?ES6 中,let 命令、const 命令、class 命令声明的全局变量,不属于顶层对象的属性,window 无法访问到。var 命令和 function 命令声明的全局变量,属于顶层对象的属性,window 能访问到。所以 情况 1 中改为:let name = ‘xiaoming’function foo () { console.log(this.name)}foo() // undefined❄️ 总结自:《你不知道的 JavaScript 上卷》第二部分 第 2 章《Node.js 开发指南》附录 A《ECMAScript 6 入门》let 和 const 命令 - 顶层对象的属性【进阶 3-5 期】深度解析 new 原理及模拟实现前端总结小本本 更新时间为:周二,周四,周六,内容不定。如想查看最新未完成总结,可请查看:https://github.com/KaronAmI/blog如有启发,欢迎 star,如有错误,请务必指出,十分感谢。???? ...

December 20, 2018 · 2 min · jiezi

理解 JavaScript this

这是本系列的第 5 篇文章。还记得上一篇文章中的闭包吗?点击查看文章 理解 JavaScript 闭包 。在聊 this 之前,先来复习一下闭包:var name = ‘Neil’;var person = { name: ‘Leo’, sayHi: function() { return function () { return ‘Hi! My name is ’ + this.name; } }};person.sayHi()(); // “Hi! My name is Neil"上一篇文章说,我们可以把闭包简单地理解为函数返回函数。所以这里的闭包结构是:// …function () { return ‘Hi! My name is ’ + this.name;}// …但是你有没有发现,这个函数执行的结果是 “Hi! My name is Neil” 。等等,我不是叫 Leo 吗?怎么给我改了个名字?!我一分析,原来是 this 在其中作祟,且听我慢慢道来这“改名的由来”。## § this 从何而来 首先,你得确保你已经清楚执行栈与执行上下文的知识。点击查看文章 理解 JavaScript 执行栈 。ECMAScript 5.1 中定义 this 的值为执行上下文中的 ThisBinding。而 ThisBinding 简单来说就是由 JS 引擎创建并维护,在执行时被设置为某个对象的引用。在 JS 中有三种情况可以创建上下文:初始化全局环境、eval() 和执行函数。§ 全局中的 thisvar num = 1;function getName () { return “Leo”;}this.num; // 1this.getName(); // Leothis == window; // true当我们在浏览器中运行这段代码,JS 引擎会将 this 设置为 window 对象。而声明的变量和函数被作为属性挂载到 window 对象上。当然,在严格模式下,全局中 this 的值设置为 undefined。“use strcit”;var num = 1;function getName () { return “Leo”;}this.num; // TypeErrorthis.getName(); // TypeErrorthis == undefined; // true开启严格模式后,全局 this 将指向 undefined,所以调用 this.num 会报错。§ eval() 中的 thiseval() 不被推荐使用,我现在对其也不太熟悉,这里尝试着说一下。初学者可以直接跳到下一节。结合所查阅的资料,目前我对 eval() 的理解如下:eval(…) 直接调用,被理解为是一个 lvalue,也有说是 left unchanged,字面理解为余下不变。什么是“余下不变”?我理解为直接调用 eval(…),其中代码的执行环境不变,依旧为当前环境,this 也依旧指向当前环境中的调用对象。而使用类似 (1, eval)(…) 的代码,被称为间接调用。(1, eval) 是一个表达式,你可以这样认为 (true && eval) 或者 (0 : 0 ? eval)。间接调用的 eval 始终认为其中的代码执行在全局环境,将 this 绑定到全局对象。var x = ‘outer’;(function() { var x = ‘inner’; // “direct call: inner” eval(‘console.log(“direct call: " + x)’); // “indirect call: outer” (1, eval)(‘console.log(“indirect call: " + x)’);})();关于 eval(),现在不敢确定,如有错误,欢迎指正。§ 函数中的 this◆ 一般情况首先,我们需要明确的是,在 JS 中函数也属于对象,它可以拥有属性,this 就是函数在执行时获得的属性。一般情况下,在全局环境中直接调用函数,函数中的 this 会在调用时被 JS 引擎设置为全局对象 window(同样在严格模式下为 undefined)。var name = “Leo”;function getName() { var name = “Neil”; console.log(this); // [object Window] return this.name;}getName(); // Leo◆ 作为对象的方法函数可以作为对象的方法被该对象调用,那么这种情况 this 会被设置为该对象。var name = ‘Neil’;var person = { name: ‘Leo’, sayHi: function() { console.log(this); // person return ‘Hi! My name is ’ + this.name; }};person.sayHi(); // “Hi! My name is Leo"当 person 对象调用 sayHi() 方法时,this 被指向 person。◆ 特殊的内置函数JS 还提供了一种供开发者自定义 this 的方式,它提供了 3 种方式。Function.prototype.call(thisArg, argArray)Function.prototype.apply(thisArg [, arg1 [, args2, …]])Function.prototype.bind(thisArg [, arg1 [, args2, …]])我们可以通过设置 thisArg 的值,来自定义函数中 this 的指向。var leo = { name: ‘Leo’, sayHi: function () { return “Hi! My name is " + this.name; }}var neil = { name: ‘Neil’};leo.sayHi(); // “Hi! My name is Leo"leo.sayHi.call(neil); // “Hi! My name is Neil"这里,我们通过 call() 将 sayHi() 中 this 的指向绑定为 neil 对象,从而取代了默认 的 this 指向 leo 对象。关于函数的 call(), apply(), bind() 我将在后面另写一篇文章,敬请期待。§ this 引起的令人费解的现象◆ 闭包通过前面的介绍,我想你对 this 已经有了初步的印象。那么,回到文章开头的问题,this 是怎么改变了我的名字?换句话说,this 在闭包的影响下指向发生了怎样的变动?再看一下代码:var name = ‘Neil’;var person = { name: ‘Leo’, sayHi: function() { return function () { return ‘Hi! My name is ’ + this.name; } }};person.sayHi()(); // “Hi! My name is Neil"通过上一篇文章 理解 JavaScript 闭包,函数返回函数会形成闭包。在这种情况下,闭包往往所执行的环境与所定义的环境不一致,而 this 的值却是在执行时决定的。所以,当上面代码中的闭包在执行时,它所在的执行上下文是全局环境,this 将被设置为 window(严格模式下为 undefined)。怎么解决?我们可以利用 call / apply / bind 来修改 this 的指向。var name = ‘Neil’;var person = { name: ‘Leo’, sayHi: function() { return function () { return ‘Hi! My name is ’ + this.name; } }};person.sayHi().call(person); // “Hi! My name is Leo"这里利用 call() 将 this 指向 person。OK,我的名字回来了,“Hi! My name is Leo” ^^当然,我们还有第二种解决方法,闭包的问题就让闭包自己解决。var name = ‘Neil’;var person = { name: ‘Leo’, sayHi: function() { var that = this; // 定义一个局部变量 that return function () { return ‘Hi! My name is ’ + that.name; // 在闭包中使用 that } }};person.sayHi()(); // “Hi! My name is Leo"在 sayHi() 方法中定义一个局部变量,闭包可以将这个局部变量保存在内存中,从而解决问题。◆ 回调函数在回调函数中 this 的指向也会发生变化。var name = ‘Neil’;var person = { name: ‘Leo’, sayHi: function() { return ‘Hi! My name is ’ + this.name; }};var btn = document.querySelector(’#btn’);btn.addEventListener(‘click’, person.sayHi);// “Hi! My name is undefined"这里 this 既不指向 person,也不指向 window。那它指向什么?btn 对象,它是一个 DOM 对象,有一个 onclick 方法,在这里定义为 person.sayHi。{ // … onclick: person.sayHi // …}所以,当我们执行上面的代码,this.name 的值为 undefined,因为 btn 对象上没有定义 name 属性。我们给 btn 对象自定义一个 name 属性来验证一下。var btn = document.querySelector(’#btn’);btn.name = ‘Jackson’;btn.addEventListener(‘click’, person.sayHi);// “Hi! My name is Jackson"原因说清楚了,解决方案同样可用过 call / apply / bind 来改变 this 的指向,使其绑定到 person 对象。btn.addEventListener(‘click’, person.sayHi.bind(person));// “Hi! My name is Leo”◆ 赋值var name = ‘Neil’;var person = { name: ‘Leo’, sayHi: function() { return ‘Hi! My name is ’ + this.name; }};person.sayHi(); // “Hi! My name is Leo"var foo = person.sayHi;foo(); // “Hi! My name is Neil"当把 person.sayHi() 赋值给一个变量,这个时候 this 的指向又发生了变化。因为 foo 执行时是在全局环境中,所以 this 指向 window(严格模式下指向 undefined)。同样,我们可以通过 call / apply / bind 来解决,这里就不贴代码了。§ 别忘了 new在 JS 中,我们声明一个类,然后 new 一个实例。function Person(name) { this.name = name;}var her = Person(‘Angelia’);console.log(her.name); // TypeErrorvar me = new Person(‘Leo’);console.log(me.name); // “Leo"如果我们直接把调用这个函数,this 将指向全局对象,Person 在这里就是一个普通函数,没有返回值,默认 undefined,而尝试访问 undefined 的属性就会报错。如果我们使用 new 操作符,那么 new 其实会生成一个新的对象,并将 this 指向这个新的对象,然后将其返回,所以 me.name 能打印出 “Leo”。关于 new 的原理,我会在后面的文章分享,敬请期待。§ 小结你看,this 是不是千变万化。但是我们得以不变应万变。在这么多场景下,this 的指向万变不离其宗:它一定是在执行时决定的,指向调用函数的对象。在闭包、回调函数、赋值等场景下我们都可以利用 call / apply / bind 来改变 this 的指向,以达到我们的预期。接下来,请期待文章《理解 JavaScript call/apply/bind》。◆ 文章参考Understand JavaScript’s “this” With Clarity, and Matser It | Richard BovellHow does the “this” keyword work | Stack OverflowECMAScript Language Specification - $11.1.1 The This Keyword(1, eval)(’this’) vs eval(’this’) in JavaScript | Stack Overflow§ JavaScript 系列文章理解 JavaScript 闭包理解 JavaScript 执行栈理解 JavaScript 作用域理解 JavaScript 数据类型与变量Be Good. Sleep Well. And Enjoy.原文发布在我的公众号 camerae。前端技术 | 个人成长 ...

December 19, 2018 · 4 min · jiezi