「中高级前端面试」JavaScript手写代码无敌秘籍(推荐)


Posted in Javascript onApril 08, 2019

1. 实现一个new操作符

new操作符做了这些事:

  1. 它创建了一个全新的对象。
  2. 它会被执行[[Prototype]](也就是__proto__)链接。
  3. 它使this指向新创建的对象。。
  4. 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上。
  5. 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用。
function New(func) {
 var res = {};
 if (func.prototype !== null) {
  res.__proto__ = func.prototype;
 }
 var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
 if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
  return ret;
 }
 return res;
}
var obj = New(A, 1, 2);
// equals to
var obj = new A(1, 2);

2. 实现一个JSON.stringify

JSON.stringify(value[, replacer [, space]]):

  1. Boolean | Number| String 类型会自动转换成对应的原始值。
  2. undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)。
  3. 不可枚举的属性会被忽略
  4. 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略。
function jsonStringify(obj) {
 let type = typeof obj;
 if (type !== "object") {
  if (/string|undefined|function/.test(type)) {
   obj = '"' + obj + '"';
  }
  return String(obj);
 } else {
  let json = []
  let arr = Array.isArray(obj)
  for (let k in obj) {
   let v = obj[k];
   let type = typeof v;
   if (/string|undefined|function/.test(type)) {
    v = '"' + v + '"';
   } else if (type === "object") {
    v = jsonStringify(v);
   }
   json.push((arr ? "" : '"' + k + '":') + String(v));
  }
  return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
 }
}
jsonStringify({x : 5}) // "{"x":5}"
jsonStringify([1, "false", false]) // "[1,"false",false]"
jsonStringify({b: undefined}) // "{"b":"undefined"}"

3. 实现一个JSON.parse

JSON.parse(text[, reviver])

用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)。

3.1 第一种:直接调用 eval

function jsonParse(opt) {
 return eval('(' + opt + ')');
}
jsonParse(jsonStringify({x : 5}))
// Object { x: 5}
jsonParse(jsonStringify([1, "false", false]))
// [1, "false", falsr]
jsonParse(jsonStringify({b: undefined}))
// Object { b: "undefined"}

避免在不必要的情况下使用 eval,eval() 是一个危险的函数, 他执行的代码拥有着执行者的权利。如果你用 eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。

它会执行JS代码,有XSS漏洞。

如果你只想记这个方法,就得对参数json做校验。

var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
if (
 rx_one.test(
  json
   .replace(rx_two, "@")
   .replace(rx_three, "]")
   .replace(rx_four, "")
 )
) {
 var obj = eval("(" +json + ")");
}

3.2 第二种:Function

来源神奇的eval()与new Function()

核心:Function与eval有相同的字符串参数特性。

var func = new Function(arg1, arg2, ..., functionBody);

在转换JSON的实际应用中,只需要这么做。

var jsonStr = '{ "age": 20, "name": "jack" }'
var json = (new Function('return ' + jsonStr))();

eval 与 Function 都有着动态编译js代码的作用,但是在实际的编程中并不推荐使用。

这里是面向面试编程,写这两种就够了。至于第三,第四种,涉及到繁琐的递归和状态机相关原理,具体可以看:

《JSON.parse 三种实现方式》

 4. 实现一个call或 apply

实现改编来源:JavaScript深入之call和apply的模拟实现 #11

call语法:

fun.call(thisArg, arg1, arg2, ...),调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)。

apply语法:

func.apply(thisArg, [argsArray]),调用一个函数,以及作为一个数组(或类似数组对象)提供的参数。

 4.1 Function.call按套路实现

call核心:

  1. 将函数设为对象的属性
  2. 执行&删除这个函数
  3. 指定this到函数并传入给定参数执行函数
  4. 如果不传入参数,默认指向为 window

为啥说是套路实现呢?因为真实面试中,面试官很喜欢让你逐步地往深考虑,这时候你可以反套路他,先写个简单版的:

4.1.1 简单版

var foo = {
 value: 1,
 bar: function() {
  console.log(this.value)
 }
}
foo.bar() // 1

4.1.2 完善版

当面试官有进一步的发问,或者此时你可以假装思考一下。然后写出以下版本:

Function.prototype.call2 = function(content = window) {
 content.fn = this;
 let args = [...arguments].slice(1);
 let result = content.fn(...args);
 delete content.fn;
 return result;
}
let foo = {
 value: 1
}
function bar(name, age) {
 console.log(name)
 console.log(age)
 console.log(this.value);
}
bar.call2(foo, 'black', '18') // black 18 1

4.2 Function.apply的模拟实现

apply()的实现和call()类似,只是参数形式不同。直接贴代码吧:

Function.prototype.apply2 = function(context = window) {
 context.fn = this
 let result;
 // 判断是否有第二个参数
 if(arguments[1]) {
  result = context.fn(...arguments[1])
 } else {
  result = context.fn()
 }
 delete context.fn
 return result
}

5. 实现一个Function.bind()

bind()方法:

会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )

此外,bind实现需要考虑实例化后对原型链的影响。

Function.prototype.bind2 = function(content) {
 if(typeof this != "function") {
  throw Error("not a function")
 }
 // 若没问参数类型则从这开始写
 let fn = this;
 let args = [...arguments].slice(1);
 
 let resFn = function() {
  return fn.apply(this instanceof resFn ? this : content,args.concat(...arguments) )
 }
 function tmp() {}
 tmp.prototype = this.prototype;
 resFn.prototype = new tmp();
 
 return resFn;
}

6. 实现一个继承

寄生组合式继承

一般只建议写这种,因为其它方式的继承会在一次实例中调用两次父类的构造函数或有其它缺点。

核心实现是:用一个 F 空的构造函数去取代执行了 Parent 这个构造函数。

function Parent(name) {
 this.name = name;
}
Parent.prototype.sayName = function() {
 console.log('parent name:', this.name);
}
function Child(name, parentName) {
 Parent.call(this, parentName); 
 this.name = name; 
}
function create(proto) {
 function F(){}
 F.prototype = proto;
 return new F();
}
Child.prototype = create(Parent.prototype);
Child.prototype.sayName = function() {
 console.log('child name:', this.name);
}
Child.prototype.constructor = Child;

var parent = new Parent('father');
parent.sayName(); // parent name: father

var child = new Child('son', 'father');

7. 实现一个JS函数柯里化

什么是柯里化?

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

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

7.1  通用版

function 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);
  }
 }
}

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);

7.2 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) //返回10
curryTest(1,2)(4)(3) //返回10
curryTest(1,2)(3,4) //返回10

8. 手写一个Promise(中高级必考)

我们来过一遍Promise/A+规范:

  1. 三种状态pending| fulfilled(resolved) | rejected
  2. 当处于pending状态的时候,可以转移到fulfilled(resolved)或者rejected状态
  3. 当处于fulfilled(resolved)状态或者rejected状态的时候,就不可变。

必须有一个then异步执行方法,then接受两个参数且必须返回一个promise:

// onFulfilled 用来接收promise成功的值
// onRejected 用来接收promise失败的原因
promise1=promise.then(onFulfilled, onRejected);

8.1 Promise的流程图分析

「中高级前端面试」JavaScript手写代码无敌秘籍(推荐)

来回顾下Promise用法:

var promise = new Promise((resolve,reject) => {
 if (操作成功) {
  resolve(value)
 } else {
  reject(error)
 }
})
promise.then(function (value) {
 // success
},function (value) {
 // failure
})

8.2 面试够用版

来源:实现一个完美符合Promise/A+规范的Promise

function myPromise(constructor){
 let self=this;
 self.status="pending" //定义状态改变前的初始状态
 self.value=undefined;//定义状态为resolved的时候的状态
 self.reason=undefined;//定义状态为rejected的时候的状态
 function resolve(value){
  //两个==="pending",保证了状态的改变是不可逆的
  if(self.status==="pending"){
   self.value=value;
   self.status="resolved";
  }
 }
 function reject(reason){
  //两个==="pending",保证了状态的改变是不可逆的
  if(self.status==="pending"){
   self.reason=reason;
   self.status="rejected";
  }
 }
 //捕获构造异常
 try{
  constructor(resolve,reject);
 }catch(e){
  reject(e);
 }
}

 

同时,需要在myPromise的原型上定义链式调用的then方法: 

myPromise.prototype.then=function(onFullfilled,onRejected){
 let self=this;
 switch(self.status){
  case "resolved":
  onFullfilled(self.value);
  break;
  case "rejected":
  onRejected(self.reason);
  break;
  default:  
 }
}

测试一下:

var p=new myPromise(function(resolve,reject){resolve(1)});
p.then(function(x){console.log(x)})
//输出1

8.3 大厂专供版

直接贴出来吧,这个版本还算好理解

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function Promise(excutor) {
 let that = this; // 缓存当前promise实例对象
 that.status = PENDING; // 初始状态
 that.value = undefined; // fulfilled状态时 返回的信息
 that.reason = undefined; // rejected状态时 拒绝的原因
 that.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数
 that.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数

 function resolve(value) { // value成功态时接收的终值
  if(value instanceof Promise) {
   return value.then(resolve, reject);
  }
  // 实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
  setTimeout(() => {
   // 调用resolve 回调对应onFulfilled函数
   if (that.status === PENDING) {
    // 只能由pending状态 => fulfilled状态 (避免调用多次resolve reject)
    that.status = FULFILLED;
    that.value = value;
    that.onFulfilledCallbacks.forEach(cb => cb(that.value));
   }
  });
 }
 function reject(reason) { // reason失败态时接收的拒因
  setTimeout(() => {
   // 调用reject 回调对应onRejected函数
   if (that.status === PENDING) {
    // 只能由pending状态 => rejected状态 (避免调用多次resolve reject)
    that.status = REJECTED;
    that.reason = reason;
    that.onRejectedCallbacks.forEach(cb => cb(that.reason));
   }
  });
 }

 // 捕获在excutor执行器中抛出的异常
 // new Promise((resolve, reject) => {
 //  throw new Error('error in excutor')
 // })
 try {
  excutor(resolve, reject);
 } catch (e) {
  reject(e);
 }
}

Promise.prototype.then = function(onFulfilled, onRejected) {
 const that = this;
 let newPromise;
 // 处理参数默认值 保证参数后续能够继续执行
 onFulfilled =
  typeof onFulfilled === "function" ? onFulfilled : value => value;
 onRejected =
  typeof onRejected === "function" ? onRejected : reason => {
   throw reason;
  };
 if (that.status === FULFILLED) { // 成功态
  return newPromise = new Promise((resolve, reject) => {
   setTimeout(() => {
    try{
     let x = onFulfilled(that.value);
     resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一个onFulfilled的返回值
    } catch(e) {
     reject(e); // 捕获前面onFulfilled中抛出的异常 then(onFulfilled, onRejected);
    }
   });
  })
 }

 if (that.status === REJECTED) { // 失败态
  return newPromise = new Promise((resolve, reject) => {
   setTimeout(() => {
    try {
     let x = onRejected(that.reason);
     resolvePromise(newPromise, x, resolve, reject);
    } catch(e) {
     reject(e);
    }
   });
  });
 }

 if (that.status === PENDING) { // 等待态
  // 当异步调用resolve/rejected时 将onFulfilled/onRejected收集暂存到集合中
  return newPromise = new Promise((resolve, reject) => {
   that.onFulfilledCallbacks.push((value) => {
    try {
     let x = onFulfilled(value);
     resolvePromise(newPromise, x, resolve, reject);
    } catch(e) {
     reject(e);
    }
   });
   that.onRejectedCallbacks.push((reason) => {
    try {
     let x = onRejected(reason);
     resolvePromise(newPromise, x, resolve, reject);
    } catch(e) {
     reject(e);
    }
   });
  });
 }
};

emmm,我还是乖乖地写回进阶版吧。

9. 手写防抖(Debouncing)和节流(Throttling)

scroll 事件本身会触发页面的重新渲染,同时 scroll 事件的 handler 又会被高频度的触发, 因此事件的 handler 内部不应该有复杂操作,例如 DOM 操作就不应该放在事件处理中。
针对此类高频度触发事件问题(例如页面 scroll ,屏幕 resize,监听用户输入等),有两种常用的解决方法,防抖和节流。

9.1 防抖(Debouncing)实现

典型例子:限制 鼠标连击 触发。
一个比较好的解释是:

当一次事件发生后,事件处理器要等一定阈值的时间,如果这段时间过去后 再也没有 事件发生,就处理最后一次发生的事件。假设还差 0.01 秒就到达指定时间,这时又来了一个事件,那么之前的等待作废,需要重新再等待指定时间。

「中高级前端面试」JavaScript手写代码无敌秘籍(推荐)

// 防抖动函数
function debounce(fn,wait=50,immediate) {
 let timer;
 return function() {
  if(immediate) {
   fn.apply(this,arguments)
  }
  if(timer) clearTimeout(timer)
  timer = setTimeout(()=> {
   fn.apply(this,arguments)
  },wait)
 }
}

结合实例:滚动防抖

// 简单的防抖动函数
// 实际想绑定在 scroll 事件上的 handler
function realFunc(){
 console.log("Success");
}

// 采用了防抖动
window.addEventListener('scroll',debounce(realFunc,500));
// 没采用防抖动
window.addEventListener('scroll',realFunc);

9.2 节流(Throttling)实现

可以理解为事件在一个管道中传输,加上这个节流阀以后,事件的流速就会减慢。实际上这个函数的作用就是如此,它可以将一个函数的调用频率限制在一定阈值内,例如 1s,那么 1s 内这个函数一定不会被调用两次

「中高级前端面试」JavaScript手写代码无敌秘籍(推荐)

简单的节流函数:

function throttle(fn, wait) {
	let prev = new Date();
	return function() { 
	 const args = arguments;
		const now = new Date();
		if (now - prev > wait) {
			fn.apply(this, args);
			prev = new Date();
		}
	}

9.3 结合实践

通过第三个参数来切换模式。

const throttle = function(fn, delay, isDebounce) {
 let timer
 let lastCall = 0
 return function (...args) {
 if (isDebounce) {
  if (timer) clearTimeout(timer)
  timer = setTimeout(() => {
  fn(...args)
  }, delay)
 } else {
  const now = new Date().getTime()
  if (now - lastCall < delay) return
  lastCall = now
  fn(...args)
 }
 }
}

10. 手写一个JS深拷贝

有个最著名的乞丐版实现,在《你不知道的JavaScript(上)》里也有提及:

「中高级前端面试」JavaScript手写代码无敌秘籍(推荐)

10.1 乞丐版

var newObj = JSON.parse( JSON.stringify( someObj ) );

10.2 面试够用版

function deepCopy(obj){
 //判断是否是简单数据类型,
 if(typeof obj == "object"){
  //复杂数据类型
  var result = obj.constructor == Array ? [] : {};
  for(let i in obj){
   result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
  }
 }else {
  //简单数据类型 直接 == 赋值
  var result = obj;
 }
 return result;
}

关于深拷贝的讨论天天有,这里就贴两种吧,毕竟我...

「中高级前端面试」JavaScript手写代码无敌秘籍(推荐)

11.实现一个instanceOf

function instanceOf(left,right) {

 let proto = left.__proto__;
 let prototype = right.prototype
 while(true) {
  if(proto === null) return false
  if(proto === prototype) return true
  proto = proto.__proto__;
 }
}

以上所述是小编给大家介绍的JavaScript手写代码详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JavaScript 编程引入命名空间的方法
Jun 29 Javascript
extjs DataReader、JsonReader、XmlReader的构造方法
Nov 07 Javascript
javascript 年月日联动实现核心代码
Dec 21 Javascript
JavaScript 数组运用实现代码
Apr 13 Javascript
浅谈javascript构造函数与实例化对象
Jun 22 Javascript
JS实现的数字格式化功能示例
Feb 10 Javascript
jQuery中layer分页器的使用
Mar 13 Javascript
Vue CLI 3搭建vue+vuex最全分析(推荐)
Sep 27 Javascript
微信小程序开发之转发分享功能
Oct 22 Javascript
ng-alain的sf如何自定义部件的流程
Jun 12 Javascript
JS ES6异步解决方案
Apr 29 Javascript
vue点击弹窗自动触发点击事件的解决办法(模拟场景)
May 25 Vue.js
微信小程序BindTap快速连续点击目标页面跳转多次问题处理
Apr 08 #Javascript
vue.js实现会动的简历(包含底部导航功能,编辑功能)
Apr 08 #Javascript
微信小程序视图控件与bindtap之间的问题的解决
Apr 08 #Javascript
微信小程序实现bindtap等事件传参
Apr 08 #Javascript
详解vue中axios请求的封装
Apr 08 #Javascript
使用taro开发微信小程序遇到的坑总结
Apr 08 #Javascript
vue+element+Java实现批量删除功能
Apr 08 #Javascript
You might like
php Sql Server连接失败问题及解决办法
2009/08/07 PHP
php中经典方法实现判断多维数组是否为空
2011/10/23 PHP
PHP实现QQ登录实例代码
2016/01/14 PHP
PHP结合Redis+MySQL实现冷热数据交换应用案例详解
2019/07/09 PHP
新浪刚打开页面出来的全屏广告代码
2007/04/02 Javascript
jquery实现两边飘浮可关闭的对联广告
2015/11/27 Javascript
Angularjs material 实现搜索框功能
2016/03/08 Javascript
jQuery实现的简单百分比进度条效果示例
2016/08/01 Javascript
在vue项目中引入高德地图及其UI组件的方法
2018/09/04 Javascript
JavaScript惰性求值的一种实现方法示例
2019/01/11 Javascript
JavaScript寄生组合式继承原理与用法分析
2019/01/11 Javascript
微信小程序如何修改本地缓存key中单个数据的详解
2019/04/26 Javascript
小程序实现左滑删除效果
2019/07/25 Javascript
[58:58]2018DOTA2亚洲邀请赛 4.4 淘汰赛 TNC vs VG 第二场
2018/04/05 DOTA
python正则匹配抓取豆瓣电影链接和评论代码分享
2013/12/27 Python
Python中使用scapy模拟数据包实现arp攻击、dns放大攻击例子
2014/10/23 Python
在GitHub Pages上使用Pelican搭建博客的教程
2015/04/25 Python
Python中enumerate函数代码解析
2017/10/31 Python
pycharm执行python时,填写参数的方法
2018/10/29 Python
Pytorch抽取网络层的Feature Map(Vgg)实例
2019/08/20 Python
关于tf.TFRecordReader()函数的用法解析
2020/02/17 Python
python实现猜拳游戏
2020/03/04 Python
Python-openpyxl表格读取写入的案例详解
2020/11/02 Python
CSS3教程(1):什么是CSS3
2009/04/02 HTML / CSS
澳大利亚拥有最好的家具和家居用品在线目的地:Nestz
2019/02/23 全球购物
白俄罗斯大卖场:21vek.by
2019/07/25 全球购物
小学英语课后反思
2014/04/26 职场文书
竞选班长演讲稿500字
2014/08/22 职场文书
出租车拒载检讨书
2015/01/28 职场文书
死者家属慰问信
2015/03/24 职场文书
优秀党员先进事迹材料2016
2016/02/29 职场文书
工作简历的自我评价
2019/05/16 职场文书
Python爬虫基础之初次使用scrapy爬虫实例
2021/06/26 Python
MySQL 分区表中分区键为什么必须是主键的一部分
2022/03/17 MySQL
Python使用Web框架Flask开发项目
2022/06/01 Python
win10重装系统后上不了网怎么办 win10重装系统网络故障的解决办法
2022/07/23 数码科技