前端面试必刷手写题系列 [4]

418 阅读6分钟

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

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

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

8. instanceof

是什么

instanceof 运算符用于检测构造函数prototype 属性是否出现在某个实例对象的原型链上

这个定义一出来,其实就是明确指导代码了,所以弄清定义非常重要

几个重点

  1. 在 JavaScript 中,对象有一个特殊的隐藏属性 [[Prototype]],它要么为 null,要么就是对另一个对象的引用。该对象被称为“原型”, 也可以理解为这个属性是指向原型对象的指针

  2. 构造函数 Fprototype 属性 在 new F 被调用时为新对象的 [[Prototype]] 赋值。

image.png

也就是说 rabbit 这个实例,它的构造函数是 Rabbit , 而它的原型是 Rabbit.prototype 属性。instanceof 就是检测这个原型是否存在于 rabbit这个实例对象的原型链上, 是,返回 true, 不是 返回 false

如果对原型不熟,看这里[核心概念] 一文说透JS中的原型和继承

简单手写实现

实现

  1. 写几个测试用例先
console.log(myInstanceof([], Array)) // true
console.log(myInstanceof([], Object)) // true
// 注意基本数据类型直接返回 false
console.log(myInstanceof("aaa", String)) // false
  1. 那我们就先写出伪代码,理清思路
function myInstanceof(left, right) {
    // 特判,基本数据类型直接返回 false
    if (基本类型) {
        return false
    }
    // left 是被检验方,先获取它的原型对象 可以用 Object.getPrototypeOf
    while(true) {
        一层层往原型链顶端查,直到 null
        中途找到 => return true
        找不到 => {
            再往原型链上层找
            为 null 都没有则 false
        }
    }
}
  1. 实现主逻辑
function myInstanceof(left, right) {
  // 基本数据类型直接返回 false
  if (typeof left !== 'object' || left === null) {
    return false
  }
  // getPrototypeOf 是 Object对象自带的一个方法,能够拿到参数的原型对象
  let proto = Object.getPrototypeOf(left)
  while (true) {
    // 查找到尽头,还没找到
    if (proto === null) {
      return false
    } 
    // 找到相同的原型对象
    if(proto === right.prototype) {
      return true;
    }
    proto = Object.getPrototypeOf(proto)
  }
}

// console.log(myInstanceof([], Array))

9. new 关键字

是什么

分析

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

然后我们必须知道 new Foo() 执行时,会发什么事情,这是 MDN 的说法

  1. 一个继承自 Foo.prototype新对象被创建
  2. 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
  3. 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤 1 创建的对象。

其实我建议看下面的实现步骤会更清楚

简单手写实现

实现

先写测试用例

// 一个学生类的构造函数
function Student (name, age) {
    this.name = name;
    this.age = age;

    this.height = '180 cm';
}
// 我们在构造函数的原型对象上挂了一个身高属性
Student.prototype.weight = '60 kg';
// 再挂上一个 sayHello 方法,好处是该方法内存指向一处,因为是原型上的
Student.prototype.sayHello = function () {
    console.log('hello ' + this.name);
}

我们先看原生 new 关键字效果

// 用 new 关键字 使用 Student 构造方法创建对象 boy1
var boy1 = new Student('Never', '18');

// 构造方法内部属性,包括原型链上的属性方法,都可以被该实例访问
console.log(boy1.name, boy1.age) // Never 18
console.log(boy1.height) // '180 cm'
console.log(boy1.weight) // 60 kg

boy1.sayHello(); // hello Never

我们需要实现一个类似效果

// 第一个参数传入这个 构造方法(Constructor),接着传入参数的调用方式创建实例
let boy2 = myNew(Student, 'More', '27')

// 需要实现 构造方法内部属性,包括原型链上的属性方法,都可以被boy2实例访问
console.log(boy2.name, boy2.age) // More 27
console.log(boy2.height) // 180 cm
console.log(boy2.weight) // 60 kg

boy2.sayHello() // hello More

详细步骤解析

下面我给你准备了一个完整的详细说明的 myNew

function myNew() {
  // 1. 先创建一个空的对象
  let obj = {}

  // 2. 获取第一个参数 就是构造函数
  // 写法同 Array.prototype.shift.call(arguments) 都为了借用 Array.prototype 上 shift 方法
  // shift方法从前面获取第一个元素返回,并从数组中删除该元素,看执行后打印
  let Constructor = [].shift.call(arguments); 
  // console.log(Constructor) // [Function: Student] 获取了第一个参数
  // console.log(arguments) // { '0': 'More', '1': '27' } 余下参数

  // 3. 将 obj 的[[Prototype]]指向构造函数的 prototype,这样 obj 就可以访问到构造函数原型中的属性
  // 可以用 obj.__proto__ = Constructor.prototype; 方式
  // 当然用下面这种写法更好,虽然效果相同 __proto__ 是 [[Prototype]] 的因历史原因而留下来的 getter/setter
  Object.setPrototypeOf(obj, Constructor.prototype)

  // 4. 使用 apply,改变构造函数 this 的指向到新建的对象,符合 new 关键字的this规则
  let res = Constructor.apply(obj, arguments);

  // 5. 如果函数没有返回对象类型Object,那么new表达式中的函数调用会自动返回这个新的对象(obj)。
  // 如果构造函数返回了对象,则直接返回这个对象
  return typeof res === "object" ? res : obj;
}

let boy2 = myNew(Student, 'More', '27')
console.log(boy2.name, boy2.age) // More 27
console.log(boy2.weight) // '180 cm'
console.log(boy2.height) // '60 kg'

boy2.sayHello() // hello More

我想这注释已经能解释绝大多数你的疑问了,如果还不明白评论区提问。

另外语言的发展,语法的支持后有很多地方可简化,原理相同,只不过用了新语法

function myNew(Constructor, ...args) {
  // 1、创建一个空的对象并链接到原型对象,obj 可以访问构造函数原型中的属性和方法
  // 这里用 Object.create 这个 api 来做 proto 链接
  let obj = Object.create(Constructor.prototype);
  // 严谨可判断下 Constructor.prototype === null ? Object.prototype : Constructor.prototype

  // 2、绑定 this 实现继承,obj 可以访问到构造函数中的属性
  let res = Constructor.apply(obj, args);

  // 3、如果构造函数返回了对象,则直接返回这个对象,否则返回该新对象 obj
  return typeof res === "object" ? res : obj;
};

let boy2 = myNew(Student, 'More', '27')
console.log(boy2.name, boy2.age) // More 27
console.log(boy2.weight) // '180 cm'
console.log(boy2.height) // '60 kg'

boy2.sayHello() // hello More

其中有个点是关于 Object.create,建议查看 MDN深入了解

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

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

参考