整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          高頻JavaScript手寫面試題及答案

          現(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)景:

          • 按鈕提交場(chǎng)景:防止多次提交按鈕,只執(zhí)行最后提交的一次
          • 服務(wù)端驗(yàn)證場(chǎng)景:表單驗(yàn)證需要服務(wù)端配合,只執(zhí)行一段連續(xù)的輸入事件的最后一次,還有搜索聯(lián)想詞功能類似

          生存環(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)景:

          • 拖拽場(chǎng)景:固定時(shí)間內(nèi)只執(zhí)行一次,防止超高頻次觸發(fā)位置變動(dòng)
          • 縮放場(chǎng)景:監(jiān)控瀏覽器resize
          • 動(dòng)畫場(chǎng)景:避免短時(shí)間內(nèi)多次觸發(fā)動(dòng)畫引起性能問題

          深克?。╠eepclone)

          簡(jiǎn)單版:

          const newObj = JSON.parse(JSON.stringify(oldObj));
          

          局限性:

          1. 他無法實(shí)現(xiàn)對(duì)函數(shù) 、RegExp等特殊對(duì)象的克隆
          2. 會(huì)拋棄對(duì)象的constructor,所有的構(gòu)造函數(shù)會(huì)指向Object
          3. 對(duì)象有循環(huán)引用,會(huì)報(bào)錯(cuò)

          面試版:

          /**
           * 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);
          };
          

          局限性:

          1. 一些特殊情況沒有處理: 例如Buffer對(duì)象、Promise、Set、Map
          2. 另外對(duì)于確保沒有循環(huán)引用的對(duì)象,我們可以省去對(duì)循環(huán)引用的特殊處理,因?yàn)檫@很消耗時(shí)間

          原理詳解實(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操作符做了這些事:

          • 它創(chuàng)建了一個(gè)全新的對(duì)象
          • 它會(huì)被執(zhí)行[[Prototype]](也就是proto)鏈接
          • 它使this指向新創(chuàng)建的對(duì)象
          • 通過new創(chuàng)建的每個(gè)對(duì)象將最終被[[Prototype]]鏈接到這個(gè)函數(shù)的prototype對(duì)象上
          • 如果函數(shù)沒有返回對(duì)象類型Object(包含F(xiàn)unctoin, Array, Date, RegExg, Error),那么new表達(dá)式中的函數(shù)調(diào)用將返回該對(duì)象引用
          // 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做了什么:

          • 將函數(shù)設(shè)為對(duì)象的屬性
          • 執(zhí)行&刪除這個(gè)函數(shù)
          • 指定this到函數(shù)并傳入給定參數(shù)執(zhí)行函數(shù)
          • 如果不傳入?yún)?shù),默認(rèn)指向?yàn)?window
          // 模擬 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要做什么

          • 返回一個(gè)函數(shù),綁定this,傳遞預(yù)置參數(shù)
          • bind返回的函數(shù)可以作為構(gòu)造函數(shù)使用。故作為構(gòu)造函數(shù)時(shí)應(yīng)使得this失效,但是傳入的參數(shù)依然有效
          // 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
          作者:程序員面試官

          avaScript筆試部分

          點(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)景:

          • 按鈕提交場(chǎng)景:防止多次提交按鈕,只執(zhí)行最后提交的一次
          • 服務(wù)端驗(yàn)證場(chǎng)景:表單驗(yàn)證需要服務(wù)端配合,只執(zhí)行一段連續(xù)的輸入事件的最后一次,還有搜索聯(lián)想詞功能類似

          生存環(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)景:

          • 拖拽場(chǎng)景:固定時(shí)間內(nèi)只執(zhí)行一次,防止超高頻次觸發(fā)位置變動(dòng)
          • 縮放場(chǎng)景:監(jiān)控瀏覽器resize
          • 動(dòng)畫場(chǎng)景:避免短時(shí)間內(nèi)多次觸發(fā)動(dòng)畫引起性能問題

          深克隆(deepclone)

          簡(jiǎn)單版:

          const newObj = JSON.parse(JSON.stringify(oldObj));
          復(fù)制代碼
          

          局限性:

          1. 他無法實(shí)現(xiàn)對(duì)函數(shù) 、RegExp等特殊對(duì)象的克隆
          2. 會(huì)拋棄對(duì)象的constructor,所有的構(gòu)造函數(shù)會(huì)指向Object
          3. 對(duì)象有循環(huán)引用,會(huì)報(bào)錯(cuò)

          面試版:

          /**
           * 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ù)制代碼
          

          局限性:

          1. 一些特殊情況沒有處理: 例如Buffer對(duì)象、Promise、Set、Map
          2. 另外對(duì)于確保沒有循環(huán)引用的對(duì)象,我們可以省去對(duì)循環(huán)引用的特殊處理,因?yàn)檫@很消耗時(shí)間

          原理詳解實(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操作符做了這些事:

          • 它創(chuàng)建了一個(gè)全新的對(duì)象
          • 它會(huì)被執(zhí)行[[Prototype]](也就是__proto__)鏈接
          • 它使this指向新創(chuàng)建的對(duì)象
          • 通過new創(chuàng)建的每個(gè)對(duì)象將最終被[[Prototype]]鏈接到這個(gè)函數(shù)的prototype對(duì)象上
          • 如果函數(shù)沒有返回對(duì)象類型Object(包含F(xiàn)unctoin, Array, Date, RegExg, Error),那么new表達(dá)式中的函數(shù)調(diào)用將返回該對(duì)象引用
          // 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做了什么:

          • 將函數(shù)設(shè)為對(duì)象的屬性
          • 執(zhí)行&刪除這個(gè)函數(shù)
          • 指定this到函數(shù)并傳入給定參數(shù)執(zhí)行函數(shù)
          • 如果不傳入?yún)?shù),默認(rèn)指向?yàn)?window
          // 模擬 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要做什么

          • 返回一個(gè)函數(shù),綁定this,傳遞預(yù)置參數(shù)
          • bind返回的函數(shù)可以作為構(gòu)造函數(shù)使用。故作為構(gòu)造函數(shù)時(shí)應(yīng)使得this失效,但是傳入的參數(shù)依然有效
          // 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ù)......


          主站蜘蛛池模板: 国产精品视频一区| 亚洲av鲁丝一区二区三区| 午夜天堂一区人妻| 亚洲一区二区三区91| 国产视频一区在线观看| 中文字幕一区二区人妻性色 | 国产精品一区12p| 加勒比无码一区二区三区| 三上悠亚精品一区二区久久| 亲子乱AV视频一区二区| 国产麻豆媒一区一区二区三区| 韩国精品一区视频在线播放| 国产一区二区三区手机在线观看| 视频在线一区二区| 人妻内射一区二区在线视频| 国产在线精品一区二区三区不卡| 日韩视频一区二区三区| 国产免费播放一区二区| 国产精品视频无圣光一区| 亚洲熟女综合色一区二区三区| 影院成人区精品一区二区婷婷丽春院影视 | 无码人妻AV免费一区二区三区| 少妇精品久久久一区二区三区| 精品国产毛片一区二区无码| 老湿机一区午夜精品免费福利| 国产成人精品一区二三区熟女| 麻豆AV一区二区三区久久| 日韩成人无码一区二区三区 | 国产精品亚洲一区二区在线观看| 国产精品自拍一区| ...91久久精品一区二区三区| 亚洲变态另类一区二区三区 | 久久综合亚洲色一区二区三区| 国产精品毛片VA一区二区三区| 91国在线啪精品一区| 波多野结衣AV无码久久一区| 国产亚洲福利精品一区| 午夜影视日本亚洲欧洲精品一区 | 精品国产精品久久一区免费式 | 精品国产免费一区二区| 无码人妻精品一区二区三区9厂 |