JavaScript——基礎篇

把知識串一串,連成線,造成體系,今後走上大神之路啦,道路可能會曲折一點,可是鹹魚也要翻一翻身撒~前端

1、變量提高

何爲變量提高?java

在JavaScript中,函數及變量的聲明都將被提高到函數的最頂部 (函數聲明的優先級高於變量聲明的優先級)

這樣就形成了一種不一樣於其餘語言的現象,初看甚至以爲有些詭異:變量能夠先使用再聲明。舉個栗子:node

x = 1;
console.log(x);  // 1
var x;
var name = 'World!';
(function () {
    if (typeof name === 'undefined') {
        var name = 'Jack';
        console.log('Goodbye ' + name);
    } else {
        console.log('Hello ' + name);
    }
})();

// 輸出爲 Goodbye Jack

爲何會出現這樣狀況呢?面試

在JavaScript中,變量聲明與賦值的分離,如 var a = 2 這個代碼是分兩步進行的,編譯階段之行變量聲明 var a,在執行階段進行賦值 a = 2,因而便形成了了變量聲明提早狀況的發生。

解析:對於第二個例子,因爲存在變量提高,因此變量聲明先於if判斷,因此此時 name = undefined,因而便輸出了 Goodbye Jack正則表達式

2、隱式轉換

前段時間,前端各大博客被一道題刷屏了編程

++[[]][+[]]+[+[]]==10?

這道題怎麼去解決呢,這就涉及到了JS的隱式轉換相關的知識了。數組

簡述隱式轉換規則

對於原始類型:Undefined、Null、Boolean、Number、Stringpromise

1,加號運算符(+):若後面的是數字,會直接相加得出結果,如 1 + 1 = 2;若後面的是字符類型,則會進行字符拼接,如 1 + '1' = '11'。
2,減號運算符(-):若後面的是數字,會直接相減得出結果;若後面的字符,則會將其轉爲數字類型,而後相減得出結果。
3,==運算負責:瀏覽器

  • undefined == null,結果爲true
  • String == Boolean,須要將兩個操做數同時轉化爲Number
  • String/Boolean == Number,須要將 String/Boolean 轉爲 Number

對於對象類型:Object
當對象與一個非對象進行比較等操做時,須要先將其轉化爲原始類型:首先調用 valueOf(),若結果是原始類型,則返回結果;若結果不是原始類型,則繼續調用toSring(),返回其結果,若結果依然不是原始類型,則會拋出一個類型錯誤。閉包

這裏有一道很火的面試題,就是利用對象的類型轉換原理:

a == 1 && a == 2 && a == 3

//答案:
var a = {num : 0};
a.valueOf = function() {
    return ++a.num;
}

以上大概爲基礎的隱式轉換規則,可能不太完善,歡迎你們留言補充。好,有了這些準備後,讓咱們再來看下一開始的題目,讓咱們來逐步拆解:

1,根據運算符的優先級,咱們能夠獲得:(++[[]][+[]])+[+[]]
2,根據隱式轉換,咱們獲得:(++[[]][0])+[0]
3,再次簡化:(++[]) + [0]
4,這個時候就很明朗了,最終劃爲字符拼接 '1' + '0' = '10';

三,閉包

什麼是閉包?

簡單的講,閉包就是指有權訪問另外一個函數做用域中的變量的函數。

MDN 上面這麼說:閉包是一種特殊的對象,是函數和聲明該函數的詞法環境的組合。

產生一個閉包

function func() {
    var a = 1;
    return function fn() {
        console.log(a);
    }
}

func()();   // 1

這裏函數func在調用後,其做用域並無被銷燬,依然能夠被函數fn訪問,因此輸出爲1。
這裏有道很經典的面試題

function fun(n,o){
  console.log(o);
  return {
    fun: function(m){
      return fun(m,n);
    }
  };
}

var a = fun(0);  // ?
a.fun(1);        // ?        
a.fun(2);        // ?
a.fun(3);        // ?

var b = fun(0).fun(1).fun(2).fun(3);  // ?

var c = fun(0).fun(1);  // ?
c.fun(2);        // ?
c.fun(3);        // ?
undefined
0
0
0
undefined, 0, 1, 2
undefined, 0
1
1

哈哈,有點繞,有興趣的同窗能夠簡單看下。

四,深、淺克隆

在實際開發或面試中,咱們常常會碰到克隆的問題,這裏咱們簡單的總結下。

淺克隆

淺克隆就是複製對象的引用,複製後的對象指向的都是同一個對象的引用,彼此之間的操做會互相影響

var a = [1,2,3];
var b = a;
b[3] = 4;
console.log(a, b);

// [1,2,3,4] [1,2,3,4]

實際開發中,若須要同步對象的變化,每每用的就是淺克隆,直接複製對象引用便可。

深克隆

開發過程當中,咱們每每須要斷開對象引用,不影響原對象,這個時候咱們就用到深克隆了,有以下方法:

方法一

JSON.parse(JSON.stringify()),對於大多數狀況均可以用這種方法解決,一步到位。可是若對象中存在正則表達式類型、函數類型等的話,會出現問題:會直接丟失相應的值,同時若是對象中存在循環引用的狀況也沒法正確處理
let a = {name: '小明'};
let b = JSON.parse(JSON.stringify(a));
b.age = 18;
console.log(a, b);

// {name: '小明'} {name: "小明", age: 18}

方法二

對於數組,咱們能夠利用Array的slice和concat方法來實現深克隆
let a = [1,2,3];
let b = a.slice();
b.push(4);
console.log(a, b);

// [1,2,3]  [1,2,3,4]
let a1 = [1,2,3];
let b1 = a.concat(4);
b1.push(5);
console.log(a, b);
// [1,2,3]  [1,2,3,4,5]

方法三

jQuery中的extend複製方法:$.extend(true, target, obj)
let a = {name: '小明'};
let b = {}
$.extend(true, b, a);
b.age = 18;
console.log(a, b);

// {name: "小明"} {name: "小明", age: 18}

5、this指向

關於this指向的問題,這裏是有必定判斷方法的:

位置:this其實是在函數被調用時發生的綁定,它指向什麼徹底取決於函數在哪裏調用
規則:默認綁定、隱式綁定、顯式綁定、new綁定

咱們在實際判斷的時候,須要將兩者結合起來。

1,默認規則

var name = '小明';
function print() {
    console.log(this.name);  // '小明'
    console.log(this);   //window對象
}
print(); 
// '小明'

解析:print()直接使用不帶任何修飾的函數引用進行的調用,這個時候只能使用默認綁定規則,即this指向全局對象,因此此題輸出爲:'小明'

2,隱式綁定

function foo() {
    console.log(this.a)
}

var obj = {
    a:2,
    foo:foo
}
obj.foo()  // 2

解析:當函數引用有上下文對象時,隱式綁定規則會把函數調用中的this綁定到這個上下文對象。因此此題的this被綁定到obj,因而this.a和obj.a是同樣的。

這裏有兩點點須要注意:
1,對象屬性引用鏈中只有上一層或者說最後一層在調用位置中起做用,舉例以下:

function foo() {
    console.log(this.a);
}

var obj2 = {
    a: 10,
    foo: foo
}

var obj1 = {
    a: 2,
    obj2: obj2
}

obj1.obj2.foo();  // 10

2,隱式丟失:被隱式綁定的函數會丟失綁定對象,也就是說它會應用默認綁定,從而把this綁定到全局對象或者undefined上。舉例以下:

var a = 'hello world';

function foo(){
    console.log(this.a)
}

var obj = {
    a:1,
    foo:foo
}

var print = obj.foo;
print();    // hello world

解析:雖然print是obj.foo的一個引用,可是實際上,它引用的是foo函數自己,因此此時print()實際上是一個不帶任何修飾的函數調用,應用了隱式綁定。

3,顯示綁定

利用call(),apply(),bind()強制綁定this指向的咱們稱之爲顯示綁定,舉例以下:

function foo() {
    console.log(this.a);
}

var obj = {
    a:1
}

foo.call(obj);  // 1

這裏有一點須要注意:顯示綁定依然沒法解決上面提到的丟失綁定問題。舉例以下:

var a = 'hello world';

function foo(){
    console.log(this.a)
}

var obj = {
    a:1,
    foo:foo
}

var print = obj.foo;
print.bind(obj)
print();    // hello world

這裏有關call、apply、bind的具體用法就再也不一一闡述了,後面的部分會詳細講解。

4,new綁定

這是最後一條this的綁定規則,使用new來調用函數,或者說發生構造函數調用時,會執行下面的操做:

  • 建立(或者說構造)一個全新的對象
  • 這個新對象會被執行[[Prototype]]鏈接
  • 這個新對象會綁定到函數調用的this
  • 若是函數沒有返回其餘對象,那麼new表達式中的函數調用會自動返回這個新對象。

這個過程當中發生了this綁定,舉例以下:

function Person(name) {
    this.name = name;
}
var p = new Person('小明');
console.log(p.name);    // 小明

5,優先級

這裏再也不一一舉例對比優先級,直接給出結論:new綁定 > 顯示綁定 > 隱式綁定 > 默認綁定,有興趣的同窗能夠實際比對一下。
常規this指向判斷流程:

  • 函數是否在new中調用(new綁定) ? 若是是的話this綁定的就是新建立的對象
  • 函數是否經過call、apply、bind(顯示綁定) ? 若是是的話,this綁定的是指定的對象
  • 函數是否在某個上下文對象中被調用(隱時綁定) ? 若是是的話,this綁定的是那個上下文對象
  • 若是都不是的話,使用默認綁定

6、call、apply、bind

1,call()

定義:

使用一個指定的this值和單獨給出的一個或多個參數來調用一個函數

語法:

fun.call(thisArg, arg1, arg2, ...)

參數:

thisArg:(1) 不傳,或者傳null,undefined, 函數中的this指向window對象
(2) 傳遞另外一個函數的函數名,函數中的this指向這個函數的引用,並不必定是該函數執行時真正的this值
(3) 值爲原始值(數字,字符串,布爾值)的this會指向該原始值的自動包裝對象,如 String、Number、Boolean(4)傳遞一個對象,函數中的this指向這個對象

arg1, arg2, ...:指定的參數列表

舉例以下:

var obj = {a: '小明'};

function print() {
    console.log(this);
}

print.call(obj);  // {a: '小明'}

實現call方法:

Function.prototype.selfCall = function(context, ...args) {
    let fn = this;
    context || (context = window);
    if (typeof fn !== 'function') throw new TypeError('this is not function');
    let caller = Symbol('caller');
    context[caller] = fn;
    let res = context[caller](...args);
    delete context[caller];
    return res;
}

2,apply()

apply()方法與call()方法類似,區別在於:call 方法接受的是若干個參數列表,而 apply 接收的是一個包含多個參數的數組。

舉例以下:

var arr = [34,5,3,6,54,6,-67,5,7,6,-8,687];

Math.max.apply(Math, arr);  // 687
Math.min.call(Math, ...arr);   // -67

3,bind()

定義:

bind()方法建立一個新的函數,在調用時設置this關鍵字爲提供的值。並在調用新函數時,將給定參數列表做爲原函數的參數序列的前若干項。

語法:

function.bind(thisArg[, arg1[, arg2[, ...]]])

參數:

thisArg:調用綁定函數時做爲this參數傳遞給目標函數的值
arg1, arg2, ...:當目標函數被調用時,預先添加到綁定函數的參數列表中的參數。

舉例以下:

function print() {
    console.log(this);
}

let obj = {name: '小明'};
let fn = print.bind(obj);

fn();    // {name: "小明"}

7、Promise

1,什麼是Promise?

Promise是JS異步編程中的重要概念,異步抽象處理對象,是目前比較流行Javascript異步編程解決方案之一

2,建立Promise

方法一:new Promise

// 聲明Promise後會當即執行
var promise = new Promise(function(resolve, reject) {
    resolve('Hello');
})
console.log(promise);   // Promise{<resolved>: "Hello"}

方法二:直接建立

var promise = Promise.resolve('Hello');
console.log(promise);   // Promise{<resolved>: "Hello"}

3,Promise狀態

promise至關於一個狀態機,具備三種狀態:

  • pending
  • fulfilled
  • rejected

(1) promise 對象初始化狀態爲 pending

(2) 當調用resolve(成功),會由pending => fulfilled

(3) 當調用reject(失敗),會由pending => rejected

注:promsie狀態 只能由 pending => fulfilled/rejected, 一旦修改就不能再變

4,Promise API

1,Promise.prototype.then()

then() 方法返回一個 Promise 。它最多須要有兩個參數:Promise 的成功 (onFulfilled) 和 失敗狀況 (onRejected) 的回調函數。

舉例以下:

var promise = new Promise((resolve, reject) => {
    // 成功
    resolve('hello');
});

promise.then((res) => {
    console.log(res);    // hello
    return Promise.reject('error');
}).then((success) => {
    console.log('success', success);
}, (err) => {
    console.log('error', err);    // error error
});

2,Promise.resolve()

Promise.resolve(value)方法返回一個以給定值解析後的Promise 對象。但若是這個值是個thenable(即帶有then方法),返回的promise會「跟隨」這個thenable的對象,採用它的最終狀態(指resolved/rejected/pending/settled);若是傳入的value自己就是promise對象,則該對象做爲Promise.resolve方法的返回值返回;不然以該值爲成功狀態返回promise對象。

舉例以下:

var promise = Promise.resolve('hello');

promise.then((res) => {
    console.log(res);
});

// hello
// Promise {<resolved>: undefined}

此時promise的狀態爲題fulfilled

3,Promise.reject()

Promise.reject(reason)方法返回一個帶有拒絕緣由reason參數的Promise對象。

舉例以下:

var promise = Promise.reject('error');

promise.then((res) => {
    console.log('success', res);
}, (res) => {
    console.log('error', res);    // error error
});

4,Promise.race()

Promise.race(iterable) 方法返回一個 promise,一旦迭代器中的某個promise解決或拒絕,返回的 promise就會解決或拒絕。

舉例以下:

var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, 'one');
});

var promise2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then(function(value) {
  console.log(value);    // two
});

5,Promise.all()

Promise.all(iterable) 方法返回一個 Promise 實例,此實例在 iterable 參數內全部的 promise 都「完成(resolved)」或參數中不包含 promise 時回調完成(resolve);若是參數中 promise 有一個失敗(rejected),此實例回調失敗(reject),失敗緣由的是第一個失敗 promise 的結果。

舉例以下:

var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(function(values) {
  console.log(values);    // [3, 42, "foo"]
});

6,Promise.prototype.finally()

finally() 方法返回一個Promise。在promise結束時,不管結果是fulfilled或者是rejected,都會執行指定的回調函數。這爲在Promise是否成功完成後都須要執行的代碼提供了一種方式。
這避免了一樣的語句須要在then()和catch()中各寫一次的狀況。

舉例以下:

var promise = Promise.resolve('Hello');
promise.then((res) => {
    console.log(res);   // Hello
}).finally((res) => {
    console.log('finally');     // finally
})

7,Promise.prototype.catch()

catch() 方法返回一個Promise,而且處理拒絕的狀況,捕獲前面then中發送的異常

只要Promsie狀態更改成reject或者拋出異常,都會進入catch方法。舉例以下:

var promise1 = Promise.reject('Hello');
promise1.then((res) => {
    console.log('success' + res);
}).catch((res) => {
    console.log('catch ' + res);     // catch Hello
})

8、Event Loop

1,前言

Event Loop即事件循環,是指瀏覽器或Node的一種解決javaScript單線程運行時不會阻塞的一種機制,也就是咱們常用異步的原理。

2,宏任務與微任務

在JavaScript中,任務被分爲兩種,一種宏任務(MacroTask)也叫Task,一種叫微任務(MicroTask)。

宏任務:

  • script所有代碼
  • setTimeout
  • setInterval
  • setImmediate (Node獨有)
  • I/O
  • UI rendering (瀏覽器獨有)

微任務:

  • process.nextTick (Node獨有)
  • Promise
  • Object.observe
  • MutationObserver

3,瀏覽器的Event Loop

瀏覽器中的事件循環機制是什麼樣子呢?不廢話,直接上圖:
圖片描述

圖片描述

過程以下:

  • 執行全局Script同步代碼,這些同步代碼有一些是同步語句,有一些是異步語句(好比setTimeout等);
  • 全局Script代碼執行完畢後,調用棧Stack會清空;
  • 檢查微任務隊列是否爲空,若不爲空,則取出位於隊首的回調任務,放入調用棧Stack中執行,隊列長度減1。如此循環往復,直至微任務隊列爲空
  • 微任務隊列爲空後,檢查宏任務隊列是否爲空,若不爲空,則取出宏隊列中位於隊首的任務,放入Stack中執行,隊列長度減1。如此循環往復,直至宏任務隊列爲空

舉例以下:

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});
console.log('script end');

答案以下:

script start、script end、promise一、promise二、setTimeout

解析:
step1

console.log('script start');

Stack Queue: [console]

Macrotask Queue: []

Microtask Queue: []

打印結果:1

step2

setTimeout(function() {
  console.log('setTimeout');
}, 0);

setTimeout屬於宏任務,因此:

Stack Queue: [setTimeout]

Macrotask Queue: [callback1]

Microtask Queue: []

step3

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

promise屬於微任務,因此有:
Stack Queue: [promise]

Macrotask Queue: [callback1]

Microtask Queue: [callback2]

step4

console.log('script end');

同步任務,直接執行

打印結果:script end

step5
遍歷微任務隊列:Microtask Queue: [callback2],執行其函數

打印順序依次爲:promise一、promise2

step6
微任務隊列爲空後,遍歷宏任務隊列:Macrotask Queue: [callback1],執行其回調函數

打印結果:setTimeout

因此最終結果爲:script start、script end、promise一、promise二、setTimeout

9、總結

因爲時間比較倉促,本次總結還存在着許多遺漏,如JS原型,node環境下的Event Loop,函數柯里化等,也有許多理解不到位的狀況,往後會逐漸完善與補充。

注:若是文章中有不許確的地方,歡迎你們留言交流。😝
相關文章
相關標籤/搜索