这是我参与更文挑战的第 5 天,活动详情查看 更文挑战
这个系列也没啥花头,就是来整平时面试的一些手写函数,考这些简单实现的好处是能看出基本编码水平,且占用时间不长,更全面地看出你的代码实力如何。一般不会出有很多边界条件的问题,那样面试时间不够用,考察不全面。
平时被考到的 api 如果不知道或不清楚,直接问面试官就行, api 怎么用这些 Google 下谁都能马上了解的知识也看不出水平。关键是在实现过程,和你的编码状态、习惯、思路清晰程度等。
注意是简单实现,不是完整实现,重要的是概念清晰和实现思路清晰,建议
先解释清除概念=>写用例=>写伪代码=>再实现具体功能,再优化,一步步来。
14. bind
是什么
MDN:
bind()方法创建一个新的函数,在bind()被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
关键点有这几个
bind()方法创建一个新的函数,但是并没有调用,只是返回了一个函数。- 指定 this 值
- 可以传参
- 柯里化应用
- bind 与 call / apply 的不同就是前者返回一个
绑定上下文的函数(但没调用),而后两者是直接执行了函数。
语法
function.bind(thisArg[, arg1[, arg2[, ...]]])
参数
- thisArg
- 调用绑定函数时
作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArg是null或undefined,执行作用域的 this 将被视为新函数的thisArg。
- 调用绑定函数时
- arg1, arg2, ...
- 当目标函数被调用时,被预置入绑定函数的参数列表中的参数。
- 返回值
- 返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。
- 描述
bind()函数会创建一个新的绑定函数(bound function,BF)。绑定函数包装了原函数对象。调用绑定函数通常会导致执行包装函数。- 绑定函数也可以使用 new 运算符构造,它会表现为目标函数已经被构建完毕了似的。
提供的 this 值会被忽略,但前置参数仍会提供给模拟函数。
如果对 this 指向问题不甚了解,可以先移步这篇 一文说透 JS 中的 this 绑定规则
bind 的实现可以用 call 或 apply 来完成指定 this 关于 call/apply 的实现可以看上一篇 前端必刷手写题系列 [6]
简单手写实现
实现
- 写个测试用例先,注意注释的分步解释,包含了 bind 的性质
var boy = {
height: 185
};
function say(name, age) {
console.log(this.height);
return {
height: this.height,
name: name,
age: age
}
}
// 返回一个 绑定上下文 this 的函数, 注意下面都绑定了 boy 的上下文
var bindToSay = say.bind(boy);
// 也可以写一个固定参数的 偏函数
var bindToSayWithFixedParams = say.bind(boy, 'More');
// 在 bindToSay 没有调用之前,是不会打印的,因为并没有函数会执行
bindToSay() // 185
// 还可以传参, 并且如果函数有返回值时会返回
console.log(bindToSay('Kael', 25))
// 185
// { height: 185, name: 'Kael', age: 25 }
// 偏函数还能再加参数
console.log(bindToSayWithFixedParams(18))
// 185
// { height: 185, name: 'More', age: 18 }
- 实现主逻辑, 我们一步步来
第一步,我们实现大体功能,其实跟 apply/call 差不多,只不过返回了 function, 注意这里形成了闭包,这样即使这个function在当前词法作用域外执行,也能访问原来定义时词法作用域内的变量。如果不太理解,请参考我之前文章 [核心概念] 一文说透 JS 中的闭包(closure),当然箭头函数也是可以的,它不适用正常的 this 绑定规则,简单了解请点击这里
Function.prototype.myBind = function (context) {
// 可以使用箭头函数方式
// return () => {
// return this.apply(context);
// }
// 或者利用词法作用域方式
let that = this
// 返回一个 function
return function() {
// 这里 return 返回函数有返回值的情况
return that.apply(context);
}
}
然后是加参数,而且是可以分步传参的
Function.prototype.myBind = function (context) {
var that = this;
// 获取从第二个到最后的参数
// [].slice.call 其实是借用数组原型上的方法同 Array.prototype.slice.call
var args = [].slice.call(arguments, 1);
return function () {
// 注意 bindArgs 是指 "bind返回的函数"所传入的参数
var bindArgs = [].slice.call(arguments);
// 注意参数进行合并
return that.apply(context, args.concat(bindArgs));
}
}
当然你也可以写点 ES6 风格的简单代码,像我这样,简洁吧 ^-^ 而且好懂
Function.prototype.myBind = function (context, ...args) {
return (...rest) => {
return this.call(context, ...args, ...rest);
}
}
// 下面就是测试用例,你可以把他们拷贝进浏览器运行看看结果
var boy = {
height: 185
};
function say(name, age) {
console.log(this.height);
return {
height: this.height,
name: name,
age: age
}
}
// 返回一个 绑定上下文 this 的函数, 注意下面都绑定了 boy 的上下文
var bindToSay = say.myBind(boy);
// 也可以写一个固定参数的 偏函数
var bindToSayWithFixedParams = say.myBind(boy, 'More');
// 在 bindToSay 没有调用之前,是不会打印的,因为并没有函数会执行
bindToSay() // 185
// 还可以传参
console.log(bindToSay('Kael', 25))
// 185
// { height: 185, name: 'Kael', age: 25 }
// 偏函数还能再加参数
console.log(bindToSayWithFixedParams(18))
// 185
// { height: 185, name: 'More', age: 18 }
好现在上面这个已经可以替换我们的用例,并且跑的跟我们用例结果相同了
上面描述中还有一句话:绑定函数也可以使用 new 运算符构造,它会表现为目标函数已经被构建完毕了似的。提供的 this 值会被忽略,但前置参数仍会提供给模拟函数。做个实验大概这样
var boy = {
height: 185
};
function say(name, age) {
console.log(this.height, '=>this.heiget');
console.log(name);
console.log(age);
// 这个 this 指向谁 => newTestObj
this.newTestProps = 'this is new props'
}
var bindToSay = say.bind(boy);
var newTestObj = new bindToSay('Kael', 25);
console.log(newTestObj.height, '=>newTestObj.height');
console.log(newTestObj.newTestProps, '=>newTestProps');
思考下结果
// 提供的 this 值会被忽略,此时已不再指向上下文 boy,而是指向新 new 出来的对象了
undefined '=>this.heiget'
// 但前置参数仍会提供给模拟函数
Kael
25
undefined '=>newTestObj.height'
// this 指向新 new 出来的对象了, 所以加了属性
this is new props =>newTestProps
所以还得考虑这层,如果对 new 实现不熟悉可以看 new 关键字手写 看实现吧
Function.prototype.myBind = function (context, ...args) {
let newFunc = (...rest) => {
if (this instanceof newFunc) {
return this.call(this, ...args, ...rest)
} else {
return this.call(context, ...args, ...rest);
}
}
newFunc.prototype = this.prototype
return newFunc
}
var boy = {
height: 185
};
function say(name, age) {
console.log(this.height, '=>this.heiget');
console.log(name);
console.log(age);
// 这个 this 指向谁 => newTestObj
this.newTestProps = 'this is new props'
}
var bindToSay = say.bind(boy);
var newTestObj = new bindToSay('Kael', 25);
console.log(newTestObj.height, '=>newTestObj.height');
console.log(newTestObj.newTestProps, '=>newTestProps');
// undefined '=>this.heiget'
// Kael
// 25
// undefined '=>newTestObj.height'
// this is new props =>newTestProps
扩展
bind 小技巧:快捷调用
平时我们会写这些方法,来在原型链上借用,某些方法
比如你可以用 Array.prototype.slice 来将一个类似于数组的对象(array-like object)转换成一个真正的数组,这种 apply 调用方式很常见吧
Array.prototype.slice.apply(arguments);
// 或者借用 shift 取 arguments 第一个参数
Array.prototype.shift.call(arguments)
现在你可以简化
var unboundSlice = Array.prototype.slice;
var mySlice = Function.prototype.apply.bind(unboundSlice);
// ...
mySlice(arguments);
到处可以用了。
有点累了,下面搞个简单的
15. 数组去重 (array unique)
是什么
这没啥好说的,纯粹是功能型函数,写下放库里就好,ES6 兼容的话 Set 方案简洁高效。
简单手写实现
实现
- 简单归简单,我们还是写个测试用例先, 这是习惯
const arr = [6, 6, '6', true, true, 'true','true', NaN, NaN,'NaN', undefined, undefined, null,null];
console.log(arrayUnique(arr));
// [ 6, '6', true, 'true', NaN, 'NaN', undefined, null ]
- 再实现逻辑
function arrayUnique (arr) {
return Array.from(new Set(arr))
}
// 或者
function arrayUnique (arr) {
return [...new Set(arr)]
}
// 当然你也可以用稍复杂的方案, 此方法效率不及上面两种
// 在面试官就像看看你写代码时候,也能写写
function arrayUnique(arr) {
const map = new Map();
const array = [];
for (let i = 0; i < arr.length; i++) {
if (!map.has(arr[i])) {
array.push(arr[i]);
map.set(arr[i], true);
}
}
return array;
}
注意一个点就是对象用此方法是无法去重的,即使你是空对象 {} 。
function arrayUnique (arr) {
return [...new Set(arr)]
}
const test = [{'a': 1}, {'a': 1}, 1, 2, 1, {}, {}];
console.log(arrayUnique(test));
// [ { a: 1 }, { a: 1 }, 1, 2, {}, {} ]
另外向大家着重推荐下另一个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列 记得点赞哈
今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友
Or 搜索我的微信号infinity_9368,可以聊天说地
加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我
presious tower shock the rever monster,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧