前端笔试和面试都难免要手撕代码,有些面试官还就喜欢看你现场写。诚然,一个程序员在写代码时很容易暴露问题,但也能展现实力,就像厨师做菜。
Dancing as no one is watching, coding as everyone is watching.
(一)手撕 new
操作符
实现 New(Func[,args])类似于 new Func([args])。
//1.创建一个新对象
//2.将构造函数的作用域赋给新对象(this 指向新创建的对象)
//3.执行构造函数中的代码
//4.返回新创建的对象.
function New(func) {
let res={};
if(func.prototype!==null){
res.__proto__=func.prototype;
}
let ret=func.apply(res,Array.prototype.slice.call(arguments,1));
if(ret!==null &&(typeof ret==='object'|| typeof ret==='function')){
return ret;
}
return res;
}
**(二)手撕 JSON.stringify
**
//Boolean | Number| String 类型会自动转换成对应的原始值。
//undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)。
//不可枚举的属性会被忽略
// 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略。
function jsonStringify(obj) {
let type=typeof obj;
//特定基本类型
if(type!=="object"){
if(/undefined|function|symbol/.test(type)){
return;
}
return String(obj);
}
//数组或非数组对象
else {
const isArr=Array.isArray(obj);
let json=[];
for(let k in obj){
let v=obj[k];
let type=typeof v;
if(v===obj){
continue;
}
if(/undefined|function|symbol/.test(type)){
if(isArr){v="null";
}
else{
continue;
}
}
else if(type==="object" && v!==null){
v=jsonStringify(v);
}
else if(type==="string"){
v='"'+v+'"';
}
isArr? json.push(String(v)) : json.push('"'+k+'"'+':'+ String(v));
}
return isArr? "[" +String(json)+ "]" : "{" +String(json)+ "}";
}
}
**(三)手撕 JSON.parse
**
以下两个方法仅应对面试,实际中还是用原生的JSON.parse.
//方法1 利用eval,但是eval并不安全,存在XSS漏洞,需要对json做校验。
function jsonParse(json) {
const rx_one = /^[\],:{}\s]*$/;
const rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
const rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
const rx_four = /(?:^|:|,)(?:\s*\[)+/g;
if (rx_one.test(
json
.replace(rx_two, "@")
.replace(rx_three, "]")
.replace(rx_four, "")
)
) {
return eval("(" +json + ")");
}
}
//方法二,利用new Function(),和eval都会动态编译js代码,实际中并不推荐
function jsonParse2(json) {
return new Function('return'+json);
}
**(四)手撕 call apply bind
**
对于call 或者 apply,实现的核心都是以下3步:
(以 foo.call(bar) 为例)
(1)将函数 foo 设为指定对象 bar 的属性;
(2)执行该函数;
(3)删除对象上的这个临时属性,并返回上一步的执行结果。
// 1.call的模拟实现
Function.prototype.call2=function (context = window) {
context.fn=this;
let args=[...arguments].slice(1);
let result=context.fn(...args);
delete context.fn;
return result;
}
//2.apply 的模拟实现
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;
}
bind
的实现要复杂些,因为要考虑到将绑定后的函数作为构造函数来new 对象实例。
关于bind的详细资料,参考MDN。
如果将绑定后的函数作为非构造函数调用,就比较简单。而作为构造函数调用时,如下所示
const foo=function(){ };// 待绑定的函数
const bar={ };// 指定的对象,传到 bind中作为第一个参数
const boundFoo=foo.bind(bar);
let bf1=new boundFoo();
需要注意:
(1)this指向
:绑定后的函数boundFoo被当作构造函数调用,其内部的 this
指向新创建的对象实例 bf1,而不是绑定 foo 时指定的对象 bar。
(2)原型链
:需要维护原型链,即构造函数 boundFoo 继承自最初待绑定的函数 foo 。
完整的 bind 模拟函数如下:
//3.bind的模拟实现
Function.prototype.bind2=function(context){
// closet thing possible to the ES5
// internal IsCallable function
if(typeof this!=='function'){
throw new TypeError('Function.prototype.bind - ' +
'what to be bound is not callable.');
}
let aArgs=Array.prototype.slice.call(arguments,1);
let fnToBind=this;
let fnNOP=function () {};
let fnBound=function () {
// this instanceof fnBound===true,说明返回的fnBound
//被当作构造函数(via new)调用
return fnToBind.apply(
this instanceof fnBound? this: context,
//获取调用(fnBound)时传入的参数
aArgs.concat(...arguments)
);
};
//维护原型关系
if(this.prototype){
// Function.prototype doesn't have a prototype property.
fnNOP.prototype=this.prototype;
}
//若fnBound作为构造函数,则通过new生成的对象的__proto__指向fNOP的实例
//(fnBound继承了fNOP)
fnBound.prototype=new fnNOP();
return fnBound;
}
关于 bind ,还有一个地方要注意,就是在调用 bind绑定时,传给 bind 的参数除了第一个被指定为 绑定后的 this,其余参数(args1)会被插入到目标函数的参数列表的开始位置,而调用绑定好的函数时,传递给被绑定函数的参数(args2)会跟在它们(args1)后面。这个在上段代码中体现为 aArgs.concat(...arguments)
。
这个被称为偏函数,会造成调用绑定好的函数时,传入的参数由于在绑定时已经传入了相应位置上的参数而被忽略,如下所示。
// test
let foo={
value:1
}
function bar(name, age) {
console.log(name);
console.log(age);
console.log(this.value);
}
// bar.call2(foo);
// bar.apply2(foo);
let boundBar=bar.bind2(foo,'Bob',25);
boundBar('Ann',23); // 'Bob',25,1
**(五)手撕 promise
**
(1)瓜皮版,可作为改进的草稿,面试实在写不出来就凑合用这个吧。
// 实现1,瓜皮版,并不完全具备promise的功能
// 缺点:(1)then返回的并不是promise对象;
// (2)实例化promise的时候无法处理异步的resolve
// (3)then 指定的回调并不是异步的
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{
//实例化promise的时候指定何时调用resolve,何时调用reject
constructor(resolve,reject);
}catch (e) {
reject(e);
}
}
MyPromise.prototype.then=function (onFulfilled, onRejected) {
let self=this;
switch(self.status){
case "resolved":
onFulfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
(2)牛逼版,不过以下代码虽然符合 promise A+规范,可以在面试官面前装装逼,但只是模拟实现,其 then 回调并不是微任务,而且使用了 setTimeout 来模拟异步。应用中还是用原生的 Promise。
来源:1
2
3
// 终极版
/**
* 1. new Promise时,需要传递一个 executor 执行器,执行器立刻执行
* 2. executor 接受两个参数,分别是 resolve 和 reject
* 3. promise 只能从 pending 到 rejected, 或者从 pending 到 fulfilled
* 4. promise 的状态一旦确认,就不会再改变
* 5. promise 都有 then 方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled,
* 和 promise 失败的回调 onRejected
* 6. 如果调用 then 时,promise已经成功,则执行 onFulfilled,并将promise的值作为参数传递进去。
* 如果promise已经失败,那么执行 onRejected, 并将 promise 失败的原因作为参数传递进去。
* 如果promise的状态是pending,需要将onFulfilled和onRejected函数存放起来,等待状态确定后,再依次将对应的函数执行(发布订阅)
* 7. then 的参数 onFulfilled 和 onRejected 可以缺省
* 8. promise 可以then多次,promise 的then 方法返回一个 promise
* 9. 如果 then 返回的是一个结果,那么就会把这个结果作为参数,传递给下一个then的成功的回调(onFulfilled)
* 10. 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个then的失败的回调(onRejected)
* 11.如果 then 返回的是一个promise,那么需要等这个promise,那么会等这个promise执行完,promise如果成功,
* 就走下一个then的成功,如果失败,就走下一个then的失败
*/
//https://juejin.im/post/5c88e427f265da2d8d6a1c84#heading-16
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function myPromise(executor) {
let self = this;
self.status = PENDING;
self.onFulfilled = [];//成功的回调
self.onRejected = []; //失败的回调
//PromiseA+ 2.1
function resolve(value) {
// 如果 value 本身就是一个promise
if(value instanceof myPromise){
return value.then(resolve,reject);
}
if (self.status === PENDING) {
self.status = FULFILLED;
self.value = value;
self.onFulfilled.forEach(fn => fn());//PromiseA+ 2.2.6.1
}
}
function reject(reason) {
if (self.status === PENDING) {
self.status = REJECTED;
self.reason = reason;
self.onRejected.forEach(fn => fn());//PromiseA+ 2.2.6.2
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
//onFulfilled 和 onFulfilled的调用需要放在setTimeout,
// 因为规范中表示: onFulfilled or onRejected must not be called
// until the execution context stack contains only platform code。
// 使用setTimeout只是模拟异步,原生Promise并非是这样实现的.
myPromise.prototype.then = function (onFulfilled, onRejected) {
//PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
let self = this;
//PromiseA+ 2.2.7
let promise2 = new myPromise((resolve, reject) => {
if (self.status === FULFILLED) {
//PromiseA+ 2.2.2
//PromiseA+ 2.2.4 --- setTimeout
setTimeout(() => {
try {
//PromiseA+ 2.2.7.1
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
//PromiseA+ 2.2.7.2
reject(e);
}
});
} else if (self.status === REJECTED) {
//PromiseA+ 2.2.3
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
// 如果promise的状态是pending,需要将onFulfilled和onRejected函数存放起来,
// 等待状态确定后,再依次将对应的函数执行(发布订阅)
else if (self.status === PENDING) {
self.onFulfilled.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
self.onRejected.push(() => {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
});
return promise2;
}
function resolvePromise(promise2, x, resolve, reject) {
let self = this;
//PromiseA+ 2.3.1
if (promise2 === x) {
reject(new TypeError('Chaining cycle'));
}
if (x && typeof x === 'object' || typeof x === 'function') {
let used; //PromiseA+2.3.3.3.3 只能调用一次
try {
let then = x.then;
//如果 x 是个 thenable 对象
if (typeof then === 'function') {
//PromiseA+2.3.3
//then.call(x, resolvePromiseFn, rejectPromiseFn)
then.call(x, (y) => {
//PromiseA+2.3.3.1
if (used) return;
used = true;
// 递归调用
resolvePromise(promise2, y, resolve, reject);
}, (r) => {
//PromiseA+2.3.3.2
if (used) return;
used = true;
reject(r);
});
}else{
//PromiseA+2.3.3.4
if (used) return;
used = true;
resolve(x);
}
} catch (e) {
//PromiseA+ 2.3.3.2
if (used) return;
used = true;
reject(e);
}
} else {
//PromiseA+ 2.3.3.4
resolve(x);
}
}
网友评论