阅读 106

前端必刷手写题系列 [7]

这是我参与更文挑战的第 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 函数的参数列表为空,或者thisArgnullundefined执行作用域的 this 将被视为新函数的 thisArg
  • arg1, arg2, ...
    • 当目标函数被调用时,被预置入绑定函数的参数列表中的参数。
  • 返回值
    • 返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。
  • 描述
    • bind() 函数会创建一个新的绑定函数(bound function,BF)。绑定函数包装了原函数对象。调用绑定函数通常会导致执行包装函数
    • 绑定函数也可以使用 new 运算符构造,它会表现为目标函数已经被构建完毕了似的。提供的 this 值会被忽略,但前置参数仍会提供给模拟函数

如果对 this 指向问题不甚了解,可以先移步这篇 一文说透 JS 中的 this 绑定规则

bind 的实现可以用 call 或 apply 来完成指定 this 关于 call/apply 的实现可以看上一篇 前端必刷手写题系列 [6]

简单手写实现

实现

  1. 写个测试用例先,注意注释的分步解释,包含了 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 }
复制代码
  1. 实现主逻辑, 我们一步步来

第一步,我们实现大体功能,其实跟 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 方案简洁高效

简单手写实现

实现

  1. 简单归简单,我们还是写个测试用例先, 这是习惯
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 ]
复制代码
  1. 再实现逻辑
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,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考

文章分类
前端
文章标签