diff options
Diffstat (limited to 'files/zh-cn/web/javascript/reference/global_objects/function/bind/index.html')
-rw-r--r-- | files/zh-cn/web/javascript/reference/global_objects/function/bind/index.html | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/files/zh-cn/web/javascript/reference/global_objects/function/bind/index.html b/files/zh-cn/web/javascript/reference/global_objects/function/bind/index.html new file mode 100644 index 0000000000..fbabcdfc13 --- /dev/null +++ b/files/zh-cn/web/javascript/reference/global_objects/function/bind/index.html @@ -0,0 +1,324 @@ +--- +title: Function.prototype.bind() +slug: Web/JavaScript/Reference/Global_Objects/Function/bind +tags: + - ECMAScript 2015 + - ECMAScript 5 + - Function + - JavaScript + - Method + - polyfill +translation_of: Web/JavaScript/Reference/Global_Objects/Function/bind +--- +<div>{{JSRef}}</div> + +<p><code><strong>bind()</strong></code> 方法创建一个新的函数,在 <code>bind()</code> 被调用时,这个新函数的 <code>this</code> 被指定为 <code>bind()</code> 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。</p> + +<div>{{EmbedInteractiveExample("pages/js/function-bind.html", "taller")}}</div> + + + +<h2 id="语法">语法</h2> + +<pre class="syntaxbox notranslate"><code><var>function</var></code>.bind(<var>thisArg</var>[, <var>arg1</var>[, <var>arg2</var>[, ...]]])</pre> + +<h3 id="参数">参数</h3> + +<dl> + <dt><code>thisArg</code></dt> + <dd>调用绑定函数时作为 <code>this</code> 参数传递给目标函数的值。 如果使用{{jsxref("Operators/new", "new")}}运算符构造绑定函数,则忽略该值。当使用 <code>bind</code> 在 <code>setTimeout</code> 中创建一个函数(作为回调提供)时,作为 <code>thisArg</code> 传递的任何原始值都将转换为 <code>object</code>。如果 <code>bind</code> 函数的参数列表为空,或者<code>thisArg</code>是<code>null</code>或<code>undefined</code>,执行作用域的 <code>this</code> 将被视为新函数的 <code>thisArg</code>。</dd> + <dt><code>arg1, arg2, ...</code></dt> + <dd>当目标函数被调用时,被预置入绑定函数的参数列表中的参数。</dd> +</dl> + +<h3 id="返回值">返回值</h3> + +<p>返回一个原函数的拷贝,并拥有指定的 <strong><code>this</code></strong> 值和初始参数。</p> + +<h2 id="描述">描述</h2> + +<div><strong>bind()</strong> 函数会创建一个新的<strong>绑定函数</strong>(<strong>bound function</strong>,BF)。绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语),它包装了原函数对象。调用<strong>绑定函数</strong>通常会导致执行<strong>包装函数</strong>。<br> +<strong>绑定函数</strong>具有以下内部属性:</div> + +<div></div> + +<div> +<ul> + <li><strong>[[BoundTargetFunction]]</strong> - 包装的函数对象</li> + <li><strong>[[BoundThis]]</strong> - 在调用包装函数时始终作为 <strong>this</strong> 值传递的值。</li> + <li><strong>[[BoundArguments]]</strong> - 列表,在对包装函数做任何调用都会优先用列表元素填充参数列表。</li> + <li><strong>[[Call]]</strong> - 执行与此对象关联的代码。通过函数调用表达式调用。内部方法的参数是一个<strong>this</strong>值和一个包含通过调用表达式传递给函数的参数的列表。</li> +</ul> + +<p>当调用绑定函数时,它调用 <strong>[[BoundTargetFunction]]</strong> 上的内部方法 <strong>[[Call]]</strong>,就像这样 <strong>Call(<em>boundThis</em>, <em>args</em>)</strong>。其中,<strong>boundThis</strong> 是 <strong>[[BoundThis]]</strong>,<strong>args</strong> 是 <strong>[[BoundArguments]]</strong> 加上通过函数调用传入的参数列表。</p> +</div> + +<div> +<p>绑定函数也可以使用 {{jsxref("Operators/new", "new")}} 运算符构造,它会表现为目标函数已经被构建完毕了似的。提供的 <code>this</code> 值会被忽略,但前置参数仍会提供给模拟函数。</p> +</div> + +<h2 id="示例">示例</h2> + +<h3 id="创建绑定函数">创建绑定函数</h3> + +<p><code>bind()</code> 最简单的用法是创建一个函数,不论怎么调用,这个函数都有同样的 <strong><code>this</code></strong> 值。JavaScript新手经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,期望方法中的 <code>this</code> 是原来的对象(比如在回调中传入这个方法)。如果不做特殊处理的话,一般会丢失原来的对象。基于这个函数,用原始的对象创建一个绑定函数,巧妙地解决了这个问题:</p> + +<pre class="brush: js notranslate">this.x = 9; // 在浏览器中,this 指向全局的 "window" 对象 +var module = { + x: 81, + getX: function() { return this.x; } +}; + +module.getX(); // 81 + +var retrieveX = module.getX; +retrieveX(); +// 返回 9 - 因为函数是在全局作用域中调用的 + +// 创建一个新函数,把 'this' 绑定到 module 对象 +// 新手可能会将全局变量 x 与 module 的属性 x 混淆 +var boundGetX = retrieveX.bind(module); +boundGetX(); // 81 +</pre> + +<h3 id="偏函数">偏函数</h3> + +<p><code>bind()</code> 的另一个最简单的用法是使一个函数拥有预设的初始参数。只要将这些参数(如果有的话)作为 <code>bind()</code> 的参数写在 <code>this</code> 后面。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面。</p> + +<pre class="brush: js notranslate">function list() { + return Array.prototype.slice.call(arguments); +} + +function addArguments(arg1, arg2) { + return arg1 + arg2 +} + +var list1 = list(1, 2, 3); // [1, 2, 3] + +var result1 = addArguments(1, 2); // 3 + +// 创建一个函数,它拥有预设参数列表。 +var leadingThirtysevenList = list.bind(null, 37); + +// 创建一个函数,它拥有预设的第一个参数 +var addThirtySeven = addArguments.bind(null, 37); + +var list2 = leadingThirtysevenList(); +// [37] + +var list3 = leadingThirtysevenList(1, 2, 3); +// [37, 1, 2, 3] + +var result2 = addThirtySeven(5); +// 37 + 5 = 42 + +var result3 = addThirtySeven(5, 10); +// 37 + 5 = 42 ,第二个参数被忽略 +</pre> + +<h3 id="配合_setTimeout">配合 <code>setTimeout</code></h3> + +<p>在默认情况下,使用 {{ domxref("window.setTimeout()") }} 时,<code>this</code> 关键字会指向 {{ domxref("window") }} (或 <code>global</code>)对象。当类的方法中需要 <code>this</code> 指向类的实例时,你可能需要显式地把 <code>this</code> 绑定到回调函数,就不会丢失该实例的引用。</p> + +<pre class="brush: js notranslate"><code>function LateBloomer() { + this.petalCount = Math.ceil(Math.random() * 12) + 1; +} + +// 在 1 秒钟后声明 bloom +LateBloomer.prototype.bloom = function() { + window.setTimeout(this.declare.bind(this), 1000); +}; + +LateBloomer.prototype.declare = function() { + console.log('I am a beautiful flower with ' + + this.petalCount + ' petals!'); +}; + +var flower = new LateBloomer(); +flower.bloom(); // 一秒钟后, 调用 'declare' 方法</code></pre> + +<h3 id="作为构造函数使用的绑定函数">作为构造函数使用的绑定函数</h3> + +<div class="warning"> +<p><strong>警告</strong> :这部分演示了 JavaScript 的能力并且记录了 <code>bind()</code> 的超前用法。以下展示的方法并不是最佳的解决方案,且可能不应该用在任何生产环境中。</p> +</div> + +<p>绑定函数自动适应于使用 {{jsxref("Operators/new", "new")}} 操作符去构造一个由目标函数创建的新实例。当一个绑定函数是用来构建一个值的,原来提供的 <code>this</code> 就会被忽略。不过提供的参数列表仍然会插入到构造函数调用时的参数列表之前。</p> + +<pre class="brush: js notranslate">function Point(x, y) { + this.x = x; + this.y = y; +} + +Point.prototype.toString = function() { + return this.x + ',' + this.y; +}; + +var p = new Point(1, 2); +p.toString(); // '1,2' + +var emptyObj = {}; +var YAxisPoint = Point.bind(emptyObj, 0/*x*/); + +// 本页下方的 polyfill 不支持运行这行代码, +// 但使用原生的 bind 方法运行是没问题的: + +var YAxisPoint = Point.bind(null, 0/*x*/); + +/*(译注:polyfill 的 bind 方法中,如果把 bind 的第一个参数加上, +即对新绑定的 this 执行 Object(this),包装为对象, +因为 Object(null) 是 {},所以也可以支持)*/ + +var axisPoint = new YAxisPoint(5); +axisPoint.toString(); // '0,5' + +axisPoint instanceof Point; // true +axisPoint instanceof YAxisPoint; // true +new YAxisPoint(17, 42) instanceof Point; // true</pre> + +<p>请注意,你不需要做特别的处理就可以用 {{jsxref("Operators/new", "new")}} 操作符创建一个绑定函数。也就是说,你不需要做特别处理就可以创建一个可以被直接调用的绑定函数,即使你更希望绑定函数是用 {{jsxref("Operators/new", "new")}} 操作符来调用。</p> + +<pre class="brush: js notranslate">// ...接着上面的代码继续的话, +// 这个例子可以直接在你的 JavaScript 控制台运行 + +// 仍然能作为一个普通函数来调用 +// (即使通常来说这个不是被期望发生的) +YAxisPoint(13); + +emptyObj.x + ',' + emptyObj.y; // '0,13' +</pre> + +<p>如果你希望一个绑定函数要么只能用 {{jsxref("Operators/new", "new")}} 操作符,要么只能直接调用,那你必须在目标函数上显式规定这个限制。</p> + +<h3 id="快捷调用">快捷调用</h3> + +<p>在你想要为一个需要特定的 <strong><code>this</code></strong> 值的函数创建一个捷径(shortcut)的时候,<code>bind()</code> 也很好用。</p> + +<p>你可以用 {{jsxref("Array.prototype.slice")}} 来将一个类似于数组的对象(array-like object)转换成一个真正的数组,就拿它来举例子吧。你可以简单地这样写:</p> + +<pre class="brush: js notranslate">var slice = Array.prototype.slice; + +// ... + +slice.apply(arguments);</pre> + +<p>用 <code>bind()</code>可以使这个过程变得简单。在下面这段代码里面,<code>slice</code> 是 {{jsxref("Function.prototype")}} 的 {{jsxref("Function.prototype.apply()", "apply()")}} 方法的绑定函数,并且将 {{jsxref("Array.prototype")}} 的 {{jsxref("Array.prototype.slice()", "slice()")}} 方法作为 <strong><code>this</code></strong> 的值。这意味着我们压根儿用不着上面那个 <code>apply()</code>调用了。</p> + +<pre class="brush: js notranslate">// 与前一段代码的 "slice" 效果相同 +var unboundSlice = Array.prototype.slice; +var slice = Function.prototype.apply.bind(unboundSlice); + +// ... + +slice(arguments);</pre> + +<h2 id="Polyfill">Polyfill</h2> + +<p>有两种实现<code>bind</code>的方法,下面第一种不支持使用<code>new</code>调用新创建的构造函数,而第二种支持。</p> + +<pre class="brush: js line-numbers language-js notranslate"><code class="language-js"><span class="comment token">// Does not work with `new (funcA.bind(thisArg, args))`</span> +<span class="keyword token">if</span> <span class="punctuation token">(</span><span class="operator token">!</span><span class="class-name token">Function</span><span class="punctuation token">.</span>prototype<span class="punctuation token">.</span>bind<span class="punctuation token">)</span> <span class="punctuation token">(</span><span class="keyword token">function</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">{</span> + <span class="keyword token">var</span> slice <span class="operator token">=</span> <span class="class-name token">Array</span><span class="punctuation token">.</span>prototype<span class="punctuation token">.</span>slice<span class="punctuation token">;</span> + <span class="class-name token">Function</span><span class="punctuation token">.</span>prototype<span class="punctuation token">.</span><span class="function function-variable token">bind</span> <span class="operator token">=</span> <span class="keyword token">function</span><span class="punctuation token">(</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="keyword token">var</span> thatFunc <span class="operator token">=</span> <span class="keyword token">this</span><span class="punctuation token">,</span> thatArg <span class="operator token">=</span> arguments<span class="punctuation token">[</span><span class="number token">0</span><span class="punctuation token">]</span><span class="punctuation token">;</span> + <span class="keyword token">var</span> args <span class="operator token">=</span> <span class="function token">slice</span><span class="punctuation token">.</span><span class="function token">call</span><span class="punctuation token">(</span>arguments<span class="punctuation token">,</span> <span class="number token">1</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="keyword token">if</span> <span class="punctuation token">(</span><span class="keyword token">typeof</span> thatFunc <span class="operator token">!==</span> <span class="string token">'function'</span><span class="punctuation token">)</span> <span class="punctuation token">{</span> + <span class="comment token">// closest thing possible to the ECMAScript 5</span> + <span class="comment token">// internal IsCallable function</span> + <span class="keyword token">throw</span> <span class="keyword token">new</span> <span class="class-name token">TypeError</span><span class="punctuation token">(</span><span class="string token">'Function.prototype.bind - '</span> <span class="operator token">+</span> + <span class="string token">'what is trying to be bound is not callable'</span><span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span> + <span class="keyword token">return</span> <span class="keyword token">function</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">{</span> + <span class="keyword token">var</span> funcArgs <span class="operator token">=</span> args<span class="punctuation token">.</span><span class="function token">concat</span><span class="punctuation token">(</span><span class="function token">slice</span><span class="punctuation token">.</span><span class="function token">call</span><span class="punctuation token">(</span>arguments<span class="punctuation token">)</span><span class="punctuation token">)</span> + <span class="keyword token">return</span> <span class="function token">thatFunc</span><span class="punctuation token">.</span><span class="function token">apply</span><span class="punctuation token">(</span>thatArg<span class="punctuation token">,</span> funcArgs<span class="punctuation token">)</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">;</span> + <span class="punctuation token">}</span><span class="punctuation token">;</span> +<span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">(</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + +<p>你可以将这段代码插入到你的脚本开头,从而使你的 <code>bind()</code> 在没有内置实现支持的环境中也可以部分地使用<code>bind</code>。</p> + +<pre class="brush: js notranslate">// Yes, it does work with `new (funcA.bind(thisArg, args))` +if (!Function.prototype.bind) (function(){ + var ArrayPrototypeSlice = Array.prototype.slice; + Function.prototype.bind = function(otherThis) { + if (typeof this !== 'function') { + // closest thing possible to the ECMAScript 5 + // internal IsCallable function + throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); + } + + var baseArgs= ArrayPrototypeSlice.call(arguments, 1), + baseArgsLength = baseArgs.length, + fToBind = this, + fNOP = function() {}, + fBound = function() { + baseArgs.length = baseArgsLength; // reset to default base arguments + baseArgs.push.apply(baseArgs, arguments); + return fToBind.apply( + fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs + ); + }; + + if (this.prototype) { + // Function.prototype doesn't have a prototype property + fNOP.prototype = this.prototype; + } + fBound.prototype = new fNOP(); + + return fBound; + }; +})();</pre> + +<p>上述算法和实际的实现算法还有许多其他的不同 (尽管可能还有其他不同之处,但已经没有必要再多列举):</p> + +<ul> + <li>这部分实现依赖于 {{jsxref("Array.prototype.slice()")}}, {{jsxref("Array.prototype.concat()")}}, {{jsxref("Function.prototype.apply()")}} 这些原生方法。</li> + <li>这部分实现创建的函数并没有 {{jsxref("Function.caller", "caller")}} 以及会在 get,set 或者 deletion 上抛出 {{jsxref("Global_Objects/TypeError", "TypeError")}}错误的 arguments 属性这两个不可改变的“毒药” 。(假如环境支持{{jsxref("Object.defineProperty()")}}, 或者实现支持{{jsxref("Object.defineGetter", "__defineGetter__")}} 和 {{jsxref("Object.defineSetter", "__defineSetter__")}} 扩展)</li> + <li>这部分实现创建的函数有 <code>prototype</code> 属性。(正确的绑定函数没有)</li> + <li>这部分实现创建的绑定函数所有的 length 属性并不是同ECMA-262标准一致的:它创建的函数的 length 是0,而在实际的实现中根据目标函数的 length 和预先指定的参数个数可能会返回非零的 length。</li> +</ul> + +<p>如果你选择使用这部分实现,<strong>你不能依赖于那些与 ECMA-262, 5th edition 规定的行为偏离的例子。</strong>在 <code>bind()</code> 函数被广泛支持之前,某些情况下(或者为了兼容某些特定需求对其做一些特定修改后)可以选择用这个实现作为过渡。</p> + +<p>请访问 <a href="https://github.com/Raynos/function-bind">https://github.com/Raynos/function-bind</a> 以查找更完整的解决方案!</p> + +<h2 id="规范" style="margin-bottom: 20px; line-height: 30px; font-size: 2.14285714285714rem;">规范</h2> + +<table class="standard-table"> + <tbody> + <tr> + <th scope="col">规范</th> + <th scope="col">状态</th> + <th scope="col">备注</th> + </tr> + <tr> + <td>{{SpecName('ES5.1', '#sec-15.3.4.5', 'Function.prototype.bind')}}</td> + <td>{{Spec2('ES5.1')}}</td> + <td>初始定义。在 JavaScript 1.8.5 中实现。</td> + </tr> + <tr> + <td>{{SpecName('ES2015', '#sec-function.prototype.bind', 'Function.prototype.bind')}}</td> + <td>{{Spec2('ES2015')}}</td> + <td></td> + </tr> + <tr> + <td>{{SpecName('ESDraft', '#sec-function.prototype.bind', 'Function.prototype.bind')}}</td> + <td>{{Spec2('ESDraft')}}</td> + <td></td> + </tr> + </tbody> +</table> + +<h2 id="浏览器兼容性">浏览器兼容性</h2> + + + +<p>{{Compat("javascript.builtins.Function.bind")}}</p> + +<h2 id="相关链接">相关链接</h2> + +<ul> + <li>{{jsxref("Function.prototype.apply()")}}</li> + <li>{{jsxref("Function.prototype.call()")}}</li> + <li>{{jsxref("Functions", "函数")}}</li> +</ul> |