### 一: 简单的模版引擎
模版引擎是在前端是非常常用的一种工具。请你完成一个简单的模版引擎的 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框架
- 小程序
- 微信专题
- 支付宝专题