本文涵蓋了前端面試常考的各類重點手寫。
建議優先掌握:html
instanceof做用:前端
判斷一個實例是不是其父類或者祖先類型的實例。es6
instanceof 在查找的過程當中會遍歷左邊變量的原型鏈,直到找到右邊變量的 prototype查找失敗,返回 false面試
let myInstanceof = (target,origin) => { while(target) { if(target.__proto__===origin.prototype) { return true } target = target.__proto__ } return false } let a = [1,2,3] console.log(myInstanceof(a,Array)); // true console.log(myInstanceof(a,Object)); // true
Array.prototype.myMap = function(fn, thisValue) { let res = [] thisValue = thisValue||[] let arr = this for(let i in arr) { res.push(fn(arr[i])) } return res }
Array.prototype.myMap = function(fn,thisValue){ var res = []; thisValue = thisValue||[]; this.reduce(function(pre,cur,index,arr){ return res.push(fn.call(thisValue,cur,index,arr)); },[]); return res; } var arr = [2,3,1,5]; arr.myMap(function(item,index,arr){ console.log(item,index,arr); })
reduce() 方法接收一個函數做爲累加器,數組中的每一個值(從左到右)開始縮減,最終爲一個值,是ES5中新增的又一個數組逐項處理方法ajax
參數:json
callback(一個在數組中每一項上調用的函數,接受四個函數:)數組
function reduce(arr, cb, initialValue){ var num = initValue == undefined? num = arr[0]: initValue; var i = initValue == undefined? 1: 0 for (i; i< arr.length; i++){ num = cb(num,arr[i],i) } return num } function fn(result, currentValue, index){ return result + currentValue } var arr = [2,3,4,5] var b = reduce(arr, fn,10) var c = reduce(arr, fn) console.log(b) // 24
數組扁平化就是把多維數組轉化成一維數組promise
1. es6提供的新方法 flat(depth)瀏覽器
let a = [1,[2,3]]; a.flat(); // [1,2,3] a.flat(1); //[1,2,3]
其實還有一種更簡單的辦法,無需知道數組的維度,直接將目標數組變成1維數組。 depth的值設置爲Infinity。服務器
let a = [1,[2,3,[4,[5]]]]; a.flat(Infinity); // [1,2,3,4,5] a是4維數組
2. 利用cancat
var arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]]; function flatten(arr) { var res = []; for (let i = 0, length = arr.length; i < length; i++) { if (Array.isArray(arr[i])) { res = res.concat(flatten(arr[i])); //concat 並不會改變原數組 //res.push(...flatten(arr[i])); //擴展運算符 } else { res.push(arr[i]); } } return res; } flatten(arr1); //[1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
柯里化的定義:接收一部分參數,返回一個函數接收剩餘參數,接收足夠參數後,執行原函數。
當柯里化函數接收到足夠參數後,就會執行原函數,如何去肯定什麼時候達到足夠的參數呢?
有兩種思路:
將這兩點結合一下,實現一個簡單 curry 函數:
/** * 將函數柯里化 * @param fn 待柯里化的原函數 * @param len 所需的參數個數,默認爲原函數的形參個數 */ function curry(fn,len = fn.length) { return _curry.call(this,fn,len) } /** * 中轉函數 * @param fn 待柯里化的原函數 * @param len 所需的參數個數 * @param args 已接收的參數列表 */ function _curry(fn,len,...args) { return function (...params) { let _args = [...args,...params]; if(_args.length >= len){ return fn.apply(this,_args); }else{ return _curry.call(this,fn,len,..._args) } } }
咱們來驗證一下:
let _fn = curry(function(a,b,c,d,e){ console.log(a,b,c,d,e) }); _fn(1,2,3,4,5); // print: 1,2,3,4,5 _fn(1)(2)(3,4,5); // print: 1,2,3,4,5 _fn(1,2)(3,4)(5); // print: 1,2,3,4,5 _fn(1)(2)(3)(4)(5); // print: 1,2,3,4,5
咱們經常使用的工具庫 lodash 也提供了 curry 方法,而且增長了很是好玩的 placeholder 功能,經過佔位符的方式來改變傳入參數的順序。
好比說,咱們傳入一個佔位符,本次調用傳遞的參數略過佔位符, 佔位符所在的位置由下次調用的參數來填充,好比這樣:
直接看一下官網的例子:
接下來咱們來思考,如何實現佔位符的功能。
對於 lodash 的 curry 函數來講,curry 函數掛載在 lodash 對象上,因此將 lodash 對象當作默認佔位符來使用。
而咱們的本身實現的 curry 函數,自己並無掛載在任何對象上,因此將 curry 函數當作默認佔位符
使用佔位符,目的是改變參數傳遞的順序,因此在 curry 函數實現中,每次須要記錄是否使用了佔位符,而且記錄佔位符所表明的參數位置。
直接上代碼:
/** * @param fn 待柯里化的函數 * @param length 須要的參數個數,默認爲函數的形參個數 * @param holder 佔位符,默認當前柯里化函數 * @return {Function} 柯里化後的函數 */ function curry(fn,length = fn.length,holder = curry){ return _curry.call(this,fn,length,holder,[],[]) } /** * 中轉函數 * @param fn 柯里化的原函數 * @param length 原函數須要的參數個數 * @param holder 接收的佔位符 * @param args 已接收的參數列表 * @param holders 已接收的佔位符位置列表 * @return {Function} 繼續柯里化的函數 或 最終結果 */ function _curry(fn,length,holder,args,holders){ return function(..._args){ //將參數複製一份,避免屢次操做同一函數致使參數混亂 let params = args.slice(); //將佔位符位置列表複製一份,新增長的佔位符增長至此 let _holders = holders.slice(); //循環入參,追加參數 或 替換佔位符 _args.forEach((arg,i)=>{ //真實參數 以前存在佔位符 將佔位符替換爲真實參數 if (arg !== holder && holders.length) { let index = holders.shift(); _holders.splice(_holders.indexOf(index),1); params[index] = arg; } //真實參數 以前不存在佔位符 將參數追加到參數列表中 else if(arg !== holder && !holders.length){ params.push(arg); } //傳入的是佔位符,以前不存在佔位符 記錄佔位符的位置 else if(arg === holder && !holders.length){ params.push(arg); _holders.push(params.length - 1); } //傳入的是佔位符,以前存在佔位符 刪除原佔位符位置 else if(arg === holder && holders.length){ holders.shift(); } }); // params 中前 length 條記錄中不包含佔位符,執行函數 if(params.length >= length && params.slice(0,length).every(i=>i!==holder)){ return fn.apply(this,params); }else{ return _curry.call(this,fn,length,holder,params,_holders) } } }
驗證一下:;
let fn = function(a, b, c, d, e) { console.log([a, b, c, d, e]); } let _ = {}; // 定義佔位符 let _fn = curry(fn,5,_); // 將函數柯里化,指定所需的參數個數,指定所需的佔位符 _fn(1, 2, 3, 4, 5); // print: 1,2,3,4,5 _fn(_, 2, 3, 4, 5)(1); // print: 1,2,3,4,5 _fn(1, _, 3, 4, 5)(2); // print: 1,2,3,4,5 _fn(1, _, 3)(_, 4,_)(2)(5); // print: 1,2,3,4,5 _fn(1, _, _, 4)(_, 3)(2)(5); // print: 1,2,3,4,5 _fn(_, 2)(_, _, 4)(1)(3)(5); // print: 1,2,3,4,5
至此,咱們已經完整實現了一個 curry 函數~~
淺拷貝和深拷貝的區別:
淺拷貝:只拷貝一層,更深層的對象級別的只拷貝引用
深拷貝:拷貝多層,每一級別的數據都會拷貝。這樣更改拷貝值就不影響另外的對象
ES6淺拷貝方法:Object.assign(target,...sources)
let obj={ id:1, name:'Tom', msg:{ age:18 } } let o={} //實現深拷貝 遞歸 能夠用於生命遊戲那個題對二維數組的拷貝, //但比較麻煩,由於已知元素都是值,直接複製就行,無需判斷 function deepCopy(newObj,oldObj){ for(var k in oldObj){ let item=oldObj[k] //判斷是數組?對象?簡單類型? if(item instanceof Array){ newObj[k]=[] deepCopy(newObj[k],item) }else if(item instanceof Object){ newObj[k]={} deepCopy(newObj[k],item) }else{ //簡單數據類型,直接賦值 newObj[k]=item } } }
手寫call
Function.prototype.myCall=function(context=window){ // 函數的方法,因此寫在Fuction原型對象上 if(typeof this !=="function"){ // 這裏if其實不必,會自動拋出錯誤 throw new Error("不是函數") } const obj=context||window //這裏可用ES6方法,爲參數添加默認值,js嚴格模式全局做用域this爲undefined obj.fn=this //this爲調用的上下文,this此處爲函數,將這個函數做爲obj的方法 const arg=[...arguments].slice(1) //第一個爲obj因此刪除,僞數組轉爲數組 res=obj.fn(...arg) delete obj.fn // 不刪除會致使context屬性愈來愈多 return res }
//用法:f.call(obj,arg1) function f(a,b){ console.log(a+b) console.log(this.name) } let obj={ name:1 } f.myCall(obj,1,2) //不然this指向window obj.greet.call({name: 'Spike'}) //打出來的是 Spike
手寫apply(arguments[this, [參數1,參數2.....] ])
Function.prototype.myApply=function(context){ // 箭頭函數從不具備參數對象!!!!!這裏不能寫成箭頭函數 let obj=context||window obj.fn=this const arg=arguments[1]||[] //如有參數,獲得的是數組 let res=obj.fn(...arg) delete obj.fn return res } function f(a,b){ console.log(a,b) console.log(this.name) } let obj={ name:'張三' } f.myApply(obj,[1,2]) //arguments[1]
手寫bind
this.value = 2 var foo = { value: 1 }; var bar = function(name, age, school){ console.log(name) // 'An' console.log(age) // 22 console.log(school) // '家裏蹲大學' } var result = bar.bind(foo, 'An') //預置了部分參數'An' result(22, '家裏蹲大學') //這個參數會和預置的參數合併到一塊兒放入bar中
簡單版本
Function.prototype.bind = function(context, ...outerArgs) { var fn = this; return function(...innerArgs) { //返回了一個函數,...rest爲實際調用時傳入的參數 return fn.apply(context,[...outerArgs, ...innerArgs]); //返回改變了this的函數, //參數合併 } }
new失敗的緣由:
例:
// 聲明一個上下文 let thovino = { name: 'thovino' } // 聲明一個構造函數 let eat = function (food) { this.food = food console.log(`${this.name} eat ${this.food}`) } eat.prototype.sayFuncName = function () { console.log('func name : eat') } // bind一下 let thovinoEat = eat.bind(thovino) let instance = new thovinoEat('orange') //實際上orange放到了thovino裏面 console.log('instance:', instance) // {}
生成的實例是個空對象
在new
操做符執行時,咱們的thovinoEat
函數能夠看做是這樣:
function thovinoEat (...innerArgs) { eat.call(thovino, ...outerArgs, ...innerArgs) }
在new操做符進行到第三步的操做thovinoEat.call(obj, ...args)
時,這裏的obj
是new操做符本身建立的那個簡單空對象{}
,但它其實並無替換掉thovinoEat
函數內部的那個上下文對象thovino
。這已經超出了call
的能力範圍,由於這個時候要替換的已經不是thovinoEat
函數內部的this
指向,而應該是thovino
對象。
換句話說,咱們但願的是new
操做符將eat
內的this
指向操做符本身建立的那個空對象。可是實際上指向了thovino
,new
操做符的第三步動做並無成功!
可new可繼承版本
Function.prototype.bind = function (context, ...outerArgs) { let that = this; function res (...innerArgs) { if (this instanceof res) { // new操做符執行時 // 這裏的this在new操做符第三步操做時,會指向new自身建立的那個簡單空對象{} that.call(this, ...outerArgs, ...innerArgs) } else { // 普通bind that.call(context, ...outerArgs, ...innerArgs) } } res.prototype = this.prototype //!!! return res }
new的過程文字描述:
function Person(name,age){ this.name=name this.age=age } Person.prototype.sayHi=function(){ console.log('Hi!我是'+this.name) } let p1=new Person('張三',18) ////手動實現new function create(){ let obj={} //獲取構造函數 let fn=[].shift.call(arguments) //將arguments對象提出來轉化爲數組,arguments並非數組而是對象 !!!這種方法刪除了arguments數組的第一個元素,!!這裏的空數組裏面填不填元素都不要緊,不影響arguments的結果 或者let arg = [].slice.call(arguments,1) obj.__proto__=fn.prototype let res=fn.apply(obj,arguments) //改變this指向,爲實例添加方法和屬性 //確保返回的是一個對象(萬一fn不是構造函數) return typeof res==='object'?res:obj } let p2=create(Person,'李四',19) p2.sayHi()
細節:
[].shift.call(arguments) 也可寫成: let arg=[...arguments] let fn=arg.shift() //使得arguments能調用數組方法,第一個參數爲構造函數 obj.__proto__=fn.prototype //改變this指向,爲實例添加方法和屬性 let res=fn.apply(obj,arg)
// Promise/A+ 規範規定的三種狀態 const STATUS = { PENDING: 'pending', FULFILLED: 'fulfilled', REJECTED: 'rejected' } class MyPromise { // 構造函數接收一個執行回調 constructor(executor) { this._status = STATUS.PENDING // Promise初始狀態 this._value = undefined // then回調的值 this._resolveQueue = [] // resolve時觸發的成功隊列 this._rejectQueue = [] // reject時觸發的失敗隊列 // 使用箭頭函數固定this(resolve函數在executor中觸發,否則找不到this) const resolve = value => { const run = () => { // Promise/A+ 規範規定的Promise狀態只能從pending觸發,變成fulfilled if (this._status === STATUS.PENDING) { this._status = STATUS.FULFILLED // 更改狀態 this._value = value // 儲存當前值,用於then回調 // 執行resolve回調 while (this._resolveQueue.length) { const callback = this._resolveQueue.shift() callback(value) } } } //把resolve執行回調的操做封裝成一個函數,放進setTimeout裏,以實現promise異步調用的特性(規範上是微任務,這裏是宏任務) setTimeout(run) } // 同 resolve const reject = value => { const run = () => { if (this._status === STATUS.PENDING) { this._status = STATUS.REJECTED this._value = value while (this._rejectQueue.length) { const callback = this._rejectQueue.shift() callback(value) } } } setTimeout(run) } // new Promise()時當即執行executor,並傳入resolve和reject executor(resolve, reject) } // then方法,接收一個成功的回調和一個失敗的回調 function then(onFulfilled, onRejected) { // 根據規範,若是then的參數不是function,則忽略它, 讓值繼續往下傳遞,鏈式調用繼續往下執行 typeof onFulfilled !== 'function' ? onFulfilled = value => value : null typeof onRejected !== 'function' ? onRejected = error => error : null // then 返回一個新的promise return new MyPromise((resolve, reject) => { const resolveFn = value => { try { const x = onFulfilled(value) // 分類討論返回值,若是是Promise,那麼等待Promise狀態變動,不然直接resolve x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } } } const rejectFn = error => { try { const x = onRejected(error) x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } switch (this._status) { case STATUS.PENDING: this._resolveQueue.push(resolveFn) this._rejectQueue.push(rejectFn) break; case STATUS.FULFILLED: resolveFn(this._value) break; case STATUS.REJECTED: rejectFn(this._value) break; } }) } catch (rejectFn) { return this.then(undefined, rejectFn) } // promise.finally方法 finally(callback) { return this.then(value => MyPromise.resolve(callback()).then(() => value), error => { MyPromise.resolve(callback()).then(() => error) }) } // 靜態resolve方法 static resolve(value) { return value instanceof MyPromise ? value : new MyPromise(resolve => resolve(value)) } // 靜態reject方法 static reject(error) { return new MyPromise((resolve, reject) => reject(error)) } // 靜態all方法 static all(promiseArr) { let count = 0 let result = [] return new MyPromise((resolve, reject) => { if (!promiseArr.length) { return resolve(result) } promiseArr.forEach((p, i) => { MyPromise.resolve(p).then(value => { count++ result[i] = value if (count === promiseArr.length) { resolve(result) } }, error => { reject(error) }) }) }) } // 靜態race方法 static race(promiseArr) { return new MyPromise((resolve, reject) => { promiseArr.forEach(p => { MyPromise.resolve(p).then(value => { resolve(value) }, error => { reject(error) }) }) }) } }
步驟
不過隨着歷史進程的推動,XML 已經被淘汰,取而代之的是 JSON。
瞭解了屬性和方法以後,根據 AJAX 的步驟,手寫最簡單的 GET 請求。
version 1.0:
myButton.addEventListener('click', function () { ajax() }) function ajax() { let xhr = new XMLHttpRequest() //實例化,以調用方法 xhr.open('get', 'https://www.google.com') //參數2,url。參數三:異步 xhr.onreadystatechange = () => { //每當 readyState 屬性改變時,就會調用該函數。 if (xhr.readyState === 4) { //XMLHttpRequest 代理當前所處狀態。 if (xhr.status >= 200 && xhr.status < 300) { //200-300請求成功 let string = request.responseText //JSON.parse() 方法用來解析JSON字符串,構造由字符串描述的JavaScript值或對象 let object = JSON.parse(string) } } } request.send() //用於實際發出 HTTP 請求。不帶參數爲GET請求 }
promise實現
function ajax(url) { const p = new Promise((resolve, reject) => { let xhr = new XMLHttpRequest() xhr.open('get', url) xhr.onreadystatechange = () => { if (xhr.readyState == 4) { if (xhr.status >= 200 && xhr.status <= 300) { resolve(JSON.parse(xhr.responseText)) } else { reject('請求出錯') } } } xhr.send() //發送hppt請求 }) return p } let url = '/data.json' ajax(url).then(res => console.log(res)) .catch(reason => console.log(reason))
防抖:
function debounce(fn, delay) { if(typeof fn!=='function') { throw new TypeError('fn不是函數') } let timer; // 維護一個 timer return function () { var _this = this; // 取debounce執行做用域的this(原函數掛載到的對象) var args = arguments; if (timer) { clearTimeout(timer); } timer = setTimeout(function () { fn.apply(_this, args); // 用apply指向調用debounce的對象,至關於_this.fn(args); }, delay); }; } input1.addEventListener('keyup', debounce(() => { console.log(input1.value) }), 600)
節流:
function throttle(fn, delay) { let timer; return function () { var _this = this; var args = arguments; if (timer) { return; } timer = setTimeout(function () { fn.apply(_this, args); // 這裏args接收的是外邊返回的函數的參數,不能用arguments // fn.apply(_this, arguments); 須要注意:Chrome 14 以及 Internet Explorer 9 仍然不接受類數組對象。若是傳入類數組對象,它們會拋出異常。 timer = null; // 在delay後執行完fn以後清空timer,此時timer爲假,throttle觸發能夠進入計時器 }, delay) } } div1.addEventListener('drag', throttle((e) => { console.log(e.offsetX, e.offsetY) }, 100))
function getData(url) { return new Promise((resolve, reject) => { $.ajax({ url, success(data) { resolve(data) }, error(err) { reject(err) } }) }) } const url1 = './data1.json' const url2 = './data2.json' const url3 = './data3.json' getData(url1).then(data1 => { console.log(data1) return getData(url2) }).then(data2 => { console.log(data2) return getData(url3) }).then(data3 => console.log(data3) ).catch(err => console.error(err) )
for(let i=0;i<=10;i++){ //用var打印的都是11 setTimeout(()=>{ console.log(i); },1000*i) }
var a for(let i=0;i<10;i++){ a=document.createElement('a') a.innerHTML=i+'<br>' a.addEventListener('click',function(e){ console.log(this) //this爲當前點擊的<a> e.preventDefault() //若是調用這個方法,默認事件行爲將再也不觸發。 //例如,在執行這個方法後,若是點擊一個連接(a標籤),瀏覽器不會跳轉到新的 URL 去了。咱們能夠用 event.isDefaultPrevented() 來肯定這個方法是否(在那個事件對象上)被調用過了。 alert(i) }) const d=document.querySelector('div') d.appendChild(a) //append向一個已存在的元素追加該元素。 }
數組扁平化 https://juejin.im/post/5c971ee16fb9a070ce31b64e#heading-3