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

519 阅读6分钟

这是我参与更文挑战的第 1 天,活动详情查看 更文挑战

这个系列也没啥花头,就是来整平时面试的一些手写函数,考这些简单实现的好处是能看出基本编码水平,且占用时间不长,更全面地看出你的代码实力如何。一般不会出有很多边界条件的问题,那样面试时间不够用,考察不全面。

平时被考到的 api 如果不知道或不清楚,直接问面试官就行, api 怎么用这些 Google 下谁都能马上了解的知识也看不出水平。关键是在实现过程,和你的编码状态习惯思路清晰程度等。

注意是简单实现,不是完整实现,重要的是概念清晰实现思路清晰,建议先解释清除概念 => 写用例 => 写伪代码 => 再实现具体功能,再优化,一步步来。

10. currying (柯里化)

是什么

柯里化 是一种转换,将 f(a,b,c) 转换为可以被以 f(a)(b)(c) 的形式进行调用。JavaScript 实现通常都保持该函数可以被正常调用,并且如果参数数量不足,则返回偏函数

柯里化不会调用函数。它只是对函数进行转换

关于柯里化概念的详细拆解分析请看 一文说透JS中的函数柯里化(Currying),这里我们只说简单手写实现,分析过程在这篇文章写的很清楚,强烈建议先阅读。

再次强调下,弄清定义非常重要,定义弄清伪代码写好,体现专业性。

简单手写实现

实现

  1. 写几个测试用例先
function sum(a, b, c) {
  return a + b + c;
}

// 我们需要实现一个方法 myCurry 用来转换 sum 这个方法,形成 curriedSum 
let curriedSum = myCurry(sum);

// curriedSum 这个方法可以按下面的方式部分调用
console.log( curriedSum(1, 2, 3) );  // 6,仍然可以被正常调用
console.log( curriedSum(1)(2, 3) );  // 6,对第一个参数的柯里化
console.log( curriedSum(1)(2)(3) );  // 6,全柯里化
console.log( curriedSum(1, 2)(3) );  // 6,部分调用

// 注意 方法的长度是 参数长度(a,b,c 3个参数)
console.log(sum.length) // 3
  1. 实现主逻辑
function myCurry(func) {
  // 我们myCurry调用应该返回一个包装器 curried,令这个函数curry化
  return function curried(...args) {
    // curry 的使用主要看参数数量, func.length 就是原函数func的参数数量
    if (args.length >= func.length) {
      // 如果传入的 args 长度与原始函数所定义的(func.length)相同或者更长,
      // 那么只需要将调用传递给它即可。直接现在就调用,返回函数结果
      return func.call(this, ...args)
    } else {
      // 否则的话,返回另一个包装器方法,递归地调用curried,将之前传入的参数与剩余新的参数拼接后一起传入。
      return function pass(...rest) {
        // 然后,在一个新的调用中,再次,我们将获得一个新的偏函数(如果参数不足的话),或者最终的结果。
        return curried.call(this, ...args, ...rest)
      }
    }
  }
}

function sum(a, b, c) {
  return a + b + c;
}

let curriedSum = myCurry(sum);

console.log( curriedSum(1, 2, 3) );  // 6,仍然可以被正常调用
console.log( curriedSum(1)(2, 3) );  // 6,对第一个参数的柯里化
console.log( curriedSum(1)(2)(3) );  // 6,全柯里化
console.log( curriedSum(1, 2)(3) );  // 6,部分调用

11. Object.create()

是什么

MDN: Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

语法

Object.create(proto,[propertiesObject])

参数

  • proto
    • 新创建对象的原型对象
  • propertiesObject
    • 可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。如果该参数被指定且不为 undefined,该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)将为新创建的对象添加指定的属性值和对应的属性描述符。

返回值

一个新对象,带着指定的原型对象和属性。

Object.create() 函数实现同样需要对原型这块深刻了解,建议读[核心概念] 一文说透JS中的原型和继承

newObject.create 两者的区别主要在于

  • new 运算符只接受构造函数,而且不能构造没有原型的空对象Object.create 可以接受 constructor.prototype 还可以接受普通对象null(没有原型的空对象)。
  • Object.create如传入构造函数的prototype属性,则不会继承原有构造函数中的属性;如为对象,则会继承原对象的属性
function Constructor(){}

obj = new Constructor();
// 上面的一句就相当于:
obj = Object.create(Constructor.prototype);
// 当然,如果在Constructor函数中有一些初始化代码,Object.create不能执行那些代码
// 所以不会继承原有构造函数中的属性

// 创建一个以另一个空对象为原型,且拥有一个属性p的对象
obj = Object.create({}, { p: { value: 42 } })
// new 是无法创建 没有原型的对象的

简单手写实现

实现

先写测试用例

const person = {
  isHuman: false,
  printIntroduction: function() {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};
// 这是原生
const me = Object.create(person);

// 我们要实现的
// const me = myObjectCreate(person);

me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten

me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"

再实现

function myObjectCreate(proto, propertiesObject) {
  // 特判,传入参数必须为对象 或者 null
  if (typeof proto !== 'object') {
      throw new TypeError('Object prototype may only be an Object');
  }
  // 内部新建一个构造函数 F
  function F() {}
  // 将构造函数的prototype属性指向传入的原型对象 proto
  F.prototype = proto;
  // 新建一构造函数的实例
  var obj = new F();
  // 如为 null, 需要再将实例的原型指向 null。否则,使用 new 新建实例时,会将原型指向 Object.prototype
  if (proto === null) {
      obj.__proto__  = null;
  }
  // 第二个参数的处理,为实例添加其他属性
  if (propertiesObject) {
    Object.defineProperties(obj, propertiesObject)
  }
  // 返回该实例
  return obj;
};

const person = {
  isHuman: false,
  printIntroduction: function() {
    console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};

const me = myObjectCreate(person, { param: { value: 42 } });

me.name = 'Matthew'; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten

me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"
console.log(me.param) // 42

拓展

我们可以使用 Object.create来实现比复制 for..in 循环中的属性更强大的对象克隆方式:

let a = {
  c: 1,
  b: {
    d: 'aa'
  }
}

const clone = (obj) => {
  return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
}

let b = clone(a)

a.c = 2
a.b.d = 'change'

console.log(a) // { c: 2, b: { d: 'change' } }
console.log(b) // { c: 2, b: { d: 'change' } }

此调用可以对 obj 进行真正准确地拷贝,包括所有的属性:可枚举和不可枚举的,数据属性和 setters/getters —— 包括所有内容,并带有正确的 [[Prototype]]

两点注意

  • 是浅copy
  • for..in 不会枚举不可枚举类型的数据,并不会附带原型。

另外向大家着重推荐下另一个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列 记得点赞哈

今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友 Or 搜索我的微信号infinity_9368,可以聊天说地 加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我 presious tower shock the rever monster,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考