「中高级前端面试」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 相关文章推荐
给Flash加一个超链接(推荐使用透明层)兼容主流浏览器
Jun 09 Javascript
js进行表单验证实例分析
Feb 10 Javascript
Jquery搜索父元素操作方法
Feb 10 Javascript
使用jquery实现仿百度自动补全特效
Jul 23 Javascript
利用JavaScript在网页实现八数码启发式A*算法动画效果
Apr 16 Javascript
js原生代码实现轮播图的实例讲解
Jul 28 Javascript
Vue项目自动转换 px 为 rem的实现方法
Oct 29 Javascript
node.js开发辅助工具nodemon安装与配置详解
Feb 06 Javascript
Javascript摸拟自由落体与上抛运动原理与实现方法详解
Apr 08 Javascript
使用Webpack 搭建 Vue3 开发环境过程详解
Jul 28 Javascript
vue实现移动端input上传视频、音频
Aug 18 Javascript
再也不怕 JavaScript 报错了,怎么看怎么处理都在这儿
Dec 09 Javascript
微信小程序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生成sitemap.xml地图函数
2013/11/13 PHP
PHP的serialize序列化数据以及JSON格式化数据分析
2015/10/10 PHP
php中注册器模式类用法实例分析
2015/11/03 PHP
php双层循环(九九乘法表)
2017/10/23 PHP
PHP使用正则表达式实现过滤非法字符串功能示例
2018/06/04 PHP
javascript 嵌套的函数(作用域链)
2010/03/15 Javascript
setTimeout和setInterval的深入理解
2013/11/08 Javascript
JavaScript 实现鼠标拖动元素实例代码
2014/02/24 Javascript
js函数名与form表单元素同名冲突的问题
2014/03/07 Javascript
jQuery使用元素属性attr赋值详解
2015/02/27 Javascript
在线所见即所得HTML编辑器的实现原理浅析
2015/04/25 Javascript
javascript比较两个日期相差天数的方法
2015/07/24 Javascript
vue实现nav导航栏的方法
2017/12/13 Javascript
bootstrap fileinput插件实现预览上传照片功能
2018/01/23 Javascript
基于Angular中ng-controller父子级嵌套的相关属性详解
2018/10/08 Javascript
JS中call()和apply()的功能及用法实例分析
2019/06/28 Javascript
Vue 使用计时器实现跑马灯效果的实例代码
2019/07/11 Javascript
微信小程序拼接图片链接无底洞深入探究
2019/09/03 Javascript
解决微信小程序中的滚动穿透问题
2019/09/16 Javascript
简单实现节流函数和防抖函数过程解析
2019/10/08 Javascript
[02:17]2016国际邀请赛中国区预选赛VG战队领队采访
2016/06/26 DOTA
用Python实现换行符转换的脚本的教程
2015/04/16 Python
Python利用lxml模块爬取豆瓣读书排行榜的方法与分析
2019/04/15 Python
Python3内置模块之json编解码方法小结【推荐】
2020/12/09 Python
Python3实现配置文件差异对比脚本
2019/11/18 Python
pytorch实现特殊的Module--Sqeuential三种写法
2020/01/15 Python
Python任务自动化工具tox使用教程
2020/03/17 Python
DNA测试:Orig3n
2019/03/01 全球购物
劳资专员岗位职责
2013/12/27 职场文书
居委会四风问题个人对照检查材料
2014/09/25 职场文书
2014年挂职干部工作总结
2014/12/06 职场文书
结婚老公保证书
2015/02/26 职场文书
《游戏公平》教学反思
2016/02/20 职场文书
2016年安全月活动总结
2016/04/06 职场文书
创业计划书之零食店(进口)
2019/09/24 职场文书
Windows下载并安装MySQL8.0.x 版本的完整教程
2022/04/10 MySQL