32個手寫JS,鞏固你的JS基礎(chǔ)(面試高頻)

來源 | https://juejin.im/post/6875152247714480136
作為前端開發(fā),JS是重中之重,最近結(jié)束了面試的高峰期,基本上offer也定下來了就等開獎,趁著這個時間總結(jié)下32個手寫JS問題,這些都是高頻面試題,希望對你能有所幫助。
關(guān)于源碼都緊遵規(guī)范,都可跑通MDN示例,其余的大多會涉及一些關(guān)于JS的應(yīng)用題和本人面試過程
01、數(shù)組扁平化
數(shù)組扁平化是指將一個多維數(shù)組變?yōu)橐粋€一維數(shù)組
const arr = [1, [2, [3, [4, 5]]], 6];// => [1, 2, 3, 4, 5, 6]
方法一:使用flat()
const res1 = arr.flat(Infinity);
方法二:利用正則
const res2 = JSON.stringify(arr).replace(/\[|\]/g, '').split(',');
但數(shù)據(jù)類型都會變?yōu)樽址?/span>
方法三:正則改良版本
const res3 = JSON.parse('[' + JSON.stringify(arr).replace(/\[|\]/g, '') + ']');
方法四:使用reduce
const flatten = arr => {return arr.reduce((pre, cur) => {return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);}, [])}const res4 = flatten(arr);
方法五:函數(shù)遞歸
const res5 = [];const fn = arr => {for (let i = 0; i < arr.length; i++) {if (Array.isArray(arr[i])) {fn(arr[i]);} else {res5.push(arr[i]);}}}fn(arr);
02、數(shù)組去重
const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];// => [1, '1', 17, true, false, 'true', 'a', {}, {}]
方法一:利用Set
const res1 = Array.from(new Set(arr));
方法二:兩層for循環(huán)+splice
const unique1 = arr => {let len = arr.length;for (let i = 0; i < len; i++) {for (let j = i + 1; j < len; j++) {if (arr[i] === arr[j]) {arr.splice(j, 1);// 每刪除一個樹,j--保證j的值經(jīng)過自加后不變。同時,len--,減少循環(huán)次數(shù)提升性能len--;j--;}}}return arr;}
方法三:利用indexOf
const unique2 = arr => {const res = [];for (let i = 0; i < arr.length; i++) {if (res.indexOf(arr[i]) === -1) res.push(arr[i]);}return res;}
當(dāng)然也可以用include、filter,思路大同小異。
方法四:利用include
const unique3 = arr => {const res = [];for (let i = 0; i < arr.length; i++) {if (!res.includes(arr[i])) res.push(arr[i]);}return res;}
方法五:利用filter
const unique4 = arr => {return arr.filter((item, index) => {return arr.indexOf(item) === index;});}
方法六:利用Map
const unique5 = arr => {const map = new Map();const res = [];for (let i = 0; i < arr.length; i++) {if (!map.has(arr[i])) {map.set(arr[i], true)res.push(arr[i]);}}return res;}
03、類數(shù)組轉(zhuǎn)化為數(shù)組
類數(shù)組是具有length屬性,但不具有數(shù)組原型上的方法。常見的類數(shù)組有arguments、DOM操作方法返回的結(jié)果。
方法一:Array.from
Array.from(document.querySelectorAll('div'))
方法二:Array.prototype.slice.call()
Array.prototype.slice.call(document.querySelectorAll('div'))
方法三:擴展運算符
[...document.querySelectorAll('div')]
方法四:利用concat
Array.prototype.concat.apply([], document.querySelectorAll('div'));
04、Array.prototype.filter()

Array.prototype.filter = function(callback, thisArg) {if (this == undefined) {throw new TypeError('this is null or not undefined');}if (typeof callback !== 'function') {throw new TypeError(callback + 'is not a function');}const res = [];// 讓O成為回調(diào)函數(shù)的對象傳遞(強制轉(zhuǎn)換對象)const O = Object(this);// >>>0 保證len為number,且為正整數(shù)const len = O.length >>> 0;for (let i = 0; i < len; i++) {// 檢查i是否在O的屬性(會檢查原型鏈)if (i in O) {// 回調(diào)函數(shù)調(diào)用傳參if (callback.call(thisArg, O[i], i, O)) {res.push(O[i]);}}}return res;}
對于>>>0有疑問的:解釋>>>0的作用
05、Array.prototype.map()

Array.prototype.map = function(callback, thisArg) {if (this == undefined) {throw new TypeError('this is null or not defined');}if (typeof callback !== 'function') {throw new TypeError(callback + ' is not a function');}const res = [];// 同理const O = Object(this);const len = O.length >>> 0;for (let i = 0; i < len; i++) {if (i in O) {// 調(diào)用回調(diào)函數(shù)并傳入新數(shù)組res[i] = callback.call(thisArg, O[i], i, this);}}return res;}
06、Array.prototype.forEach()
forEach跟map類似,唯一不同的是forEach是沒有返回值的。
Array.prototype.forEach = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined');}if (typeof callback !== "function") {throw new TypeError(callback + ' is not a function');}const O = Object(this);const len = O.length >>> 0;let k = 0;while (k < len) {if (k in O) {callback.call(thisArg, O[k], k, O);}k++;}}
07、Array.prototype.reduce()

Array.prototype.reduce = function(callback, initialValue) {if (this == undefined) {throw new TypeError('this is null or not defined');}if (typeof callback !== 'function') {throw new TypeError(callbackfn + ' is not a function');}const O = Object(this);const len = this.length >>> 0;let accumulator = initialValue;let k = 0;// 如果第二個參數(shù)為undefined的情況下// 則數(shù)組的第一個有效值作為累加器的初始值if (accumulator === undefined) {while (k < len && !(k in O)) {k++;}// 如果超出數(shù)組界限還沒有找到累加器的初始值,則TypeErrorif (k >= len) {throw new TypeError('Reduce of empty array with no initial value');}accumulator = O[k++];}while (k < len) {if (k in O) {accumulator = callback.call(undefined, accumulator, O[k], k, O);}k++;}return accumulator;}
08、Function.prototype.apply()
第一個參數(shù)是綁定的this,默認(rèn)為window,第二個參數(shù)是數(shù)組或類數(shù)組
Function.prototype.apply = function(context = window, args) {if (typeof this !== 'function') {throw new TypeError('Type Error');}const fn = Symbol('fn');context[fn] = this;const res = context[fn](...args);delete context[fn];return res;}
09、Function.prototype.call
于call唯一不同的是,call()方法接受的是一個參數(shù)列表
Function.prototype.call = function(context = window, ...args) {if (typeof this !== 'function') {throw new TypeError('Type Error');}const fn = Symbol('fn');context[fn] = this;const res = context[fn](...args);delete context[fn];return res;}
10、Function.prototype.bind
Function.prototype.bind = function(context, ...args) {if (typeof this !== 'function') {throw new Error("Type Error");}// 保存this的值var self = this;return function F() {// 考慮new的情況if(this instanceof F) {return new self(...args, ...arguments)}return self.apply(context, [...args, ...arguments])}}
11、debounce(防抖)
觸發(fā)高頻時間后n秒內(nèi)函數(shù)只會執(zhí)行一次,如果n秒內(nèi)高頻時間再次觸發(fā),則重新計算時間。
const debounce = (fn, time) => {let timeout = null;return function() {clearTimeout(timeout)timeout = setTimeout(() => {fn.apply(this, arguments);}, time);}};
防抖常應(yīng)用于用戶進行搜索輸入節(jié)約請求資源,window觸發(fā)resize事件時進行防抖只觸發(fā)一次。
12、throttle(節(jié)流)
高頻時間觸發(fā),但n秒內(nèi)只會執(zhí)行一次,所以節(jié)流會稀釋函數(shù)的執(zhí)行頻率。
const throttle = (fn, time) => {let flag = true;return function() {if (!flag) return;flag = false;setTimeout(() => {fn.apply(this, arguments);flag = true;}, time);}}
節(jié)流常應(yīng)用于鼠標(biāo)不斷點擊觸發(fā)、監(jiān)聽滾動事件。
13、函數(shù)珂里化
指的是將一個接受多個參數(shù)的函數(shù) 變?yōu)?接受一個參數(shù)返回一個函數(shù)的固定形式,這樣便于再次調(diào)用,例如f(1)(2)
經(jīng)典面試題:實現(xiàn)add(1)(2)(3)(4)=10; 、 add(1)(1,2,3)(2)=9;
function add() {const _args = [...arguments];function fn() {_args.push(...arguments);return fn;}fn.toString = function() {return _args.reduce((sum, cur) => sum + cur);}return fn;}
14、模擬new操作
3個步驟:
以
ctor.prototype為原型創(chuàng)建一個對象。執(zhí)行構(gòu)造函數(shù)并將this綁定到新創(chuàng)建的對象上。
判斷構(gòu)造函數(shù)執(zhí)行返回的結(jié)果是否是引用數(shù)據(jù)類型,若是則返回構(gòu)造函數(shù)執(zhí)行的結(jié)果,否則返回創(chuàng)建的對象。
function newOperator(ctor, ...args) {if (typeof ctor !== 'function') {throw new TypeError('Type Error');}const obj = Object.create(ctor.prototype);const res = ctor.apply(obj, args);const isObject = typeof res === 'object' && res !== null;const isFunction = typeof res === 'function';return isObject || isFunction ? res : obj;}
15、instanceof
instanceof運算符用于檢測構(gòu)造函數(shù)的prototype屬性是否出現(xiàn)在某個實例對象的原型鏈上。
const myInstanceof = (left, right) => {// 基本數(shù)據(jù)類型都返回falseif (typeof left !== 'object' || left === null) return false;let proto = Object.getPrototypeOf(left);while (true) {if (proto === null) return false;if (proto === right.prototype) return true;proto = Object.getPrototypeOf(proto);}}
16、原型繼承
這里只寫寄生組合繼承了,中間還有幾個演變過來的繼承但都有一些缺陷
function Parent() {this.name = 'parent';}function Child() {Parent.call(this);this.type = 'children';}Child.prototype = Object.create(Parent.prototype);Child.prototype.constructor = Child;
17、Object.is
Object.is解決的主要是這兩個問題:
+0 === -0 // trueNaN === NaN // false
const is= (x, y) => {if (x === y) {// +0和-0應(yīng)該不相等return x !== 0 || y !== 0 || 1/x === 1/y;} else {return x !== x && y !== y;}}
18、Object.assign
Object.assign()方法用于將所有可枚舉屬性的值從一個或多個源對象復(fù)制到目標(biāo)對象。它將返回目標(biāo)對象(請注意這個操作是淺拷貝)
Object.defineProperty(Object, 'assign', {value: function(target, ...args) {if (target == null) {return new TypeError('Cannot convert undefined or null to object');}// 目標(biāo)對象需要統(tǒng)一是引用數(shù)據(jù)類型,若不是會自動轉(zhuǎn)換const to = Object(target);for (let i = 0; i < args.length; i++) {// 每一個源對象const nextSource = args[i];if (nextSource !== null) {// 使用for...in和hasOwnProperty雙重判斷,確保只拿到本身的屬性、方法(不包含繼承的)for (const nextKey in nextSource) {if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {to[nextKey] = nextSource[nextKey];}}}}return to;},// 不可枚舉enumerable: false,writable: true,configurable: true,})
19、深拷貝
遞歸的完整版本(考慮到了Symbol屬性):
const cloneDeep1 = (target, hash = new WeakMap()) => {// 對于傳入?yún)?shù)處理if (typeof target !== 'object' || target === null) {return target;}// 哈希表中存在直接返回if (hash.has(target)) return hash.get(target);const cloneTarget = Array.isArray(target) ? [] : {};hash.set(target, cloneTarget);// 針對Symbol屬性const symKeys = Object.getOwnPropertySymbols(target);if (symKeys.length) {symKeys.forEach(symKey => {if (typeof target[symKey] === 'object' && target[symKey] !== null) {cloneTarget[symKey] = cloneDeep1(target[symKey]);} else {cloneTarget[symKey] = target[symKey];}})}for (const i in target) {if (Object.prototype.hasOwnProperty.call(target, i)) {cloneTarget[i] =typeof target[i] === 'object' && target[i] !== null? cloneDeep1(target[i], hash): target[i];}}return cloneTarget;}
20、Promise
實現(xiàn)思路:Promise源碼實現(xiàn)
// 模擬實現(xiàn)Promise// Promise利用三大手段解決回調(diào)地獄:// 1. 回調(diào)函數(shù)延遲綁定// 2. 返回值穿透// 3. 錯誤冒泡// 定義三種狀態(tài)const PENDING = 'PENDING'; // 進行中const FULFILLED = 'FULFILLED'; // 已成功const REJECTED = 'REJECTED'; // 已失敗class Promise {constructor(exector) {// 初始化狀態(tài)this.status = PENDING;// 將成功、失敗結(jié)果放在this上,便于then、catch訪問this.value = undefined;this.reason = undefined;// 成功態(tài)回調(diào)函數(shù)隊列this.onFulfilledCallbacks = [];// 失敗態(tài)回調(diào)函數(shù)隊列this.onRejectedCallbacks = [];const resolve = value => {// 只有進行中狀態(tài)才能更改狀態(tài)if (this.status === PENDING) {this.status = FULFILLED;this.value = value;// 成功態(tài)函數(shù)依次執(zhí)行this.onFulfilledCallbacks.forEach(fn => fn(this.value));}}const reject = reason => {// 只有進行中狀態(tài)才能更改狀態(tài)if (this.status === PENDING) {this.status = REJECTED;this.reason = reason;// 失敗態(tài)函數(shù)依次執(zhí)行this.onRejectedCallbacks.forEach(fn => fn(this.reason))}}try {// 立即執(zhí)行executor// 把內(nèi)部的resolve和reject傳入executor,用戶可調(diào)用resolve和rejectexector(resolve, reject);} catch(e) {// executor執(zhí)行出錯,將錯誤內(nèi)容reject拋出去reject(e);}}then(onFulfilled, onRejected) {onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;onRejected = typeof onRejected === 'function'? onRejected :reason => { throw new Error(reason instanceof Error ? reason.message : reason) }// 保存thisconst self = this;return new Promise((resolve, reject) => {if (self.status === PENDING) {self.onFulfilledCallbacks.push(() => {// try捕獲錯誤try {// 模擬微任務(wù)setTimeout(() => {const result = onFulfilled(self.value);// 分兩種情況:// 1. 回調(diào)函數(shù)返回值是Promise,執(zhí)行then操作// 2. 如果不是Promise,調(diào)用新Promise的resolve函數(shù)result instanceof Promise ? result.then(resolve, reject) : resolve(result);})} catch(e) {reject(e);}});self.onRejectedCallbacks.push(() => {// 以下同理try {setTimeout(() => {const result = onRejected(self.reason);// 不同點:此時是rejectresult instanceof Promise ? result.then(resolve, reject) : reject(result);})} catch(e) {reject(e);}})} else if (self.status === FULFILLED) {try {setTimeout(() => {const result = onFulfilled(self.value);result instanceof Promise ? result.then(resolve, reject) : resolve(result);});} catch(e) {reject(e);}} else if (self.status === REJECTED) {try {setTimeout(() => {const result = onRejected(self.reason);result instanceof Promise ? result.then(resolve, reject) : resolve(result);})} catch(e) {reject(e);}}});}catch(onRejected) {return this.then(null, onRejected);}static resolve(value) {if (value instanceof Promise) {// 如果是Promise實例,直接返回return value;} else {// 如果不是Promise實例,返回一個新的Promise對象,狀態(tài)為FULFILLEDreturn new Promise((resolve, reject) => resolve(value));}}static reject(reason) {return new Promise((resolve, reject) => {reject(reason);})}static all(promiseArr) {const len = promiseArr.length;const values = new Array(len);// 記錄已經(jīng)成功執(zhí)行的promise個數(shù)let count = 0;return new Promise((resolve, reject) => {for (let i = 0; i < len; i++) {// Promise.resolve()處理,確保每一個都是promise實例Promise.resolve(promiseArr[i]).then(val => {values[i] = val;count++;// 如果全部執(zhí)行完,返回promise的狀態(tài)就可以改變了if (count === len) resolve(values);},err => reject(err),);}})}static race(promiseArr) {return new Promise((resolve, reject) => {promiseArr.forEach(p => {Promise.resolve(p).then(val => resolve(val),err => reject(err),)})})}}
21、Promise.all
Promise.all是支持鏈?zhǔn)秸{(diào)用的,本質(zhì)上就是返回了一個Promise實例,通過resolve和reject來改變實例狀態(tài)。
Promise.myAll = function(promiseArr) {return new Promise((resolve, reject) => {const ans = [];let index = 0;for (let i = 0; i < promiseArr.length; i++) {promiseArr[i].then(res => {ans[i] = res;index++;if (index === promiseArr.length) {resolve(ans);}}).catch(err => reject(err));}})}
22、Promise.race
Promise.race = function(promiseArr) {return new Promise((resolve, reject) => {promiseArr.forEach(p => {// 如果不是Promise實例需要轉(zhuǎn)化為Promise實例Promise.resolve(p).then(val => resolve(val),err => reject(err),)})})}
23、Promise并行限制
就是實現(xiàn)有并行限制的Promise調(diào)度器問題。
詳細(xì)實現(xiàn)思路:某條高頻面試原題:實現(xiàn)有并行限制的Promise調(diào)度器
class Scheduler {constructor() {this.queue = [];this.maxCount = 2;this.runCounts = 0;}add(promiseCreator) {this.queue.push(promiseCreator);}taskStart() {for (let i = 0; i < this.maxCount; i++) {this.request();}}request() {if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {return;}this.runCounts++;this.queue.shift()().then(() => {this.runCounts--;this.request();});}}const timeout = time => new Promise(resolve => {setTimeout(resolve, time);})const scheduler = new Scheduler();const addTask = (time,order) => {scheduler.add(() => timeout(time).then(()=>console.log(order)))}addTask(1000, '1');addTask(500, '2');addTask(300, '3');addTask(400, '4');scheduler.taskStart()// 2// 3// 1//?4
24、JSONP
script標(biāo)簽不遵循同源協(xié)議,可以用來進行跨域請求,優(yōu)點就是兼容性好但僅限于GET請求
const jsonp = ({ url, params, callbackName }) => {const generateUrl = () => {let dataSrc = '';for (let key in params) {if (Object.prototype.hasOwnProperty.call(params, key)) {dataSrc += `${key}=${params[key]}&`;}}dataSrc += `callback=${callbackName}`;return `${url}?${dataSrc}`;}return new Promise((resolve, reject) => {const scriptEle = document.createElement('script');scriptEle.src = generateUrl();document.body.appendChild(scriptEle);window[callbackName] = data => {resolve(data);document.removeChild(scriptEle);}})}
25、AJAX
const getJSON = function(url) {return new Promise((resolve, reject) => {const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');xhr.open('GET', url, false);xhr.setRequestHeader('Accept', 'application/json');xhr.onreadystatechange = function() {if (xhr.readyState !== 4) return;if (xhr.status === 200 || xhr.status === 304) {resolve(xhr.responseText);} else {reject(new Error(xhr.responseText));}}xhr.send();})}
26、event模塊
實現(xiàn)node中回調(diào)函數(shù)的機制,node中回調(diào)函數(shù)其實是內(nèi)部使用了觀察者模式。
觀察者模式:定義了對象間一種一對多的依賴關(guān)系,當(dāng)目標(biāo)對象Subject發(fā)生改變時,所有依賴它的對象Observer都會得到通知。
function EventEmitter() {this.events = new Map();}// 需要實現(xiàn)的一些方法:// addListener、removeListener、once、removeAllListeners、emit// 模擬實現(xiàn)addlistener方法const wrapCallback = (fn, once = false) => ({ callback: fn, once });EventEmitter.prototype.addListener = function(type, fn, once = false) {const hanlder = this.events.get(type);if (!hanlder) {// 沒有type綁定事件this.events.set(type, wrapCallback(fn, once));} else if (hanlder && typeof hanlder.callback === 'function') {// 目前type事件只有一個回調(diào)this.events.set(type, [hanlder, wrapCallback(fn, once)]);} else {// 目前type事件數(shù)>=2hanlder.push(wrapCallback(fn, once));}}// 模擬實現(xiàn)removeListenerEventEmitter.prototype.removeListener = function(type, listener) {const hanlder = this.events.get(type);if (!hanlder) return;if (!Array.isArray(this.events)) {if (hanlder.callback === listener.callback) this.events.delete(type);else return;}for (let i = 0; i < hanlder.length; i++) {const item = hanlder[i];if (item.callback === listener.callback) {hanlder.splice(i, 1);i--;if (hanlder.length === 1) {this.events.set(type, hanlder[0]);}}}}// 模擬實現(xiàn)once方法EventEmitter.prototype.once = function(type, listener) {this.addListener(type, listener, true);}// 模擬實現(xiàn)emit方法EventEmitter.prototype.emit = function(type, ...args) {const hanlder = this.events.get(type);if (!hanlder) return;if (Array.isArray(hanlder)) {hanlder.forEach(item => {item.callback.apply(this, args);if (item.once) {this.removeListener(type, item);}})} else {hanlder.callback.apply(this, args);if (hanlder.once) {this.events.delete(type);}}return true;}EventEmitter.prototype.removeAllListeners = function(type) {const hanlder = this.events.get(type);if (!hanlder) return;this.events.delete(type);}
27、圖片懶加載
可以給img標(biāo)簽統(tǒng)一自定義屬性src='default.png',當(dāng)檢測到圖片出現(xiàn)在窗口之后再補充src屬性,此時才會進行圖片資源加載。
function lazyload() {const imgs = document.getElementsByTagName('img');const len = imgs.length;// 視口的高度const viewHeight = document.documentElement.clientHeight;// 滾動條高度const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;for (let i = 0; i < len; i++) {const offsetHeight = imgs[i].offsetTop;if (offsetHeight < viewHeight + scrollHeight) {const src = imgs[i].dataset.src;imgs[i].src = src;}}}// 可以使用節(jié)流優(yōu)化一下window.addEventListener('scroll', lazyload);
28、滾動加載
原理就是監(jiān)聽頁面滾動事件,分析clientHeight、scrollTop、scrollHeight三者的屬性關(guān)系。
window.addEventListener('scroll', function() {const clientHeight = document.documentElement.clientHeight;const scrollTop = document.documentElement.scrollTop;const scrollHeight = document.documentElement.scrollHeight;if (clientHeight + scrollTop >= scrollHeight) {// 檢測到滾動至頁面底部,進行后續(xù)操作// ...}}, false);
一個Demo:頁面滾動加載的Demo
29、渲染幾萬條數(shù)據(jù)不卡住頁面
渲染大數(shù)據(jù)時,合理使用createDocumentFragment和requestAnimationFrame,將操作切分為一小段一小段執(zhí)行。
setTimeout(() => {// 插入十萬條數(shù)據(jù)const total = 100000;// 一次插入的數(shù)據(jù)const once = 20;// 插入數(shù)據(jù)需要的次數(shù)const loopCount = Math.ceil(total / once);let countOfRender = 0;const ul = document.querySelector('ul');// 添加數(shù)據(jù)的方法function add() {const fragment = document.createDocumentFragment();for(let i = 0; i < once; i++) {const li = document.createElement('li');li.innerText = Math.floor(Math.random() * total);fragment.appendChild(li);}ul.appendChild(fragment);countOfRender += 1;loop();}function loop() {if(countOfRender < loopCount) {window.requestAnimationFrame(add);}}loop();}, 0)
30、打印出當(dāng)前網(wǎng)頁使用了多少種HTML元素
一行代碼可以解決:
const fn = () => {return [...new Set([...document.querySelectorAll('*')].map(el => el.tagName))].length;}
值得注意的是:DOM操作返回的是類數(shù)組,需要轉(zhuǎn)換為數(shù)組之后才可以調(diào)用數(shù)組的方法。
31、將VirtualDom轉(zhuǎn)化為真實DOM結(jié)構(gòu)
這是當(dāng)前SPA應(yīng)用的核心概念之一
// vnode結(jié)構(gòu):// {// tag,// attrs,// children,// }//Virtual DOM => DOMfunction render(vnode, container) {container.appendChild(_render(vnode));}function _render(vnode) {// 如果是數(shù)字類型轉(zhuǎn)化為字符串if (typeof vnode === 'number') {vnode = String(vnode);}// 字符串類型直接就是文本節(jié)點if (typeof vnode === 'string') {return document.createTextNode(vnode);}// 普通DOMconst dom = document.createElement(vnode.tag);if (vnode.attrs) {// 遍歷屬性Object.keys(vnode.attrs).forEach(key => {const value = vnode.attrs[key];dom.setAttribute(key, value);})}// 子數(shù)組進行遞歸操作vnode.children.forEach(child => render(child, dom));return dom;}
32、字符串解析問題
var a = {b: 123,c: '456',e: '789',}var str=`a{a.b}aa{a.c}aa {a.d}aaaa`;// => 'a123aa456aa {a.d}aaaa'
實現(xiàn)函數(shù)使得將str字符串中的{}內(nèi)的變量替換,如果屬性不存在保持原樣(比如{a.d})
類似于模版字符串,但有一點出入,實際上原理大差不差
const fn1 = (str, obj) => {let res = '';// 標(biāo)志位,標(biāo)志前面是否有{let flag = false;let start;for (let i = 0; i < str.length; i++) {if (str[i] === '{') {flag = true;start = i + 1;continue;}if (!flag) res += str[i];else {if (str[i] === '}') {flag = false;res += match(str.slice(start, i), obj);}}}return res;}// 對象匹配操作const match = (str, obj) => {const keys = str.split('.').slice(1);let index = 0;let o = obj;while (index < keys.length) {const key = keys[index];if (!o[key]) {return `{${str}}`;} else {o = o[key];}index++;}return o;}
本文完~

