Q: 解析 URL Params 為物件
儘可能的全面正確的解析一個任意 url 的所有引數為 Object,注意邊界條件的處理。
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;
}
複製程式碼
模板渲染
Q: 實現一個簡單的模板引擎:
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]; // 查詢當前模板裡第一個模板字串的欄位 template = template.replace(reg, data[name]); // 將第一個模板字串渲染 return render(template, data); // 遞迴的渲染並返回渲染後的結構 } return template; // 如果模板沒有模板字串直接返回 } 複製程式碼
-
一行程式碼實現方式
function render(template, data) { return template.replace(new RegExp('{{(.*?)}}', 'g'), (match, key) => data[key.trim()]); } 複製程式碼
參考
Q: 實現一個簡單的虛擬 DOM 渲染
let domNode = {
tagName: 'ul',
props: { class: 'list' },
children: [{
tagName: 'li',
children: ['item1']
}, {
tagName: 'li',
children: ['item1']
}]
};
// 構建一個 render 函式,將 domNode 物件渲染為 以下 dom
<ul class="list">
<li>item1</li>
<li>item2</li>
</ul>
複製程式碼
答案:
function render(domNode) {
if (!domNode) return document.createDocumentFragment();
let $el
if (typeof domNode === 'object') {
$el = document.createElement(domNode.tagName);
if (domNode.hasOwnProperty('props')) {
for (let key in domNode.props) {
$el.setAttribute(key, domNode.props[key]);
}
}
if (domNode.hasOwnProperty('children')) {
domNode.children.forEach(val => {
const $childEl = render(val);
$el.appendChild($childEl);
})
}
} else {
$el = document.createTextNode(domNode);
}
return $el;
}
複製程式碼
Q: 字串查詢
請使用最基本的遍歷來實現判斷字串 a 是否被包含在字串 b 中,並返回第一次出現的位置(找不到返回 -1)。
例子:
a='34';b='1234567'; // 返回 2
a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
isContain(a,b);
複製程式碼
答案:
function isContain(a, b) {
for (let i in b) {
if (a[0] === b[i]) {
let tmp = true;
for (let j in a) {
if (a[j] !== b[~~i + ~~j]) {
tmp = false;
}
}
if (tmp) {
return i;
}
}
}
return -1;
}
複製程式碼
Q:將一個任意長的數字變成逗號分割的格式
例子:
// 保留三位小數
parseToMoney(1234.56); // return '1,234.56'
parseToMoney(123456789); // return '123,456,789'
parseToMoney(1087654.321); // return '1,087,654.321'
複製程式碼
答:
function parseToMoney(num) {
num = parseFloat(num.toFixed(3));
let [integer, decimal] = String.prototype.split.call(num, '.');
integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');
return integer + '.' + (decimal ? decimal : '');
}
複製程式碼
Q: 資料繫結最基本的實現
// 實現一個方法,可以給 obj 所有的屬性新增動態繫結事件,當屬性值發生變化時會觸發事件
let obj = {
key_1: 1,
key_2: 2
}
function func(key) {
console.log(key + ' 的值發生改變:' + this[key]);
}
bindData(obj, func);
obj.key_1 = 2; // 此時自動輸出 "key_1 的值發生改變:2"
obj.key_2 = 1; // 此時自動輸出 "key_2 的值發生改變:1"
複製程式碼
答:
function bindData(obj, fn) {
for (let key in obj) {
Object.defineProperty(obj, key, {
set(newVal) {
if (this.value !== newVal) {
this.value = newVal;
fn.call(obj, key);
}
},
get() {
return this.value;
}
})
}
}
複製程式碼
Q: 資料結構處理
有一個祖先樹狀 json 物件,當一個人有一個兒子的時候,其 child 為其兒子物件,如果有多個兒子,child 為兒子物件的陣列。
請實現一個函式,找出這個家族中所有有多個兒子的人的名字(name),輸出一個陣列。
// 樣例資料
let data = {
name: 'jack',
child: [
{ name: 'jack1' },
{
name: 'jack2',
child: [{
name: 'jack2-1',
child: { name: 'jack2-1-1' }
}, {
name: 'jack2-2'
}]
},
{
name: 'jack3',
child: { name: 'jack3-1' }
}
]
}
複製程式碼
答案:
-
用遞迴
function findMultiChildPerson(data) { let nameList = []; function tmp(data) { if (data.hasOwnProperty('child')) { if (Array.isArray(data.child)) { nameList.push(data.name); data.child.forEach(child => tmp(child)); } else { tmp(data.child); } } } tmp(data); return nameList; } 複製程式碼
-
非遞迴
function findMultiChildPerson(data) { let list = [data]; let nameList = []; while (list.length > 0) { const obj = list.shift(); if (obj.hasOwnProperty('child')) { if (Array.isArray(obj.child)) { nameList.push(obj.name); list = list.concat(obj.child); } else { list.push(obj.child); } } } return nameList; } 複製程式碼