題目一:找出數組中最接近指定值的數
// 邊界值的處理
// 變量的命名
// API的熟悉程度
function findNext(num, arr) {
/ your code ... /
if (!num || Object.prototype.toString.call(num) !== '[object Number]'){
throw new TypeError('num參數不可省略且必須爲Number類型');
}
arr = arr || [];
var diffArr = [];
arr.map(function(x){
// 對數組各個數值求差值
diffArr.push(Math.abs(x - num));
});
var minimum = Math.min.apply(null, diffArr);
// 最後是返回數組中最接近指定值數的索引
var result = [];
diffArr.forEach(function(d, i){
if(d === minimum) {
result.push(i)
}
})
return result;
}
/ TEST CASE/
findNext('s');
findNext(1,[1,2,1]); // [0,2]
findNext(4,[2,6,10]); // [0,1]
題目二:CSS 多邊框實現
// your css code ...
// 第一種
div {
box-shadow: 0 0 0 10px red,0 0 0 16px green, 0 2px 5px 16px rgba(0,0,0,.5);
}
// 第二種 (有侷限,只能模擬出雙邊框)
div {
outline: 1px solid red;
border: 1px solid green;
}
// 如果使用嵌套dom結構,檢查對普通css屬性的理解
題目三:實現一個EventEmitter用於事件監聽、觸發、移除
// 可以追問,如果要控制每個方法上回調函數數量怎麼做
// 增加一個 once api 調用一次即註銷如何實現
function Emitter() {
}
const proto = Emitter.prototype;
proto._getEvents = function() {
if (!this._events) {
this._events = {};
}
return this._events;
};
// 註冊事件
proto.on = function(event, listener) {
const events = this._getEvents();
events[event] = events[event] || [];
events[event].push(listener);
return this;
};
// 移除事件
proto.off = function(event, listener) {
const events = this._getEvents();
// 移除所有事件
if (arguments.length === 0) {
this._events = {};
return this;
}
const listeners = events[event];
if (!listeners) {
return this;
}
// 移除指定事件下的所有監聽器
if (arguments.length === 1) {
delete events[event];
return this;
}
let cb;
for (let i = 0; i < listeners.length; i++) {
cb = listeners[i];
if (cb === listener) {
listeners.splice(i, 1);
break;
}
}
return this;
};
// 觸發事件
proto.emit = function(event) {
const events = this._getEvents();
let listeners = events[event];
let i;
const args = [];
for (let i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
if (listeners) {
listeners = listeners.slice(0);
for (i = 0; i < listeners.length; i++) {
listeners[i].apply(this, args);
}
}
return this;
};
題目四: 深度克隆函數deepClone
/**
* 取決於你深度拷貝的內容是什麼,是一個真正的JSON object 還是js中的任何對象
* var cloned = JSON.parse(JSON.stringify(objectToClone)); 這個處理不了function、undefined、Infinity等對象的
* 注意 Object.assign({}, original) / { ...original }也是淺拷貝函數
*/
function type(obj) {
var toString = Object.prototype.toString;
var map = {
'[object Boolean]' : 'boolean',
'[object Number]' : 'number',
'[object String]' : 'string',
'[object Function]' : 'function',
'[object Array]' : 'array',
'[object Date]' : 'date',
'[object RegExp]' : 'regExp',
'[object Undefined]' : 'undefined',
'[object Null]' : 'null',
'[object Object]' : 'object'
};
return map[toString.call(obj)];
}
function deepClone(data){
let dataType = type(data);
if (dataType === 'null' || dataType === 'undefined' || dataType === 'number' || dataType === 'string' || dataType === 'boolean') {
return data;
}
// Date
if (dataType === 'date') {
return new Date(data.getTime());
}
// RegExp
if (dataType === 'regExp') {
return new RegExp(data.source, data.flags);
}
// HtmlNode
if (data.nodeType && typeof data.cloneNode === "function"){
return data.cloneNode(true);
}
// Array
if (dataType === 'array') {
var result = [];
data.forEach(function(child, index) {
result[index] = deepClone(child);
});
return result;
}
// Object
if (dataType === 'object'){
var result = {};
if (!data.prototype){
// 普通對象
for (var i in data) {
result[i] = deepClone(data[i]);
}
} else {
// 函數
if (data.constructor) {
// 這裏會有什麼副作用
result = new data.constructor();
} else {
result = data;
}
}
}
return result;
}
// 每當調用postMessage時,都會使用結構化克隆算法。
// 我們可以創建一個MessageChannel併發送消息。在接收端,消息包含我們原始數據對象的結構化克隆。
// 如果能回答出這個方法加分
// 這個方法也是有缺陷的:一是不能處理dom節點 另外是異步的
function structuralClone(obj) {
return new Promise(resolve => {
const {port1, port2} = new MessageChannel();
port2.onmessage = ev => resolve(ev.data);
port1.postMessage(obj);
});
}
題目五:針對下段html文檔實現一個簡單 AST 解析方法
//
// 正則表達式
//
const htmlStr = `<div class="parent"><div class="child has-more" style="widht:100px; height:200px;"><a target="_blank" href="positionDetail.htm" title="歡迎應聘螞蟻金服前端工程師">FE</a></div></div>`
var tagRE = /<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g;
var attrRE = /([\w-]+)|['"]{1}([^'"]*)['"]{1}/g;
function parseTag (tag) {
var i = 0;
var key;
var res = {
type: 'tag',
name: '',
attrs: {},
children: []
};
tag.replace(attrRE, function (match) {
if (i % 2) {
key = match;
} else {
if (i === 0) {
res.name= match;
} else {
res.attrs[key] = match.replace(/['"]/g, '');
}
}
i++;
});
return res;
};
function parse(html, options) {
options || (options = {});
var result = [];
var current;
var level = -1;
var arr = [];
var byTag = {};
html.replace(tagRE, function (tag, index) {
var isOpen = tag.charAt(1) !== '/';
var start = index + tag.length;
var nextChar = html.charAt(start);
var parent;
if (isOpen) {
level++;
current = parseTag(tag);
if ( nextChar && nextChar !== '<') {
current.children.push({
type: 'text',
content: html.slice(start, html.indexOf('<', start))
});
}
byTag[current.tagName] = current;
if (level === 0) {
result.push(current);
}
parent = arr[level - 1];
if (parent) {
parent.children.push(current);
}
arr[level] = current;
} else {
level--;
if ( nextChar !== '<' && nextChar) {
parent = level === -1 ? result : arr[level].children;
var end = html.indexOf('<', start);
var content = html.slice(start, end === -1 ? undefined : end);
if (!/^\s*$/.test(content)) {
parent.push({
type: 'text',
content: content
});
}
}
}
});
return result;
};
parse(htmlStr)