
还在害怕手写题吗,文帮本文可以帮你扩展并巩固自己的搞定JS基础,顺便搞定90%的手手写手写题。在工作中还可以对常用的写题需求进行手写实现,比如深拷贝、面试防抖节流等可以直接用于往后的文帮项目中,提高项目开发效率。搞定不说废话了,手手写下面就直接上代码吧。写题
1.call的面试实现
第一个参数为null或者undefined时,this指向全局对象window,文帮值为原始值的搞定
指向该原始值的自动包装对象,如 String、手手写Number、写题Boolean 为了避免函数名与上下文(context)的面试属性发生冲突,使用Symbol类型作为唯一值 将函数作为传入的上下文(context)属性执行 函数执行完成后删除该属性 返回执行结果 Function.prototype.myCall = function(context,...args){ let cxt = context || window; //将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this) //新建一个唯一的Symbol变量避免重复 let func = Symbol() cxt[func] = this; argsargs = args ? args : [] //以对象调用形式调用func,此时this指向cxt 也就是传入的需要绑定的this指向 const res = args.length > 0 ? cxt[func](...args) : cxt[func](); //删除该方法,不然会对传入对象造成污染(添加该方法) delete cxt[func]; return res; }
2.apply的实现
前部分与call一样 第二个参数可以不传,但类型必须为数组或者类数组 Function.prototype.myApply = function(context,args = []){ let cxt = context || window; //将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this) //新建一个唯一的
源码库Symbol变量避免重复 let func = Symbol() cxt[func] = this; //以对象调用形式调用func,此时this指向cxt 也就是传入的需要绑定的this指向 const res = args.length > 0 ? cxt[func](...args) : cxt[func](); delete cxt[func]; return res; }
3.bind的实现
需要考虑:
bind() 除了 this 外,还可传入多个参数; bind 创建的新函数可能传入多个参数; 新函数可能被当做构造函数调用; 函数可能有返回值;
实现方法:
bind 方法不会立即执行,需要返回一个待执行的函数;(闭包) 实现作用域绑定(apply) 参数传递(apply 的数组传参) 当作为构造函数的时候,进行原型继承 Function.prototype.myBind = function (context, ...args) { //新建一个变量赋值为this,表示当前函数 const fn = this //判断有没有传参进来,若为空则赋值[] argsargs = args ? args : [] //返回一个newFn函数,在里面调用fn return function newFn(...newFnArgs) { if (this instanceof newFn) { return new fn(...args, ...newFnArgs) } return fn.apply(context, [...args,...newFnArgs]) } } 测试 let name = 小王,age =17; let obj = { name:小张, age: this.age, myFun: function(from,to){ console.log(this.name + 年龄 + this.age+来自 +from+去往+ to) } } let db = { name: 德玛, age: 99 } //结果 obj.myFun.myCall(db,成都,上海); // 德玛 年龄 99 来自 成都去往上海 obj.myFun.myApply(db,[成都,上海]); // 德玛 年龄 99 来自 成都去往上海 obj.myFun.myBind(db,成都,上海)(); // 德玛 年龄 99 来自 成都去往上海 obj.myFun.myBind(db,[成都,上海])(); // 德玛 年龄 99 来自 成都, 上海去往 undefined
4.寄生式组合继承
function Person(obj) { this.name = obj.name this.age = obj.age } Person.prototype.add = function(value){ console.log(value) } var p1 = new Person({ name:"番茄", age: 18}) function Person1(obj) { Person.call(this, obj) this.sex = obj.sex } // 这一步是继承的关键 Person1.prototype = Object.create(Person.prototype); Person1Person1.prototype.constructor = Person1; Person1.prototype.play = function(value){ console.log(value) } var p2 = new Person1({ name:"鸡蛋", age: 118, sex: "男"})
5.ES6继承
//class 相当于es5中构造函数 //class中定义方法时,前后不能加function,全部定义在class的protopyte属性中 //class中定义的所有方法是不可枚举的 //class中只能定义方法,不能定义对象,变量等 //class和方法内默认都是严格模式 //es5中constructor为隐式属性 class People{ constructor(name=wang,age=27){ this.name = name; this.age = age; } eat(){ console.log(`${ this.name} ${ this.age} eat food`) } } //继承父类 class Woman extends People{ constructor(name = ren,age = 27){ //继承父类属性 super(name, age); } eat(){ //继承父类方法 super.eat() } } let wonmanObj=new Woman(xiaoxiami); wonmanObj.eat(); //es5继承先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。 //es6继承是使用关键字super先创建父类的实例对象this,最后在子类class中修改this。
源码下载 6.new的实现
一个继承自 Foo.prototype 的新对象被创建。 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。 一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤 function Ctor(){ .... } function myNew(ctor,...args){ if(typeof ctor !== function){ throw myNew function the first param must be a function; } var newObj = Object.create(ctor.prototype); //创建一个继承自ctor.prototype的新对象 var ctorctorReturnResult = ctor.apply(newObj, args); //将构造函数ctor的this绑定到newObj中 var isObject = typeof ctorReturnResult === object && ctorReturnResult !== null; var isFunction = typeof ctorReturnResult === function; if(isObject || isFunction){ return ctorReturnResult; } return newObj; } let c = myNew(Ctor);
7.instanceof的实现
instanceof 是用来判断A是否为B的实例,表达式为:A instanceof B,如果A是
云南idc服务商B的实例,则返回true,否则返回false。 instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。 不能检测基本数据类型,在原型链上的结果未必准确,不能检测null,undefined 实现:遍历左边变量的原型链,直到找到右边变量的 prototype,如果没有找到,返回 false function myInstanceOf(a,b){ let left = a.__proto__; let right = b.prototype; while(true){ if(left == null){ return false } if(left == right){ return true } leftleft = left.__proto__ } } //instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。 function myInstanceof(left, right) { let proto = Object.getPrototypeOf(left), // 获取对象的原型 prototype = right.prototype; // 获取构造函数的 prototype 对象 // 判断构造函数的 prototype 对象是否在对象的原型链上 while (true) { if (!proto) return false; if (proto === prototype) return true; proto = Object.getPrototypeOf(proto); } }
8.Object.create()的实现
MDN文档 Object.create()会将参数对象作为一个新创建的空对象的原型, 并返回这个空对象 //简略版 function myCreate(obj){ // 新声明一个函数 function C(){ }; // 将函数的原型指向obj C.prototype = obj; // 返回这个函数的实力化对象 return new C() } //官方版Polyfill if (typeof Object.create !== "function") { Object.create = function (proto, propertiesObject) { if (typeof proto !== object && typeof proto !== function) { throw new TypeError(Object prototype may only be an Object: + proto); } else if (proto === null) { throw new Error("This browsers implementation of Object.create is a shim and doesnt support null as the first argument."); } if (typeof propertiesObject !== undefined) throw new Error("This browsers implementation of Object.create is a shim and doesnt support a second argument."); function F() { } F.prototype = proto; return new F(); }; }
9.实现 Object.assign
Object.assign2 = function(target, ...source) { if (target == null) { throw new TypeError(Cannot convert undefined or null to object) } let ret = Object(target) source.forEach(function(obj) { if (obj != null) { for (let key in obj) { if (obj.hasOwnProperty(key)) { ret[key] = obj[key] } } } }) return ret }
10.Promise的实现
实现 Promise 需要完全读懂 Promise A+ 规范,不过从总体的实现上看,有如下几个点需要考虑到:
Promise本质是一个状态机,且状态只能为以下三种:Pending(等待态)、Fulfilled(执行态)、Rejected(拒绝态),状态的变更是单向的,只能从Pending -> Fulfilled 或 Pending -> Rejected,状态变更不可逆 then 需要支持链式调用 class Promise { callbacks = []; state = pending;//增加状态 value = null;//保存结果 constructor(fn) { fn(this._resolve.bind(this), this._reject.bind(this)); } then(onFulfilled, onRejected) { return new Promise((resolve, reject) => { this._handle({ onFulfilled: onFulfilled || null, onRejected: onRejected || null, resolve: resolve, reject: reject }); }); } _handle(callback) { if (this.state === pending) { this.callbacks.push(callback); return; } let cb = this.state === fulfilled ? callback.onFulfilled : callback.onRejected; if (!cb) { //如果then中没有传递任何东西 cb = this.state === fulfilled ? callback.resolve : callback.reject; cb(this.value); return; } let ret = cb(this.value); cb = this.state === fulfilled ? callback.resolve : callback.reject; cb(ret); } _resolve(value) { if (value && (typeof value === object || typeof value === function)) { var then = value.then; if (typeof then === function) { then.call(value, this._resolve.bind(this), this._reject.bind(this)); return; } } this.state = fulfilled;//改变状态 this.value = value;//保存结果 this.callbacks.forEach(callback => this._handle(callback)); } _reject(error) { this.state = rejected; this.value = error; this.callbacks.forEach(callback => this._handle(callback)); } }
Promise.resolve
Promsie.resolve(value) 可以将任何值转成值为 value 状态是 fulfilled 的 Promise,但如果传入的值本身是 Promise 则会原样返回它。 Promise.resolve(value) { if (value && value instanceof Promise) { return value; } else if (value && typeof value === object && typeof value.then === function) { let then = value.then; return new Promise(resolve => { then(resolve); }); } else if (value) { return new Promise(resolve => resolve(value)); } else { return new Promise(resolve => resolve()); } }
Promise.reject
和 Promise.resolve() 类似,Promise.reject() 会实例化一个 rejected 状态的 Promise。但与 Promise.resolve() 不同的是,如果给 Promise.reject() 传递一个 Promise 对象,则这个对象会成为新 Promise 的值。 Promise.reject = function(reason) { return new Promise((resolve, reject) => reject(reason)) }
Promise.all
传入的所有 Promsie 都是 fulfilled,则返回由他们的值组成的,状态为 fulfilled 的新 Promise; 只要有一个 Promise 是 rejected,则返回 rejected 状态的新 Promsie,且它的值是第一个 rejected 的 Promise 的值; 只要有一个 Promise 是 pending,则返回一个 pending 状态的新 Promise; Promise.all = function(promiseArr) { let index = 0, result = [] return new Promise((resolve, reject) => { promiseArr.forEach((p, i) => { Promise.resolve(p).then(val => { index++ result[i] = val if (index === promiseArr.length) { resolve(result) } }, err => { reject(err) }) }) }) }
Promise.race
Promise.race 会返回一个由所有可迭代实例中第一个 fulfilled 或 rejected 的实例包装后的新实例。 Promise.race = function(promiseArr) { return new Promise((resolve, reject) => { promiseArr.forEach(p => { Promise.resolve(p).then(val => { resolve(val) }, err => { rejecte(err) }) }) }) }
11.Ajax的实现
function ajax(url,method,body,headers){ return new Promise((resolve,reject)=>{ let req = new XMLHttpRequest(); req.open(methods,url); for(let key in headers){ req.setRequestHeader(key,headers[key]) } req.onreadystatechange(()=>{ if(req.readystate == 4){ if(req.status >= 200 && req.status <= 300){ resolve(req.responeText) }else{ reject(req) } } }) req.send(body) }) }
12.实现防抖函数(debounce)
连续触发在最后一次执行方法,场景:输入框匹配 let debounce = (fn,time = 1000) => { let timeLock = null return function (...args){ clearTimeout(timeLock) timeLock = setTimeout(()=>{ fn(...args) },time) } }
13.实现节流函数(throttle)
在一定时间内只触发一次,场景:长列表滚动节流 let throttle = (fn,time = 1000) => { let flag = true; return function (...args){ if(flag){ flag = false; setTimeout(()=>{ flag = true; fn(...args) },time) } } }
14.深拷贝(deepclone)
判断类型,正则和日期直接返回新对象 空或者非对象类型,直接返回原值 考虑循环引用,判断如果hash中含有直接返回hash中的值 新建一个相应的new obj.constructor加入hash 遍历对象递归(普通key和key是symbol情况) function deepClone(obj,hash = new WeakMap()){ if(obj instanceof RegExp) return new RegExp(obj); if(obj instanceof Date) return new Date(obj); if(obj === null || typeof obj !== object) return obj; //循环引用的情况 if(hash.has(obj)){ return hash.get(obj) } //new 一个相应的对象 //obj为Array,相当于new Array() //obj为Object,相当于new Object() let constr = new obj.constructor(); hash.set(obj,constr); for(let key in obj){ if(obj.hasOwnProperty(key)){ constr[key] = deepClone(obj[key],hash) } } //考虑symbol的情况 let symbolObj = Object.getOwnPropertySymbols(obj) for(let i=0;i<symbolObj.length;i++){ if(obj.hasOwnProperty(symbolObj[i])){ constr[symbolObj[i]] = deepClone(obj[symbolObj[i]],hash) } } return constr }
15.数组扁平化的实现(flat)
let arr = [1,2,[3,4,[5,[6]]]] console.log(arr.flat(Infinity))//flat参数为指定要提取嵌套数组的结构深度,默认值为 1 //用reduce实现 function fn(arr){ return arr.reduce((prev,cur)=>{ return prev.concat(Array.isArray(cur)?fn(cur):cur) },[]) }
16.函数柯里化
function sumFn(a,b,c){ return a+ b + c}; let sum = curry(sumFn); sum(2)(3)(5)//10 sum(2,3)(5)//10 function curry(fn,...args){ let fnfnLen = fn.length, argsargsLen = args.length; //对比函数的参数和当前传入参数 //若参数不够就继续递归返回curry //若参数够就调用函数返回相应的值 if(fnLen > argsLen){ return function(...arg2s){ return curry(fn,...args,...arg2s) } }else{ return fn(...args) } }
17.使用闭包实现每隔一秒打印 1,2,3,4
for (var i=1; i<=5; i++) { (function (i) { setTimeout(() => console.log(i), 1000*i) })(i) }
18.手写一个 jsonp
const jsonp = function (url, data) { return new Promise((resolve, reject) => { // 初始化url let dataString = url.indexOf(?) === -1 ? ? : let callbackName = `jsonpCB_${ Date.now()}` url += `${ dataString}callback=${ callbackName}` if (data) { // 有请求参数,依次添加到url for (let k in data) { url += `${ k}=${ data[k]}` } } let jsNode = document.createElement(script) jsNode.src = url // 触发callback,触发后删除js标签和绑定在window上的callback window[callbackName] = result => { delete window[callbackName] document.body.removeChild(jsNode) if (result) { resolve(result) } else { reject(没有返回数据) } } // js加载异常的情况 jsNode.addEventListener(error, () => { delete window[callbackName] document.body.removeChild(jsNode) reject(JavaScript资源加载失败) }, false) // 添加js节点到document上时,开始请求 document.body.appendChild(jsNode) }) } jsonp(http://192.168.0.103:8081/jsonp, { a: 1, b: heiheihei }) .then(result => { console.log(result) }) .catch(err => { console.error(err) })
19.手写一个观察者模式
class Subject{ constructor(name){ this.name = name this.observers = [] this.state = XXXX } // 被观察者要提供一个接受观察者的方法 attach(observer){ this.observers.push(observer) } // 改变被观察着的状态 setState(newState){ this.state = newState this.observers.forEach(o=>{ o.update(newState) }) } } class Observer{ constructor(name){ this.name = name } update(newState){ console.log(`${ this.name}say:${ newState}`) } } // 被观察者 灯 let sub = new Subject(灯) let mm = new Observer(小明) let jj = new Observer(小健) // 订阅 观察者 sub.attach(mm) sub.attach(jj) sub.setState(灯亮了来电了)
20.EventEmitter 实现
class EventEmitter { constructor() { this.events = { }; } on(event, callback) { let callbacks = this.events[event] || []; callbacks.push(callback); this.events[event] = callbacks; return this; } off(event, callback) { let callbacks = this.events[event]; this.events[event] = callbacks && callbacks.filter(fn => fn !== callback); return this; } emit(event, ...args) { let callbacks = this.events[event]; callbacks.forEach(fn => { fn(...args); }); return this; } once(event, callback) { let wrapFun = function (...args) { callback(...args); this.off(event, wrapFun); }; this.on(event, wrapFun); return this; } }
21.生成随机数的各种方法?
function getRandom(min, max) { return Math.floor(Math.random() * (max - min)) + min }
22.如何实现数组的随机排序?
let arr = [2,3,454,34,324,32] arr.sort(randomSort) function randomSort(a, b) { return Math.random() > 0.5 ? -1 : 1; }
23.写一个通用的事件侦听器函数。
const EventUtils = { // 视能力分别使用dom0||dom2||IE方式 来绑定事件 // 添加事件 addEvent: function(element, type, handler) { if (element.addEventListener) { element.addEventListener(type, handler, false); } else if (element.attachEvent) { element.attachEvent("on" + type, handler); } else { element["on" + type] = handler; } }, // 移除事件 removeEvent: function(element, type, handler) { if (element.removeEventListener) { element.removeEventListener(type, handler, false); } else if (element.detachEvent) { element.detachEvent("on" + type, handler); } else { element["on" + type] = null; } }, // 获取事件目标 getTarget: function(event) { return event.target || event.srcElement; }, // 获取 event 对象的引用,取到事件的所有信息,确保随时能使用 event getEvent: function(event) { return event || window.event; }, // 阻止事件(主要是事件冒泡,因为 IE 不支持事件捕获) stopPropagation: function(event) { if (event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble = true; } }, // 取消事件的默认行为 preventDefault: function(event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; } } };
24.使用迭代的方式实现 flatten 函数。
var arr = [1, 2, 3, [4, 5], [6, [7, [8]]]] /** * 使用递归的方式处理 * wrap 内保 存结果 ret * 返回一个递归函数 **/ function wrap() { var ret = []; return function flat(a) { for (var item of a) { if (item.constructor === Array) { ret.concat(flat(item)) } else { ret.push(item) } } return ret } } console.log(wrap()(arr));
25.怎么实现一个sleep
sleep函数作用是让线程休眠,等到指定时间在重新唤起。 function sleep(delay) { var start = (new Date()).getTime(); while ((new Date()).getTime() - start < delay) { continue; } } function test() { console.log(111); sleep(2000); console.log(222); } test()
26.实现正则切分千分位(10000 => 10,000)
//无小数点 let num1 = 1321434322222 num1.replace(/(\d)(?=(\d{ 3})+$)/g,$1,) //有小数点 let num2 = 342243242322.3432423 num2.replace(/(\d)(?=(\d{ 3})+\.)/g,$1,)
27.对象数组去重
输入: [{ a:1,b:2,c:3},{ b:2,c:3,a:1},{ d:2,c:2}] 输出: [{ a:1,b:2,c:3},{ d:2,c:2}] 首先写一个函数把对象中的key排序,然后再转成字符串 遍历数组利用Set将转为字符串后的对象去重 function objSort(obj){ let newObj = { } //遍历对象,并将key进行排序 Object.keys(obj).sort().map(key => { newObj[key] = obj[key] }) //将排序好的数组转成字符串 return JSON.stringify(newObj) } function unique(arr){ let set = new Set(); for(let i=0;i<arr.length;i++){ let str = objSort(arr[i]) set.add(str) } //将数组中的字符串转回对象 arr = [...set].map(item => { return JSON.parse(item) }) return arr }
28.解析 URL Params 为对象
let url = http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled; parseParam(url) /* 结果 { user: anonymous, id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型 city: 北京, // 中文需解码 enabled: true, // 未指定值得 key 约定为 true } */ function parseParam(url) { const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来 const paramsArr = paramsStr.split(&); // 将字符串以 & 分割后存到数组中 let paramsObj = { }; // 将 params 存到对象中 paramsArr.forEach(param => { if (/=/.test(param)) { // 处理有 value 的参数 let [key, val] = param.split(=); // 分割 key 和 value val = decodeURIComponent(val); // 解码 val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字 if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值 paramsObj[key] = [].concat(paramsObj[key], val); } else { // 如果对象没有这个 key,创建 key 并设置值 paramsObj[key] = val; } } else { // 处理没有 value 的参数 paramsObj[param] = true; } }) return paramsObj; }
29.模板引擎实现
let template = 我是{ { name}},年龄{ { age}},性别{ { sex}}; let data = { name: 姓名, age: 18 } render(template, data); // 我是姓名,年龄18,性别undefined function render(template, data) { const reg = /\{ \{ (\w+)\}\}/; // 模板字符串正则 if (reg.test(template)) { // 判断模板里是否有模板字符串 const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段 templatetemplate = template.replace(reg, data[name]); // 将第一个模板字符串渲染 return render(template, data); // 递归的渲染并返回渲染后的结构 } return template; // 如果模板没有模板字符串直接返回 }
30.转化为驼峰命名
var s1 = "get-element-by-id" // 转化为 getElementById var f = function(s) { return s.replace(/-\w/g, function(x) { return x.slice(1).toUpperCase(); }) }
31.查找字符串中出现最多的字符和个数
例: abbcccddddd -> 字符最多的是d,出现了5次 let str = "abcabcabcbbccccc"; let num = 0; let char = ; // 使其按照一定的次序排列 strstr = str.split().sort().join(); // "aaabbbbbcccccccc" // 定义正则表达式 let re = /(\w)\1+/g; str.replace(re,($0,$1) => { if(num < $0.length){ num = $0.length; char = $1; } }); console.log(`字符最多的是${ char},出现了${ num}次`);
32.图片懒加载
let imgList = [...document.querySelectorAll(img)] let length = imgList.length const imgLazyLoad = function() { let count = 0 return (function() { let deleteIndexList = [] imgList.forEach((img, index) => { let rect = img.getBoundingClientRect() if (rect.top < window.innerHeight) { imgimg.src = img.dataset.src deleteIndexList.push(index) count++ if (count === length) { document.removeEventListener(scroll, imgLazyLoad) } } }) imgListimgList = imgList.filter((img, index) => !deleteIndexList.includes(index)) })() } // 这里最好加上防抖处理 document.addEventListener(scroll, imgLazyLoad)