写点什么

滴滴前端一面高频手写面试题汇总

  • 2023-02-28
    浙江
  • 本文字数:11758 字

    阅读完需:约 39 分钟

实现一个 JS 函数柯里化

预先处理的思想,利用闭包的机制

  • 柯里化的定义:接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数

  • 函数柯里化的主要作用和特点就是参数复用提前返回延迟执行


  • 柯里化把多次传入的参数合并,柯里化是一个高阶函数

  • 每次都返回一个新函数

  • 每次入参都是一个


当柯里化函数接收到足够参数后,就会执行原函数,如何去确定何时达到足够的参数呢?


有两种思路:


  • 通过函数的 length 属性,获取函数的形参个数,形参的个数就是所需的参数个数

  • 在调用柯里化工具函数时,手动指定所需的参数个数


将这两点结合一下,实现一个简单 curry 函数


通用版


// 写法1function curry(fn, args) {  var length = fn.length;  var args = args || [];  return function(){      newArgs = args.concat(Array.prototype.slice.call(arguments));      if (newArgs.length < length) {          return curry.call(this,fn,newArgs);      }else{          return fn.apply(this,newArgs);      }  }}
复制代码


// 写法2// 分批传入参数// redux 源码的compose也是用了类似柯里化的操作const curry = (fn, arr = []) => {// arr就是我们要收集每次调用时传入的参数  let len = fn.length; // 函数的长度,就是参数的个数
return function(...args) { let newArgs = [...arr, ...args] // 收集每次传入的参数
// 如果传入的参数个数等于我们指定的函数参数个数,就执行指定的真正函数 if(newArgs.length === len) { return fn(...newArgs) } else { // 递归收集参数 return curry(fn, newArgs) } }}
复制代码


// 测试function multiFn(a, b, c) {  return a * b * c;}
var multi = curry(multiFn);
multi(2)(3)(4);multi(2,3,4);multi(2)(3,4);multi(2,3)(4)
复制代码


ES6 写法


const curry = (fn, arr = []) => (...args) => (  arg => arg.length === fn.length    ? fn(...arg)    : curry(fn, arg))([...arr, ...args])
复制代码


// 测试let curryTest=curry((a,b,c,d)=>a+b+c+d)curryTest(1,2,3)(4) //返回10curryTest(1,2)(4)(3) //返回10curryTest(1,2)(3,4) //返回10
复制代码


// 柯里化求值// 指定的函数function sum(a,b,c,d,e) {  return a + b + c + d + e}
// 传入指定的函数,执行一次let newSum = curry(sum)
// 柯里化 每次入参都是一个参数newSum(1)(2)(3)(4)(5)
// 偏函数newSum(1)(2)(3,4,5)
复制代码


// 柯里化简单应用// 判断类型,参数多少个,就执行多少次收集function isType(type, val) {  return Object.prototype.toString.call(val) === `[object ${type}]`}
let newType = curry(isType)
// 相当于把函数参数一个个传了,把第一次先缓存起来let isString = newType('String')let isNumber = newType('Number')
isString('hello world')isNumber(999)
复制代码

模拟 new 操作

3 个步骤:


  1. ctor.prototype为原型创建一个对象。

  2. 执行构造函数并将 this 绑定到新创建的对象上。

  3. 判断构造函数执行返回的结果是否是引用数据类型,若是则返回构造函数执行的结果,否则返回创建的对象。


function newOperator(ctor, ...args) {  if (typeof ctor !== 'function') {    throw new TypeError('Type Error');  }  const obj = Object.create(ctor.prototype);  const res = ctor.apply(obj, args);
const isObject = typeof res === 'object' && res !== null; const isFunction = typeof res === 'function'; return isObject || isFunction ? res : obj;}
复制代码

实现 apply 方法

apply 原理与 call 很相似,不多赘述


// 模拟 applyFunction.prototype.myapply = function(context, arr) {  var context = Object(context) || window;  context.fn = this;
var result; if (!arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push("arr[" + i + "]"); } result = eval("context.fn(" + args + ")"); }
delete context.fn; return result;};
复制代码

判断是否是电话号码

function isPhone(tel) {    var regx = /^1[34578]\d{9}$/;    return regx.test(tel);}

复制代码

实现每隔一秒打印 1,2,3,4

// 使用闭包实现for (var i = 0; i < 5; i++) {  (function(i) {    setTimeout(function() {      console.log(i);    }, i * 1000);  })(i);}// 使用 let 块级作用域for (let i = 0; i < 5; i++) {  setTimeout(function() {    console.log(i);  }, i * 1000);}
复制代码

实现数组的 push 方法

let arr = [];Array.prototype.push = function() {    for( let i = 0 ; i < arguments.length ; i++){        this[this.length] = arguments[i] ;    }    return this.length;}
复制代码


参考 前端进阶面试题详细解答

将 js 对象转化为树形结构

// 转换前:source = [{            id: 1,            pid: 0,            name: 'body'          }, {            id: 2,            pid: 1,            name: 'title'          }, {            id: 3,            pid: 2,            name: 'div'          }]// 转换为: tree = [{          id: 1,          pid: 0,          name: 'body',          children: [{            id: 2,            pid: 1,            name: 'title',            children: [{              id: 3,              pid: 1,              name: 'div'            }]          }        }]
复制代码


代码实现:


function jsonToTree(data) {  // 初始化结果数组,并判断输入数据的格式  let result = []  if(!Array.isArray(data)) {    return result  }  // 使用map,将当前对象的id与当前对象对应存储起来  let map = {};  data.forEach(item => {    map[item.id] = item;  });  //   data.forEach(item => {    let parent = map[item.pid];    if(parent) {      (parent.children || (parent.children = [])).push(item);    } else {      result.push(item);    }  });  return result;}
复制代码

实现 prototype 继承

所谓的原型链继承就是让新实例的原型等于父类的实例:


//父方法function SupperFunction(flag1){    this.flag1 = flag1;}
//子方法function SubFunction(flag2){ this.flag2 = flag2;}
//父实例var superInstance = new SupperFunction(true);
//子继承父SubFunction.prototype = superInstance;
//子实例var subInstance = new SubFunction(false);//子调用自己和父的属性subInstance.flag1; // truesubInstance.flag2; // false
复制代码

交换 a,b 的值,不能用临时变量

巧妙的利用两个数的和、差:


a = a + bb = a - ba = a - b
复制代码

实现数组的 flat 方法

function _flat(arr, depth) {  if(!Array.isArray(arr) || depth <= 0) {    return arr;  }  return arr.reduce((prev, cur) => {    if (Array.isArray(cur)) {      return prev.concat(_flat(cur, depth - 1))    } else {      return prev.concat(cur);    }  }, []);}
复制代码

实现数组的乱序输出

主要的实现思路就是:


  • 取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行交换。

  • 第二次取出数据数组第二个元素,随机产生一个除了索引为 1 的之外的索引值,并将第二个元素与该索引值对应的元素进行交换

  • 按照上面的规律执行,直到遍历完成


var arr = [1,2,3,4,5,6,7,8,9,10];for (var i = 0; i < arr.length; i++) {  const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;  [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];}console.log(arr)
复制代码


还有一方法就是倒序遍历:


var arr = [1,2,3,4,5,6,7,8,9,10];let length = arr.length,    randomIndex,    temp;  while (length) {    randomIndex = Math.floor(Math.random() * length--);    temp = arr[length];    arr[length] = arr[randomIndex];    arr[randomIndex] = temp;  }console.log(arr)
复制代码

实现简单路由

// hash路由class Route{  constructor(){    // 路由存储对象    this.routes = {}    // 当前hash    this.currentHash = ''    // 绑定this,避免监听时this指向改变    this.freshRoute = this.freshRoute.bind(this)    // 监听    window.addEventListener('load', this.freshRoute, false)    window.addEventListener('hashchange', this.freshRoute, false)  }  // 存储  storeRoute (path, cb) {    this.routes[path] = cb || function () {}  }  // 更新  freshRoute () {    this.currentHash = location.hash.slice(1) || '/'    this.routes[this.currentHash]()  }}
复制代码

实现浅拷贝

浅拷贝是指,一个新的对象对原始对象的属性值进行精确地拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。如果其中一个对象的引用内存地址发生改变,另一个对象也会发生变化。

(1)Object.assign()

Object.assign()是 ES6 中对象的拷贝方法,接受的第一个参数是目标对象,其余参数是源对象,用法:Object.assign(target, source_1, ···),该方法可以实现浅拷贝,也可以实现一维对象的深拷贝。


注意:


  • 如果目标对象和源对象有同名属性,或者多个源对象有同名属性,则后面的属性会覆盖前面的属性。

  • 如果该函数只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,会先将参数转为对象然后返回。

  • 因为nullundefined 不能转化为对象,所以第一个参数不能为nullundefined,会报错。


let target = {a: 1};let object2 = {b: 2};let object3 = {c: 3};Object.assign(target,object2,object3);  console.log(target);  // {a: 1, b: 2, c: 3}
复制代码

(2)扩展运算符

使用扩展运算符可以在构造字面量对象的时候,进行属性的拷贝。语法:let cloneObj = { ...obj };


let obj1 = {a:1,b:{c:1}}let obj2 = {...obj1};obj1.a = 2;console.log(obj1); //{a:2,b:{c:1}}console.log(obj2); //{a:1,b:{c:1}}obj1.b.c = 2;console.log(obj1); //{a:2,b:{c:2}}console.log(obj2); //{a:1,b:{c:2}}
复制代码

(3)数组方法实现数组浅拷贝

1)Array.prototype.slice


  • slice()方法是 JavaScript 数组的一个方法,这个方法可以从已有数组中返回选定的元素:用法:array.slice(start, end),该方法不会改变原始数组。

  • 该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。


let arr = [1,2,3,4];console.log(arr.slice()); // [1,2,3,4]console.log(arr.slice() === arr); //false
复制代码


2)Array.prototype.concat


  • concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。

  • 该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝。


let arr = [1,2,3,4];console.log(arr.concat()); // [1,2,3,4]console.log(arr.concat() === arr); //false
复制代码

(4)手写实现浅拷贝

// 浅拷贝的实现;
function shallowCopy(object) { // 只拷贝对象 if (!object || typeof object !== "object") return;
// 根据 object 的类型判断是新建一个数组还是对象 let newObject = Array.isArray(object) ? [] : {};
// 遍历 object,并且判断是 object 的属性才拷贝 for (let key in object) { if (object.hasOwnProperty(key)) { newObject[key] = object[key]; } }
return newObject;}// 浅拷贝的实现;
function shallowCopy(object) { // 只拷贝对象 if (!object || typeof object !== "object") return;
// 根据 object 的类型判断是新建一个数组还是对象 let newObject = Array.isArray(object) ? [] : {};
// 遍历 object,并且判断是 object 的属性才拷贝 for (let key in object) { if (object.hasOwnProperty(key)) { newObject[key] = object[key]; } }
return newObject;}// 浅拷贝的实现;function shallowCopy(object) { // 只拷贝对象 if (!object || typeof object !== "object") return; // 根据 object 的类型判断是新建一个数组还是对象 let newObject = Array.isArray(object) ? [] : {}; // 遍历 object,并且判断是 object 的属性才拷贝 for (let key in object) { if (object.hasOwnProperty(key)) { newObject[key] = object[key]; } } return newObject;}
复制代码

实现 add(1)(2)(3)

函数柯里化概念: 柯里化(Currying)是把接受多个参数的函数转变为接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术。


1)粗暴版


function add (a) {return function (b) {     return function (c) {      return a + b + c;     }}}console.log(add(1)(2)(3)); // 6
复制代码


2)柯里化解决方案


  • 参数长度固定


var add = function (m) {  var temp = function (n) {    return add(m + n);  }  temp.toString = function () {    return m;  }  return temp;};console.log(add(3)(4)(5)); // 12console.log(add(3)(6)(9)(25)); // 43
复制代码


对于 add(3)(4)(5),其执行过程如下:


  1. 先执行 add(3),此时 m=3,并且返回 temp 函数;

  2. 执行 temp(4),这个函数内执行 add(m+n),n 是此次传进来的数值 4,m 值还是上一步中的 3,所以 add(m+n)=add(3+4)=add(7),此时 m=7,并且返回 temp 函数

  3. 执行 temp(5),这个函数内执行 add(m+n),n 是此次传进来的数值 5,m 值还是上一步中的 7,所以 add(m+n)=add(7+5)=add(12),此时 m=12,并且返回 temp 函数

  4. 由于后面没有传入参数,等于返回的 temp 函数不被执行而是打印,了解 JS 的朋友都知道对象的 toString 是修改对象转换字符串的方法,因此代码中 temp 函数的 toString 函数 return m 值,而 m 值是最后一步执行函数时的值 m=12,所以返回值是 12。


  • 参数长度不固定


function add (...args) {    //求和    return args.reduce((a, b) => a + b)}function currying (fn) {    let args = []    return function temp (...newArgs) {        if (newArgs.length) {            args = [                ...args,                ...newArgs            ]            return temp        } else {            let val = fn.apply(this, args)            args = [] //保证再次调用时清空            return val        }    }}let addCurry = currying(add)console.log(addCurry(1)(2)(3)(4, 5)())  //15console.log(addCurry(1)(2)(3, 4, 5)())  //15console.log(addCurry(1)(2, 3, 4, 5)())  //15
复制代码

实现一个迭代器生成函数

ES6 对迭代器的实现

JS 原生的集合类型数据结构,只有Array(数组)和Object(对象);而ES6中,又新增了MapSet。四种数据结构各自有着自己特别的内部实现,但我们仍期待以同样的一套规则去遍历它们,所以ES6在推出新数据结构的同时也推出了一套 统一的接口机制 ——迭代器(Iterator)。


ES6约定,任何数据结构只要具备Symbol.iterator属性(这个属性就是Iterator的具体实现,它本质上是当前数据结构默认的迭代器生成函数),就可以被遍历——准确地说,是被for...of...循环和迭代器的 next 方法遍历。 事实上,for...of...的背后正是对next方法的反复调用。


在 ES6 中,针对ArrayMapSetStringTypedArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都可以通过for...of...进行遍历。原理都是一样的,此处我们拿最简单的数组进行举例,当我们用for...of...遍历数组时:


const arr = [1, 2, 3]const len = arr.lengthfor(item of arr) {   console.log(`当前元素是${item}`)}
复制代码


之所以能够按顺序一次一次地拿到数组里的每一个成员,是因为我们借助数组的Symbol.iterator生成了它对应的迭代器对象,通过反复调用迭代器对象的next方法访问了数组成员,像这样:


const arr = [1, 2, 3]// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()
// 对迭代器对象执行next,就能逐个访问集合的成员iterator.next()iterator.next()iterator.next()
复制代码


丢进控制台,我们可以看到next每次会按顺序帮我们访问一个集合成员:



for...of...做的事情,基本等价于下面这通操作:


// 通过调用iterator,拿到迭代器对象const iterator = arr[Symbol.iterator]()
// 初始化一个迭代结果let now = { done: false }
// 循环往外迭代成员while(!now.done) { now = iterator.next() if(!now.done) { console.log(`现在遍历到了${now.value}`) }}
复制代码


可以看出,for...of...其实就是iterator循环调用换了种写法。在 ES6 中我们之所以能够开心地用for...of...遍历各种各种的集合,全靠迭代器模式在背后给力。


ps:此处推荐阅读迭代协议 (opens new window),相信大家读过后会对迭代器在 ES6 中的实现有更深的理解。

Promise

// 模拟实现Promise// Promise利用三大手段解决回调地狱:// 1. 回调函数延迟绑定// 2. 返回值穿透// 3. 错误冒泡
// 定义三种状态const PENDING = 'PENDING'; // 进行中const FULFILLED = 'FULFILLED'; // 已成功const REJECTED = 'REJECTED'; // 已失败
class Promise { constructor(exector) { // 初始化状态 this.status = PENDING; // 将成功、失败结果放在this上,便于then、catch访问 this.value = undefined; this.reason = undefined; // 成功态回调函数队列 this.onFulfilledCallbacks = []; // 失败态回调函数队列 this.onRejectedCallbacks = [];
const resolve = value => { // 只有进行中状态才能更改状态 if (this.status === PENDING) { this.status = FULFILLED; this.value = value; // 成功态函数依次执行 this.onFulfilledCallbacks.forEach(fn => fn(this.value)); } } const reject = reason => { // 只有进行中状态才能更改状态 if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; // 失败态函数依次执行 this.onRejectedCallbacks.forEach(fn => fn(this.reason)) } } try { // 立即执行executor // 把内部的resolve和reject传入executor,用户可调用resolve和reject exector(resolve, reject); } catch(e) { // executor执行出错,将错误内容reject抛出去 reject(e); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function'? onRejected : reason => { throw new Error(reason instanceof Error ? reason.message : reason) } // 保存this const self = this; return new Promise((resolve, reject) => { if (self.status === PENDING) { self.onFulfilledCallbacks.push(() => { // try捕获错误 try { // 模拟微任务 setTimeout(() => { const result = onFulfilled(self.value); // 分两种情况: // 1. 回调函数返回值是Promise,执行then操作 // 2. 如果不是Promise,调用新Promise的resolve函数 result instanceof Promise ? result.then(resolve, reject) : resolve(result); }) } catch(e) { reject(e); } }); self.onRejectedCallbacks.push(() => { // 以下同理 try { setTimeout(() => { const result = onRejected(self.reason); // 不同点:此时是reject result instanceof Promise ? result.then(resolve, reject) : resolve(result); }) } catch(e) { reject(e); } }) } else if (self.status === FULFILLED) { try { setTimeout(() => { const result = onFulfilled(self.value); result instanceof Promise ? result.then(resolve, reject) : resolve(result); }); } catch(e) { reject(e); } } else if (self.status === REJECTED) { try { setTimeout(() => { const result = onRejected(self.reason); result instanceof Promise ? result.then(resolve, reject) : resolve(result); }) } catch(e) { reject(e); } } }); } catch(onRejected) { return this.then(null, onRejected); } static resolve(value) { if (value instanceof Promise) { // 如果是Promise实例,直接返回 return value; } else { // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED return new Promise((resolve, reject) => resolve(value)); } } static reject(reason) { return new Promise((resolve, reject) => { reject(reason); }) } static all(promiseArr) { const len = promiseArr.length; const values = new Array(len); // 记录已经成功执行的promise个数 let count = 0; return new Promise((resolve, reject) => { for (let i = 0; i < len; i++) { // Promise.resolve()处理,确保每一个都是promise实例 Promise.resolve(promiseArr[i]).then( val => { values[i] = val; count++; // 如果全部执行完,返回promise的状态就可以改变了 if (count === len) resolve(values); }, err => reject(err), ); } }) } static race(promiseArr) { return new Promise((resolve, reject) => { promiseArr.forEach(p => { Promise.resolve(p).then( val => resolve(val), err => reject(err), ) }) }) }}
复制代码

实现观察者模式

观察者模式(基于发布订阅模式) 有观察者,也有被观察者


观察者需要放到被观察者中,被观察者的状态变化需要通知观察者 我变化了 内部也是基于发布订阅模式,收集观察者,状态变化后要主动通知观察者


class Subject { // 被观察者 学生  constructor(name) {    this.state = 'happy'    this.observers = []; // 存储所有的观察者  }  // 收集所有的观察者  attach(o){ // Subject. prototype. attch    this.observers.push(o)  }  // 更新被观察者 状态的方法  setState(newState) {    this.state = newState; // 更新状态    // this 指被观察者 学生    this.observers.forEach(o => o.update(this)) // 通知观察者 更新它们的状态  }}
class Observer{ // 观察者 父母和老师 constructor(name) { this.name = name } update(student) { console.log('当前' + this.name + '被通知了', '当前学生的状态是' + student.state) }}
let student = new Subject('学生');
let parent = new Observer('父母'); let teacher = new Observer('老师');
// 被观察者存储观察者的前提,需要先接纳观察者student. attach(parent); student. attach(teacher); student. setState('被欺负了');
复制代码

实现类的继承

实现类的继承-简版

类的继承在几年前是重点内容,有 n 种继承方式各有优劣,es6 普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。


// 寄生组合继承function Parent(name) {  this.name = name}Parent.prototype.say = function() {  console.log(this.name + ` say`);}Parent.prototype.play = function() {  console.log(this.name + ` play`);}
function Child(name, parent) { // 将父类的构造函数绑定在子类上 Parent.call(this, parent) this.name = name}
/** 1. 这一步不用Child.prototype = Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类 2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性3. Object.create是创建了父类原型的副本,与父类原型完全隔离*/Child.prototype = Object.create(Parent.prototype);Child.prototype.say = function() { console.log(this.name + ` say`);}
// 注意记得把子类的构造指向子类本身Child.prototype.constructor = Child;
复制代码


// 测试var parent = new Parent('parent');parent.say() 
var child = new Child('child');child.say() child.play(); // 继承父类的方法
复制代码

ES5 实现继承-详细

第一种方式是借助 call 实现继承


function Parent1(){    this.name = 'parent1';}function Child1(){    Parent1.call(this);    this.type = 'child1'    }console.log(new Child1);
复制代码


这样写的时候子类虽然能够拿到父类的属性值,但是问题是父类中一旦存在方法那么子类无法继承。那么引出下面的方法


第二种方式借助原型链实现继承:


function Parent2() {    this.name = 'parent2';    this.play = [1, 2, 3]  }  function Child2() {    this.type = 'child2';  }  Child2.prototype = new Parent2();
console.log(new Child2());
复制代码


看似没有问题,父类的方法和属性都能够访问,但实际上有一个潜在的不足。举个例子:


var s1 = new Child2();  var s2 = new Child2();  s1.play.push(4);  console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]
复制代码


明明我只改变了 s1 的 play 属性,为什么 s2 也跟着变了呢?很简单,因为两个实例使用的是同一个原型对象


第三种方式:将前两种组合:


function Parent3 () {    this.name = 'parent3';    this.play = [1, 2, 3];  }  function Child3() {    Parent3.call(this);    this.type = 'child3';  }  Child3.prototype = new Parent3();  var s3 = new Child3();  var s4 = new Child3();  s3.play.push(4);  console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]
复制代码


之前的问题都得以解决。但是这里又徒增了一个新问题,那就是 Parent3 的构造函数会多执行了一次(Child3.prototype = new Parent3();)。这是我们不愿看到的。那么如何解决这个问题?


第四种方式: 组合继承的优化 1


function Parent4 () {    this.name = 'parent4';    this.play = [1, 2, 3];  }  function Child4() {    Parent4.call(this);    this.type = 'child4';  }  Child4.prototype = Parent4.prototype;
复制代码


这里让将父类原型对象直接给到子类,父类构造函数只执行一次,而且父类属性和方法均能访问,但是我们来测试一下


var s3 = new Child4();  var s4 = new Child4();  console.log(s3)
复制代码


子类实例的构造函数是 Parent4,显然这是不对的,应该是 Child4。


第五种方式(最推荐使用):优化 2


function Parent5 () {    this.name = 'parent5';    this.play = [1, 2, 3];  }  function Child5() {    Parent5.call(this);    this.type = 'child5';  }  Child5.prototype = Object.create(Parent5.prototype);  Child5.prototype.constructor = Child5;
复制代码


这是最推荐的一种方式,接近完美的继承。

实现单例模式

核心要点: 用闭包和Proxy属性拦截


function proxy(func) {    let instance;    let handler = {        constructor(target, args) {            if(!instance) {                instance = Reflect.constructor(fun, args);            }            return instance;        }    }    return new Proxy(func, handler);}
复制代码

Array.prototype.map()

Array.prototype.map = function(callback, thisArg) {  if (this == undefined) {    throw new TypeError('this is null or not defined');  }  if (typeof callback !== 'function') {    throw new TypeError(callback + ' is not a function');  }  const res = [];  // 同理  const O = Object(this);  const len = O.length >>> 0;  for (let i = 0; i < len; i++) {    if (i in O) {      // 调用回调函数并传入新数组      res[i] = callback.call(thisArg, O[i], i, this);    }  }  return res;}
复制代码


用户头像

还未添加个人签名 2022-07-31 加入

还未添加个人简介

评论

发布
暂无评论
滴滴前端一面高频手写面试题汇总_JavaScript_helloworld1024fd_InfoQ写作社区