1. 什么是bind从“丢失的this”说起如果你写过JavaScript尤其是接触过一些稍微复杂的对象和事件处理那你大概率遇到过这个经典的报错Cannot read properties of undefined。很多时候这个错误的根源都指向同一个问题——函数执行时其内部的this关键字指向“跑偏”了。bind()方法正是JavaScript为解决this绑定问题而提供的一把关键钥匙。简单来说Function.prototype.bind()是一个用于永久性绑定函数执行上下文this值并预设参数的方法。它不立即执行原函数而是返回一个全新的、被“定制”过的函数副本。当你调用这个副本时无论调用方式如何它的this值都已经被锁定为你当初绑定的那个对象并且你预先传入的参数也会被固定下来。这就像你给一个函数穿上了“制服”并分配了固定的“工位”无论它在哪里被调用它都知道自己是谁this以及手头有哪些基础工具预设参数。为什么我们需要它因为JavaScript中函数的this值是在调用时才确定的它的指向非常灵活但也极易丢失。最常见的场景就是当你将一个对象的方法作为回调函数传递时比如传递给setTimeout、事件监听器addEventListener或者在React的JSX中传递事件处理器该方法就会与原始对象“失联”this会指向全局对象浏览器中是window或undefined严格模式下导致程序出错。bind()就是用来提前“焊死”这个关系确保函数在任何环境下都能访问到正确的上下文。2. bind的语法与核心机制拆解2.1 官方语法解析让我们先彻底吃透bind()的官方定义这是理解其一切行为的基础。function.bind(thisArg[, arg1[, arg2[, ...]]])这个语法结构里包含了三个核心部分thisArg必选/核心这是bind()方法的灵魂参数。它指定了当返回的新函数被调用时其内部this关键字所指向的对象。这里有几个关键细节绑定目标通常是你希望方法所属的那个对象实例。null与undefined的特殊处理如果你传入null或undefined在非严格模式下this会被绑定到全局对象浏览器中为window在严格模式下use strictthis值就是null或undefined本身。这个特性有时被用于“忽略”this只进行参数预设即创建偏函数。原始值转换如果thisArg是原始值如数字、字符串、布尔值在绑定过程中它会被转换为对应的包装对象如new Number(1)。不过当绑定的函数被调用时如果这个函数处于非严格模式this又会变回原始值。这是一个容易让人困惑的细节但在日常开发中直接绑定对象即可很少需要关注原始值绑定。arg1, arg2, ...可选/强大之处从第二个参数开始你可以传入任意数量的参数。这些参数会被“预制”到返回的新函数中。当新函数被调用时这些预设参数会排在新调用传入的参数之前共同组成最终传入原函数的参数列表。这个特性是bind()除了绑定this外另一个强大的功能——柯里化Currying或创建偏函数Partial Application的基础。返回值核心产出bind()方法返回一个原函数的拷贝。请注意是“拷贝”而不是“调用”。这个新函数被称为“绑定函数”bound function。它包装了原函数的逻辑但拥有固定的this值和可能预设的参数。原函数本身不会被改变。2.2 一个简单的例子看清this的“变”与“定”理论总是抽象的我们用一个最简单的例子直观感受this的飘忽和bind()的定力。const player { name: Alice, greet: function() { console.log(Hello, Im ${this.name}); } }; // 场景1直接通过对象调用this指向对象本身 player.greet(); // 输出: Hello, Im Alice // 场景2将方法赋值给一个变量再调用 const greetFunc player.greet; greetFunc(); // 输出: Hello, Im undefined (非严格模式可能输出 window.name 的值) // this 丢失了它现在指向全局对象或 undefined。 // 场景3使用bind创建一个this被永久绑定的新函数 const boundGreet player.greet.bind(player); boundGreet(); // 输出: Hello, Im Alice // 无论在哪里调用this 都稳稳地指向 player 对象。 // 场景4即使把绑定函数再赋值、再传递this依然不变 const anotherRef boundGreet; anotherRef(); // 输出: Hello, Im Alice setTimeout(boundGreet, 1000); // 1秒后依然输出: Hello, Im Alice这个例子清晰地展示了问题的核心函数的this值取决于它的调用方式作为对象方法、直接调用、作为构造函数等而不是它被定义的位置。bind()的作用就是打破这个规则让一个函数无论以何种方式被调用都拥有一个预先确定的this上下文。2.3 不只是this参数预设偏函数应用bind()的第二个魔法是参数预设。这在你需要多次调用一个函数且部分参数总是相同的情况下非常有用。function calculate(x, y, z) { return (x y) * z; } // 普通调用 console.log(calculate(10, 20, 2)); // 输出: 60 // 使用bind预设前两个参数创建一个新的专用函数 const calculateWithFixedBase calculate.bind(null, 10, 20); // thisArg 为 null我们不关心this只关心参数。 // 新函数 calculateWithFixedBase 只需要一个参数 z。 console.log(calculateWithFixedBase(2)); // 输出: 60 (相当于 calculate(10, 20, 2)) console.log(calculateWithFixedBase(3)); // 输出: 90 (相当于 calculate(10, 20, 3)) console.log(calculateWithFixedBase(5)); // 输出: 150 (相当于 calculate(10, 20, 5))这里calculateWithFixedBase成了一个“乘数器”它总是基于固定的基数10和20进行计算。这在配置化、工厂模式或事件处理中非常常见。例如你有一个日志函数log(level, message)你可以用bind创建一个logError log.bind(null, ERROR)这样调用logError(Something went wrong!)就等价于log(ERROR, Something went wrong!)。注意预设参数和新传入参数是按顺序合并的。bind(this, a, b)返回的函数f调用f(c, d)时最终传入原函数的参数列表是[a, b, c, d]。3. 手写一个自己的bind深入理解其实现原理面试官爱问手写bind不是要为难你而是因为它完美地考察了对this、闭包、原型链、apply/call乃至构造函数行为的综合理解。让我们一步步拆解实现一个具备核心功能的myBind。3.1 基础版本实现this绑定和参数合并我们先实现最核心的功能返回一个新函数该函数被调用时能以指定的this上下文和合并后的参数执行原函数。Function.prototype.myBind function(thisArg, ...bindArgs) { // 1. 保存原函数。这里的 this 就是调用 myBind 的那个函数。 const originalFunc this; // 2. 返回一个新的绑定函数 const boundFunc function(...callArgs) { // 3. 当绑定函数被调用时用 apply 执行原函数。 // thisArg 作为 apply 的第一个参数决定了原函数内部的 this。 // 参数是 bindArgs (预设) 和 callArgs (新传入) 的合并。 return originalFunc.apply(thisArg, [...bindArgs, ...callArgs]); }; // 4. 返回这个绑定函数 return boundFunc; };代码解读与注意事项originalFunc this在方法内部this指向调用该方法的函数对象。这是实现的基础。闭包的使用boundFunc是一个闭包它“记住”了创建时的环境即originalFunc、thisArg和bindArgs。这使得无论boundFunc被传递到哪里它都能访问到这些值。参数合并我们使用ES6的剩余参数...语法来优雅地处理不定数量的参数。bindArgs是调用myBind时传入的预设参数callArgs是调用返回的boundFunc时传入的新参数。通过[...bindArgs, ...callArgs]将它们合并为一个新数组。使用applyapply方法允许我们以数组形式传递参数列表并显式设置函数执行的this值这正好符合我们的需求。这个基础版本已经可以处理大部分简单场景了。但是它忽略了一个关键情况绑定函数是否可以作为构造函数使用即用new调用3.2 进阶版本支持new操作符构造函数场景这是手写bind的难点和精华所在。我们需要思考当用new调用一个由bind创建的绑定函数时会发生什么根据ECMAScript规范此时绑定的thisArg应该被忽略new操作符会创建一个新的对象实例并将其作为this上下文传递给函数。我们的myBind需要能判断当前是否被new调用。Function.prototype.myBind function(thisArg, ...bindArgs) { const originalFunc this; // 定义绑定函数 const boundFunc function(...callArgs) { // 关键判断this 是否是 boundFunc 的实例 // 如果是说明是通过 new 调用的。 const isConstructorCall this instanceof boundFunc; // 决定最终执行原函数时的 this 值 const finalThisArg isConstructorCall ? this : thisArg; // 执行原函数 return originalFunc.apply(finalThisArg, [...bindArgs, ...callArgs]); }; // 维护原型链这是支持 new 操作符的关键 // 我们需要让绑定函数boundFunc继承原函数originalFunc的原型属性。 // 这样通过 new boundFunc() 创建的对象才能访问原函数原型上的方法。 if (originalFunc.prototype) { // 方法一直接赋值简单但有副作用见下文分析 // boundFunc.prototype originalFunc.prototype; // 方法二使用Object.create创建纯净的原型链推荐 boundFunc.prototype Object.create(originalFunc.prototype); // 可选修正 constructor 指向使其指向 boundFunc 本身更符合直觉 boundFunc.prototype.constructor boundFunc; } return boundFunc; };核心难点解析如何检测new调用我们通过this instanceof boundFunc来判断。当使用new boundFunc()时运算符内部会创建一个新对象并将其原型指向boundFunc.prototype然后将这个新对象作为this执行boundFunc函数体。因此在函数体内this就是boundFunc的一个实例。this值的动态决定如果检测到是构造函数调用isConstructorCall为true我们就忽略外部传入的thisArg使用new创建的新对象即函数内的this作为最终上下文。否则就使用绑定的thisArg。原型链的维护重中之重为了让new boundFunc()创建的对象能沿着正确的原型链查找比如访问原函数原型上定义的方法我们必须设置boundFunc.prototype。这里有两个选择直接赋值 (boundFunc.prototype originalFunc.prototype)这样做有严重副作用。因为boundFunc.prototype和originalFunc.prototype指向了同一个对象。后续如果修改了boundFunc.prototype比如添加方法会直接污染原函数的原型这绝对不是我们想要的。不推荐。使用Object.createObject.create(originalFunc.prototype)会创建一个新对象并将这个新对象的[[Prototype]]即__proto__指向originalFunc.prototype。这样boundFunc的实例既能通过原型链访问原函数原型上的属性和方法又拥有自己独立的原型对象避免了污染。这是标准且安全的做法。3.3 完整实现与测试让我们整合一个更健壮、更接近原生bind的版本并对其进行测试。Function.prototype.myBind function(thisArg, ...bindArgs) { // 类型安全检查非必须但更健壮 if (typeof this ! function) { throw new TypeError(Function.prototype.bind called on incompatible this); } const originalFunc this; const boundFunc function(...callArgs) { // 判断是否作为构造函数被调用 // 通过 new 调用时this 的 __proto__ 是 boundFunc.prototype const isConstructorCall this instanceof boundFunc; // 决定最终的 this 值 const finalThisArg isConstructorCall ? this : thisArg; // 使用 apply 调用原函数 // 注意原函数可能有返回值我们需要将其返回 return originalFunc.apply(finalThisArg, [...bindArgs, ...callArgs]); }; // 处理原型链以支持 new 操作符 // 如果原函数没有 prototype如箭头函数则跳过 if (originalFunc.prototype) { // 使用 Object.create 建立原型链避免直接修改原函数的原型 boundFunc.prototype Object.create(originalFunc.prototype); // 可选修正 constructor 属性使其更清晰 boundFunc.prototype.constructor boundFunc; } return boundFunc; }; // 测试用例 function Person(name, age) { this.name name; this.age age; } Person.prototype.introduce function() { console.log(Hi, Im ${this.name}, ${this.age} years old.); }; const obj { x: 100 }; // 测试1普通绑定this指向obj const BoundPerson1 Person.myBind(obj, Alice); const instance1 BoundPerson1(25); // 作为普通函数调用 console.log(obj); // 输出: { x: 100, name: Alice, age: 25 } // 注意此时原函数Person没有返回值所以instance1是undefined。obj被修改了。 // 测试2作为构造函数调用this指向新创建的对象 const BoundPerson2 Person.myBind(obj, Bob); // 同样绑定了obj const instance2 new BoundPerson2(30); // 使用 new 调用 console.log(instance2); // 输出: Person { name: Bob, age: 30 } console.log(obj); // 输出: { x: 100, name: Alice, age: 25 }obj没有被修改 instance2.introduce(); // 输出: Hi, Im Bob, 30 years old. (成功访问到原型方法) // 测试3参数预设功能 function sum(a, b, c) { return a b c; } const addFive sum.myBind(null, 2, 3); // 预设前两个参数 console.log(addFive(10)); // 输出: 15 (2310)通过这个完整的实现和测试你应该能深刻理解bind方法内部是如何运作的特别是它对构造函数场景的特殊处理。这不仅仅是应付面试更是对你JavaScript功底的一次扎实锤炼。4. bind在实际开发中的高频应用场景理解了原理我们来看看bind在真实项目中是如何大显身手的。它绝不是象牙塔里的概念而是解决实际问题的利器。4.1 解决回调函数中的this丢失问题这是bind最经典、最高频的应用场景。当把对象方法作为回调函数传递给其他API如定时器、事件监听器、Promise处理器、数组迭代方法等时this会丢失。场景一事件监听器class ToggleButton { constructor(elementId) { this.isOn false; this.button document.getElementById(elementId); // 错误做法直接传递方法this会丢失 // this.button.addEventListener(click, this.toggle); // 正确做法使用bind绑定this this.button.addEventListener(click, this.toggle.bind(this)); // 或者在构造函数中一次性绑定好避免每次渲染都创建新函数性能更佳 this.boundToggle this.toggle.bind(this); this.button.addEventListener(click, this.boundToggle); } toggle() { this.isOn !this.isOn; console.log(Button is now: ${this.isOn ? ON : OFF}); this.updateButtonText(); } updateButtonText() { this.button.textContent this.isOn ? Turn OFF : Turn ON; } }场景二setTimeout/setIntervalclass Logger { constructor(name) { this.name name; this.logCount 0; } startLogging() { // 如果不bindsetTimeout回调里的this指向全局对象无法访问this.name setInterval(this.logMessage.bind(this), 1000); } logMessage() { this.logCount; console.log([${this.name}] Log #${this.logCount} at ${new Date().toLocaleTimeString()}); } } const myLogger new Logger(AppMonitor); myLogger.startLogging();场景三数组高阶函数如 forEach, map, filterconst processor { prefix: Data: , processItems(items) { // 在forEach的回调中this默认指向undefined(严格模式)或window const processed items.map(function(item) { return this.prefix item.toUpperCase(); // 这里的this是错的 }); return processed; }, processItemsCorrectly(items) { // 使用bind将回调函数的this绑定到processor对象 const processed items.map(function(item) { return this.prefix item.toUpperCase(); // 现在this正确了 }.bind(this)); // 关键在这里 return processed; } }; const data [apple, banana]; console.log(processor.processItemsCorrectly(data)); // 输出: [Data: APPLE, Data: BANANA]实操心得在现代开发中对于数组方法我们更常使用箭头函数来避免this绑定问题因为箭头函数没有自己的this它会继承外层作用域的this。上面的例子可以简化为items.map(item this.prefix item.toUpperCase())。但在某些必须使用普通函数的场合或者需要预设参数时bind依然不可或缺。4.2 React类组件中的事件处理在React的类组件时代16.8之前或现在仍使用class的组件bind是处理事件监听器的标准方式之一。import React from react; class MyButton extends React.Component { constructor(props) { super(props); this.state { count: 0 }; // 在构造函数中一次性绑定提高性能。 // 如果在render中绑定如 onClick{this.handleClick.bind(this)} // 每次渲染都会创建一个新函数可能导致子组件不必要的重渲染。 this.handleClick this.handleClick.bind(this); } handleClick() { // 如果没有bind这里的this将是undefined因为React在调用事件处理器时是直接调用的 this.setState(prevState ({ count: prevState.count 1 })); } render() { return ( button onClick{this.handleClick} Clicked {this.state.count} times /button ); } }为什么必须bindReact合成事件系统中当你将this.handleClick传递给onClick时你传递的只是一个函数的引用。当事件触发React调用这个函数时它是直接调用的类似于func()而不是作为对象方法调用obj.func()。在JavaScript严格模式下React默认启用直接调用的函数其内部this为undefined。现代替代方案公共类字段语法推荐使用箭头函数定义方法箭头函数自动绑定定义时的this。handleClick () { this.setState({ count: this.state.count 1 }); };在render中使用箭头函数onClick{() this.handleClick()}。这种方式简洁但同样存在每次渲染创建新函数的问题对于性能敏感的组件需谨慎。4.3 创建偏函数与函数柯里化这是bind在函数式编程范式中的一个优雅应用。通过固定一个多参数函数的部分参数来创建一个更具体、更专用的新函数。场景创建具有特定配置的工具函数// 一个通用的请求函数 function makeRequest(method, url, data, headers) { console.log(Sending ${method} request to ${url}); // ... 实际的请求逻辑 } // 为特定的API创建一个专用的POST请求函数 const postToUsersAPI makeRequest.bind(null, POST, https://api.example.com/users); // 现在 postToUsersAPI 只需要 data 和 headers 参数 postToUsersAPI({ name: Alice }, { Content-Type: application/json }); // 进一步特化创建一个添加默认JSON头部的POST请求函数 const postJsonToUsersAPI postToUsersAPI.bind(null, null, { Content-Type: application/json }); // 现在只需要提供 data 参数 postJsonToUsersAPI({ name: Bob });场景延迟计算与参数复用// 一个计算折扣的函数 function calculateDiscount(price, discountRate, taxRate) { const discounted price * (1 - discountRate); return discounted * (1 taxRate); } // 黑色星期五所有商品打8折税率固定为8% const blackFridayPrice calculateDiscount.bind(null, null, 0.2, 0.08); // 现在 blackFridayPrice 只需要一个参数原价 console.log(blackFridayPrice(100)); // 计算原价100的商品在黑五的最终价格 console.log(blackFridayPrice(250)); // 计算原价250的商品 // 会员日折扣不同 const memberDayPrice calculateDiscount.bind(null, null, 0.15, 0.08);这种模式极大地提高了代码的复用性和可读性。它将一个通用的、参数较多的函数“特化”为多个职责单一、参数更少的函数使代码意图更加清晰。5. 深入辨析bind vs call vs applybind、call和apply是JavaScript中操纵函数执行上下文的“三剑客”。它们功能相似但用法和目的有本质区别。彻底理解它们的差异是灵活运用的前提。特性bind(thisArg, arg1, arg2, ...)call(thisArg, arg1, arg2, ...)apply(thisArg, [argsArray])核心作用创建一个新的绑定函数不立即执行。立即调用原函数并指定this和参数。立即调用原函数并指定this和参数数组形式。返回值返回一个新的函数原函数的拷贝。返回原函数执行后的结果。返回原函数执行后的结果。参数传递可以预设部分参数返回的函数接受剩余参数。参数逐个传递。参数以数组或类数组对象形式传递。执行时机延迟执行。需要后续手动调用返回的函数。立即执行。立即执行。典型用途1. 创建固定this的回调函数。2. 创建偏函数柯里化。3. 需要延迟执行或多次复用时。1. 在特定上下文中借用并立即执行某个方法。2. 实现继承在子类构造函数中调用父类构造函数。1. 参数数量不确定时动态传递参数数组。2. 与Math.max等接受参数列表的函数配合使用。直观的例子const person { name: John }; function introduce(greeting, punctuation) { console.log(${greeting}, Im ${this.name}${punctuation}); } // 1. call - 立即执行参数逐个传递 introduce.call(person, Hello, !); // 输出: Hello, Im John! // 2. apply - 立即执行参数以数组传递 introduce.apply(person, [Hi, .]); // 输出: Hi, Im John. // 3. bind - 不执行返回一个新函数可以预设参数 const boundIntroduce introduce.bind(person, Hey); // 预设了第一个参数 boundIntroduce(?); // 输出: Hey, Im John? (调用时传入第二个参数) boundIntroduce(!!!); // 输出: Hey, Im John!!!一个经典的apply用例求数组最大值const numbers [5, 6, 2, 3, 7]; // Math.max 接受参数列表如 Math.max(5,6,2,3,7)不接受数组。 const max1 Math.max.apply(null, numbers); // 使用 apply 展开数组 console.log(max1); // 输出: 7 // ES6 更优雅的方式使用扩展运算符 ... const max2 Math.max(...numbers); // 输出: 7选择哪一个如果你想立即调用一个函数并改变它的this根据参数形式选择call参数明确或apply参数是数组。如果你想创建一个新的函数以便后续调用如作为回调并且需要固定它的this和/或部分参数那么就用bind。6. 常见陷阱、性能考量与最佳实践即使知道了怎么用在实际项目中踩坑也是难免的。下面是一些我总结的“血泪教训”和优化建议。6.1 陷阱过度绑定与内存泄漏bind()每次调用都会返回一个新的函数引用。在频繁调用的场景下比如在React的render方法中如果不加注意会导致大量匿名函数被创建可能引发性能问题和内存泄漏。反面教材React中常见class BadExample extends React.Component { render() { return ( div {/* 每次render都会创建一个全新的函数 */} button onClick{this.handleClick.bind(this)}Click Me/button {/* 使用箭头函数也是同样的问题 */} button onClick{() this.handleClick()}Click Me Too/button /div ); } handleClick() { /* ... */ } }最佳实践在构造函数中一次性绑定类组件constructor(props) { super(props); this.handleClick this.handleClick.bind(this); } // 然后在render中直接引用 render() { return button onClick{this.handleClick}Click Me/button; }使用类属性箭头函数现代React类组件handleClick () { // 箭头函数自动绑定this }; render() { return button onClick{this.handleClick}Click Me/button; }使用React Hooks函数组件彻底告别this绑定问题。function GoodExample() { const handleClick useCallback(() { // 函数组件没有this }, [/* dependencies */]); return button onClick{handleClick}Click Me/button; }6.2 陷阱绑定后的函数无法再次修改thisbind()创建的绑定函数其this值是硬绑定的。即使你再次对它使用call或apply也无法覆盖。const obj1 { name: Obj1 }; const obj2 { name: Obj2 }; function getName() { return this.name; } const boundToObj1 getName.bind(obj1); console.log(boundToObj1()); // 输出: Obj1 console.log(boundToObj1.call(obj2)); // 输出: Obj1call无法覆盖bind的绑定 console.log(boundToObj1.apply(obj2)); // 输出: Obj1apply也无法覆盖这个特性既是优点也是缺点。优点是保证了上下文的绝对稳定缺点则是失去了灵活性。在设计API时需要考虑清楚。6.3 性能考量bind有开销bind()操作本身需要创建一个新函数并形成闭包比直接调用或使用箭头函数略慢。但在绝大多数应用场景中这种微小的性能差异可以忽略不计。代码的清晰度和可维护性应优先于微优化。避免在热代码路径中频繁bind如果在循环体内部或每秒执行多次的函数中调用bind()则可能成为性能瓶颈。在这种情况下应该在循环外部预先绑定好函数。6.4 箭头函数与bind的抉择ES6箭头函数的出现在很多场景下替代了bind。箭头函数没有自己的this它继承自定义时所在的作用域。语法简洁是定义回调函数尤其是在数组方法、Promise链中的首选。// 使用箭头函数this 自动指向外层MyClass实例 class MyClass { handleTimeout () { console.log(this); // 正确指向MyClass实例 } start() { setTimeout(this.handleTimeout, 1000); } }bind当你需要预设参数或者需要处理一个已存在的、非你编写的函数比如第三方库的方法时bind是唯一的选择。// 假设有一个第三方库的函数 thirdPartyLib.doSomething(param1, param2); // 你想固定第一个参数创建一个快捷方式 const doSomethingSpecial thirdPartyLib.doSomething.bind(null, fixedParam); doSomethingSpecial(dynamicParam); // 调用: doSomething(fixedParam, dynamicParam)简单法则在新代码中优先使用箭头函数来处理this绑定问题。当需要柯里化预设参数或处理非箭头函数时再使用bind。7. 从bind看JavaScript的this设计与函数式思想bind方法的存在深刻地反映了JavaScript语言的两大特点动态的this机制和函数作为一等公民的支持。动态this的利与弊JavaScript函数中this的灵活性使得方法复用、借用变得非常方便例如你可以用Array.prototype.slice.call(arguments)将类数组转为数组。但这种灵活性也带来了混乱尤其是在面向对象编程和异步编程中。bind、call、apply正是为了给开发者提供控制这种灵活性的工具让你能明确指定“这个函数此刻应该属于谁”。函数式编程的桥梁bind通过预设参数来创建新函数的能力是柯里化和偏函数应用的典型实现。这是函数式编程的核心概念之一它鼓励将多参数函数转化为一系列单参数函数使函数组合和复用更加灵活。虽然JavaScript不是纯函数式语言但bind的存在让函数式风格编程成为可能促进了更声明式、更少副作用的代码编写方式。理解bind不仅仅是掌握一个API。它是你理解JavaScript执行上下文、闭包、函数式编程思想的一扇重要窗口。下次当你看到或写下.bind(this)时希望你能会心一笑清楚地知道它背后发生的一切并能在call、apply、箭头函数等众多方案中为当前场景做出最恰当的选择。这才是真正“熟悉”了bind。