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

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

基本數據類型:javascript

number、html

string、前端

boolean、java

null、git

undefined 引用數據類型: object(普通對象、數組對象、正則對象、日期對象、Math、實例、 prototype等)github

function(普通函數和類) 特殊數據類型:Symbolweb

基本數據類型按值操做,引用數據類型按照堆內存的引用地址來操做面試

數據類型檢測四種方式: typeof編程

instanceofjson

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....]
複製代碼
相關文章
相關標籤/搜索