JS原生基礎面試題 以及內建原始碼重寫 -->後期還會更新

Fe_從零到壹發表於2019-09-03

前言

文章涉及的內容可能不全面,但量很多,需要慢慢看。我花了很長的時間整理,用心分享心得,希望對大家有所幫助。但是難免會有打字的錯誤或理解的錯誤點,希望發現的可以郵箱告訴我ghui_master@163.com,我會及時的進行修改,只希望對你有所幫助,謝謝。

JS中的資料型別都有哪些?它們之間有什麼區別?該如何檢測?

基本資料型別:

number、

string、

boolean、

null、

undefined 引用資料型別: object(普通物件、陣列物件、正則物件、日期物件、Math、例項、 prototype等)

function(普通函式和類) 特殊資料型別:Symbol

基本資料型別按值操作,引用資料型別按照堆記憶體的引用地址來操作

資料型別檢測四種方式: typeof

instanceof

constructor

Object.prototype.toString.call()

  1. 常用瀏覽器的核心都有哪些? 3. 陣列中常用的迭代方法有哪些?都是什麼意思?(至少四種)

    webkit、

    Gecko、

    Trident、

    Presto等

  2. 說一下你對閉包的理解,以及工作中什麼地方你用到了閉包?

    函式執行會形成一個私有的作用域(私有棧記憶體),這就是閉包,在真實專案 中主要應用閉包的兩大作用 保護

    儲存 之前研究過像JQUERY等類庫的原始碼,為了防止全域性變數汙染,基本上所有程式碼 都是放到閉包中保護起來的 專案中遇到迴圈事件繫結,也會基於閉包儲存的作用,去儲存對應的索引 之前研究過JS高階程式設計技巧:柯理化函式程式設計思想,這個就是閉包的應用,重 寫過debounce、throttle函式節流和防抖的方法 ...... 因為閉包會產生不釋放的棧記憶體,所以儘可能在專案中少使用

    什麼是物件導向? 談談你的理解

    物件導向是一種程式設計思想,js本身就是基於物件導向構建出來的(例如:js中有很多內建類,像Promise就是ES6中新增的一個內建類,我們可以基於new Promise來建立一個例項,來管理非同步程式設計,我們在專案中Promise也經常用,自己也研究過他的原始碼。。。)我們平時用的VUE/REACT/JQUERY也是基於物件導向構建出來的,他們都是類,平時開發的時候都是建立他們的額例項來操作的;當然我自己在真實專案中,也封裝過一些元件外掛,(例如:DIALOG、拖拽、、、)也是基於物件導向開發的,這樣可以創造不同的例項,來管理私有的屬性和公有的方法,很方便。。。。

    JS中的面向對物件,和其他程式語言還略有不同,JS中類和例項都是基於原型和原型鏈機制來處理的; 而且js中關於類的過載、重寫、繼承也和其他語言不太一樣

  3. window.onload VS $(document).ready()

    // 這個題我知道,我之前看過部分JQ原始碼

    // 1.$(document).ready() 採用的是DOM2事件繫結,監聽的是DOMContentLoaded這個事件,所以只要DOM結構載入完成就會被觸發執行,而且同一個頁面中可以使用多次(繫結不同的方法,因為基於DOM2事件池繫結機制完成的) // 2.window.onload必須等待所有資源都載入完成才會被觸發執行,採用DOM0事件繫結,同一個頁面只能繫結一次(一個方法),想繫結多個也需要改為window.addEventListener('load', function () {})DOM2繫結方式

  4. 闡述一下let/var/const三者之間的區別?

    let 和 var 的區別

    1. 不存在變數提升
    2. 不允許重複宣告
    3. 在全域性作用域下設定變數不會給window設定屬性
    4. 存在塊級作用域
    5. 解決了一些暫時性死區問題

    let 和 const 的區別 const 建立的是常量,儲存的值不能被修改(準確說是不能修改變數的指 向)

  5. 闡述一下call/apply/bind三者之間的區別,以及應用場景?

    call 和 apply 的區別 給方法傳參的時候,call是按順序,一個個傳遞;apply是把傳遞的引數放 到一個陣列中傳遞;

    在傳遞引數較多的情況下, call的效能要高於apply; call 和 bind 的區別 call在改變函式this指向的時候,會把函式立即執行,而bind不會把函式立 即執行,只是預先處理了this和實參資訊;

    真實專案中,我們需要改變this指向的時候,會應用這三個方法,例如: 給元素進行事件繫結,我們需要把事件觸發,所執行的函式中this和引數進 行預先處理,此時可以使用bind進行處理;

    我們可以基於call方法,讓類陣列借用陣列原型上的方法,例如:把類陣列 轉換為陣列

    可以基於apply傳參是一個陣列,借用Math.max獲取陣列中的最大值 ......

    陣列中常用的迭代方法有哪些?(至少四種)

    forEach、

    map、

    find、

    some、

    filter、

    reduce、

    every、

    sort等

    有A和B兩個開發者,他們同時開發一款產品(最後的程式碼需要合併),為了防止相互之前產生變數汙染,他們決定採用高階單例模式來進行模組化開發,請編寫相應的程式碼示例!

    // 開發者A
    let AModule = (function () {
    	let n = 10;
    	let query = function () {
    		//...
    	};
    	let fn = function () {
    		//...
    		//調取開發者B編寫的QUERY方法
    		BModule.query();
    	};
    
    	return {
    		query: query,
    		init: function () {
    			query();
    			fn();
    		}
    	}
    })();
    
    // 開發者B
    let BModule = (function () {
    	let n = 20;
    	let query = function () {
    		//...
    	};
    	let sum = function () {
    		//...
    		//調取開發者A編寫的QUERY方法
    		AModule.query();
    	};
    
    	return {
    		query: query,
    		init: function () {
    			query();
    			sum();
    		}
    	}
    })();
    
    AModule.init();
    BModule.init();
     
    複製程式碼

    ES6中的新語法規範

    • let / const
    • class 建立類
    • import / export :ES6 Module 模組的匯入匯出規範(JS中的模組化規範 AMD -> CMD -> CommonJS -> ES6 Module)
    • Arrow Function 箭頭函式
    • 模板字串
    • 解構賦值
    • “...” 擴充、展開、剩餘運算子
    • Promise / async / await
    • for of迴圈
    • Set / Map
    • Array / Object ... 提供的新方法
    • ......

    var r2 = typeof typeof typeof sum; // "string"

    執行順序:先執行最後一個 typeof sum -> "function" -> typeof typeof "function" -> typeof "string" -> "string"

    只要是兩個及以上的typeof 最後返回結果就是 "string"

    TCP傳輸的詳細過程是怎樣的?

    進行三次握手,建立TCP連線。
  • 第一次握手:建立連線。客戶端傳送連線請求報文段,將SYN位置為1,Sequence Number為x;然後,客戶端進入SYN_SEND狀態,等待伺服器的確認;

  • 第二次握手:伺服器收到SYN報文段。伺服器收到客戶端的SYN報文段,需要對這個SYN報文段進行確認,設定Acknowledgment Number為x+1(Sequence Number+1);同時,自己自己還要傳送SYN請求資訊,將SYN位置為1,Sequence Number為y;伺服器端將上述所有資訊放到一個報文段(即SYN+ACK報文段)中,一併傳送給客戶端,此時伺服器進入SYN_RECV狀態;

  • 第三次握手:客戶端收到伺服器的SYN+ACK報文段。然後將Acknowledgment Number設定為y+1,向伺服器傳送ACK報文段,這個報文段傳送完畢以後,客戶端和伺服器端都進入ESTABLISHED狀態,完成TCP三次握手。

  • 完成了三次握手,客戶端和伺服器端就可以開始傳送資料。

  • ACK:此標誌表示應答域有效,就是說前面所說的TCP應答號將會包含在TCP資料包中;有兩個取值:0和1,為1的時候表示應答域有效,反之為0。

  • TCP協議規定,只有ACK=1時有效,也規定連線建立後所有傳送的報文的ACK必須為1。

  • SYN(SYNchronization) : 在連線建立時用來同步序號。當SYN=1而ACK=0時,表明這是一個連線請求報文。對方若同意建立連線,則應在響應報文中使SYN=1和ACK=1. 因此, SYN置1就表示這是一個連線請求或連線接受報文。 FIN (finis)即完,終結的意思, 用來釋放一個連線。當 FIN = 1 時,表明此報文段的傳送方的資料已經傳送完畢,並要求釋放連線。

  • 傳送HTTP請求,伺服器處理請求,返回響應結果 TCP連線建立後,瀏覽器就可以利用HTTP/HTTPS協議向伺服器傳送請求了。伺服器接受到請求,就解析請求頭,如果頭部有快取相關資訊如if-none-match與if-modified-since,則驗證快取是否有效,若有效則返回狀態碼為304,若無效則重新返回資源,狀態碼為200. 關閉TCP連線

  • 第一次分手:主機1(可以使客戶端,也可以是伺服器端),設定Sequence Number和Acknowledgment Number,向主機2傳送一個FIN報文段;此時,主機1進入FIN_WAIT_1狀態;這表示主機1沒有資料要傳送給主機2了;

  • 第二次分手:主機2收到了主機1傳送的FIN報文段,向主機1回一個ACK報文段,Acknowledgment Number為Sequence Number加1;主機1進入FIN_WAIT_2狀態;主機2告訴主機1,我“同意”你的關閉請求;

  • 第三次分手:主機2向主機1傳送FIN報文段,請求關閉連線,同時主機2進入LAST_ACK狀態;

  • 第四次分手:主機1收到主機2傳送的FIN報文段,向主機2傳送ACK報文段,然後主機1進入TIME_WAIT狀態;主機2收到主機1的ACK報文段以後,就關閉連線;此時,主機1等待2MSL後依然沒有收到回覆,則證明Server端已正常關閉,那好,主機1也可以關閉連線了。

瞭解跨域嗎,一般什麼情況下會導致跨域

小提示: 如果平常自身有使用場景可結合使用場景進行講解,比如我在這裡使用過的場景是CORS和Nginx反向代理。

跨域行為

  • 同源策略限制、安全性考慮
  • 協議、IP和埠不一致都是跨域行為

JSONP

小提示:如果你提到JSONP,面試官肯定會問你整個詳細的實現過程,所以一定要搞懂JSONP的實現原理,如果不是很理解可以自己起一個Express服務實踐一下。

Web前端事先定義一個用於獲取跨域響應資料的回撥函式,並通過沒有同源策略限制的script標籤發起一個請求(將回撥函式的名稱放到這個請求的query引數裡),然後服務端返回這個回撥函式的執行,並將需要響應的資料放到回撥函式的引數裡,前端的script標籤請求到這個執行的回撥函式後會立馬執行,於是就拿到了執行的響應資料。

缺點: JSONP只能發起GET請求

如何實現一個JSONP

這裡給出幾個連結:

segmentfault.com/a/119000001… zhangguixu.github.io/2016/12/02/… www.cnblogs.com/iovec/p/531…

JSONP安全性問題

CSRF攻擊

前端構造一個惡意頁面,請求JSONP介面,收集服務端的敏感資訊。如果JSONP介面還涉及一些敏感操作或資訊(比如登入、刪除等操作),那就更不安全了。

解決方法:驗證JSONP的呼叫來源(Referer),服務端判斷Referer是否是白名單,或者部署隨機Token來防禦。

XSS漏洞

不嚴謹的 content-type導致的 XSS 漏洞,想象一下 JSONP 就是你請求 http://youdomain.com?callback=douniwan, 然後返回 douniwan({ data }),那假如請求 http://youdomain.com?callback=<script>alert(1)</script> 不就返回 <script>alert(1)</script>({ data })了嗎,如果沒有嚴格定義好 Content-Type( Content-Type: application/json ),再加上沒有過濾 callback 引數,直接當 html 解析了,就是一個赤裸裸的 XSS 了。

解決方法:嚴格定義 Content-Type: application/json,然後嚴格過濾 callback 後的引數並且限制長度(進行字元轉義,例如<換成&lt,>換成&gt)等,這樣返回的指令碼內容會變成文字格式,指令碼將不會執行。

伺服器被黑,返回一串惡意執行的程式碼

可以將執行的程式碼轉發到服務端進行校驗JSONP內容校驗,再返回校驗結果。

CORS(跨域資款共享)

小提示:如果你回答跨域解決方案CORS,那麼面試官一定會問你實現CORS的響應頭資訊Access-Control-Allow-Origin。

什麼是CORS

CORS(跨域資源共享 Cross-origin resource sharing)允許瀏覽器向跨域伺服器發出XMLHttpRequest請求,從而克服跨域問題,它需要瀏覽器和伺服器的同時支援。

  • 瀏覽器端會自動向請求頭新增origin欄位,表明當前請求來源。
  • 伺服器端需要設定響應頭的Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Allow-Origin等欄位,指定允許的方法,頭部,源等資訊。
  • 請求分為簡單請求和非簡單請求,非簡單請求會先進行一次OPTION方法進行預檢,看是否允許當前跨域請求。

簡單請求

請求方法是以下三種方法之一:

  • HEAD
  • GET
  • POST

HTTP的請求頭資訊不超出以下幾種欄位:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain

後端的響應頭資訊:

  • Access-Control-Allow-Origin:該欄位是必須的。它的值要麼是請求時Origin欄位的值,要麼是一個*,表示接受任意域名的請求。
  • Access-Control-Allow-Credentials:該欄位可選。它的值是一個布林值,表示是否允許傳送Cookie。
  • Access-Control-Expose-Headers:該欄位可選。CORS請求時,XMLHttpRequest物件的getResponseHeader()方法只能拿到6個基本欄位:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他欄位,就必須在Access-Control-Expose-Headers裡面指定。

非簡單請求

非簡單請求是那種對伺服器有特殊要求的請求,比如請求方法是PUT或DELETE,或者Content-Type欄位的型別是application/json。非簡單請求的CORS請求,會在正式通訊之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。

  • Access-Control-Request-Method:該欄位是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT。
  • Access-Control-Request-Headers:該欄位是一個逗號分隔的字串,指定瀏覽器CORS請求會額外傳送的頭資訊欄位,上例是X-Custom-Header。

如果瀏覽器否定了"預檢"請求,會返回一個正常的HTTP回應,但是沒有任何CORS相關的頭資訊欄位。這時,瀏覽器就會認定,伺服器不同意預檢請求,因此觸發一個錯誤,被XMLHttpRequest物件的onerror回撥函式捕獲。

JSONP和CORS的對比

  • JSONP只支援GET請求,CORS支援所有型別的HTTP請求
  • JSONP的優勢在於支援老式瀏覽器,以及可以向不支援CORS的網站請求資料

其他跨域解決方案

  • Nginx反向代理

  • postMessage

  • document.domain

    基於原生實現內建類方法重寫

    重寫內建 new

    function Dog(name) {
    this.name = name;
    }
    Dog.prototype={
    bark(){
    console.log('wangwang');
    },
    sayName(){
    console.log('my name is ' + this.name);
    }
    };
    function _new() {
    //=>完成你的程式碼
    }
    let sanmao = _new(Dog, '三毛');
    sanmao.bark(); //=>"wangwang"
    sanmao.sayName(); //=>"my name is 三毛"
    console.log(sanmao instanceof Dog); //=>true
    ================================================
     //——> 重寫 _new
    function _new(Fn, ...arg) {
    let obj = {};
    obj.__proto__ = Fn.prototype;
    Fn.call(obj, ...arg);
    return obj;
    }
    複製程式碼

    實現一個$attr(name,value)遍歷

    /* 實現一個$attr(name,value)遍歷
    * 屬性為name
    * 值為value的元素集合
    * 例如下面示例:
    */
    let ary = $attr('class','box'); //=>獲取頁面中所有class為box的元素
    =====================================
         
         
    function $attr(property, value) {
    let elements = document.getElementsByTagName('*'),
    arr = [];
    elements = Array.from(elements);
    elements.forEach(item => {
    let itemValue = item.getAttribute(property);
    if (property==='class') {
    new RegExp("\\b" + value +
    "\\b").test(itemValue)?arr.push(item):null;
    return;
    }
    if (itemValue === value) {
    arr.push(item);
    }
    });
    return arr;
    }
    console.log($attr('class', 'box'));
    複製程式碼

    bind重寫

    ~function(){
    //=>bind方法在IE6~8中不相容,接下來我們自己基於原生JS實現這個方法
    function bind(){
    };
    Function.prototype.bind=bind;
    }();
    var obj = {name:'zhufeng'};
    function func(){
    console.log(this,arguments);
    //=>當點選BODY的時候,執行func方法,輸出:obj
    [100,200,MouseEvent事件物件]
    }
    document.body.onclick = func.bind(obj,100,200);
    
    =============================================
     ~function(){
    //=>bind方法在IE6~8中不相容,接下來我們自己基於原生JS實現這個方法
    function bind(context){
    context=context||window;
    var _this = this,
    outerArg=[].slice.call(arguments,1);
    return function anonymous() {
    var innerArg=[].slice.call(arguments,0);
    _this.apply(context, outerArg.concat(innerArg));
    }
    };
    Function.prototype.bind=bind;
    }();    
    複製程式碼

    class重構

    function Modal(x,y){
    this.x=x;
    this.y=y;
    }
    Modal.prototype.z=10;
    Modal.prototype.getX=function(){
    console.log(this.x);
    }
    Modal.prototype.getY=function(){
    console.log(this.y);
    }
    Modal.n=200;
    Modal.setNumber=function(n){
    this.n=n;
    };
    let m = new Model(10,20);
    ===============================================
         
    class Modal{
    constructor(x,y){
    this.x=x;
    this.y=y;
    }
    getX(){
    console.log(this.x);
    }
    getY(){
    console.log(this.y);
    }
    static setNumber(n){
    this.n=n;
    }
    }
    Modal.n=200;
    Modal.prototype.z=10;     
    複製程式碼

    call重寫

    ~function(){
        function changeThis(context){
           context=context||window;
            //let arg=[].slice.call(arguments,1);
            let arg=[],
                _this=this,
                result=null;
            for(let i=1;i<arguments.length;i++){
                arg.push(arguments[i]);
            }
            context.$fn=_this;
            result=context.$fn(...arg);
            delete context.$fn;
            return result;
       };
        Function.prototype.changeThis=changeThis;
    }();
    
    let obj = {name:'Alibaba',$fn:100};
    function fn(x,y){
        this.total=x+y;
        return this;
    }
    let res = fn.changeThis(obj,100,200);
    //res => {name:'Alibaba',total:300}
    複製程式碼

    apply重寫

    ~function(){
        /*生成隨機函式名:時間戳的方式*/
        function queryRandomName(){
            let time=new Date().getTime();
            return '$zhufeng'+time;
        }
        /*模擬CALL方法改變函式中的THIS*/
        function changeThis(context=window,arg=[]){
            let _this=this,
                result=null,
                ran=queryRandomName();
            context[ran]=_this;
            result=context[ran](...arg);
            delete context[ran];
            return result;
       };
        Function.prototype.changeThis=changeThis;
    }();
    let res = fn.changeThis(obj,[100,200]);
    複製程式碼

    怎麼讓一個 div 水平垂直居中?(不少於三種解決方案)

/* 已知寬高 */
.box {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px;
margin-left: -50px;
width: 100px;
height: 100px;
}
========================
/* 未知寬高 */
.box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
==========================
.box {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
===============================
.container {
display: flex;
justify-content: center;
align-items: center;
}
.container .box {}
複製程式碼

編寫一個方法“flatten”,將陣列扁平化 (至少兩種辦法)

/*
* 1.編寫一個方法“flatten”,將陣列扁平化 (至少兩種辦法)
* 2.然後實現“unique”陣列去重方法,把陣列進行去重 (至少兩種辦法)
*/
let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9,[11, 12, [12,
13, [14]]]], 10];
ary.flatten().unique().sort((a,b)=>a-b); //=>[1, 2, 3, 4, 5,
6, 7, 8, 9....]
============================================
~function(){
function flatten(){
/*第一種*/
return JSON.stringify(this).replace(/(\
[|\])/g,'').split(',').map(item=>{
return Number(item);
});
/*第二種*/
return this.flat(Infinity);
/*第三種*/
return this.toString().split(',').map(item=>{
return Number(item);
});
/*第四種*/
let arr=this.slice(0);
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
/*第五種*/
let result = [];
let fn = function (ary) {
for (let i = 0; i < ary.length; i++) {
let item = ary[i];
if (Array.isArray(ary[i])) {
fn(item);
} else {
result.push(item);
}
}
}
fn(this);
return result;
}
function unique(){
/*第一種*/
return Array.from(new Set(this));
/*第二種*/
return [...new Set(this)];
/*第三種*/
let obj={};
for(let i=0;i<this.length;i++){
let item=this[i];
if(typeof obj[item]!=='undefined'){
this[i]=this[this.length-1];
this.length--;
i--;
continue;
}
obj[item]=item;
}
return this;
}
Array.prototype.flatten=flatten;
Array.prototype.unique=unique;
}();
let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9,[11, 12, [12,
13, [14]]]], 10];
ary.flatten().unique().sort((a,b)=>a-b); //=>[1, 2, 3, 4, 5,
6, 7, 8, 9....]
複製程式碼

相關文章