思考
- 需要理解bind是位于Function.prototype上的
- polyfill:polyfill 在英文中有垫片的意思,意为兜底的东西。在计算机科学中,指的是对未能实现的客户端上进行的"兜底"操作。打补丁在前端er是件习以为常的事情,结合笔者日常工作经验,总结出3种打补丁方式
- 手动打补丁:在石器时代,我们是手动导入所需的补丁,以 ES6 的 object#assign 为例 ,即使在 IE 11上,仍会报错。所以我们需要打上相应的补丁。可以用第三方成熟的package ,也可以使用 MDN 提供的模板进行打补丁。问题是解决了,但优势和劣势也相当明显:优势是保持最小化引入,不会有额外的冗余代码开销,保证了应用的性能。劣势是手动导入不易管理和维护,对于多样化的 polyfill 和变化多端的 Web 应用维护成本比较大
- 根据覆盖率自动打补丁
- 根据浏览器特性,动态打补丁:以上两种方法都有一个弊端——补丁的冗余。以 Object#assign 来说,在支持这个特性的浏览器来说,就没必要引入这个补丁,势必造成了一定的补丁冗余,这就有了根据浏览器特性动态打补丁的方案。
Polyfill.io 就是实现这个方案的服务,它会根据浏览器的UA不同,返回不一样的补丁。如想要 Promise 补丁,在页面引入
- fn.bind(asThis)
- fn.bind(asThis,param1,param2)
- fn.bind(asThis)()
- fn.bind(asThis,param1,param2)()
- fn.bind(asThis)(param1)
- fn.bind(asThis,param1,param2)(p3,p4)
进阶思考
- 新语法的写法,不支持旧的api
- 旧版的写法需要支持new
- 还需要了解原型链
- new的实际转换过程
new Fn()
var tmp = {};
tmp._proto_=fn.prototype
fn.call(tmp,'x');
return this
代码实现
var slice = Array.prototype.slice;
function bind(asThis){
var args = slice.call(arguments,1);
var fn = this;
if(typeof fn !== 'function'){
throw new Error('bind 必须调用在函数身上');
}
function resultFn(){
var args2 = slice.call(arguments,0);
return fn.apply(
resultFn.prototype.isPrototypeOf(this) ? this : asThis,
args.concat(args2)
);
}
resultFn.prototype = fn.prototype;
return resultFn;
}
function _bind(asThis,...args){
const fn = this;
function resultFn(...args2){
return fn.call(this instanceof resultFn? this : asThis,...args,...args2);
}
resultFn.prototype = fn.prototype;
return resultFn;
}
if(!Function.prototype.bind){
Function.prototype.bind = _bind;
}
module.exports = _bind;
测试案例
const bind = require("../src/index");
test1("fn.bind 能用");
test2("this 绑定成功");
test3("this, p1, p2 绑定成功");
test4("this, p1 绑定成功,后传 p2 调用成功");
test5("new 的时候绑定了 p1, p2");
test6("new 的时候绑定了 p1, p2,并且 fn 有 prototype.sayHi");
test7("不用 new 但是用类似的对象");
function test1(message) {
console.log(message);
Function.prototype.bind2 = bind;
console.assert(Function.prototype.bind2 !== undefined);
}
function test2(message) {
console.log(message);
Function.prototype.bind2 = bind;
const fn1 = function() {
return this;
};
const newFn1 = fn1.bind2({ name: "frank" });
console.assert(newFn1().name === "frank");
}
function test3(message) {
console.log(message);
Function.prototype.bind2 = bind;
const fn2 = function(p1, p2) {
return [this, p1, p2];
};
const newFn2 = fn2.bind2({ name: "frank" }, 124, 456);
console.assert(newFn2()[0].name === "frank", "this");
console.assert(newFn2()[1] === 124, "p1");
console.assert(newFn2()[2] === 456, "p2");
}
function test4(message) {
console.log(message);
Function.prototype.bind2 = bind;
const fn2 = function(p1, p2) {
return [this, p1, p2];
};
const anotherFn2 = fn2.bind2({ name: "frank" }, 123);
console.assert(anotherFn2(245)[0].name === "frank", "this");
console.assert(anotherFn2(245)[1] === 123, "p1");
console.assert(anotherFn2(245)[2] === 245, "p22");
}
function test5(message) {
console.log(message);
Function.prototype.bind2 = bind;
const fn = function(p1, p2) {
this.p1 = p1;
this.p2 = p2;
};
const fn2 = fn.bind2(undefined, "x", "y");
const object = new fn2();
console.assert(object.p1 === "x", "x");
console.assert(object.p2 === "y", "y");
}
function test6(message) {
console.log(message);
Function.prototype.bind2 = bind;
const fn = function(p1, p2) {
this.p1 = p1;
this.p2 = p2;
};
fn.prototype.sayHi = function() {};
const fn2 = fn.bind2(undefined, "x", "y");
const object = new fn2();
console.assert(object.p1 === "x", "x");
console.assert(object.p2 === "y", "y");
console.assert(fn.prototype.isPrototypeOf(object));
console.assert(typeof object.sayHi === "function");
}
function test7(message) {
console.log(message);
Function.prototype.bind2 = bind;
const fn = function(p1, p2) {
this.p1 = p1;
this.p2 = p2;
};
fn.prototype.sayHi = function() {};
const object1 = new fn("a", "b");
const fn2 = fn.bind2(object1, "x", "y");
const object = fn2();
console.assert(object === undefined, "object 为空");
console.assert(object1.p1 === "x", "x");
console.assert(object1.p2 === "y", "y");
}