現(xiàn)防抖函數(shù)(debounce)
防抖函數(shù)原理:在事件被觸發(fā)n秒后再執(zhí)行回調(diào),如果在這n秒內(nèi)又被觸發(fā),則重新計(jì)時(shí)。
那么與節(jié)流函數(shù)的區(qū)別直接看這個(gè)動(dòng)畫實(shí)現(xiàn)即可。
手寫簡(jiǎn)化版:
// 防抖函數(shù) const debounce = (fn, delay) => { let timer = null; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); }; };
適用場(chǎng)景:
生存環(huán)境請(qǐng)用lodash.debounce
我自己是一名從事了多年開發(fā)的web前端老程序員,目前辭職在做自己的web前端私人定制課程,今年年初我花了一個(gè)月整理了一份最適合2019年學(xué)習(xí)的web前端學(xué)習(xí)干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關(guān)注我的頭條號(hào)并在后臺(tái)私信我:前端,即可免費(fèi)獲取。
實(shí)現(xiàn)節(jié)流函數(shù)(throttle)
防抖函數(shù)原理:規(guī)定在一個(gè)單位時(shí)間內(nèi),只能觸發(fā)一次函數(shù)。如果這個(gè)單位時(shí)間內(nèi)觸發(fā)多次函數(shù),只有一次生效。
// 手寫簡(jiǎn)化版
// 節(jié)流函數(shù) const throttle = (fn, delay = 500) => { let flag = true; return (...args) => { if (!flag) return; flag = false; setTimeout(() => { fn.apply(this, args); flag = true; }, delay); }; };
適用場(chǎng)景:
深克?。╠eepclone)
簡(jiǎn)單版:
const newObj = JSON.parse(JSON.stringify(oldObj));
局限性:
面試版:
/** * deep clone * @param {[type]} parent object 需要進(jìn)行克隆的對(duì)象 * @return {[type]} 深克隆后的對(duì)象 */ const clone = parent => { // 判斷類型 const isType = (obj, type) => { if (typeof obj !== "object") return false; const typeString = Object.prototype.toString.call(obj); let flag; switch (type) { case "Array": flag = typeString === "[object Array]"; break; case "Date": flag = typeString === "[object Date]"; break; case "RegExp": flag = typeString === "[object RegExp]"; break; default: flag = false; } return flag; }; // 處理正則 const getRegExp = re => { var flags = ""; if (re.global) flags += "g"; if (re.ignoreCase) flags += "i"; if (re.multiline) flags += "m"; return flags; }; // 維護(hù)兩個(gè)儲(chǔ)存循環(huán)引用的數(shù)組 const parents = []; const children = []; const _clone = parent => { if (parent === null) return null; if (typeof parent !== "object") return parent; let child, proto; if (isType(parent, "Array")) { // 對(duì)數(shù)組做特殊處理 child = []; } else if (isType(parent, "RegExp")) { // 對(duì)正則對(duì)象做特殊處理 child = new RegExp(parent.source, getRegExp(parent)); if (parent.lastIndex) child.lastIndex = parent.lastIndex; } else if (isType(parent, "Date")) { // 對(duì)Date對(duì)象做特殊處理 child = new Date(parent.getTime()); } else { // 處理對(duì)象原型 proto = Object.getPrototypeOf(parent); // 利用Object.create切斷原型鏈 child = Object.create(proto); } // 處理循環(huán)引用 const index = parents.indexOf(parent); if (index != -1) { // 如果父數(shù)組存在本對(duì)象,說明之前已經(jīng)被引用過,直接返回此對(duì)象 return children[index]; } parents.push(parent); children.push(child); for (let i in parent) { // 遞歸 child[i] = _clone(parent[i]); } return child; }; return _clone(parent); };
局限性:
原理詳解實(shí)現(xiàn)深克隆
實(shí)現(xiàn)Event(event bus)
event bus既是node中各個(gè)模塊的基石,又是前端組件通信的依賴手段之一,同時(shí)涉及了訂閱-發(fā)布設(shè)計(jì)模式,是非常重要的基礎(chǔ)。
簡(jiǎn)單版:
class EventEmeitter { constructor() { this._events = this._events || new Map(); // 儲(chǔ)存事件/回調(diào)鍵值對(duì) this._maxListeners = this._maxListeners || 10; // 設(shè)立監(jiān)聽上限 } } // 觸發(fā)名為type的事件 EventEmeitter.prototype.emit = function(type, ...args) { let handler; // 從儲(chǔ)存事件鍵值對(duì)的this._events中獲取對(duì)應(yīng)事件回調(diào)函數(shù) handler = this._events.get(type); if (args.length > 0) { handler.apply(this, args); } else { handler.call(this); } return true; }; // 監(jiān)聽名為type的事件 EventEmeitter.prototype.addListener = function(type, fn) { // 將type事件以及對(duì)應(yīng)的fn函數(shù)放入this._events中儲(chǔ)存 if (!this._events.get(type)) { this._events.set(type, fn); } };
面試版:
class EventEmeitter { constructor() { this._events = this._events || new Map(); // 儲(chǔ)存事件/回調(diào)鍵值對(duì) this._maxListeners = this._maxListeners || 10; // 設(shè)立監(jiān)聽上限 } } // 觸發(fā)名為type的事件 EventEmeitter.prototype.emit = function(type, ...args) { let handler; // 從儲(chǔ)存事件鍵值對(duì)的this._events中獲取對(duì)應(yīng)事件回調(diào)函數(shù) handler = this._events.get(type); if (args.length > 0) { handler.apply(this, args); } else { handler.call(this); } return true; }; // 監(jiān)聽名為type的事件 EventEmeitter.prototype.addListener = function(type, fn) { // 將type事件以及對(duì)應(yīng)的fn函數(shù)放入this._events中儲(chǔ)存 if (!this._events.get(type)) { this._events.set(type, fn); } }; // 觸發(fā)名為type的事件 EventEmeitter.prototype.emit = function(type, ...args) { let handler; handler = this._events.get(type); if (Array.isArray(handler)) { // 如果是一個(gè)數(shù)組說明有多個(gè)監(jiān)聽者,需要依次此觸發(fā)里面的函數(shù) for (let i = 0; i < handler.length; i++) { if (args.length > 0) { handler[i].apply(this, args); } else { handler[i].call(this); } } } else { // 單個(gè)函數(shù)的情況我們直接觸發(fā)即可 if (args.length > 0) { handler.apply(this, args); } else { handler.call(this); } } return true; }; // 監(jiān)聽名為type的事件 EventEmeitter.prototype.addListener = function(type, fn) { const handler = this._events.get(type); // 獲取對(duì)應(yīng)事件名稱的函數(shù)清單 if (!handler) { this._events.set(type, fn); } else if (handler && typeof handler === "function") { // 如果handler是函數(shù)說明只有一個(gè)監(jiān)聽者 this._events.set(type, [handler, fn]); // 多個(gè)監(jiān)聽者我們需要用數(shù)組儲(chǔ)存 } else { handler.push(fn); // 已經(jīng)有多個(gè)監(jiān)聽者,那么直接往數(shù)組里push函數(shù)即可 } }; EventEmeitter.prototype.removeListener = function(type, fn) { const handler = this._events.get(type); // 獲取對(duì)應(yīng)事件名稱的函數(shù)清單 // 如果是函數(shù),說明只被監(jiān)聽了一次 if (handler && typeof handler === "function") { this._events.delete(type, fn); } else { let postion; // 如果handler是數(shù)組,說明被監(jiān)聽多次要找到對(duì)應(yīng)的函數(shù) for (let i = 0; i < handler.length; i++) { if (handler[i] === fn) { postion = i; } else { postion = -1; } } // 如果找到匹配的函數(shù),從數(shù)組中清除 if (postion !== -1) { // 找到數(shù)組對(duì)應(yīng)的位置,直接清除此回調(diào) handler.splice(postion, 1); // 如果清除后只有一個(gè)函數(shù),那么取消數(shù)組,以函數(shù)形式保存 if (handler.length === 1) { this._events.set(type, handler[0]); } } else { return this; } } };
實(shí)現(xiàn)instanceOf
// 模擬 instanceof function instance_of(L, R) { //L 表示左表達(dá)式,R 表示右表達(dá)式 var O = R.prototype; // 取 R 的顯示原型 L = L.__proto__; // 取 L 的隱式原型 while (true) { if (L === null) return false; if (O === L) // 這里重點(diǎn):當(dāng) O 嚴(yán)格等于 L 時(shí),返回 true return true; L = L.__proto__; } }
模擬new
new操作符做了這些事:
// objectFactory(name, 'cxk', '18') function objectFactory() { const obj = new Object(); const Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; const ret = Constructor.apply(obj, arguments); return typeof ret === "object" ? ret : obj; }
實(shí)現(xiàn)一個(gè)call
call做了什么:
// 模擬 call bar.mycall(null); //實(shí)現(xiàn)一個(gè)call方法: Function.prototype.myCall = function(context) { //此處沒有考慮context非object情況 context.fn = this; let args = []; for (let i = 1, len = arguments.length; i < len; i++) { args.push(arguments[i]); } context.fn(...args); let result = context.fn(...args); delete context.fn; return result; };
具體實(shí)現(xiàn)參考JavaScript深入之call和apply的模擬實(shí)現(xiàn)
實(shí)現(xiàn)apply方法
apply原理與call很相似,不多贅述
// 模擬 apply Function.prototype.myapply = function(context, arr) { var context = Object(context) || window; context.fn = this; var result; if (!arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push("arr[" + i + "]"); } result = eval("context.fn(" + args + ")"); } delete context.fn; return result; };
實(shí)現(xiàn)bind
實(shí)現(xiàn)bind要做什么
// mdn的實(shí)現(xiàn) if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== 'function') { // closest thing possible to the ECMAScript 5 // internal IsCallable function throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function() {}, fBound = function() { // this instanceof fBound === true時(shí),說明返回的fBound被當(dāng)做new的構(gòu)造函數(shù)調(diào)用 return fToBind.apply(this instanceof fBound ? this : oThis, // 獲取調(diào)用時(shí)(fBound)的傳參.bind 返回的函數(shù)入?yún)⑼沁@么傳遞的 aArgs.concat(Array.prototype.slice.call(arguments))); }; // 維護(hù)原型關(guān)系 if (this.prototype) { // Function.prototype doesn't have a prototype property fNOP.prototype = this.prototype; } // 下行的代碼使fBound.prototype是fNOP的實(shí)例,因此 // 返回的fBound若作為new的構(gòu)造函數(shù),new生成的新對(duì)象作為this傳入fBound,新對(duì)象的__proto__就是fNOP的實(shí)例 fBound.prototype = new fNOP(); return fBound; }; }
詳解請(qǐng)移步JavaScript深入之bind的模擬實(shí)現(xiàn) #12
模擬Object.create
Object.create()方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象的proto。
// 模擬 Object.create function create(proto) { function F() {} F.prototype = proto; return new F(); }
實(shí)現(xiàn)類的繼承
類的繼承在幾年前是重點(diǎn)內(nèi)容,有n種繼承方式各有優(yōu)劣,es6普及后越來越不重要,那么多種寫法有點(diǎn)『回字有四樣寫法』的意思,如果還想深入理解的去看紅寶書即可,我們目前只實(shí)現(xiàn)一種最理想的繼承方式。
function Parent(name) { this.parent = name } Parent.prototype.say = function() { console.log(`${this.parent}: 你打籃球的樣子像kunkun`) } function Child(name, parent) { // 將父類的構(gòu)造函數(shù)綁定在子類上 Parent.call(this, parent) this.child = name } /** 1. 這一步不用Child.prototype =Parent.prototype的原因是怕共享內(nèi)存,修改父類原型對(duì)象就會(huì)影響子類 2. 不用Child.prototype = new Parent()的原因是會(huì)調(diào)用2次父類的構(gòu)造方法(另一次是call),會(huì)存在一份多余的父類實(shí)例屬性 3. Object.create是創(chuàng)建了父類原型的副本,與父類原型完全隔離 */ Child.prototype = Object.create(Parent.prototype); Child.prototype.say = function() { console.log(`${this.parent}好,我是練習(xí)時(shí)長(zhǎng)兩年半的${this.child}`); } // 注意記得把子類的構(gòu)造指向子類本身 Child.prototype.constructor = Child; var parent = new Parent('father'); parent.say() // father: 你打籃球的樣子像kunkun var child = new Child('cxk', 'father'); child.say() // father好,我是練習(xí)時(shí)長(zhǎng)兩年半的cxk
實(shí)現(xiàn)JSON.parse
var json = '{"name":"cxk", "age":25}'; var obj = eval("(" + json + ")");
此方法屬于黑魔法,極易容易被xss攻擊,還有一種new Function大同小異。
簡(jiǎn)單的教程看這個(gè)半小時(shí)實(shí)現(xiàn)一個(gè) JSON 解析器
實(shí)現(xiàn)Promise
我很早之前實(shí)現(xiàn)過一版,而且注釋很多,但是居然找不到了,這是在網(wǎng)絡(luò)上找了一版帶注釋的,目測(cè)沒有大問題,具體過程可以看這篇史上最易讀懂的 Promise/A+ 完全實(shí)現(xiàn)
var PromisePolyfill = (function () { // 和reject不同的是resolve需要嘗試展開thenable對(duì)象 function tryToResolve (value) { if (this === value) { // 主要是防止下面這種情況 // let y = new Promise(res => setTimeout(res(y))) throw TypeError('Chaining cycle detected for promise!') } // 根據(jù)規(guī)范2.32以及2.33 對(duì)對(duì)象或者函數(shù)嘗試展開 // 保證S6之前的 polyfill 也能和ES6的原生promise混用 if (value !== null && (typeof value === 'object' || typeof value === 'function')) { try { // 這里記錄這次then的值同時(shí)要被try包裹 // 主要原因是 then 可能是一個(gè)getter, 也也就是說 // 1. value.then可能報(bào)錯(cuò) // 2. value.then可能產(chǎn)生副作用(例如多次執(zhí)行可能結(jié)果不同) var then = value.then // 另一方面, 由于無法保證 then 確實(shí)會(huì)像預(yù)期的那樣只調(diào)用一個(gè)onFullfilled / onRejected // 所以增加了一個(gè)flag來防止resolveOrReject被多次調(diào)用 var thenAlreadyCalledOrThrow = false if (typeof then === 'function') { // 是thenable 那么嘗試展開 // 并且在該thenable狀態(tài)改變之前this對(duì)象的狀態(tài)不變 then.bind(value)( // onFullfilled function (value2) { if (thenAlreadyCalledOrThrow) return thenAlreadyCalledOrThrow = true tryToResolve.bind(this, value2)() }.bind(this), // onRejected function (reason2) { if (thenAlreadyCalledOrThrow) return thenAlreadyCalledOrThrow = true resolveOrReject.bind(this, 'rejected', reason2)() }.bind(this) ) } else { // 擁有then 但是then不是一個(gè)函數(shù) 所以也不是thenable resolveOrReject.bind(this, 'resolved', value)() } } catch (e) { if (thenAlreadyCalledOrThrow) return thenAlreadyCalledOrThrow = true resolveOrReject.bind(this, 'rejected', e)() } } else { // 基本類型 直接返回 resolveOrReject.bind(this, 'resolved', value)() } } function resolveOrReject (status, data) { if (this.status !== 'pending') return this.status = status this.data = data if (status === 'resolved') { for (var i = 0; i < this.resolveList.length; ++i) { this.resolveList[i]() } } else { for (i = 0; i < this.rejectList.length; ++i) { this.rejectList[i]() } } } function Promise (executor) { if (!(this instanceof Promise)) { throw Error('Promise can not be called without new !') } if (typeof executor !== 'function') { // 非標(biāo)準(zhǔn) 但與Chrome谷歌保持一致 throw TypeError('Promise resolver ' + executor + ' is not a function') } this.status = 'pending' this.resolveList = [] this.rejectList = [] try { executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected')) } catch (e) { resolveOrReject.bind(this, 'rejected', e)() } } Promise.prototype.then = function (onFullfilled, onRejected) { // 返回值穿透以及錯(cuò)誤穿透, 注意錯(cuò)誤穿透用的是throw而不是return,否則的話 // 這個(gè)then返回的promise狀態(tài)將變成resolved即接下來的then中的onFullfilled // 會(huì)被調(diào)用, 然而我們想要調(diào)用的是onRejected if (typeof onFullfilled !== 'function') { onFullfilled = function (data) { return data } } if (typeof onRejected !== 'function') { onRejected = function (reason) { throw reason } } var executor = function (resolve, reject) { setTimeout(function () { try { // 拿到對(duì)應(yīng)的handle函數(shù)處理this.data // 并以此為依據(jù)解析這個(gè)新的Promise var value = this.status === 'resolved' ? onFullfilled(this.data) : onRejected(this.data) resolve(value) } catch (e) { reject(e) } }.bind(this)) } // then 接受兩個(gè)函數(shù)返回一個(gè)新的Promise // then 自身的執(zhí)行永遠(yuǎn)異步與onFullfilled/onRejected的執(zhí)行 if (this.status !== 'pending') { return new Promise(executor.bind(this)) } else { // pending return new Promise(function (resolve, reject) { this.resolveList.push(executor.bind(this, resolve, reject)) this.rejectList.push(executor.bind(this, resolve, reject)) }.bind(this)) } } // for prmise A+ test Promise.deferred = Promise.defer = function () { var dfd = {} dfd.promise = new Promise(function (resolve, reject) { dfd.resolve = resolve dfd.reject = reject }) return dfd } // for prmise A+ test if (typeof module !== 'undefined') { module.exports = Promise } return Promise })() PromisePolyfill.all = function (promises) { return new Promise((resolve, reject) => { const result = [] let cnt = 0 for (let i = 0; i < promises.length; ++i) { promises[i].then(value => { cnt++ result[i] = value if (cnt === promises.length) resolve(result) }, reject) } }) } PromisePolyfill.race = function (promises) { return new Promise((resolve, reject) => { for (let i = 0; i < promises.length; ++i) { promises[i].then(resolve, reject) } }) }
解析 URL Params 為對(duì)象
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled'; parseParam(url) /* 結(jié)果 { user: 'anonymous', id: [ 123, 456 ], // 重復(fù)出現(xiàn)的 key 要組裝成數(shù)組,能被轉(zhuǎn)成數(shù)字的就轉(zhuǎn)成數(shù)字類型 city: '北京', // 中文需解碼 enabled: true, // 未指定值得 key 約定為 true } */
function parseParam(url) { const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 將 ? 后面的字符串取出來 const paramsArr = paramsStr.split('&'); // 將字符串以 & 分割后存到數(shù)組中 let paramsObj = {}; // 將 params 存到對(duì)象中 paramsArr.forEach(param => { if (/=/.test(param)) { // 處理有 value 的參數(shù) let [key, val] = param.split('='); // 分割 key 和 value val = decodeURIComponent(val); // 解碼 val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判斷是否轉(zhuǎn)為數(shù)字 if (paramsObj.hasOwnProperty(key)) { // 如果對(duì)象有 key,則添加一個(gè)值 paramsObj[key] = [].concat(paramsObj[key], val); } else { // 如果對(duì)象沒有這個(gè) key,創(chuàng)建 key 并設(shè)置值 paramsObj[key] = val; } } else { // 處理沒有 value 的參數(shù) paramsObj[param] = true; } }) return paramsObj; }
模板引擎實(shí)現(xiàn)
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]; // 查找當(dāng)前模板里第一個(gè)模板字符串的字段 template = template.replace(reg, data[name]); // 將第一個(gè)模板字符串渲染 return render(template, data); // 遞歸的渲染并返回渲染后的結(jié)構(gòu) } return template; // 如果模板沒有模板字符串直接返回 }
轉(zhuǎn)化為駝峰命名
var s1 = "get-element-by-id" // 轉(zhuǎn)化為 getElementById
var f = function(s) { return s.replace(/-\w/g, function(x) { return x.slice(1).toUpperCase(); }) }
查找字符串中出現(xiàn)最多的字符和個(gè)數(shù)
例: abbcccddddd -> 字符最多的是d,出現(xiàn)了5次
let str = "abcabcabcbbccccc"; let num = 0; let char = ''; // 使其按照一定的次序排列 str = str.split('').sort().join(''); // "aaabbbbbcccccccc" // 定義正則表達(dá)式 let re = /(\w)\1+/g; str.replace(re,($0,$1) => { if(num < $0.length){ num = $0.length; char = $1; } }); console.log(`字符最多的是${char},出現(xiàn)了${num}次`);
字符串查找
請(qǐng)使用最基本的遍歷來實(shí)現(xiàn)判斷字符串 a 是否被包含在字符串 b 中,并返回第一次出現(xiàn)的位置(找不到返回 -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; }
實(shí)現(xiàn)千位分隔符
// 保留三位小數(shù) 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 : ''); }
正則表達(dá)式(運(yùn)用了正則的前向聲明和反前向聲明):
function parseToMoney(str){ // 僅僅對(duì)位置進(jìn)行匹配 let re = /(?=(?!\b)(\d{3})+$)/g; return str.replace(re,','); }
判斷是否是電話號(hào)碼
function isPhone(tel) { var regx = /^1[34578]\d{9}$/; return regx.test(tel); }
驗(yàn)證是否是郵箱
function isEmail(email) { var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/; return regx.test(email); }
驗(yàn)證是否是身份證
function isCardNo(number) { var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/; return regx.test(number); }
原文鏈接:https://mp.weixin.qq.com/s/CY-igdCB_U-DtA4E2BKhhA
作者:程序員面試官
點(diǎn)擊關(guān)注本公眾號(hào)獲取文檔最新更新,并可以領(lǐng)取配套于本指南的 《前端面試手冊(cè)》 以及最標(biāo)準(zhǔn)的簡(jiǎn)歷模板.
實(shí)現(xiàn)防抖函數(shù)(debounce)
防抖函數(shù)原理:在事件被觸發(fā)n秒后再執(zhí)行回調(diào),如果在這n秒內(nèi)又被觸發(fā),則重新計(jì)時(shí)。
那么與節(jié)流函數(shù)的區(qū)別直接看這個(gè)動(dòng)畫實(shí)現(xiàn)即可。
手寫簡(jiǎn)化版:
// 防抖函數(shù) const debounce = (fn, delay) => { let timer = null; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); }; }; 復(fù)制代碼
適用場(chǎng)景:
生存環(huán)境請(qǐng)用lodash.debounce
實(shí)現(xiàn)節(jié)流函數(shù)(throttle)
防抖函數(shù)原理:規(guī)定在一個(gè)單位時(shí)間內(nèi),只能觸發(fā)一次函數(shù)。如果這個(gè)單位時(shí)間內(nèi)觸發(fā)多次函數(shù),只有一次生效。
// 手寫簡(jiǎn)化版
// 節(jié)流函數(shù) const throttle = (fn, delay = 500) => { let flag = true; return (...args) => { if (!flag) return; flag = false; setTimeout(() => { fn.apply(this, args); flag = true; }, delay); }; }; 復(fù)制代碼
適用場(chǎng)景:
深克隆(deepclone)
簡(jiǎn)單版:
const newObj = JSON.parse(JSON.stringify(oldObj)); 復(fù)制代碼
局限性:
面試版:
/** * deep clone * @param {[type]} parent object 需要進(jìn)行克隆的對(duì)象 * @return {[type]} 深克隆后的對(duì)象 */ const clone = parent => { // 判斷類型 const isType = (obj, type) => { if (typeof obj !== "object") return false; const typeString = Object.prototype.toString.call(obj); let flag; switch (type) { case "Array": flag = typeString === "[object Array]"; break; case "Date": flag = typeString === "[object Date]"; break; case "RegExp": flag = typeString === "[object RegExp]"; break; default: flag = false; } return flag; }; // 處理正則 const getRegExp = re => { var flags = ""; if (re.global) flags += "g"; if (re.ignoreCase) flags += "i"; if (re.multiline) flags += "m"; return flags; }; // 維護(hù)兩個(gè)儲(chǔ)存循環(huán)引用的數(shù)組 const parents = []; const children = []; const _clone = parent => { if (parent === null) return null; if (typeof parent !== "object") return parent; let child, proto; if (isType(parent, "Array")) { // 對(duì)數(shù)組做特殊處理 child = []; } else if (isType(parent, "RegExp")) { // 對(duì)正則對(duì)象做特殊處理 child = new RegExp(parent.source, getRegExp(parent)); if (parent.lastIndex) child.lastIndex = parent.lastIndex; } else if (isType(parent, "Date")) { // 對(duì)Date對(duì)象做特殊處理 child = new Date(parent.getTime()); } else { // 處理對(duì)象原型 proto = Object.getPrototypeOf(parent); // 利用Object.create切斷原型鏈 child = Object.create(proto); } // 處理循環(huán)引用 const index = parents.indexOf(parent); if (index != -1) { // 如果父數(shù)組存在本對(duì)象,說明之前已經(jīng)被引用過,直接返回此對(duì)象 return children[index]; } parents.push(parent); children.push(child); for (let i in parent) { // 遞歸 child[i] = _clone(parent[i]); } return child; }; return _clone(parent); }; 復(fù)制代碼
局限性:
原理詳解實(shí)現(xiàn)深克隆
實(shí)現(xiàn)Event(event bus)
event bus既是node中各個(gè)模塊的基石,又是前端組件通信的依賴手段之一,同時(shí)涉及了訂閱-發(fā)布設(shè)計(jì)模式,是非常重要的基礎(chǔ)。
簡(jiǎn)單版:
class EventEmeitter { constructor() { this._events = this._events || new Map(); // 儲(chǔ)存事件/回調(diào)鍵值對(duì) this._maxListeners = this._maxListeners || 10; // 設(shè)立監(jiān)聽上限 } } // 觸發(fā)名為type的事件 EventEmeitter.prototype.emit = function(type, ...args) { let handler; // 從儲(chǔ)存事件鍵值對(duì)的this._events中獲取對(duì)應(yīng)事件回調(diào)函數(shù) handler = this._events.get(type); if (args.length > 0) { handler.apply(this, args); } else { handler.call(this); } return true; }; // 監(jiān)聽名為type的事件 EventEmeitter.prototype.addListener = function(type, fn) { // 將type事件以及對(duì)應(yīng)的fn函數(shù)放入this._events中儲(chǔ)存 if (!this._events.get(type)) { this._events.set(type, fn); } }; 復(fù)制代碼
面試版:
class EventEmeitter { constructor() { this._events = this._events || new Map(); // 儲(chǔ)存事件/回調(diào)鍵值對(duì) this._maxListeners = this._maxListeners || 10; // 設(shè)立監(jiān)聽上限 } } // 觸發(fā)名為type的事件 EventEmeitter.prototype.emit = function(type, ...args) { let handler; // 從儲(chǔ)存事件鍵值對(duì)的this._events中獲取對(duì)應(yīng)事件回調(diào)函數(shù) handler = this._events.get(type); if (args.length > 0) { handler.apply(this, args); } else { handler.call(this); } return true; }; // 監(jiān)聽名為type的事件 EventEmeitter.prototype.addListener = function(type, fn) { // 將type事件以及對(duì)應(yīng)的fn函數(shù)放入this._events中儲(chǔ)存 if (!this._events.get(type)) { this._events.set(type, fn); } }; // 觸發(fā)名為type的事件 EventEmeitter.prototype.emit = function(type, ...args) { let handler; handler = this._events.get(type); if (Array.isArray(handler)) { // 如果是一個(gè)數(shù)組說明有多個(gè)監(jiān)聽者,需要依次此觸發(fā)里面的函數(shù) for (let i = 0; i < handler.length; i++) { if (args.length > 0) { handler[i].apply(this, args); } else { handler[i].call(this); } } } else { // 單個(gè)函數(shù)的情況我們直接觸發(fā)即可 if (args.length > 0) { handler.apply(this, args); } else { handler.call(this); } } return true; }; // 監(jiān)聽名為type的事件 EventEmeitter.prototype.addListener = function(type, fn) { const handler = this._events.get(type); // 獲取對(duì)應(yīng)事件名稱的函數(shù)清單 if (!handler) { this._events.set(type, fn); } else if (handler && typeof handler === "function") { // 如果handler是函數(shù)說明只有一個(gè)監(jiān)聽者 this._events.set(type, [handler, fn]); // 多個(gè)監(jiān)聽者我們需要用數(shù)組儲(chǔ)存 } else { handler.push(fn); // 已經(jīng)有多個(gè)監(jiān)聽者,那么直接往數(shù)組里push函數(shù)即可 } }; EventEmeitter.prototype.removeListener = function(type, fn) { const handler = this._events.get(type); // 獲取對(duì)應(yīng)事件名稱的函數(shù)清單 // 如果是函數(shù),說明只被監(jiān)聽了一次 if (handler && typeof handler === "function") { this._events.delete(type, fn); } else { let postion; // 如果handler是數(shù)組,說明被監(jiān)聽多次要找到對(duì)應(yīng)的函數(shù) for (let i = 0; i < handler.length; i++) { if (handler[i] === fn) { postion = i; } else { postion = -1; } } // 如果找到匹配的函數(shù),從數(shù)組中清除 if (postion !== -1) { // 找到數(shù)組對(duì)應(yīng)的位置,直接清除此回調(diào) handler.splice(postion, 1); // 如果清除后只有一個(gè)函數(shù),那么取消數(shù)組,以函數(shù)形式保存 if (handler.length === 1) { this._events.set(type, handler[0]); } } else { return this; } } }; 復(fù)制代碼
實(shí)現(xiàn)具體過程和思路見實(shí)現(xiàn)event
實(shí)現(xiàn)instanceOf
// 模擬 instanceof function instance_of(L, R) { //L 表示左表達(dá)式,R 表示右表達(dá)式 var O = R.prototype; // 取 R 的顯示原型 L = L.__proto__; // 取 L 的隱式原型 while (true) { if (L === null) return false; if (O === L) // 這里重點(diǎn):當(dāng) O 嚴(yán)格等于 L 時(shí),返回 true return true; L = L.__proto__; } } 復(fù)制代碼
模擬new
new操作符做了這些事:
// objectFactory(name, 'cxk', '18') function objectFactory() { const obj = new Object(); const Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; const ret = Constructor.apply(obj, arguments); return typeof ret === "object" ? ret : obj; } 復(fù)制代碼
實(shí)現(xiàn)一個(gè)call
call做了什么:
// 模擬 call bar.mycall(null); //實(shí)現(xiàn)一個(gè)call方法: Function.prototype.myCall = function(context) { //此處沒有考慮context非object情況 context.fn = this; let args = []; for (let i = 1, len = arguments.length; i < len; i++) { args.push(arguments[i]); } context.fn(...args); let result = context.fn(...args); delete context.fn; return result; }; 復(fù)制代碼
具體實(shí)現(xiàn)參考JavaScript深入之call和apply的模擬實(shí)現(xiàn)
實(shí)現(xiàn)apply方法
apply原理與call很相似,不多贅述
// 模擬 apply Function.prototype.myapply = function(context, arr) { var context = Object(context) || window; context.fn = this; var result; if (!arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push("arr[" + i + "]"); } result = eval("context.fn(" + args + ")"); } delete context.fn; return result; }; 復(fù)制代碼
實(shí)現(xiàn)bind
實(shí)現(xiàn)bind要做什么
// mdn的實(shí)現(xiàn) if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== 'function') { // closest thing possible to the ECMAScript 5 // internal IsCallable function throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function() {}, fBound = function() { // this instanceof fBound === true時(shí),說明返回的fBound被當(dāng)做new的構(gòu)造函數(shù)調(diào)用 return fToBind.apply(this instanceof fBound ? this : oThis, // 獲取調(diào)用時(shí)(fBound)的傳參.bind 返回的函數(shù)入?yún)⑼沁@么傳遞的 aArgs.concat(Array.prototype.slice.call(arguments))); }; // 維護(hù)原型關(guān)系 if (this.prototype) { // Function.prototype doesn't have a prototype property fNOP.prototype = this.prototype; } // 下行的代碼使fBound.prototype是fNOP的實(shí)例,因此 // 返回的fBound若作為new的構(gòu)造函數(shù),new生成的新對(duì)象作為this傳入fBound,新對(duì)象的__proto__就是fNOP的實(shí)例 fBound.prototype = new fNOP(); return fBound; }; } 復(fù)制代碼
詳解請(qǐng)移步JavaScript深入之bind的模擬實(shí)現(xiàn) #12
模擬Object.create
Object.create()方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象的__proto__。
// 模擬 Object.create function create(proto) { function F() {} F.prototype = proto; return new F(); } 復(fù)制代碼
實(shí)現(xiàn)類的繼承
類的繼承在幾年前是重點(diǎn)內(nèi)容,有n種繼承方式各有優(yōu)劣,es6普及后越來越不重要,那么多種寫法有點(diǎn)『回字有四樣寫法』的意思,如果還想深入理解的去看紅寶書即可,我們目前只實(shí)現(xiàn)一種最理想的繼承方式。
function Parent(name) { this.parent = name } Parent.prototype.say = function() { console.log(`${this.parent}: 你打籃球的樣子像kunkun`) } function Child(name, parent) { // 將父類的構(gòu)造函數(shù)綁定在子類上 Parent.call(this, parent) this.child = name } /** 1. 這一步不用Child.prototype =Parent.prototype的原因是怕共享內(nèi)存,修改父類原型對(duì)象就會(huì)影響子類 2. 不用Child.prototype = new Parent()的原因是會(huì)調(diào)用2次父類的構(gòu)造方法(另一次是call),會(huì)存在一份多余的父類實(shí)例屬性 3. Object.create是創(chuàng)建了父類原型的副本,與父類原型完全隔離 */ Child.prototype = Object.create(Parent.prototype); Child.prototype.say = function() { console.log(`${this.parent}好,我是練習(xí)時(shí)長(zhǎng)兩年半的${this.child}`); } // 注意記得把子類的構(gòu)造指向子類本身 Child.prototype.constructor = Child; var parent = new Parent('father'); parent.say() // father: 你打籃球的樣子像kunkun var child = new Child('cxk', 'father'); child.say() // father好,我是練習(xí)時(shí)長(zhǎng)兩年半的cxk 復(fù)制代碼
實(shí)現(xiàn)JSON.parse
var json = '{"name":"cxk", "age":25}'; var obj = eval("(" + json + ")"); 復(fù)制代碼
此方法屬于黑魔法,極易容易被xss攻擊,還有一種new Function大同小異。
簡(jiǎn)單的教程看這個(gè)半小時(shí)實(shí)現(xiàn)一個(gè) JSON 解析器
實(shí)現(xiàn)Promise
我很早之前實(shí)現(xiàn)過一版,而且注釋很多,但是居然找不到了,這是在網(wǎng)絡(luò)上找了一版帶注釋的,目測(cè)沒有大問題,具體過程可以看這篇史上最易讀懂的 Promise/A+ 完全實(shí)現(xiàn)
var PromisePolyfill = (function () { // 和reject不同的是resolve需要嘗試展開thenable對(duì)象 function tryToResolve (value) { if (this === value) { // 主要是防止下面這種情況 // let y = new Promise(res => setTimeout(res(y))) throw TypeError('Chaining cycle detected for promise!') } // 根據(jù)規(guī)范2.32以及2.33 對(duì)對(duì)象或者函數(shù)嘗試展開 // 保證S6之前的 polyfill 也能和ES6的原生promise混用 if (value !== null && (typeof value === 'object' || typeof value === 'function')) { try { // 這里記錄這次then的值同時(shí)要被try包裹 // 主要原因是 then 可能是一個(gè)getter, 也也就是說 // 1. value.then可能報(bào)錯(cuò) // 2. value.then可能產(chǎn)生副作用(例如多次執(zhí)行可能結(jié)果不同) var then = value.then // 另一方面, 由于無法保證 then 確實(shí)會(huì)像預(yù)期的那樣只調(diào)用一個(gè)onFullfilled / onRejected // 所以增加了一個(gè)flag來防止resolveOrReject被多次調(diào)用 var thenAlreadyCalledOrThrow = false if (typeof then === 'function') { // 是thenable 那么嘗試展開 // 并且在該thenable狀態(tài)改變之前this對(duì)象的狀態(tài)不變 then.bind(value)( // onFullfilled function (value2) { if (thenAlreadyCalledOrThrow) return thenAlreadyCalledOrThrow = true tryToResolve.bind(this, value2)() }.bind(this), // onRejected function (reason2) { if (thenAlreadyCalledOrThrow) return thenAlreadyCalledOrThrow = true resolveOrReject.bind(this, 'rejected', reason2)() }.bind(this) ) } else { // 擁有then 但是then不是一個(gè)函數(shù) 所以也不是thenable resolveOrReject.bind(this, 'resolved', value)() } } catch (e) { if (thenAlreadyCalledOrThrow) return thenAlreadyCalledOrThrow = true resolveOrReject.bind(this, 'rejected', e)() } } else { // 基本類型 直接返回 resolveOrReject.bind(this, 'resolved', value)() } } function resolveOrReject (status, data) { if (this.status !== 'pending') return this.status = status this.data = data if (status === 'resolved') { for (var i = 0; i < this.resolveList.length; ++i) { this.resolveList[i]() } } else { for (i = 0; i < this.rejectList.length; ++i) { this.rejectList[i]() } } } function Promise (executor) { if (!(this instanceof Promise)) { throw Error('Promise can not be called without new !') } if (typeof executor !== 'function') { // 非標(biāo)準(zhǔn) 但與Chrome谷歌保持一致 throw TypeError('Promise resolver ' + executor + ' is not a function') } this.status = 'pending' this.resolveList = [] this.rejectList = [] try { executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected')) } catch (e) { resolveOrReject.bind(this, 'rejected', e)() } } Promise.prototype.then = function (onFullfilled, onRejected) { // 返回值穿透以及錯(cuò)誤穿透, 注意錯(cuò)誤穿透用的是throw而不是return,否則的話 // 這個(gè)then返回的promise狀態(tài)將變成resolved即接下來的then中的onFullfilled // 會(huì)被調(diào)用, 然而我們想要調(diào)用的是onRejected if (typeof onFullfilled !== 'function') { onFullfilled = function (data) { return data } } if (typeof onRejected !== 'function') { onRejected = function (reason) { throw reason } } var executor = function (resolve, reject) { setTimeout(function () { try { // 拿到對(duì)應(yīng)的handle函數(shù)處理this.data // 并以此為依據(jù)解析這個(gè)新的Promise var value = this.status === 'resolved' ? onFullfilled(this.data) : onRejected(this.data) resolve(value) } catch (e) { reject(e) } }.bind(this)) } // then 接受兩個(gè)函數(shù)返回一個(gè)新的Promise // then 自身的執(zhí)行永遠(yuǎn)異步與onFullfilled/onRejected的執(zhí)行 if (this.status !== 'pending') { return new Promise(executor.bind(this)) } else { // pending return new Promise(function (resolve, reject) { this.resolveList.push(executor.bind(this, resolve, reject)) this.rejectList.push(executor.bind(this, resolve, reject)) }.bind(this)) } } // for prmise A+ test Promise.deferred = Promise.defer = function () { var dfd = {} dfd.promise = new Promise(function (resolve, reject) { dfd.resolve = resolve dfd.reject = reject }) return dfd } // for prmise A+ test if (typeof module !== 'undefined') { module.exports = Promise } return Promise })() PromisePolyfill.all = function (promises) { return new Promise((resolve, reject) => { const result = [] let cnt = 0 for (let i = 0; i < promises.length; ++i) { promises[i].then(value => { cnt++ result[i] = value if (cnt === promises.length) resolve(result) }, reject) } }) } PromisePolyfill.race = function (promises) { return new Promise((resolve, reject) => { for (let i = 0; i < promises.length; ++i) { promises[i].then(resolve, reject) } }) } 復(fù)制代碼
解析 URL Params 為對(duì)象
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled'; parseParam(url) /* 結(jié)果 { user: 'anonymous', id: [ 123, 456 ], // 重復(fù)出現(xiàn)的 key 要組裝成數(shù)組,能被轉(zhuǎn)成數(shù)字的就轉(zhuǎn)成數(shù)字類型 city: '北京', // 中文需解碼 enabled: true, // 未指定值得 key 約定為 true } */ 復(fù)制代碼 function parseParam(url) { const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 將 ? 后面的字符串取出來 const paramsArr = paramsStr.split('&'); // 將字符串以 & 分割后存到數(shù)組中 let paramsObj = {}; // 將 params 存到對(duì)象中 paramsArr.forEach(param => { if (/=/.test(param)) { // 處理有 value 的參數(shù) let [key, val] = param.split('='); // 分割 key 和 value val = decodeURIComponent(val); // 解碼 val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判斷是否轉(zhuǎn)為數(shù)字 if (paramsObj.hasOwnProperty(key)) { // 如果對(duì)象有 key,則添加一個(gè)值 paramsObj[key] = [].concat(paramsObj[key], val); } else { // 如果對(duì)象沒有這個(gè) key,創(chuàng)建 key 并設(shè)置值 paramsObj[key] = val; } } else { // 處理沒有 value 的參數(shù) paramsObj[param] = true; } }) return paramsObj; } 復(fù)制代碼
模板引擎實(shí)現(xiàn)
let template = '我是{{name}},年齡{{age}},性別{{sex}}'; let data = { name: '姓名', age: 18 } render(template, data); // 我是姓名,年齡18,性別undefined 復(fù)制代碼 function render(template, data) { const reg = /\{\{(\w+)\}\}/; // 模板字符串正則 if (reg.test(template)) { // 判斷模板里是否有模板字符串 const name = reg.exec(template)[1]; // 查找當(dāng)前模板里第一個(gè)模板字符串的字段 template = template.replace(reg, data[name]); // 將第一個(gè)模板字符串渲染 return render(template, data); // 遞歸的渲染并返回渲染后的結(jié)構(gòu) } return template; // 如果模板沒有模板字符串直接返回 } 復(fù)制代碼
轉(zhuǎn)化為駝峰命名
var s1 = "get-element-by-id" // 轉(zhuǎn)化為 getElementById 復(fù)制代碼 var f = function(s) { return s.replace(/-\w/g, function(x) { return x.slice(1).toUpperCase(); }) } 復(fù)制代碼
查找字符串中出現(xiàn)最多的字符和個(gè)數(shù)
例: abbcccddddd -> 字符最多的是d,出現(xiàn)了5次
let str = "abcabcabcbbccccc"; let num = 0; let char = ''; // 使其按照一定的次序排列 str = str.split('').sort().join(''); // "aaabbbbbcccccccc" // 定義正則表達(dá)式 let re = /(\w)\1+/g; str.replace(re,($0,$1) => { if(num < $0.length){ num = $0.length; char = $1; } }); console.log(`字符最多的是${char},出現(xiàn)了${num}次`); 復(fù)制代碼
字符串查找
請(qǐng)使用最基本的遍歷來實(shí)現(xiàn)判斷字符串 a 是否被包含在字符串 b 中,并返回第一次出現(xiàn)的位置(找不到返回 -1)。
a='34';b='1234567'; // 返回 2 a='35';b='1234567'; // 返回 -1 a='355';b='12354355'; // 返回 5 isContain(a,b); 復(fù)制代碼 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; } 復(fù)制代碼
實(shí)現(xiàn)千位分隔符
// 保留三位小數(shù) parseToMoney(1234.56); // return '1,234.56' parseToMoney(123456789); // return '123,456,789' parseToMoney(1087654.321); // return '1,087,654.321' 復(fù)制代碼 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 : ''); } 復(fù)制代碼
正則表達(dá)式(運(yùn)用了正則的前向聲明和反前向聲明):
function parseToMoney(str){ // 僅僅對(duì)位置進(jìn)行匹配 let re = /(?=(?!\b)(\d{3})+$)/g; return str.replace(re,','); } 復(fù)制代碼
判斷是否是電話號(hào)碼
function isPhone(tel) { var regx = /^1[34578]\d{9}$/; return regx.test(tel); } 復(fù)制代碼
驗(yàn)證是否是郵箱
function isEmail(email) { var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/; return regx.test(email); } 復(fù)制代碼
驗(yàn)證是否是身份證
function isCardNo(number) { var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/; return regx.test(number); } 復(fù)制代碼
公眾號(hào)
想要實(shí)時(shí)關(guān)注筆者最新的文章和最新的文檔更新請(qǐng)關(guān)注公眾號(hào)程序員面試官,后續(xù)的文章會(huì)優(yōu)先在公眾號(hào)更新.
簡(jiǎn)歷模板: 關(guān)注公眾號(hào)回復(fù)「模板」獲取
《前端面試手冊(cè)》: 配套于本指南的突擊手冊(cè),關(guān)注公眾號(hào)回復(fù)「fed」獲取
1
在一個(gè)結(jié)構(gòu)良好的web網(wǎng)頁(yè)里,多個(gè)h1標(biāo)簽會(huì)不利于SEO嗎?
點(diǎn)擊空白處查看答案
不影響
02
Doctype作用? 嚴(yán)格模式與混雜模式如何區(qū)分?它們有何意義?
點(diǎn)擊空白處查看答案
(1)聲明位于文檔中的最前面,處于標(biāo)簽之前。告知瀏覽器的解析器,用什么文檔類型規(guī)范來解析這個(gè)文檔。
(2)嚴(yán)格模式的排版和 JS 運(yùn)作模式是以該瀏覽器支持的最高標(biāo)準(zhǔn)運(yùn)行。
(3)在混雜模式中,頁(yè)面以寬松的向后兼容的方式顯示,模擬老式瀏覽器的行為以防止站點(diǎn)無法工作。
03
行內(nèi)元素有哪些?塊級(jí)元素有哪些?
點(diǎn)擊空白處查看答案
(1)CSS規(guī)范規(guī)定,每個(gè)元素都有display屬性,確定該元素的類型,每個(gè)元素都有默認(rèn)的display值,比如div默認(rèn)display屬性值為“block”,成為“塊級(jí)”元素;span默認(rèn)display屬性值為“inline”,是“行內(nèi)”元素。
(2)行內(nèi)元素有:a b span img input select strong(強(qiáng)調(diào)的語氣)。
(3) 塊級(jí)元素有:div ul ol li dl dt dd h1 h2 h3 h4…p。
04
link 和@import 的區(qū)別是?
點(diǎn)擊空白處查看答案
(1)link屬于XHTML標(biāo)簽,而@import是CSS提供的;
(2)頁(yè)面被加載的時(shí),link會(huì)同時(shí)被加載,而@import引用的CSS會(huì)等到頁(yè)面被加載完再加載;
(3)import只在IE5以上才能識(shí)別,而link是XHTML標(biāo)簽,無兼容問題;
(4)link方式的樣式的權(quán)重高于@import的權(quán)重。
05
當(dāng)下列的HTML代碼加載時(shí)會(huì)觸發(fā)新的HTTP請(qǐng)求嗎?
<div style="display: none;">
<img src="mypic.jpg" alt="My photo">
</div>
點(diǎn)擊空白處查看答案
會(huì)
06
對(duì)WEB標(biāo)準(zhǔn)以及W3C的理解與認(rèn)識(shí)?
點(diǎn)擊空白處查看答案
標(biāo)簽閉合、標(biāo)簽小寫、不亂嵌套、提高搜索機(jī)器人搜索幾率、使用外鏈css和js腳本、結(jié)構(gòu)行為表現(xiàn)的分離、文件下載與頁(yè)面速度更快、內(nèi)容能被更多的用戶所訪問、內(nèi)容能被更廣泛的設(shè)備所訪問、更少的代碼和組件,容易維護(hù)、改版方便,不需要變動(dòng)頁(yè)面內(nèi)容、提供打印版本而不需要復(fù)制內(nèi)容、提高網(wǎng)站易用性。
07
iframe有哪些缺點(diǎn)?
點(diǎn)擊空白處查看答案
iframe會(huì)阻塞主頁(yè)面的Onload事件;
iframe和主頁(yè)面共享連接池,而瀏覽器對(duì)相同域的連接有限制,所以會(huì)影響頁(yè)面的并行加載。使用iframe之前需要考慮這兩個(gè)缺點(diǎn)。如果需要使用iframe,最好是通過javascript動(dòng)態(tài)給iframe添加src屬性值,這樣可以可以繞開以上兩個(gè)問題。
08
XHTML和HTML有什么區(qū)別?
點(diǎn)擊空白處查看答案
HTML是一種基本的WEB網(wǎng)頁(yè)設(shè)計(jì)語言,XHTML是一個(gè)基于XML的置標(biāo)語言。
最主要的不同:
XHTML 元素必須被正確地嵌套;
XHTML 元素必須被關(guān)閉,標(biāo)簽名必須用小寫字母;
XHTML 文檔必須擁有根元素。
09
寫出幾種IE6 BUG的解決方法。
點(diǎn)擊空白處查看答案
1.雙邊距BUG float引起的 使用display;
2.3像素問題 使用float引起的 使用dislpay:inline -3px;
3.超鏈接hover 點(diǎn)擊后失效 使用正確的書寫順序 link visited hover active;
4.Ie z-index問題給父級(jí)添加position:relative;
5.Png 透明 使用js代碼改;
6.Min-height 最小高度 !Important 解決;
7.select 在ie6下遮蓋 使用iframe嵌套;
8.為什么沒有辦法定義1px左右的寬度容器(IE6默認(rèn)的行高造成的,使用over:hidden,zoom:0.08 line-height:1px);
9.ie 6 不支持!important;
10
img標(biāo)簽上title與alt屬性的區(qū)別是什么?
點(diǎn)擊空白處查看答案
Alt 當(dāng)圖片不顯示是用文字代表;
Title 為該屬性提供信息。
11
你如何對(duì)網(wǎng)站的文件和資源進(jìn)行優(yōu)化?
點(diǎn)擊空白處查看答案
文件合并
文件最小化/文件壓縮
使用CDN托管
緩存的使用
12
簡(jiǎn)述一下src與href的區(qū)別。
點(diǎn)擊空白處查看答案
href 是指向網(wǎng)絡(luò)資源所在位置,建立和當(dāng)前元素(錨點(diǎn))或當(dāng)前文檔(鏈接)之間的鏈接,用于超鏈接。
src是指向外部資源的位置,指向的內(nèi)容將會(huì)嵌入到文檔中當(dāng)前標(biāo)簽所在位置;在請(qǐng)求src資源時(shí)會(huì)將其指向的資源下載并應(yīng)用到文檔內(nèi),例如js腳本,img圖片和frame等元素。當(dāng)瀏覽器解析到該元素時(shí),會(huì)暫停其他資源的下載和處理,直到將該資源加載、編譯、執(zhí)行完畢,圖片和框架等元素也如此,類似于將所指向資源嵌入當(dāng)前標(biāo)簽內(nèi)。這也是為什么將js腳本放在底部而不是頭部。
13
什么叫優(yōu)雅降級(jí)和漸進(jìn)增強(qiáng)?
點(diǎn)擊空白處查看答案
漸進(jìn)增強(qiáng)progressive enhancement:
針對(duì)低版本瀏覽器進(jìn)行構(gòu)建頁(yè)面,保證最基本的功能,然后再針對(duì)高級(jí)瀏覽器進(jìn)行效果、交互等改進(jìn)和追加功能達(dá)到更好的用戶體驗(yàn)。
優(yōu)雅降級(jí)graceful degradation:
一開始就構(gòu)建完整的功能,然后再針對(duì)低版本瀏覽器進(jìn)行兼容。
區(qū)別:
a. 優(yōu)雅降級(jí)是從復(fù)雜的現(xiàn)狀開始,并試圖減少用戶體驗(yàn)的供給;
b. 漸進(jìn)增強(qiáng)則是從一個(gè)非?;A(chǔ)的,能夠起作用的版本開始,并不斷擴(kuò)充,以適應(yīng)未來環(huán)境的需要;
c. 降級(jí)(功能衰減)意味著往回看;而漸進(jìn)增強(qiáng)則意味著朝前看,同時(shí)保證其根基處于安全地帶。
14
瀏覽器的內(nèi)核分別是什么?
點(diǎn)擊空白處查看答案
IE: trident內(nèi)核
Firefox:gecko內(nèi)核
Safari:webkit內(nèi)核
Opera:以前是presto內(nèi)核,Opera現(xiàn)已改用Google Chrome的Blink內(nèi)核
Chrome:Blink(基于webkit,Google與Opera Software共同開發(fā))
15
一次完整的HTTP事務(wù)是怎樣的一個(gè)過程(瀏覽器渲染頁(yè)面的過程)?
點(diǎn)擊空白處查看答案
基本流程:
a.用戶輸入U(xiǎn)RL地址;
b.瀏覽器解析URL,解析出主機(jī)名;
c.瀏覽器將主機(jī)名轉(zhuǎn)換成服務(wù)器IP地址(瀏覽器先查找本地DNS緩存列表,如果沒有就向?yàn)g覽器默認(rèn)的DNS服務(wù)器發(fā)送查詢請(qǐng)求,同時(shí)緩存);
d.瀏覽器將端口號(hào)從URL中解析出來;
b.瀏覽器建立一條與目標(biāo)web服務(wù)器的TCP連接,發(fā)起TCP的3次握手;
e.瀏覽器向服務(wù)器發(fā)送一條HTTP請(qǐng)求報(bào)文;
f.服務(wù)器端響應(yīng)http請(qǐng)求,瀏覽器得到html代碼;
g.瀏覽器解析html代碼,并請(qǐng)求html代碼中的資源;
h.瀏覽器對(duì)頁(yè)面進(jìn)行渲染呈現(xiàn)給用戶;
未完待續(xù)......
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。