【前端知識體系-JS相關】JS基礎知識總結

1 變量類型和計算

1.1 值類型和引用類型的區別?

  1. 值類型:每一個變量都會存儲各自的值。不會相互影響
  2. 引用類型:不一樣變量的指針執行了同一個對象(數組,對象,函數)

1.2 typeof能夠及檢測的數據類型有哪些?

[!NOTE]html

  • 基本數據類型:Undefined null bool string number
  • 關鍵點:typeof只能區分值類型,不能區分引用類型
  1. JS中的基本數據類型:null, undefined, bool, string, number(typeof能夠區分除了null之外的四種值類型)
  2. typeof 6種類型:Object({},[],null), Undefined, Boolean, Number, Function, String
  3. typeof能夠區分值類型,typeof null = Object

1.3 JS中===和==的區別?

1.3.1 區別?

== 會進行強制類型轉換以後再比較,=== 不會進行強制類型轉換的前端

1.3.2 應用場景?

  1. (用於判斷對象屬性是否存在):if (obj == null) ===>>> 等價於if (obj == null || obj == undefined),能夠簡化代碼,其餘情形都使用===進行比較
  2. 用於判斷函數的參數是否存在: function(a, b){ if(a == null) { // ... }}
  3. 對於函數內部或者是一個對象的參數進行判斷只會出現undefined, 而不會報錯(慎用)

1.3.3 其餘?

js中類型轉換爲false的有哪些(6種):null, undefined, NaN, '', false, 0ajax

1.4 JS中的內置函數有哪些?

[!NOTE]json

  • 內置函數: Object Array Boolean Number String Function Date RegExp Error
  • 內置對象:Math, JSON

2 原型和原型鏈

2.1 原型鏈的5條規則

  1. 全部的引用類型(數組,對象,函數),都是具備對象特性的,便可以自由擴展屬性(除了null之外)
  2. 全部的引用類型(數組、對象、函數),都有一個__proto__ 屬性(隱式原型),這個屬性的值是一個普通對象
  3. 全部的函數,都有一個prototype屬性(顯式原型),這個屬性值是一個普通的對象
  4. 全部的引用類型(數組、對象、函數),__proto__的屬性值指向(徹底相等)它的構造函數的「prototype」的屬性值
  5. 當試圖獲得一個對象的某一個屬性的時候,若是一個對象自己沒有這個屬性的話,就會去它的__proto__( 也就是它的構造函數中去尋找這個屬性)

2.2 JS中尋找對象屬性的過程

  1. 當一個對象沒有這個toString()這個屬性的時候,就回去本身的隱式原型__proto__中去尋找這個屬性,也就是去本身額構造函數的顯示原型prototype中尋找這個屬性(對象自身的隱式原型就是他的構造函數的顯式原型)
  2. 發現FOO.prototype中也沒有這個toString屬性,這也是一個對象,name就去這個對象{}的__proto__中尋找toString()這個屬性

2.3 instanceof的做用?

是用於判斷【引用類型】屬於哪一個【構造函數】的方法
[!NOTE]
總結:f.__proto__ 一層一層向上尋找,可否找到FOO.prototype,找到爲true,不然爲false數組

2.4 寫一個原型繼承的例子

/**
     * 動物類
     * @constructor
     * */
    function Animal (name){
        this.name = name;
        this.eat = function () {
            console.log('My name is ', name, ' I am eating Foods…………');
        }
    }

    /**
     * 小狗類
     * @constructor
     */
    function Dog(){
        this.bark = function () {
            console.log("I am a dog, I am barking……");
        }
    }

    // 如何讓這個小狗繼承這個Animal的屬性呢?
    // 實現思路:每個函數都有一個prototype屬性,這個屬性值是一個普通的對象
    Dog.prototype = new Animal();
    var dog = new Dog();
    // 這個小狗有eat() 這個屬性嗎?發現自身沒有,那麼就會去dog這個對象的__proto__裏面去尋找,也就是他的構造函數Dog的prototype上面去尋找
    // 發現這個對象Dog構造函數的prototype的值是一個對象new Animal(), 這個對象裏面是有eat這個屬性的,所以就找到了
    dog.eat(); // My name is  undefined  I am eating Foods…………
    console.log(dog.__proto__ === Dog.prototype);       // tru

2.5 描述一下new一個對象的過程

[!NOTE]瀏覽器

  1. 建立一個新對象
  2. this指向這個新對象
  3. 執行代碼(對this賦值)
  4. 返回this
// v1
  function objectFactory() {
      var obj = new Object(),
          // 由於 shift 會修改原數組,因此 arguments 會被去除第一個參數
          Constructor = [].shift.call(arguments);     // 拿到僞數組中的第一個參數
      // 取出參數中的第一個參數,就是咱們要傳入的構造函數,創建繼承關係
      obj.__proto__ = Constructor.prototype;
      Constructor.apply(obj, arguments);
      return obj;
  }

  // v2 : 還須要判斷返回的值是否是一個對象,若是是一個對象,咱們就返回這個對象,若是沒有,咱們該返回什麼就返回什麼。
  function objectFactory() {
      var obj = new Object(),
          Constructor = [].shift.call(arguments);
      // 創建繼承關係(兩者之間的關係)
      obj.__proto__ = Constructor.prototype;
      // 開始執行這個構造函數
      var ret = Constructor.apply(obj, arguments);
      // 看一下構造函數的返回值,是對象仍是一個基本數據類型?
      return typeof ret === 'object' ? ret : obj;
  }

 
  // v4:Object.create的原理
    // var obj = Object.create(Constructor.prototype);
    // 等價於:
    // var obj = new Object();
    // obj.__proto__ = Constructor.prototype;
    const _new = function () {
        var Constructor = [].shift.call(arguments);
        // 1. 建立一個對象,這個對象要繼承與構造函數的原型對象
        var obj = Object.create(Constructor.prototype);
        // 2. 執行這個構造函數
        var ret = Constructor.apply(obj, arguments);
        return typeof ret === 'object' ? ret || obj : obj;
    }
    
    // v5: 實現一個本身的new構造函數
    const _new = function() {
        // 從Object.prototype上克隆一個對象 
        var obj = new Object();
        // 取出來外部傳入的構造器
        var Constructor = [].shift.call(arguments);
        
        // 使用一箇中間的函數來維護原型的關係
        var F = function(){};
        F.prototype = Constructor.prototype;
        obj = new F();
        
        // 開始執行這個構造函數
        var res = Constructor.apply(obj, arguments);
        // 確保構造器老是返回一個對象(使用res || obj 的方式來防止返回null參數)
        return typeof res === 'object' ? res || obj : obj;
    }

2.6 zepto或其餘框架中是如何實現原型鏈的?

var Zepto = (function(){
    var $,
        zepto = {}
    
    // ...省略N行代碼...

    zepto.Z = function(dom, selector) {
      dom = dom || []
      dom.__proto__ = $.fn
      dom.selector = selector || ''
      return dom
    }
    
    zepto.init = function(selector, context) {
        var dom
        
        // 針對參數狀況,分別對dom賦值
        
        // 最終調用 zepto.Z 返回的數據
        return zepto.Z(dom, selector)
    }
    
    $ = function(selector, context){
        return zepto.init(selector, context)
    }
    
    $.fn = {
        // 裏面有若干個工具函數
    }
    
    // ...省略N行代碼...
    
    return $
})()

window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)

3 做用域和閉包

3.1 函數表達式和函數聲明的區別?

  1. 函數聲明中函數名是必須的,函數表達式中則是可選的。
  2. 用函數聲明定義的函數,函數能夠在函數聲明以前調用,而用函數表達式定義的函數則只能在聲明以後調用。
var handsome='handsome';
function handsomeToUgly(){
    alert(handsome);
    var handsome='ugly';
    alert(handsome);
}
handsomeToUgly();

// 正常的解析編譯流程以下:
var handsome='handsome';
function handsomeToUgly(){
    var handsome;
    alert(handsome);
    var handsome='ugly';
    alert(handsome);
}
handsomeToUgly();

[!NOTE]
總結:( 根本緣由在於解析器對於這兩種定義方式讀取的順序不一樣:解析器會實現讀取函數聲明,即函數聲明放在任意位置均可以被調用;而對於函數表達式,解析器只有在讀到函數表達式所在那一行時纔會開始執行)網絡

3.2 對執行上下文的理解?

[!NOTE]
執行上下文能夠理解爲當前代碼的執行環境,它會造成一個做用域。JavaScript中的運行環境大概包括三種狀況。閉包

  • a. 全局環境:JavaScript代碼運行起來會首先進入的環境app

  • b. 函數環境:當函數被調用執行時,會進入被調用的函數中執行代碼框架

  • c. eval(不推薦使用會對JS的執行效率產生影響)

所以在一個JavaScript程序中,一定會產生多個執行上下文,JavaScript引擎會以棧的方式來處理它們,這個棧,咱們稱其爲函數調用棧(call stack)。棧底永遠都是全局上下文,而棧頂就是當前正在執行的上下文。當代碼在執行過程當中,遇到以上三種狀況,都會生成一個執行上下文,放入棧中,而處於棧頂的上下文執行完畢以後,就會自動出棧。

function f1(){
    var n=999;
    function f2(){
        alert(n);
    }
    return f2;
}
var result=f1();
result(); // 999

上面代碼函數調用棧的過程:
JS執行上下文
參考:https://www.cnblogs.com/ashen137/p/11422136.html

3.3 對this的理解?

  1. 做爲構造函數執行
  2. 做爲對象屬性執行
  3. 做爲普通函數執行
  4. call apply bind

3.3.1 setTimeout、setInterval中的this

var obj ={ 
    fn:function(){
        console.log(this);
    }
}
function fn2(){
    console.log(this);
}
setTimeout(obj.fn, 0);   //Window
setTimeout(fn2, 0);//Window
setInterval( obj.fn,1000 );//Window

從上述例子中能夠看到setTimeout,setInterval中函數內的this是指向了window對象,這是因爲setTimeout(),setInterval()調用的代碼運行在與所在函數徹底分離的執行環境上。這會致使這些代碼中包含的 this 關鍵字會指向 window (或全局)對象。

3.3.2 嚴格模式下的this

(1)全局做用域中的this

"use strict"
console.log("this === window",this === window);   //true

在嚴格模式下的全局做用域中 this 指向window對象

(2)全局做用域中函數中的this

"use strict"
function fn(){
    console.log('fn的this:'+this);
    function fn2(){
        console.log('fn2的this:'+this);
    }
    fn2();    
}
fn();

// fn的this:undefined
// fn2的this:undefined

嚴格模式下: 在全局做用域中函數的 this 指向 undefined

(3)對象的方法中的this

"use strict"
let name = 'zhaosi';
let obj = {
    name: 'liuneng',
    fn:function(){
        console.log(this.name)
        console.log(this);
    }
}
obj.fn();
//  liuneng
// {name: "liuneng", fn: ƒ}

嚴格模式下,對象的函數中的this指向該對象

(4)構造函數中的this

"use strict"
function Person( name){
    this.name = name;
    this.say = function(){
        console.log('my name is:'+this.name);
        console.log(this);
    }
}
var lzx = new Person('lzx');
lzx.say();  
//my name is lzx
//Person { name:"lzx", say:f }

嚴格模式下,構造函數中的this指向new出來的對象

(5)事件處理函數中的this

"use strict"
var oBtn = document.getElementById("btn");
oBtn.onclick = fn;
function fn(){
    console.log(this);
}
//<button id="btn">點擊</button>

3.3.3 箭頭函數中的this

首先,箭頭函數沒有本身的this,箭頭函數中的this是在定義函數的時候綁定,它會捕獲其所在的上下文的this做爲本身的this,而不像普通函數那樣是在執行函數的時候綁定。

var a = 10;
var obj = {
    a: 99,
    fn1:()=>{ console.log(this.a) },
    fn2:function(){ console.log(this.a) }
}
obj.fn1(); //10
obj.fn2(); //99

箭頭函數this指向它定義時的上下文(在這裏是window),而普通函數的this則指向調用它的對象
參考博文:https://blog.csdn.net/lizhengxv/article/details/84639342

3.4 call,apply,bind的區別

3.4.1 相同點

 三個函數都會改變this的指向(調用這三個函數的函數內部的this)

3.4.2 不一樣點

 1)、bind會產生新的函數,(把對象和函數綁定死後,產生新的函數)

 2)、call和apply不會產生新的函數,只是在調用時,綁定一下而已。

 3)、call和apply的區別,第一個參數都是要綁定的this,apply第二個參數是數組(是函數的全部參數),call把apply的第二個參數單列出來。

3.5 對JS中做用域的理解?

[!NOTE]
注意點:JS是沒有塊級做用域的,只有函數和全局做用域

3.6 什麼是自由變量和做用域鏈?

[!NOTE]
自由變量:當前做用域沒有定義的變量,就是‘自由變量’, 這裏的變量是一個aaaaa,發現這個函數做用域裏面沒有定義這個變量,就開始去父級做用域中尋找aaaaa這個變量(全局做用域中),發現仍是沒有找到這個變量的定義, 最終就會報錯。

做用域鏈:實際上就是函數做用域中沒有定義的變量,會去它的父級做用域中尋找的過程。

// 實際開發中閉包的應用場景(判斷用戶傳遞的參數是否已經傳遞過來)
// 閉包主要應用於封裝變量,收斂權限
function isFirstLoad() {
    // 這裏的目的實際上就是把_list 封裝爲一個變量(自由變量)
    // 防止暴露爲一個全局變量,外部沒法拿到這個變量,外部是沒法修改的,從而達到了一個封裝變量的做用
    var _list = [];
    return function (id) {
        // _list 就是一個自由變量,並且全局只有這一個(函數做用域範圍中這一個)
        if (_list.indexOf(id) >= 0)
            return false;
        else
            _list.push(id);
        return true;
    }
}

3.7 閉包的使用場景有哪些?

3.7.1 函數做爲返回值

一個函數的返回值是一個函數,父級做用域是指,定義時候的做用域,而不是執行的做用域

3.7.2 函數做爲參數傳遞

【重點】:函數做用域中的自由變量會在聲明的父級做用域中去尋找,而不是去執行的時候的做用域中去尋找變量

3.8 建立10個a標籤,點擊的時候,彈出對應的序號

// 使用當即函數+閉包實現的
function createHrefElementsTwo() {
    for (var i = 0; i < 10; i++) {
        // 一個函數就是一個做用域,這裏至關因而建立了10個函數做用域,每個做用域裏面都是有本身的一歌變量i的,這是當用戶須要這個自由變量的時候,
        // 就會直接去這個這個函數做用域範圍內尋找這個變量,每個裏面就是一個單獨的函數做用域
        (function (i) {
            // 這裏面的就是單獨的一個函數做用域
            var a = document.createElement('a');
            a.innerText = '我是當即函數中的' + i;
            a.href = '#'
            a.addEventListener('click', function (e) {
                e.preventDefault();
                // 分析:由這個for循環能夠發現,這個i其實是一個函數做用域的變量,這個i一直是變化的,當i等於9的時候,建立完畢最後一個元素,
                // 最後執行完畢i++, i的最終結果就是10,所以對於每個a標籤點擊都是一個10
                alert(i);                   // 這裏的i最後能夠實現點擊就彈出對應的編號嗎?
            })
            document.body.appendChild(a);
            document.body.appendChild(document.createElement('br'));
        })(i);      //  這裏的i和這個裏面的狀態信息保持一致的
    }
}

3.9 對函數curry化的理解?

需求:要實現一個這樣的加法函數,使得:add(1,2,3)(1)(2)(3)(4,5,6)(7,8)() === 42

  • 分析這個函數的特色: 當這個函數有參數的時候,返回的是一個函數
  • 若是沒有參數的時候,就會去執行這個函數,獲得計算的結果
// 1. 函數curry化的實現:console.log(add(1, 2, 3)(1)(2)(3)(4, 5, 6)(7, 8)());//42
    function add() {
        // 思路:當傳遞參數的時候,開始計算;沒有參數的時候,返回計算結果
        let _list = [];
        _list = _list.concat([].slice.call(arguments))
        return function () {
            let args = [].slice.call(arguments),
                len = args.length;
            // 這裏也是須要接受參數的
            if (len !== 0) {
                // 說明用戶又傳遞參數進來了
                // this.callee();
                // 把每次傳遞的數字全局記錄下來
                _list = _list.concat(args);

                // 繼續回調,返回一個函數
                // 這裏返回一個函數(這裏也是能夠直接返回一個函數表達式的引用的)
                return arguments.callee;
            } else {
                // 開始遍歷數組,計算結果, reduce 返回的也是一個最終的計算結果
                return _list.reduce((a, b) => {
                    return a + b
                })
            }
        }
    }

// 測試:
console.log(add(1, 2, 3)(1)(2)(3)(4, 5, 6)(7, 8)());

3.10 如何實現一個bind函數?

Function.prototype._bind = function () {
        // 思路:bind函數實際上每次返回的是一個函數
        // bind 函數的第一個參數是this的指向,其餘參數是傳遞的參數
        // 1. 接受參數,解析this和args
        let _bindthis = arguments[0],
            _self = this,
            _args = [].slice.call(arguments).shift();        // 刪除第0個元素以後剩餘的元素數組


        return function () {
            // 處理用戶傳遞的參數,arguments 就是用戶傳遞給個人函數參數
            let args = arguments;
            console.log('用戶傳遞的函數參數', args);
            // 修改this的指向, 沒有修改以前,因爲bind這個函數是fn調用的,所以this指向的是fn
            _self.apply(_bindthis, [].slice.call(args).concat(_args));
        }
    }
// 測試:
// 實際上第一個調用的函數參數在第二個函數參數也是能夠直接一塊兒使用的
    console.log(minus._bind({name: 'zhangsan'}, 2)(7, 8));

4 異步和單線程

4.1 什麼是異步?前端使用異步的場景有哪些?

console.log(100);
setTimeout(function () {            
    // 異步執行的代碼不會阻塞下面代碼的執行
        console.log(200);
}, 1000)
console.log(300);


// 上面的代碼執行流程以下:
// 1. 執行第一行,輸出100;
// 2. 執行setTimeout函數後,傳入setTimeout裏面的函數function(){console.log(200)}會被暫存起來(放入到事件隊列裏面),
//    不會當即執行該函數(單線程的特色,不會當即幹兩件事);
// 3. 執行最後一行代碼,輸出100;
// 4. 等待全部的同步代碼執行完畢以後,處於空閒狀態的時候,會從事件隊列中取出以前暫存的函數;
// 5. 發現暫存起來的setTimeout中的暫存函數,須要等待1s以後纔會執行,該函數就會等待1s以後纔會執行
// 使用異步的場景:
// 1. 定時任務: setTimeout, setInterval
// 2. 網絡請求: ajax請求, 動態<img>加載
// 3. 事件綁定
// 同步和異步的區別:
// 同步會【阻塞】代碼的執行,異步不會;alert是一個同步的例子,setTimeout是一個異步的例子

4.2 如何實現一個本身的ajax?

// 建立對象
    var xhr = new XMLHttpRequest();
    // 打開鏈接GET請求,請求地址,是否同步
    xhr.open('GET', '/JS-Professional/data.json', false);
    xhr.onreadyStateChange = function () {
// readyState狀態碼:
        // 0- (未初始化):尚未調用send()方法
        // 1- (載入):已經調用send()方法,正在發送請求
        // 2- (載入完成):send()方法執行完成,已經接收到所有響應內容
        // 3. (交互)正在解析響應的內容
        // 4. (完成)響應內容解析完成,能夠在客戶端調用了
   
        // 2** - 表示成功處理請求,如200
        // 3** - 須要重定向,瀏覽器直接跳轉,如301
        // 4** - 客戶端請求錯誤,如404
        // 5** - 服務端錯誤,如500

        if (xhr.readyState === 4){
            //  此時表示請求已經發送成功(已經接受到服務端返回的信息)
            if (xhr.status === 200){
                console.log('請求發送成功', xhr.responseText)
            }
        }
    }
    xhr.send(null);

5 其餘知識點

5.1 如何獲取2017-06-10 格式的日期?

function formatDate(date) {
        var dt = date || new Date(),
            year = dt.getFullYear(),
            month = (dt.getMonth() + 1).toString(),
            day = dt.getDate().toString();
        month = month.length === 1 ? '0' + month : month;
        day = day.length === 1 ? '0' + day : day;
        return year + '-' + month + '-' + day;
    }
// 注意點:月是從0開始計數的

5.2 如何獲取一個長度一致的隨機字符串?

[!NOTE]
思路:使用一個長度固定的字符串,如「00000000」做爲後綴,而後進行字符串截取。

// 獲取一個長度一致的字符串的隨機數字
function randomStr(str) {
    var random = str || Math.random();
    random = random + '0000000000';     // 小技巧:防止程序報錯
    random = random.slice(0, 10);
    return random;
}
randomStr();

5.3 寫一個能遍歷對象和數組通用的forEach函數

function forEach(obj, fn) {
        if (typeof obj === 'object') {
            // 對象遍歷
            for (var key in obj) {
                // console.log(key, obj[key]);
                // 這裏只去輸出本身的屬性,原型鏈上面的屬性信息不用管(繼承父類的屬性信息是不會輸出的)
                if (obj.hasOwnProperty(key))
                    fn(key, obj[key]);          // 直接執行這個函數
            }
        } else if (obj instanceof Array) {
            // 數組遍歷
            obj.forEach(function (item, index) {
                fn(index, item);
            })
        }
    }

5.4 數組中經常使用的方法有哪些?

  1. forEach 遍歷全部的元素
  2. every 判斷全部的元素是否都符合條件
  3. some 判斷是否至少有一個元素符合條件
  4. sort 排序
  5. map 對數組中的元素從新組裝,生成一個新的數組
  6. filter 過濾符合條件的元素

[!WARNING]
注意點:splice,concat,sprt是直接在原地操做數組的。

相關文章
相關標籤/搜索