bind 函数我们经常用到,主要用于换绑 this。其返回值是一个方法。此外我们还可以把一些参数固定下来,相当于封装了一个简易函数。
换绑 this:
1 2 3 4 5 6 7 8 9 10 11 12
| const obj = { bbb: "hello", sayHi, };
function sayHi() { console.log("Hi", this.bbb); }
sayHi(); const a = sayHi.bind(obj); a();
|
减少参数:
1 2 3 4 5 6 7 8 9 10 11 12
| const obj = { bbb: "hello", sayHi, };
function sayHi(a, b) { console.log("Hi", this.bbb, a, b); }
sayHi(); const a = sayHi.bind(obj, "jjjj"); a("123");
|
不过如果 bind 返回的方法用作构造函数的话,绑定 this 动作就失效了,且会返回原方法构造出的新对象。
用作构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const obj = { bbb: "hello", sayHi, };
function sayHi(a, b) { console.log(this, this.bbb); }
const a = sayHi.bind(obj, "jjjj"); a(); const b = new a(); console.log(b);
|
基于这些特性我们自己模拟实现一个 bind 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Function.prototype.bindFn = function bind(thisArg) { if (typeof this !== "function") { throw TypeError(this + "is not a function"); } const self = this; const bindArgs = [].slice.call(arguments, 1); function bound() { const boundArgs = [].slice.call(arguments); const finalArgs = bindArgs.concat(boundArgs); if (new.target === bound) { return new self(); } else { return self.apply(thisArg, finalArgs); } } return bound; };
|
函数的.name和.length属性
此外函数还有一些默认行为,比如.name打印函数名和.length打印形参个数。如果是 bind 函数返回的话,会在前面加个bound
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| console.log(Function.prototype.bind.name); console.log(Function.prototype.bind().name); const obj = { bbb: "hello", sayHi, };
function sayHi(a, b, c) { console.log(this, this.bbb, b, c); }
console.log(sayHi.name, sayHi.length);
const a = sayHi.bind(obj, "jjjj"); console.log(a.name, a.length); console.log(function () {}.bind().name);
|
我们构建的 bind 在这个方面依旧跟原生 bind 的表现不一样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| Function.prototype.bindFn = function bind(thisArg) { if (typeof this !== "function") { throw TypeError(this + "is not a function"); } const self = this; const bindArgs = [].slice.call(arguments, 1); function bound() { const boundArgs = [].slice.call(arguments); const finalArgs = bindArgs.concat(boundArgs); if (new.target === bound) { return new self(); } else { return self.apply(thisArg, finalArgs); } } return bound; };
const obj = { bbb: "hello", sayHi, };
function sayHi(a, b, c) { console.log(this, this.bbb); }
const a = sayHi.bindFn(obj, "jjjj"); console.log(a.name, a.length); const b = sayHi.bind(obj, "jjjjj"); console.log(b.name, b.length);
|
函数的.name和.length属性是无法修改的:
1 2 3 4
| var aa = function () {}; aa.name = "ccc"; aa.length = 4; console.log(aa.name, aa.length);
|
原因是writable: false,但可以自己配置:
1 2 3 4 5
| var a = () => {}; console.log(a.name); Object.getOwnPropertyDescriptor(a, "name"); Object.defineProperty(a, "name", { writable: true, value: "bb" }); console.log(a.name);
|
new.target的用处
如果用this instanceof bound来替代new.target === bound会有如下问题:
1 2 3 4 5 6 7 8 9 10 11
| function Student(name) { if (this instanceof Student) { this.name = name; console.log("name", name); } else { throw new Error("必须通过new关键字来调用Student。"); } } var student = new Student("若"); var notAStudent = Student.call(student, "川"); console.log(student, "student", notAStudent, "notAStudent");
|
可见并非是实例就可以,而必须是用 new 调用才是构造函数
最终版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Function.prototype.bindFn = function bind(thisArg) { if (typeof this !== "function") { throw TypeError(this + "is not a function"); } const self = this; const bindArgs = [].slice.call(arguments, 1); function bound() { const boundArgs = [].slice.call(arguments); const finalArgs = bindArgs.concat(boundArgs); if (new.target === bound) { return new self(); } else { return self.apply(thisArg, finalArgs); } } const fnName = bound.name + " " + self.name; Object.defineProperty(bound, "name", { writable: true, value: fnName }); Object.defineProperty(bound, "name", { writable: false }); const fnLength = self.length - bindArgs.length; Object.defineProperty(bound, "length", { writable: true, value: fnLength }); Object.defineProperty(bound, "length", { writable: false }); return bound; };
|
参考