前端面試題整理——javaScript部分

(1)typeof 和 instanceof

一、typeof 對於基本數據類型(boolean、null、undefined、number、string、symbol)來講,除了 null 均可以顯示正確的類型;對於對象來講,除了函數都會顯示 object。
二、instanceof 是經過原型鏈來判斷的。能夠判斷一個對象的正確類型,可是對於基本數據類型的沒法判斷。
三、instanceof能正確判斷對象的原理:
經過判斷對象的原型鏈中是否是能找到類型的原型 [].__proto__ == Array.prototypejavascript

function myInstanceof(left, right) {
  let prototype = right.prototype
  left = left.__proto__
  while (true) {
    if (left === null || left === undefined)
      return false
    if (prototype === left)
      return true
    left = left.__proto__
  }
}

instanceof 主要的實現原理就是隻要右邊變量的 prototype 在左邊變量的原型鏈上便可。所以,instanceof 在查找的過程當中會遍歷左邊變量的原型鏈,直到找到右邊變量的 prototype,若是查找失敗,則會返回 false,告訴咱們左邊變量並不是是右邊變量的實例。
四、相關筆試題:
1)JavaScript中如何檢測一個變量是一個String類型?請寫出函數實現。java

var str = 'ssssssss';
typeof str === 'string'
str.constructor === String

(2)閉包

一、閉包是指有權訪問另外一個函數做用域中的變量的函數。
建立閉包的常見方式,就是在一個函數內部建立另外一個函數。面試

function a(){
        var num = 100;
        function b(){
            console.log(num);
        }
        return b;
    }
    var c = a();
    c(); //100

如上所示:函數b能夠訪問到函數a中的變量,函數b就是閉包。正則表達式

二、閉包的用途:1)讀取函數內部的變量;2)讓這些變量始終保存在內存中
因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存。過分使用閉包可能會致使內存佔用過多,建議只在絕對必要時再考慮使用閉包。數組

三、相關面試題
1)循環中使用閉包解決‘var’定義函數的問題閉包

for(var i = 1; i <= 5; i++){
        setTimeout(function timer(){
            console.log(i);
        },2000)
    }

這裏由於setTimeout是異步執行,循環會先執行完畢,這時i等於6,而後就會輸出5個6。解決方法以下所示:
第一種是使用letapp

for(let i = 1; i <= 5; i++){
        setTimeout(function timer(){
            console.log(i);
        },2000)
    }

第二種是使用閉包異步

for(var i = 1; i <= 5; i++){
        (function(j){
            setTimeout(function timer(){
                console.log(j);
            },2000)
        })(i)
    }

這裏首先使用了當即執行函數將i傳入函數內部,這個時候值就被固定在了參數j上面不會改變,當下次執行timer這個閉包的時候,就可使用外部函數的變量j。函數

(3)原型和原型鏈

一、咱們建立的每個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象。而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。prototype就是經過調動構造函數而建立的那個對象實例的原型對象。
使用原型對象的好處是可讓全部對象實例共享它所包含的屬性和方法。
示例:this

function Person(){}
    var p1 = new Person();
    var p2 = new Person();
    Person.prototype.name = 'CoCo';
    console.log(p1.name); //CoCo
    console.log(p2.name); //CoCo

Person構造函數下有一個prototype屬性。Person.prototype就是原型對象,也就是實例p一、p2的原型。

二、在默認狀況下,全部原型對象都會自動得到一個constructor屬性,這個屬性包含一個指向prototype屬性所在函數的指針。

function Person(){}
console.log(Person.prototype.constructor === Person); //true

三、Firefox、Safari和Chrome在每一個對象上都支持一個屬性__proto__,這個屬性對腳本是徹底不可見的。__proto__用於將實例與構造函數的原型對象相連。
鏈接實例與構造函數的原型對象之間。

function Person(){}
    var p1 = new Person();
    console.log(p1.__proto__ === Person.prototype); //true
    //isPrototypeOf:檢測一個對象是不是另外一個對象的原型。或者說一個對象是否被包含在另外一個對象的原型鏈中
    console.log(Person.prototype.isPrototypeOf(p1)); //true
    //getPrototypeOf:返回對象__proto__指向的原型prototype
    console.log(Object.getPrototypeOf(p1) == Person.prototype); //true

(4)call、apply、bind

每一個函數都包含兩個非繼承而來的方法: appy()和call()。這兩個方法的用途都是在特定的做用域中調用函數,而後能夠設置調用函數的this指向。
一、apply()第一個參數是this所要指向的那個對象,若是設爲null或undefined或者this,則等同於指定全局對象。二是參數(能夠是數組也能夠是arguments對象)。

var a = 1;
   function fn1(){
        console.log(this.a);
   }
   var obj = {
       a:2
   };
   fn1(); //1
   fn1.apply(obj); //2
   fn1.call(obj);//2
   fn1.bind(obj)();//2

二、call()方法能夠傳遞兩個參數。第一個參數是指定函數內部中this的指向(也就是函數執行時所在的做用域),第二個參數是函數調用時須要傳遞的參數。第二個參數必須一個個添加。
三、bind()方法能夠傳遞兩個參數。第一個參數是指定函數內部中this的指向,第二個參數是函數調用時須要傳遞的參數。第二個參數必須一個個添加。
四、三者區別:
1)均可以在函數調用時傳遞參數,call、bind方法須要直接傳入,而apply方法能夠以數組或者arguments的形式傳入。
2)call、apply方法是在調用以後當即執行函數,而bind方法沒有當即執行,須要將函數再執行一遍。
五、手寫三種函數
1)apply

Function.prototype.myApply = function(context){
            if(typeof this !== 'function'){
                throw new TypeError('Error');
            }
            context = context || window;
            context.fn = this;
            let result;
            if(arguments[1]){
               result = context.fn(...arguments[1]);
            }else{
                result = context.fn();
            }
            delete context.fn;
            return result;
        };

2)call

Function.prototype.myCall = function(context){
            if(typeof this !== 'function'){
                throw new TypeError('Error');
            }
            context = context || window;
            context.fn = this;
            const args = [...arguments].slice(1);
            const result = context.fn(...args);
            delete context.fn;
            return result;
        };

3)bind

Function.prototype.myBind = function(context){
            if(typeof this !== 'function'){
                throw new TypeError('Error');
            }
           const _this = this;
            const args = [...arguments].slice(1);
             return function F(){
                 if(this instanceof F){
                     return new _this(...args,...arguments);
                 }
                 return _this.apply(context,args.concat(...arguments));
             }
        };

bind返回了一個函數,對於函數來講有兩種方式調用,一種是直接調用,一種是經過new的方式:
直接調用,這裏選擇了apply的方式實現,由於由於 bind 能夠實現相似這樣的代碼 f.bind(obj, 1)(2),因此咱們須要將兩邊的參數拼接起來,因而就有了這樣的實現 args.concat(...arguments)。
最後來講經過 new 的方式,對於 new 的狀況來講,不會被任何方式改變 this,因此對於這種狀況咱們須要忽略傳入的 this

(5)深拷貝淺拷貝

一、淺拷貝

let a = {
  age: 1
}
let b = a
a.age = 2
console.log(b.age) // 2

基本數據類型是數據拷貝,會從新開闢一個空間存放拷貝的值。對象類型在賦值的過程當中實際上是複製了地址,從而會致使改變了一方其餘也都被改變的狀況。一般在開發中咱們不但願出現這樣的問題,咱們可使用淺拷貝來解決這個狀況。

1)概念:對於對象類型,淺拷貝就是對對象地址的拷貝,拷貝的結果是兩個對象指向同一個地址,並無開闢新的棧,修改其中一個對象的屬性,另外一個對象的屬性也會改變。

2)實現:
a、經過Object.assign來實現。Object.assign()方法的第一個參數是目標對象,後面的參數都是源對象。用於對象的合併,將源對象的全部可枚舉屬性複製到目標對象。
Object.assign 只會拷貝全部的屬性值到新的對象中,若是屬性值是對象的話,拷貝的是地址,因此並非深拷貝。

let a = {
  age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1

b、經過展開運算符 ... 來實現淺拷貝

let a = {
  age: 1
}
let b = { ...a }
a.age = 2
console.log(b.age) // 1

c、循環實現
這裏是淺拷貝,由於對象仍是拷貝的地址。

var a = {
        name:'coco',
        age:33,
        fn:function(){
            console.log("111")
        },
        children:{
            age:66
        }
    };
    var b = {};
    for(var i in a){
        b[i] = a[i];
    }
    b.children.age = 100;
    console.log(a);
    console.log(b);

二、深拷貝

let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = { ...a }
a.jobs.first = 'native'
console.log(b.jobs.first) // native

淺拷貝只解決了第一層的問題,若是接下去的值中還有對象的話,那麼就又回到最開始的話題了,二者享有相同的地址。要解決這個問題,咱們就得使用深拷貝了。

1)概念:對於對象類型,深拷貝會開闢新的棧,兩個對象對應兩個不一樣的地址,修改其中一個對象的屬性,另外一個對象的屬性不會改變。
2)原理:深複製--->實現原理,先新建一個空對象,內存中新開闢一塊地址,把被複制對象的全部可枚舉的(注意可枚舉的對象)屬性方法一一複製過來,注意要用遞歸來複制子對象裏面的全部屬性和方法,直到子子.....屬性爲基本數據類型。
3)實現:
a、經過 JSON.parse(JSON.stringify(object)) 來解決
缺點:能夠知足基本的深拷貝,可是對於正則表達式、函數類型則沒法進行拷貝。它還會拋棄對象的constructor。

let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

b、遞歸

var a = {
        name:'kiki',
        age:25,
        children:{
            name:'CoCo',
            age:12
        },
        arr:['111','222'],
        fn:function(){
            console.log(1);
        }
    };

    function copy(obj){
        if(obj instanceof Array){
            var newObj = [];
        }else{
            var newObj = {};
        }
       for(var i in obj){
            if(typeof obj[i] == 'object'){
                newObj[i] = copy(obj[i]);
            }else{
                newObj[i] = obj[i];
            }
       }
       return newObj;
    }
    var b = copy(a);
    console.log(a);
    console.log(b);

c、jQuery.extend() jQuery.extend([deep],target,object1,object2...),deep位Boolean類型,若是爲true則進行深拷貝。

相關文章
相關標籤/搜索