前言
系列首发于公众号『前端进阶圈』 ,若不想错过更多精彩内容,请“星标”一下,敬请关注公众号最新消息。
this 之谜揭底:从浅入深理解 JavaScript 中的 this 关键字(二)
调用位置
- 在理解 this 的绑定过程之前,首先要理解
调用位置:调用位置就是函数在代码中被调用的位置(而不是声明的位置)。 - 通常来说,寻找调用位置就是寻找"函数被调用的位置", 最重要的要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。运行代码时,调用器会在那个位置暂停,同时会在展示当前位置的函数调用列表,这就是调用栈。
绑定规则
函数的调用位置决定了 this 的绑定对象,通常情况下分为以下几种规则:
默认绑定
最常用的函数调用类型:
独立函数调用。可把这条规则看到是无法应用其他规则时的默认规则。function foo(){ console.log(this.a); } var a = 2; foo(); // 2当调用 foo() 时,this.a 被解析成了全局变量 a。为什么?
- 因为在上述代码中,函数调用时应用了this 的默认绑定,因此 this 指向全局对象。(要理解 this,就要先理解调用位置)
如果使用严格模式(strict mode),那全局对象将无法使用默认绑定,因此 this 会绑定到 undefined。function foo(){ "use strict"; console.log(this.a); } var a = 2; foo(); // Type: this is undefined虽然 this 的绑定规则完全取决于调用位置,但是
只有 foo() 运行在非 strict mode下时,默认绑定才能绑定到全局对象;严格模式下与 foo() 的调用位置无关。var a = 2; (function (){ "use strict"; foo(); // 2 })" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>){ <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span>); } <span class="hljs-keyword">var</span> a = <span class="hljs-number">2</span>; (<span class="hljs-keyword">function</span> (<span class="hljs-params"></span>){ <span class="hljs-meta">"use strict"</span>; <span class="hljs-title function_">foo</span>(); <span class="hljs-comment">// 2</span> })</pre></li><li>通常情况下,尽量减少在代码中混合使用 <code>strict mode</code> 与 <code>non-strict mode</code>,尽量减少在代码中混合使用 strict mode 和 non-strict mode。</li></ul><h4>隐式绑定</h4><ul><li><code>另一条规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或包裹。</code></li><li><p>考虑以下代码:</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo() { console.log(this.a); // 2 } var obj = { a: 2, foo: foo } obj.foo();" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span>); <span class="hljs-comment">// 2</span> } <span class="hljs-keyword">var</span> obj = { <span class="hljs-attr">a</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">foo</span>: foo } obj.<span class="hljs-title function_">foo</span>();</pre></li><li>上述代码中,调用位置使用 obj 的上下文来引用函数,可以说函数被调用时 obj 对象拥有或包含它。</li><li><code>当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象上</code>,因此在调用 foo() 时 this 被绑定到了 obj 上,所以 this.a 与 obj.a 是一样的。</li><li>注意:<code>对象属性引用链中只有最顶层或最后一层会影响调用位置</code>。</li><li><p>如下代码:</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.foo(); // 42" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> ); } <span class="hljs-keyword">var</span> obj2 = { <span class="hljs-attr">a</span>: <span class="hljs-number">42</span>, <span class="hljs-attr">foo</span>: foo }; <span class="hljs-keyword">var</span> obj1 = { <span class="hljs-attr">a</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">obj2</span>: obj2 }; obj1.<span class="hljs-property">obj2</span>.<span class="hljs-title function_">foo</span>(); <span class="hljs-comment">// 42</span></pre></li><li>隐式丢失:在被隐式绑定的函数会丢失绑定对象,也就是说它会默认绑定,从而把 this 绑定到全局对象或 undefined 上,这取决于是否是严格模式。</li><li><p>如下代码:</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var bar = obj.foo; // 函数别名! var a = "oops, global"; // a 是全局对象的属性 bar(); // "oops, global"" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> ); } <span class="hljs-keyword">var</span> obj = { <span class="hljs-attr">a</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">foo</span>: foo }; <span class="hljs-keyword">var</span> bar = obj.<span class="hljs-property">foo</span>; <span class="hljs-comment">// 函数别名!</span> <span class="hljs-keyword">var</span> a = <span class="hljs-string">"oops, global"</span>; <span class="hljs-comment">// a 是全局对象的属性</span> <span class="hljs-title function_">bar</span>(); <span class="hljs-comment">// "oops, global"</span></pre></li><li><p>还有一种奇怪的方式,就是在传入回调函数时隐式丢失</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo() { console.log( this.a ); } function doFoo(fn) { // fn其实引用的是 foo fn(); // <-- 调用位置! } var obj = { a: 2, foo: foo }; var a = "oops, global"; // a 是全局对象的属性 doFoo( obj.foo ); // "oops, global"" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> ); } <span class="hljs-keyword">function</span> <span class="hljs-title function_">doFoo</span>(<span class="hljs-params">fn</span>) { <span class="hljs-comment">// fn其实引用的是 foo</span> <span class="hljs-title function_">fn</span>(); <span class="hljs-comment">// <-- 调用位置!</span> } <span class="hljs-keyword">var</span> obj = { <span class="hljs-attr">a</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">foo</span>: foo }; <span class="hljs-keyword">var</span> a = <span class="hljs-string">"oops, global"</span>; <span class="hljs-comment">// a 是全局对象的属性</span> <span class="hljs-title function_">doFoo</span>( obj.<span class="hljs-property">foo</span> ); <span class="hljs-comment">// "oops, global"</span></pre></li><li>在我们传入函数时也会被隐式赋值。</li><li><p>那如果传入的函数不是自定义的函数,而是语言内置的函数呢?结果还是一样的,没有区别</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var a = "oops, global"; // a 是全局对象的属性 setTimeout( obj.foo, 100 ); // "oops, global"" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> ); } <span class="hljs-keyword">var</span> obj = { <span class="hljs-attr">a</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">foo</span>: foo }; <span class="hljs-keyword">var</span> a = <span class="hljs-string">"oops, global"</span>; <span class="hljs-comment">// a 是全局对象的属性</span> <span class="hljs-built_in">setTimeout</span>( obj.<span class="hljs-property">foo</span>, <span class="hljs-number">100</span> ); <span class="hljs-comment">// "oops, global"</span></pre></li></ul><h4>显示绑定</h4><ul><li><p>那我们不想在对象内部包含函数引用,而是想在某个对象上强制调用函数,该如何操作?</p><ul><li><p>那就必须要使用 <code>call() 和 apply()。第一个参数是一个对象,也就是需要绑定的对象,第二个参数传入的参数,而两者之间的区别就在于第二个参数,call 的第二个参数是一个个参数,而 apply 则是一个参数数组。</code></p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="// call() function foo() { console.log( this.a ); } var obj = { a:2 }; foo.call( obj ); // 2 // apply() function foo(something) { console.log( this.a, something ); return this.a + something; } var obj = { a:2 }; var bar = function() { return foo.apply( obj, arguments ); }; var b = bar( 3 ); // 2 3 console.log( b ); // 5" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-comment">// call()</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> ); } <span class="hljs-keyword">var</span> obj = { <span class="hljs-attr">a</span>:<span class="hljs-number">2</span> }; foo.<span class="hljs-title function_">call</span>( obj ); <span class="hljs-comment">// 2</span> <span class="hljs-comment">// apply()</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params">something</span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span>, something ); <span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> + something; } <span class="hljs-keyword">var</span> obj = { <span class="hljs-attr">a</span>:<span class="hljs-number">2</span> }; <span class="hljs-keyword">var</span> bar = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) { <span class="hljs-keyword">return</span> foo.<span class="hljs-title function_">apply</span>( obj, <span class="hljs-variable language_">arguments</span> ); }; <span class="hljs-keyword">var</span> b = <span class="hljs-title function_">bar</span>( <span class="hljs-number">3</span> ); <span class="hljs-comment">// 2 3</span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( b ); <span class="hljs-comment">// 5</span></pre><h4>new绑定</h4></li></ul></li><li>在传统的语言中,构造函数时一个特殊方法,使用 new 初始化需要调用的类,通常形式下是 <code>let something = new MyClass();</code>。</li><li><p>在使用 new 来调用函数,会自动执行以下操作:</p><ol><li>创建一个新对象</li><li>让新对象的 <code>__proto__</code>(隐式原型) 等于函数的 prototype(显式原型)</li><li>绑定 this, 让新象绑定于函数的 this 指向</li><li>判断返回值,如果返回值不是一个对象,则返回刚新建的新对象。</li></ol></li></ul><h3 id="item-0-5">优先级</h3><ul><li>如果在某个调用位置应用多条规则该如何?那为了解决此问题,那就引申出了优先级问题。</li><li>毫无疑问,默认绑定的优先级是四条规则中最低的,可以先不考虑它。</li><li><p>先来看看隐式绑定和显式绑定那个优先级更高?</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo() { console.log( this.a ); } var obj1 = { a: 2, foo: foo }; var obj2 = { a: 3, foo: foo }; // 隐式绑定 obj1.foo(); // 2 obj2.foo(); // 3 // 显式绑定 obj1.foo.call( obj2 ); // 3 obj2.foo.call( obj1 ); // 2" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> ); } <span class="hljs-keyword">var</span> obj1 = { <span class="hljs-attr">a</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">foo</span>: foo }; <span class="hljs-keyword">var</span> obj2 = { <span class="hljs-attr">a</span>: <span class="hljs-number">3</span>, <span class="hljs-attr">foo</span>: foo }; <span class="hljs-comment">// 隐式绑定</span> obj1.<span class="hljs-title function_">foo</span>(); <span class="hljs-comment">// 2</span> obj2.<span class="hljs-title function_">foo</span>(); <span class="hljs-comment">// 3</span> <span class="hljs-comment">// 显式绑定</span> obj1.<span class="hljs-property">foo</span>.<span class="hljs-title function_">call</span>( obj2 ); <span class="hljs-comment">// 3</span> obj2.<span class="hljs-property">foo</span>.<span class="hljs-title function_">call</span>( obj1 ); <span class="hljs-comment">// 2</span></pre></li><li>可以看出,<code>显式绑定的优先级更高</code>,也就是说在判断时应当考虑是否可以应用显式绑定。</li><li><p>再来看看new绑定和隐式绑定的优先级?</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo(something) { this.a = something; } var obj1 = { foo: foo }; var obj2 = {}; // 隐式绑定 obj1.foo( 2 ); console.log( obj1.a ); // 2 obj1.foo.call( obj2, 3 ); console.log( obj2.a ); // 3 // new绑定 var bar = new obj1.foo( 4 ); console.log( obj1.a ); // 2 console.log( bar.a ); // 4" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params">something</span>) { <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> = something; } <span class="hljs-keyword">var</span> obj1 = { <span class="hljs-attr">foo</span>: foo }; <span class="hljs-keyword">var</span> obj2 = {}; <span class="hljs-comment">// 隐式绑定</span> obj1.<span class="hljs-title function_">foo</span>( <span class="hljs-number">2</span> ); <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( obj1.<span class="hljs-property">a</span> ); <span class="hljs-comment">// 2</span> obj1.<span class="hljs-property">foo</span>.<span class="hljs-title function_">call</span>( obj2, <span class="hljs-number">3</span> ); <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( obj2.<span class="hljs-property">a</span> ); <span class="hljs-comment">// 3</span> <span class="hljs-comment">// new绑定</span> <span class="hljs-keyword">var</span> bar = <span class="hljs-keyword">new</span> obj1.<span class="hljs-title function_">foo</span>( <span class="hljs-number">4</span> ); <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( obj1.<span class="hljs-property">a</span> ); <span class="hljs-comment">// 2</span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( bar.<span class="hljs-property">a</span> ); <span class="hljs-comment">// 4</span></pre></li><li>可以看出,<code>new 绑定比隐式绑定的优先级更高</code>,但 new 绑定和显式绑定谁的优先级更高呢?</li><li>new 与 call/apply 无法一起使用,因此无法通过 new foo.call(obj1) 来进行测试,但可以通过硬绑定来测试他两的优先级。</li><li>硬绑定:Function.prototype.bind(...) 会创建一个新的包装函数,这个函数会忽略当前的this绑定(无论绑定的对象是什么),并把我们提供的对象绑定到this上。</li><li><p>这样看起来硬绑定(也是显式绑定的一种)似乎比 new 绑定的优先级更高,无法使用 new 来控制 this 绑定。</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo(something) { this.a = something; } var obj1 = {}; var bar = foo.bind( obj1 ); bar( 2 ); console.log( obj1.a ); // 2 var baz = new bar(3); console.log( obj1.a ); // 2 console.log( baz.a ); // 3" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params">something</span>) { <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> = something; } <span class="hljs-keyword">var</span> obj1 = {}; <span class="hljs-keyword">var</span> bar = foo.<span class="hljs-title function_">bind</span>( obj1 ); <span class="hljs-title function_">bar</span>( <span class="hljs-number">2</span> ); <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( obj1.<span class="hljs-property">a</span> ); <span class="hljs-comment">// 2</span> <span class="hljs-keyword">var</span> baz = <span class="hljs-keyword">new</span> <span class="hljs-title function_">bar</span>(<span class="hljs-number">3</span>); <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( obj1.<span class="hljs-property">a</span> ); <span class="hljs-comment">// 2</span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( baz.<span class="hljs-property">a</span> ); <span class="hljs-comment">// 3</span></pre></li><li>出乎意料! bar 被硬绑定到 obj1 上,但是 new bar(3) 并没有像我们预计的那样把 obj1.a 修改为 3。相反, new 修改了硬绑定(到 obj1 的)调用 bar(..) 中的 this。因为使用了 new 绑定,我们得到了一个名字为 baz 的新对象,并且 baz.a 的值是 3。</li><li><p>硬绑定中的bind(...) 的功能之一就是可以把除了第一个参数(第一个参数用于绑定this)之外的其他参数传递给下层的函数(这种技术称为"部分应用",是"柯里化"的一种)。</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="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" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params">p1,p2</span>) { <span class="hljs-variable language_">this</span>.<span class="hljs-property">val</span> = p1 + p2; } <span class="hljs-comment">// 之所以使用 null 是因为在本例中我们并不关心硬绑定的 this 是什么</span> <span class="hljs-comment">// 反正使用 new 时 this 会被修改</span> <span class="hljs-keyword">var</span> bar = foo.<span class="hljs-title function_">bind</span>( <span class="hljs-literal">null</span>, <span class="hljs-string">"p1"</span> ); <span class="hljs-keyword">var</span> baz = <span class="hljs-keyword">new</span> <span class="hljs-title function_">bar</span>( <span class="hljs-string">"p2"</span> ); baz.<span class="hljs-property">val</span>; <span class="hljs-comment">// p1p2</span></pre></li><li><p><strong>判断this</strong></p><ol><li>是否在 new 中调用(new 绑定), this 指向新创建的对象</li><li>是否通过 call、apply(显示绑定),this 指向绑定的对象</li><li>是否在某个对象中调用(隐式绑定),this 指向绑定的上下文对象</li><li>如果都不是,则是默认绑定,在严格模式下,this 指向 undefined, 非严格模式下,this 指向全局对象。</li></ol></li><li><p><strong>优先级问题</strong></p><ul><li>显式绑定:call()、apply()。(硬绑定也是显式绑定的其中一种: bind())</li><li>new 绑定: new Foo()</li><li>隐式绑定: obj.foo();</li><li>默认绑定: foo();</li></ul></li><li><code>排序:显式绑定 > new 绑定 > 隐式绑 定 > 默认绑定</code></li></ul><h3 id="item-0-6">绑定例子</h3><h4>被忽略的this</h4><ul><li><p><code>如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则:</code></p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo() { console.log( this.a ); } var a = 2; foo.call( null ); // 2" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> ); } <span class="hljs-keyword">var</span> a = <span class="hljs-number">2</span>; foo.<span class="hljs-title function_">call</span>( <span class="hljs-literal">null</span> ); <span class="hljs-comment">// 2</span></pre></li><li><p>那在什么情况下会传入 null 呢?</p><ul><li><p><code>一种非常常见的做法是使用 apply(..) 来“展开”一个数组,并当作参数传入一个函数。</code></p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo(a,b) { console.log( "a:" + a + ", b:" + b ); } // 把数组“展开”成参数 foo.apply( null, [2, 3] ); // a:2, b:3 // 使用 bind(..) 进行柯里化 var bar = foo.bind( null, 2 ); bar( 3 ); // a:2, b:3" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params">a,b</span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( <span class="hljs-string">"a:"</span> + a + <span class="hljs-string">", b:"</span> + b ); } <span class="hljs-comment">// 把数组“展开”成参数</span> foo.<span class="hljs-title function_">apply</span>( <span class="hljs-literal">null</span>, [<span class="hljs-number">2</span>, <span class="hljs-number">3</span>] ); <span class="hljs-comment">// a:2, b:3</span> <span class="hljs-comment">// 使用 bind(..) 进行柯里化</span> <span class="hljs-keyword">var</span> bar = foo.<span class="hljs-title function_">bind</span>( <span class="hljs-literal">null</span>, <span class="hljs-number">2</span> ); <span class="hljs-title function_">bar</span>( <span class="hljs-number">3</span> ); <span class="hljs-comment">// a:2, b:3</span></pre></li></ul></li><li>但总是用 null 来忽略 this 绑定可能会产生一些副作用。</li><li><p><strong>更安全的this</strong></p><ul><li>DMZ(demilitarized zone)空委托对象</li></ul></li><li><p><code>在 JavaScript 中创建一个空对象最简单的方法都是 Object.create(null)。Object.create(null) 和 {} 很 像, 但 是 并 不 会 创 建 Object.prototype 这个委托,所以它比 {}“更空”:</code></p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo(a,b) { console.log( "a:" + a + ", b:" + b ); } // 我们的 DMZ 空对象 var ø = Object.create( null ); // 把数组展开成参数 foo.apply( ø, [2, 3] ); // a:2, b:3 // 使用 bind(..) 进行柯里化 var bar = foo.bind( ø, 2 ); bar( 3 ); // a:2, b:3" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params">a,b</span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( <span class="hljs-string">"a:"</span> + a + <span class="hljs-string">", b:"</span> + b ); } <span class="hljs-comment">// 我们的 DMZ 空对象</span> <span class="hljs-keyword">var</span> ø = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">create</span>( <span class="hljs-literal">null</span> ); <span class="hljs-comment">// 把数组展开成参数</span> foo.<span class="hljs-title function_">apply</span>( ø, [<span class="hljs-number">2</span>, <span class="hljs-number">3</span>] ); <span class="hljs-comment">// a:2, b:3</span> <span class="hljs-comment">// 使用 bind(..) 进行柯里化</span> <span class="hljs-keyword">var</span> bar = foo.<span class="hljs-title function_">bind</span>( ø, <span class="hljs-number">2</span> ); <span class="hljs-title function_">bar</span>( <span class="hljs-number">3</span> ); <span class="hljs-comment">// a:2, b:3</span></pre><h4>间接引用</h4><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="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" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> ); } <span class="hljs-keyword">var</span> a = <span class="hljs-number">2</span>; <span class="hljs-keyword">var</span> o = { <span class="hljs-attr">a</span>: <span class="hljs-number">3</span>, <span class="hljs-attr">foo</span>: foo }; <span class="hljs-keyword">var</span> p = { <span class="hljs-attr">a</span>: <span class="hljs-number">4</span> }; o.<span class="hljs-title function_">foo</span>(); <span class="hljs-comment">// 3</span> (p.<span class="hljs-property">foo</span> = o.<span class="hljs-property">foo</span>)(); <span class="hljs-comment">// 2</span></pre></li><li>赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 foo() 而不是 p.foo() 或者 o.foo()。根据我们之前说过的,这里会应用默认绑定。</li><li><code>注意:对于默认绑定来说,决定 this 绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this 会被绑定到 undefined,否则this 会被绑定到全局对象。</code></li></ul><h4>软绑定</h4><ul><li>硬绑定这种方式可以把 this 强制绑定到指定的对象(除了使用 new 时),防止函数调用应用默认绑定规则。使用硬绑定会大大降低函数的灵活性,使用硬绑定之后就无法使用隐式绑定或显示绑定来修改 this。</li><li><p>可通过一种软绑定的方法来实现:</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="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; }; }" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">if</span> (!<span class="hljs-title class_">Function</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>.<span class="hljs-property">softBind</span>) { <span class="hljs-title class_">Function</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>.<span class="hljs-property">softBind</span> = <span class="hljs-keyword">function</span>(<span class="hljs-params">obj</span>) { <span class="hljs-keyword">var</span> fn = <span class="hljs-variable language_">this</span>; <span class="hljs-comment">// 捕获所有 curried 参数</span> <span class="hljs-keyword">var</span> curried = [].<span class="hljs-property">slice</span>.<span class="hljs-title function_">call</span>( <span class="hljs-variable language_">arguments</span>, <span class="hljs-number">1</span> ); <span class="hljs-keyword">var</span> bound = <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) { <span class="hljs-keyword">return</span> fn.<span class="hljs-title function_">apply</span>( (!<span class="hljs-variable language_">this</span> || <span class="hljs-variable language_">this</span> === (<span class="hljs-variable language_">window</span> || <span class="hljs-variable language_">global</span>)) ? obj : <span class="hljs-variable language_">this</span> curried.<span class="hljs-property">concat</span>.<span class="hljs-title function_">apply</span>( curried, <span class="hljs-variable language_">arguments</span> ) ); }; bound.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span> = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">create</span>( fn.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span> ); <span class="hljs-keyword">return</span> bound; }; }</pre></li><li><p>实现软绑定功能:</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo() { console.log("name: " + this.name); } var obj = { name: "obj" }, obj2 = { name: "obj2" }, obj3 = { name: "obj3" }; var fooOBJ = foo.softBind( obj ); fooOBJ(); // name: obj obj2.foo = foo.softBind(obj); obj2.foo(); // name: obj2 <---- 看!!! fooOBJ.call( obj3 ); // name: obj3 <---- 看! setTimeout( obj2.foo, 10 ); // name: obj <---- 应用了软绑定" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>) { <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">"name: "</span> + <span class="hljs-variable language_">this</span>.<span class="hljs-property">name</span>); } <span class="hljs-keyword">var</span> obj = { <span class="hljs-attr">name</span>: <span class="hljs-string">"obj"</span> }, obj2 = { <span class="hljs-attr">name</span>: <span class="hljs-string">"obj2"</span> }, obj3 = { <span class="hljs-attr">name</span>: <span class="hljs-string">"obj3"</span> }; <span class="hljs-keyword">var</span> fooOBJ = foo.<span class="hljs-title function_">softBind</span>( obj ); <span class="hljs-title function_">fooOBJ</span>(); <span class="hljs-comment">// name: obj</span> obj2.<span class="hljs-property">foo</span> = foo.<span class="hljs-title function_">softBind</span>(obj); obj2.<span class="hljs-title function_">foo</span>(); <span class="hljs-comment">// name: obj2 <---- 看!!!</span> fooOBJ.<span class="hljs-title function_">call</span>( obj3 ); <span class="hljs-comment">// name: obj3 <---- 看!</span> <span class="hljs-built_in">setTimeout</span>( obj2.<span class="hljs-property">foo</span>, <span class="hljs-number">10</span> ); <span class="hljs-comment">// name: obj <---- 应用了软绑定</span></pre></li><li><p>可以看到,软绑定的 foo() 可手动将 this 绑定到 obj2 或 obj3 上,但如果应用默认绑定,则会将 this 绑定到 obj。</p><h3 id="item-0-7">this 词法</h3></li><li>在 ES6 中出现了一种无法使用这些规则的特殊函数类型:<code>箭头函数</code></li><li><p><code>箭头函数不适用 this 的四种标准规则,而是根据外层(函数或全局)的作用域来决定 this</code></p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo() { // 返回一个箭头函数 return (a) => { //this 继承自 foo() console.log( this.a ); }; } var obj1 = { a:2 }; var obj2 = { a:3 }; var bar = foo.call( obj1 ); bar.call( obj2 ); // 2, 不是 3 !" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>) { <span class="hljs-comment">// 返回一个箭头函数</span> <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">a</span>) =></span> { <span class="hljs-comment">//this 继承自 foo()</span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( <span class="hljs-variable language_">this</span>.<span class="hljs-property">a</span> ); }; } <span class="hljs-keyword">var</span> obj1 = { <span class="hljs-attr">a</span>:<span class="hljs-number">2</span> }; <span class="hljs-keyword">var</span> obj2 = { <span class="hljs-attr">a</span>:<span class="hljs-number">3</span> }; <span class="hljs-keyword">var</span> bar = foo.<span class="hljs-title function_">call</span>( obj1 ); bar.<span class="hljs-title function_">call</span>( obj2 ); <span class="hljs-comment">// 2, 不是 3 !</span></pre></li><li>foo() 内部创建的箭头函数会捕获调用时 foo() 的 this。由于 foo() 的 this 绑定到 obj1, bar(引用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无法被修改。(new 也不行!)</li><li><p>在 ES6 之前,我们也有使用和箭头函数一样的模式,如下代码:</p><div class="widget-codetool" style="display: none;"> <div class="widget-codetool--inner"> <button type="button" class="btn btn-dark far fa-copy rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="function foo() { var self = this; // this 快照 setTimeout( function(){ console.log( self.a ); }, 100 ); } var obj = { a: 2 }; foo.call( obj ); // 2" title="" data-bs-original-title="复制" aria-label="复制"></button> </div> </div><pre class="js hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">foo</span>(<span class="hljs-params"></span>) { <span class="hljs-keyword">var</span> self = <span class="hljs-variable language_">this</span>; <span class="hljs-comment">// this 快照</span> <span class="hljs-built_in">setTimeout</span>( <span class="hljs-keyword">function</span>(<span class="hljs-params"></span>){ <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>( self.<span class="hljs-property">a</span> ); }, <span class="hljs-number">100</span> ); } <span class="hljs-keyword">var</span> obj = { <span class="hljs-attr">a</span>: <span class="hljs-number">2</span> }; foo.<span class="hljs-title function_">call</span>( obj ); <span class="hljs-comment">// 2</span></pre></li><li>虽然 self = this 和箭头函数看起来都可以取代 bind(..),但是从本质上来说,它们想替代的是 this 机制。</li></ul><h3 id="item-0-8">小结</h3><ol><li><p><strong>判断 this 指向</strong></p><ol><li>是否在 new 中调用(new 绑定), this 指向新创建的对象</li><li>是否通过 call、apply(显示绑定),this 指向绑定的对象</li><li>是否在某个对象中调用(隐式绑定),this 指向绑定对象的上下文</li><li>如果都不是,则是默认绑定,在严格模式下,this 指向 undefined, 非严格模式下,this 指向全局对象。</li></ol></li><li>箭头函数不会使用上述的四条规则,而是根据当前的词法作用域来决定 this 的。箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。与 ES6 之前的 self = this 的机制一样。</li><li>注意:对于默认绑定来说,决定 this 绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this 会被绑定到 undefined,否则this 会被绑定到全局对象。</li></ol><h2 id="item-0-9">特殊字符描述:</h2><ol><li>问题标注 <code>Q:(question)</code></li><li>答案标注 <code>R:(result)</code></li><li>注意事项标准:<code>A:(attention matters)</code></li><li>详情描述标注:<code>D:(detail info)</code></li><li>总结标注:<code>S:(summary)</code></li><li>分析标注:<code>Ana:(analysis)</code></li><li><p>提示标注:<code>T:(tips)</code></p><h2 id="item-0-10">往期推荐:</h2></li><li><a href="https://link.segmentfault.com/?enc=kzTMEwXDjFwAyu%2Bg1u%2Fd1g%3D%3D.%2B4ziDKMSDi32RWHEHinD0Oqry22ju3WgizMeoId2%2FEx4zlx3ofx96vQaSjq8Rf8tGryKGhOPpU5EGvSfrTAoCA%3D%3D" rel="nofollow" target="_blank">前端面试实录HTML篇</a></li><li><a href="https://link.segmentfault.com/?enc=CAJbGjdQtlbN%2BO9KLcFL7A%3D%3D.6Y01C0vMOifxSygAugXqh2x2wAokF9GQLybvxIkS9r7T66RNmmg0nqUdCYwrfsE2WRKR7QTmHCwMa91AlEbA3w%3D%3D" rel="nofollow" target="_blank">前端面试实录CSS篇</a></li><li><a href="https://link.segmentfault.com/?enc=QtLPTC1v3x%2Fe2pzH%2FOWEeQ%3D%3D.UnrtN%2FsQSv7E5nVj7%2BdGmchXSKoRrXVGYSoFCKJXSw%2Bp%2Bd9vi%2FSnZbP%2F90gZ32pCBz2IvSjNPURw2Vv6Rrni%2FA%3D%3D" rel="nofollow" target="_blank">JS 如何判断一个元素是否在可视区域内?</a></li><li><a href="https://link.segmentfault.com/?enc=Jcskzim%2Bke0nlMyawkySvQ%3D%3D.%2Bgg2uCj5Y%2FNqVsCbkF0NtBojKYB7VK7TKaz17fbrxas0OohY2IQIW%2FHSBZ2T9%2B9zGzudTJ3HU9BOOqHqxHd01w%3D%3D" rel="nofollow" target="_blank">Vue2、3 生命周期及作用?</a></li><li><a href="https://link.segmentfault.com/?enc=pxDMCTO7cQTtAfKnEGY2xA%3D%3D.um3QmRQluZwrFZzsPoP9xfkEfiNkqwEr2LljoCrii2VGQdz6mP0bbHdNqjcpWGKmqDbtqPukvfOezBJiurcdRQ%3D%3D" rel="nofollow" target="_blank">排序算法:QuickSort</a></li><li><a href="https://link.segmentfault.com/?enc=tTb4MRmrJe8MOPV0Av6h%2Bw%3D%3D.NWLppY5jOKi6zT9c2Ao4bNAVm7uO5cw8Q4VdqbMmzAdrU%2BxY8n6ZU4uIceTHuAWt3VanF3NNFDrrfLQXZ1pWXg%3D%3D" rel="nofollow" target="_blank">箭头函数与普通函数的区别?</a></li><li><a href="https://link.segmentfault.com/?enc=m%2BV50q%2BNG4UnqZ8gCNXMDQ%3D%3D.UO4mAirlVPjl61ZVDx6d6cOeIxsR%2FcGK9%2FpMHURDKm7ZXpDK2eU7UIUX6dEHzjMWPuctKAhlEjguetbfjomgRw%3D%3D" rel="nofollow" target="_blank">这是你理解的CSS选择器权重吗?</a></li><li><a href="https://link.segmentfault.com/?enc=WnCtzWrU%2FbbBSW6NFG0XPQ%3D%3D.jM%2FPztqI19pz7U5a7mIXrYUTGLhQW4Nt66hYtjt7nskH7zjCEuOzRkJZUEGzgXGkvAr4L%2FIR6VlQ0F%2BGP1WC1Q%3D%3D" rel="nofollow" target="_blank">JS 中 call, apply, bind 概念、用法、区别及实现?</a></li><li><a href="https://link.segmentfault.com/?enc=wssYMfF5gcPEHwu6%2BeLLYQ%3D%3D.Ld%2B5Gogn4ivOEfJ3TpWSndA28pcMRbXLEL%2FWfZn4du%2FcaU8xNui6v0%2Fx%2BaX9yQcCEPyHJOaa1Me2A40NooL2gw%3D%3D" rel="nofollow" target="_blank">常用位运算方法?</a></li><li><a href="https://link.segmentfault.com/?enc=kkhm5Vx19PgHdphZ9c07Pg%3D%3D.tXllfkCeolqxe53BwdthSvlIgmYjWqEioCO%2Bf4YZf13IFlCM9mKOFCn1b%2BoBbKo7Mv5fzSnKFnT6nm6h44hLog%3D%3D" rel="nofollow" target="_blank">Vue数据监听Object.definedProperty()方法的实现</a></li><li><a href="https://link.segmentfault.com/?enc=gVxFHX76MGbbkYlrB1xrxw%3D%3D.6xWAbkCeGumTn0dQk%2BrlSV6P0eYlLq0yT%2BexULhAzYScVOdtED6LMLg5dpkrISJrLXNgVvAqeXnfnSqGC2sogA%3D%3D" rel="nofollow" target="_blank">为什么 0.1+ 0.2 != 0.3,如何让其相等?</a></li><li><a href="https://link.segmentfault.com/?enc=1%2BOe2%2BAfpVEZGavAVrmXWA%3D%3D.%2FYp%2F3h1IcbQ3qkBltlID3byLqX33WOs4q6iaKCn6IEFhCezgXTk0tonmAXf72YunHJgdlpL9afPS31Sgu%2F9dow%3D%3D" rel="nofollow" target="_blank">聊聊对 this 的理解?</a></li><li><p><a href="https://link.segmentfault.com/?enc=vTvUuy%2BHwOlTdUObN9ht7Q%3D%3D.%2FOjb9SUHhJg3o9tnnQBvCFr87wvXUKIPS6UTdQlFirHd3QgYP1krT0JadNg6R0hVDrrMAlR119O9au1PzVqj%2BA%3D%3D" rel="nofollow" target="_blank">JavaScript 为什么要进行变量提升,它导致了什么问题?</a></p><h2 id="item-0-11">最后:</h2></li><li>欢迎关注 <a href="https://link.segmentfault.com/?enc=6VBQSRt5atnW3caydtj2sQ%3D%3D.%2FI9LPRd4dMCNEE4ibKU68syEakv4iZdteS5GEh0IbjhEJ5JlZBso%2FgGvvpSbSJKie07h4bG7bxjmcvydVEYh6shNWXH0RIwH1KLSh%2FozP5DWpDhwGmBpLA%2FUh15nP2bzBZE9nYJkMwLlcx%2BCdcKwi%2FL%2B70NOkmZVgfHoxVWJWDprtbw3Xgvm9vCkBngkx7Upg0XaPGsCNz5ZLOVk4HDvtGwradG1MjvyqCiCCDkfDyUmOQtlpGamsrXcCSpHO%2B8nBqeZkyf0Ku8EyECfv%2FADg07RJ%2BwY%2Fjxby%2B2oH%2B7Mr6Q%3D" rel="nofollow" target="_blank">『前端进阶圈』</a> 公众号 ,一起探索学习前端技术......</li><li>公众号回复 <a href="https://link.segmentfault.com/?enc=Mp%2BsHHnjFN%2FagpxW9HpIIw%3D%3D.8dnhAem3T0l1eLtp6wasP36JAcLx7LGG9S1Xfc3vPTw2ZvCjXIYWsD6tCcofH64czM1CX6TnKYuCxSB55%2FbPj6uHaSXIKiKtram1K0GDAKzvEbgztDh9b0wAsvhui70BFcaCWJe89EEEnKeNMIc%2FrCInuXbuqBf6StGQT2nP4N%2Bcck8uRV9j6h6GO5pQztrLKnmetcBfFjBc2HdqZbrqWmo9bAWmyoRtRbZEXP6jsfFnTvukHId8Aj6XTr05mSncum2%2B26emxvL5oWJkfXD22TRu%2BsJC16VA%2FFB81HCkfhs%3D" rel="nofollow" target="_blank">加群</a> 或 <a href="https://link.segmentfault.com/?enc=5MddU2iOsL%2FhD3LWSGNa7A%3D%3D.nttuOaXrmAixUfpR6opSPfyTVXkdBzpYqTSvFdqhF0HSLoeGbLTE%2FBh%2BtueIVTTyHx%2B1FrvbH1o5gUEKyCH8%2FM0ebcd%2BzvKRre%2FGYyb%2BNAF9Fvs5FgUZQIdLsHjB14aevuGSfLZQeV2QQ7dMySwKUP3U9OeLZa6NeStTnyqWrZreG%2FpDzsu%2BTHeMeZ%2B5Pv0rNTS2NtNwFIWtNbEhtsPf4NhpdsDhdxx2nA5f7uAQF2%2F9mQF1qc3tAJNMen7IM%2FF%2B2ZFvCwq2JzB2ZYxKYQtCRZatKJzUPCG2%2FawEWi7v%2FHA%3D" rel="nofollow" target="_blank">扫码</a>, 即可加入前端交流学习群,一起快乐摸鱼和学习......</li><li>公众号回复 <a href="https://link.segmentfault.com/?enc=zv%2ByUyPpp3JZZtw84Ui6vg%3D%3D.S1Y3nDylfPb5r9hZjbz8lIQfhYsQaXX9VXdkimuziRO0csfrRopy%2BSkEtzXzdOchmfZ2Da4IVbvI31rkjBIB8SiEniaL9vnVxDJ%2BIMi4%2BlJ4niEoiud6nVZ8nXrzUMpeo3Vhukv9wJTU72Vg0uIPWE2iS7%2BVGGxmDiCalK1a26kLSnbIw33MACHPumr2rZH1di1ZN%2Bs1R%2FjQ9DeMkgRB2pmSaCSoXW76iQp8f5w%2FY%2FSgMUUzO3JVA8lEoyQsKCXJNMmxPBp8NKCBTAuBFbSJE4sY99Ak5A4SZNgPAOGCfck%3D" rel="nofollow" target="_blank">加好友</a>,即可添加为好友<br></li></ol>