把知識串一串,連成線,造成體系,今後走上大神之路啦,道路可能會曲折一點,可是鹹魚也要翻一翻身撒~前端
何爲變量提高?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正則表達式
前段時間,前端各大博客被一道題刷屏了編程
++[[]][+[]]+[+[]]==10?
這道題怎麼去解決呢,這就涉及到了JS的隱式轉換相關的知識了。數組
對於原始類型:Undefined、Null、Boolean、Number、Stringpromise
1,加號運算符(+):若後面的是數字,會直接相加得出結果,如 1 + 1 = 2;若後面的是字符類型,則會進行字符拼接,如 1 + '1' = '11'。
2,減號運算符(-):若後面的是數字,會直接相減得出結果;若後面的字符,則會將其轉爲數字類型,而後相減得出結果。
3,==運算負責:瀏覽器
對於對象類型: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}
關於this指向的問題,這裏是有必定判斷方法的:
位置:this其實是在函數被調用時發生的綁定,它指向什麼徹底取決於函數在哪裏調用
規則:默認綁定、隱式綁定、顯式綁定、new綁定
咱們在實際判斷的時候,須要將兩者結合起來。
var name = '小明'; function print() { console.log(this.name); // '小明' console.log(this); //window對象 } print(); // '小明'
解析:print()直接使用不帶任何修飾的函數引用進行的調用,這個時候只能使用默認綁定規則,即this指向全局對象,因此此題輸出爲:'小明'
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()實際上是一個不帶任何修飾的函數調用,應用了隱式綁定。
利用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的具體用法就再也不一一闡述了,後面的部分會詳細講解。
這是最後一條this的綁定規則,使用new來調用函數,或者說發生構造函數調用時,會執行下面的操做:
這個過程當中發生了this綁定,舉例以下:
function Person(name) { this.name = name; } var p = new Person('小明'); console.log(p.name); // 小明
這裏再也不一一舉例對比優先級,直接給出結論:new綁定 > 顯示綁定 > 隱式綁定 > 默認綁定,有興趣的同窗能夠實際比對一下。
常規this指向判斷流程:
定義:
使用一個指定的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; }
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
定義:
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: "小明"}
Promise是JS異步編程中的重要概念,異步抽象處理對象,是目前比較流行Javascript異步編程解決方案之一
方法一: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"}
promise至關於一個狀態機,具備三種狀態:
(1) promise 對象初始化狀態爲 pending
(2) 當調用resolve(成功),會由pending => fulfilled
(3) 當調用reject(失敗),會由pending => rejected
注:promsie狀態 只能由 pending => fulfilled/rejected, 一旦修改就不能再變
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 })
Event Loop即事件循環,是指瀏覽器或Node的一種解決javaScript單線程運行時不會阻塞的一種機制,也就是咱們常用異步的原理。
在JavaScript中,任務被分爲兩種,一種宏任務(MacroTask)也叫Task,一種叫微任務(MicroTask)。
宏任務:
微任務:
瀏覽器中的事件循環機制是什麼樣子呢?不廢話,直接上圖:
過程以下:
舉例以下:
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
因爲時間比較倉促,本次總結還存在着許多遺漏,如JS原型,node環境下的Event Loop,函數柯里化等,也有許多理解不到位的狀況,往後會逐漸完善與補充。
注:若是文章中有不許確的地方,歡迎你們留言交流。😝