### 一: 簡單的模版引擎
模版引擎是在前端是非常常用的一種工具。請你完成一個簡單的模版引擎的 render 函數,它可以接受模版字符串和一個數據對象作為參數。函數執行返回渲染以后的模版字符串,例如:
const templateStr = `
<ul class="users">
<% users.forEach((user) => { %>
<li class="user-item">
<%= 'My name is ' + user.name %>
</li>
<% }) %>
</ul>
`
const data = {
users: [
{ name: 'Jerry', age: 12 },
{ name: 'Lucy', age: 13 },
{ name: 'Tomy', age: 14 }
]
}
render(templateStr, data)
/*返回結果:
<ul class="users">
<li class="user-item">
My name is Jerry
</li>
<li class="user-item">
My name is Lucy
</li>
<li class="user-item">
My name is Tomy
</li>
</ul>
*/
<% 和 %> 之間可以放置任意的 JavaScript 代碼,而 <%= 和 %> 之間執行任意的 JavaScript 表達式并且輸出在模版上;傳入的 data 可以作為模版引擎執行的上下文進行數據的引用,請你完成 render 函數。
(提示:你可以結合 執行任意表達式 來實現)
答案:
~~~
function render(template, data){
const evalExpr = /<%=(.+?)%>/g
const expr = /<%([\s\S]+?)%>/g
template = template
.replace(evalExpr, '`); \n __echo( $1 ); \n __echo(`')
.replace(expr, '`); \n $1 \n __echo(`')
template = '__echo(`' + template + '`);'
const script =`
let output = ""
const __echo = (html) => output += html
${template}
return output
`
return new Function(...Object.keys(data), script)(...Object.values(data))
}
~~~
### 二:字符串居中補全
完成函數 centerPad 可以讓一個字符串居中包裹在指定的可重復填充的字符串中間,例如:
centerPad('Hello', 13, 'abc') // => 'abcaHelloabca'
centerPad('Gook Luck!', 30, '*~') // => '*~*~*~*~*~Gook Luck!*~*~*~*~*~'
第一個參數為被包裹字符串,第二個參數為最終的字符串長度,第三個參數為用來填充的字符。
如果字符串無法完全居中,那么讓字符串偏左,例如:
centerPad('Hello', 10, 'abc') // => 'abHelloabc'
如果第二個參數傳入的字符串長度比原來長度要短,直接返回原有字符串即可,例如:
centerPad('Hello', 1, 'abc') // => 'Hello'
請你完成 centerPad 函數。
(提示:可以充分利用 ES6 新增的擴展方法)
答案:
~~~
const centerPad = (str, len, pad) => {
const half = Math.floor((len - str.length) / 2) + str.length
return str.padStart(half, pad).padEnd(len, pad)
}
~~~
### 三:迷你 MVVM
做一個小型的 MVVM 庫,可以做到數據和視圖之間的自動同步。
你需要做的就是完成一個函數 bindViewToData,它接受一個 DOM 節點和一個對象 data 作為參數。bindViewToData 會分析這個 DOM 節點下的所有文本節點,并且分析其中被 {{ 和 }} 包裹起來的表達式,然后把其中的內容替換成在 data 上下文執行該表達式的結果。例如:
<div id='app'>
<p>
My name is {{firstName + ' ' + lastName}}, I am {{age}} years old.
</p>
<div>
const appData = {
firstName: 'Lucy',
lastName: 'Green',
age: 13
}
bindViewToData(document.getElementById('app'), appData)
// div 里面的 p 元素的內容為
// My name is Lucy Green, I am 13 years old.
appData.firstName = 'Jerry'
appData.age = 16
// div 里面的 p 元素的內容自動變為
// My name is Jerry Green, I am 16 years old.
當數據改變的時候,會自動地把相應的表達式的內容重新計算并且插入文本節點。
答案:
~~~
const bindViewToData = (el, data) => {
Object.keys(data).forEach((k) => {
let v = data[k]
Object.defineProperty(data, k, {
get () {
return v
},
set (nv) {
v = nv
applyChanges()
}
})
})
let changes = []
const parse = (el) => {
Array.from(el.childNodes).forEach((c) => {
if(!(c instanceof Text)) parse(c)
else {
changes.push(((originExp) => () => {
insertExpForTextNode(c, originExp)
})(c.nodeValue))
}
})
}
const insertExpForTextNode = (node, originExp) => {
const newValue = originExp.replace(/{{[\s\S]+?}}/g, function (exp) {
exp = exp.replace(/[{}]/g, '')
return execute(exp)
})
if (newValue !== node.nodeValue) node.nodeValue = newValue
}
const execute = (exp) => {
return new Function(...Object.keys(data), `return ${exp}`)(...Object.values(data))
}
const applyChanges = () => {
setTimeout(() => {
changes.forEach((f) => f())
})
}
parse(el)
applyChanges()
}
~~~
### 四:翻箱倒柜
完成一個類 Box,實例化的時候給它傳入一個數組。Box 的實例支持 for...of 操作,可以把初始化的時候傳給 Box 的數組內容遍歷出來:
const box = new Box(['book', 'money', 'toy'])
for (let stuff of box) {
console.log(stuff) // => 依次打印 'book', 'money', 'toy'
}
你不能在 constructor 里面直接返回數組。
請你完成 Box 類。
答案:
~~~
class Box {
constructor (stuffs = []) {
this.stuffs = stuffs
}
[Symbol.iterator] () {
let i = 0
return {
next: () => {
if (i >= this.stuffs.length) return { done: true }
else return { value: this.stuffs[i++], done: false }
}
}
}
}
~~~
### 五: Symbol 轉換
請你完成 convertSymbolToNormalStr 函數,它會把一個鍵全是 Symbol 的對象轉換成鍵全是 String 的對象,而同時值保持不變。例如:
convertSymbolToNormalStr({ [Symbol('name')]: 'Jerry' }) // => { name: 'Jerry' }
答案:
~~~
const convertSymbolToNormalStr = (kv) => {
return Object.getOwnPropertySymbols(kv).reduce((o, s) => {
o[s.toString().replace(/(^Symbol\()|(\)$)/g, '')] = kv[s]
return o
}, {})
}
~~~
### 六:全選和不選
現在頁面上有很多 checkbox,有一個 id 為 check-all 的 checkbox 表示全選。check-all 的勾選狀態和其他的 checkbox 的狀態緊密關聯:
勾選了 check-all 表示全部選中其他 checkbox,不勾選 check-all 表示全部不選。
在勾選了 check-all 以后,再把其他任意 checkbox 去掉勾選,那么 check-all 也該也去掉勾選,因為已經不是全部選中了。
在不勾選 check-all 的情況下,其他的 checkbox 被手動一個個選中了,那么 check-all 也應該被選中。
請你完成 initCheckBox 函數,可以初始化上述功能。
HTML
<ul>
<li>全選:<input type='checkbox' id='check-all'></li>
<li>1 選項 <input type='checkbox' class='check-item'></li>
<li>2 選項 <input type='checkbox' class='check-item'></li>
<li>3 選項 <input type='checkbox' class='check-item'></li>
<li>4 選項 <input type='checkbox' class='check-item'></li>
<li>5 選項 <input type='checkbox' class='check-item'></li>
<li>6 選項 <input type='checkbox' class='check-item'></li>
</ul>
答案:
js:
~~~
function initCheckBox () {
const checkAll = document.getElementById('check-all')
const checkItems = document.querySelectorAll('.check-item')
checkAll.addEventListener('change', () => {
Array.from(checkItems).forEach((item) => {
item.checked = checkAll.checked
})
})
Array.from(checkItems).forEach((item) => {
item.addEventListener('change', () => {
if (item.checked) {
checkIfCheckedAll()
} else {
uncheckMaster()
}
})
})
function checkIfCheckedAll () {
let checkedCount = 0
Array.from(checkItems).forEach((item) => {
if (item.checked) checkedCount++
})
if (checkedCount === checkItems.length) checkMaster()
}
function checkMaster () {
checkAll.checked = true
}
function uncheckMaster () {
checkAll.checked = false
}
}
function k() {
return 'goo'
}
~~~
### 七:監聽數組變化
在前端的 MVVM 框架當中,我們經常需要監聽數據的變化,而數組是需要監聽的重要對象。請你完成 ObserverableArray,它的實例和普通的數組實例功能相同,但是當調用:
push
pop
shift
unshift
splice
sort
reverse
這些方法的時候,除了執行相同的操作,還會把方法名打印出來。 例如:
const arr = new ObserverableArray()
arr.push('Good') // => 打印 'push',a 變成了 ['Good']
注意,你不能修改 Array 的 prototype。還有函數 return 的值和原生的操作保持一致。
答案:
~~~
class ObserverableArray extends Array {
push (...args) {
console.log('push')
return super.push(...args)
}
reverse (...args) {
console.log('reverse')
return super.reverse(...args)
}
unshift (...args) {
console.log('unshift')
return super.unshift(...args)
}
sort (...args) {
console.log('sort')
return super.sort(...args)
}
splice (...args) {
console.log('splice')
return super.splice(...args)
}
pop (...args) {
console.log('pop')
return super.pop(...args)
}
shift (...args) {
console.log('shift')
return super.shift(...args)
}
}
~~~
### 八: shallowEqual
在 React、Redux 當中,經常會用到一種 shallowEqual 來做性能優化。shallowEqual 結合 immutable 的共享數據結構可以幫助我們簡單地檢測到哪些數據沒有發生變化,就不需要做額外的渲染等操作,優化效果撥群。
簡單來說,shallowEqual 接受兩個參數,如果這兩個參數的值相同、或者這兩個參數都是對象并且對象的第一層數據相同,那么就返回 true;否則就返回 false。例如:
shallowEqual(1, 1) // true
shallowEqual(1, 2) // false
shallowEqual('foo', 'foo') // true
shallowEqual(window, window) // true
shallowEqual('foo', 'bar') // false
shallowEqual([], []) // true
shallowEqual([1, 2, 3], [1, 2, 3]) // true
shallowEqual({ name: 'Jerry' }, { name: 'Jerry' }) // true
shallowEqual({ age: NaN }, { age: NaN }) // true
shallowEqual(null, { age: NaN }) // false
var a = { name: 'Jerry' }
var b = { age: 12 }
shallowEqual({ a, b }, { a, b }) // true
shallowEqual({ name: { a, b } }, { name: { a, b } } // false
shallowEqual({ a, b }, { a }) // false
請你完成 shallowEqual 函數。
答案:
~~~
function shallowEqual(objA, objB) {
const hasOwn = Object.prototype.hasOwnProperty
if (Object.is(objA, objB)) return true
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false
}
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
if (keysA.length !== keysB.length) return false
for (let i = 0; i < keysA.length; i++) {
if (!hasOwn.call(objB, keysA[i]) ||
!Object.is(objA[keysA[i]], objB[keysA[i]])) {
return false
}
}
return true
}
~~~
### 九: 到底一不一樣?
完成 is 函數,它接受兩個參數,你返回 true 和 false 來表示這兩個參數是否有 相同的值。例如:
is('foo', 'foo'); // true
is(window, window); // true
is('foo', 'bar'); // false
is([], []); // false
var test = { a: 1 };
is(test, test); // true
is(null, null); // true
is(0, -0); // false
is(-0, -0); // true
is(NaN, 0/0); // true
答案:
~~~
function is (x, y) {
// SameValue algorithm
if (x === y) { // Steps 1-5, 7-10
// Steps 6.b-6.e: +0 != -0
return x !== 0 || 1 / x === 1 / y;
} else {
// Step 6.a: NaN == NaN
return x !== x && y !== y;
}
};
~~~
### 十:你是五年的程序員嗎
每天都是快樂的一天,比如看到一個帖子 說了這么一個故事:
面試一個5年的前端,卻連原型鏈也搞不清楚,滿口都是Vue,React之類的實現,這樣的人該用嗎?
最后還是拒絕。還有其他的原因。一個問題,輸入m.n參數,獲取一個m長度的都是n的數組,不能用循環,他不會寫。問他他們公司項目的webpack配置entry有幾個,他一會說1個,一會說很多個,不知道他到底懂不懂。
那么,為證明你的實力,請寫出一個函數 initArray ,接受兩個參數 m 和 n,返回一個數組,它的長度是 m,每個值都是 n。
答案:
~~~
const initArray = (m, n) => {
if (m === 0) return []
return (n + ' ').repeat(m).trim().split(' ').map(n => +n)
}
~~~
- 前端入門
- 前端入職須知
- 正確看待前端
- 前端自我定位
- pc與手機頁面差別
- 前端書單
- 前端技術棧
- 前端資源導航
- 前端切圖
- 插件
- 組件、控件和插件的區別
- 技術文檔
- layui
- layer彈框在實際項目中的一些應用
- 前端面試題
- bat面試題庫
- 中小公司的leader
- 項目相關
- 職業規劃如何
- 前端經典筆試題
- javascript基礎(一)
- JavaScript基礎二
- JavaScript基礎面試題(三)
- JavaScript基礎面試題(四)
- JavaScript基礎面試題(五)
- JavaScript基礎面試題(六)
- JavaScript基礎面試題(七)
- JavaScript基礎面試題(八)
- JavaScript基礎面試題(九)
- JavaScript基礎面試題(十)
- dom經典面試題
- 正則表達式
- 史上最難面試題
- 簡單算法
- 前端idea
- vsc快速上手指南
- 微信開發者工具
- sublime的使用
- hbuilder入門
- 前端那些事
- 前端的注釋該怎么寫
- 前端架構師是怎么煉成的
- 細數前端的那些技術大牛
- 前端leader的那些事
- ps
- 圖片類型及其區別
- 基本概念及其常用工具
- ps操作技巧
- ps站點資源導航
- ui站點導航
- html
- css
- js
- 插件庫
- git教程
- web
- web兼容思想
- ui框架
- 小程序
- 微信專題
- 支付寶專題