摘要:這個過程中發生了綁定,舉例如下小明小明,優先級這里不再一一舉例對比優先級,直接給出結論綁定顯示綁定隱式綁定默認綁定,有興趣的同學可以實際比對一下。
把知識串一串,連成線,形成體系,從此走上大神之路啦,道路可能會曲折一點,但是咸魚也要翻一翻身撒~
一、變量提升何為變量提升?
在JavaScript中,函數及變量的聲明都將被提升到函數的最頂部 (函數聲明的優先級高于變量聲明的優先級)
這樣就造成了一種不同于其他語言的現象,初看甚至覺得有些詭異:變量可以先使用再聲明。舉個栗子:
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、String
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}五、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綁定的是那個上下文對象
如果都不是的話,使用默認綁定
六、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); // -673,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: "小明"}七、Promise 1,什么是Promise?
Promise是JS異步編程中的重要概念,異步抽象處理對象,是目前比較流行Javascript異步編程解決方案之一2,創建Promise
方法一:new Promise
// 聲明Promise后會立即執行 var promise = new Promise(function(resolve, reject) { resolve("Hello"); }) console.log(promise); // Promise{: "Hello"}
方法二:直接創建
var promise = Promise.resolve("Hello"); console.log(promise); // Promise{3,Promise狀態: "Hello"}
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?{: 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 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、promise1、promise2、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],執行其函數
打印順序依次為:promise1、promise2
step6
微任務隊列為空后,遍歷宏任務隊列:Macrotask Queue: [callback1],執行其回調函數
打印結果:setTimeout
所以最終結果為:script start、script end、promise1、promise2、setTimeout
九、總結由于時間比較倉促,本次總結還存在著許多遺漏,如JS原型,node環境下的Event Loop,函數柯里化等,也有許多理解不到位的情況,日后會逐漸完善與補充。
注:如果文章中有不準確的地方,歡迎大家留言交流。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/110277.html
摘要:個人前端文章整理從最開始萌生寫文章的想法,到著手開始寫,再到現在已經一年的時間了,由于工作比較忙,更新緩慢,后面還是會繼更新,現將已經寫好的文章整理一個目錄,方便更多的小伙伴去學習。 showImg(https://segmentfault.com/img/remote/1460000017490740?w=1920&h=1080); 個人前端文章整理 從最開始萌生寫文章的想法,到著手...
摘要:基礎鞏固基礎總結使用已經好幾年了,由于工作主要是做服務端開發,在工作中逐漸發現的使用范圍原來越廣泛。這里要注意,務必將基礎部分掌握牢靠,磨刀不誤砍柴功,只有將基礎部分掌握并建立起系統的知識體系,在后面學習衍生的其他模式才能游刃有余。 基礎鞏固:JavaScript基礎總結 使用JavaScript已經好幾年了,由于工作主要是做服務端開發,在工作中逐漸發現JavaScript的使用范圍原...
摘要:中基礎數據類型數據類型名稱數據類型說明只有一個值,即,聲明變量的初始值。只有一個值,即,表示空指針,的值是派生的值。由零或多個位字符組成只有兩個值,即和該類型使用來表示整數和浮點數。中的對象其實就是一組數據和功能的集合。 JavaScript 中基礎數據類型 數據類型名稱 數據類型說明 Undefined 只有一個值,即 undefined ,聲明變量的初始值。 Nul...
摘要:在前端這個領域里面,請求非常常見,相信每一個前端都寫過下面的代碼前提引入上面這段代碼中的和被稱為回調函數。多個請求希望有一個共同的回調響應繼續使用最初的方法,假設有多個請求,希望在全部完成后執行回調函數。異步編程延遲對象篇 在前端這個領域里面,ajax請求非常常見,相信每一個前端er都寫過下面的代碼: // 前提引入jquery $.ajax({ type: get, ...
jasmine 簡介 Jasmine 是一個含有豐富的斷言庫的測試框架。目前我用的最新的版本是:2.6 基礎篇 命令行中環境中使用jasmine 安裝 npm install -g jasmine //這里采用全局安裝,好處是直接cmd就能用,也可以采用本地安裝 初始化配置文件 jasmine init 生成的配置文件如下jasmine.json: { spec_dir: spec, //s...
閱讀 3062·2021-10-12 10:12
閱讀 1569·2021-09-09 11:39
閱讀 1845·2019-08-30 15:44
閱讀 2339·2019-08-29 15:23
閱讀 2898·2019-08-29 15:18
閱讀 2960·2019-08-29 13:02
閱讀 2688·2019-08-26 18:36
閱讀 733·2019-08-26 12:08