您好,登錄后才能下訂單哦!
這篇文章主要介紹“分析web前端中的JS”,在日常操作中,相信很多人在分析web前端中的JS問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”分析web前端中的JS”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
當執(zhí)行JS代碼時,會生成執(zhí)行環(huán)境,只要代碼不是寫在函數(shù)中的,就是在堆棧執(zhí)行環(huán)境中,函數(shù)中的代碼會產(chǎn)生函數(shù)執(zhí)行環(huán)境,僅此兩種執(zhí)行環(huán)境。
b() // call b console.log(a) // undefined var a = 'Hello world' function b() { console.log('call b') }
想必高于上述的輸出大家肯定都已經(jīng)明白了,這是因為函數(shù)和變量提升的原因。通常提升的解釋是說將聲明的代碼移動到了頂部,這其實沒有什么錯誤,便于大家理解。應該是:在生成執(zhí)行環(huán)境時,會有兩個階段。第一個階段是創(chuàng)建的階段,JS解釋器會尋找需要提升的變量和函數(shù),并且給他們提前在內(nèi)存中開辟好空間,函數(shù)的話會將整個函數(shù)存入內(nèi)存中,變量只聲明和賦值undefined,所以在第二個階段,也就是代碼執(zhí)行階段,我們可以直接提前使用
在提升的過程中,相同的函數(shù)會覆蓋上一個函數(shù),并且函數(shù)優(yōu)先于變量提升
b() // call b second function b() { console.log('call b fist') } function b() { console.log('call b second') } var b = 'Hello world'
var會產(chǎn)生很多錯誤,所以在ES6中約會了let。let不能在聲明前使用,但是這不是常說的let不會提升,提升let了,在第一階段內(nèi)存也已經(jīng)為他開辟好了空間,但是因為這個聲明的特性導致了并不能在聲明前使用
call和apply都是為了解決改變this的指向。作用都是相同的,只是傳參的方式不同。
除了第一個參數(shù)外,call可以接收一個參數(shù)列表,apply只接受一個參數(shù)列表
let a = { value: 1 } function getValue(name, age) { console.log(name) console.log(age) console.log(this.value) } getValue.call(a, 'yck', '24') getValue.apply(a, ['yck', '24'])
bind和其他兩個方法作用也是一致的,只是該方法會返回一個函數(shù)。并且我們可以通過
bind實現(xiàn)柯里化
對于實現(xiàn)以下幾個函數(shù),可以從幾個方面思考
不預定第一個參數(shù),那么預設為 window
改變this思路指向,讓新的對象可以執(zhí)行該函數(shù)。那么思路是否可以變成給新的對象添加一個函數(shù),然后在執(zhí)行完以后刪除?
Function.prototype.myBind = function (context) { if (typeof this !== 'function') { throw new TypeError('Error') } var _this = this var args = [...arguments].slice(1) // 返回一個函數(shù) return function F() { // 因為返回了一個函數(shù),我們可以 new F(),所以需要判斷 if (this instanceof F) { return new _this(...args, ...arguments) } return _this.apply(context, args.concat(...arguments)) } }
Function.prototype.myCall = function (context) { var context = context || window // 給 context 添加一個屬性 // getValue.call(a, 'yck', '24') => a.fn = getValue context.fn = this // 將 context 后面的參數(shù)取出來 var args = [...arguments].slice(1) // getValue.call(a, 'yck', '24') => a.fn('yck', '24') var result = context.fn(...args) // 刪除 fn delete context.fn return result }
Function.prototype.myApply = function (context) { var context = context || window context.fn = this var result // 需要判斷是否存儲第二個參數(shù) // 如果存在,就將第二個參數(shù)展開 if (arguments[1]) { result = context.fn(...arguments[1]) } else { result = context.fn() } delete context.fn return result }
每個函數(shù)都有prototype屬性,除了Function.prototype.bind(),該屬性指向原型。
每個對象都有__proto__屬性,指向了創(chuàng)建該對象的構(gòu)造函數(shù)的原型。其實這個屬性指向了[[prototype]],但是[[prototype]]是內(nèi)部屬性,我們并不能訪問到,所以使用_proto_來訪問。
對象可以通過__proto__來尋找不屬于該對象的屬性,__proto__將對象連接起來組成了原型鏈。
可以通過Object.prototype.toString.call(xx)。這樣我們就可以獲得類似[object Type]的字符串。
instanceof 可以正確的判斷對象的類型,因為內(nèi)部機制是通過判斷對象的原型鏈中是不是能找到類型的 prototype
function a() { return () => { return () => { console.log(this) } } } console.log(a()()())
函數(shù)箭頭的英文其實沒有this的,函數(shù)這個中的this只取決于他外面的第一個不是箭頭函數(shù)的函數(shù)的this。在這個例子中,調(diào)用因為a符合前面代碼中的第一個情況,所以this的英文window。并且this一旦綁定了一部分,就不會被任何代碼改變
function foo() {
console.log(this.a)
}
var a = 1
foo()
var obj = {
a: 2,
foo: foo
}
obj.foo()
// 以上兩者情況 `this` 只依賴于調(diào)用函數(shù)前的對象,優(yōu)先級是第二個情況大于第一個情況
// 以下情況是優(yōu)先級最高的,`this` 只會綁定在 `c` 上,不會被任何方式修改 `this` 指向
var c = new foo()
c.a = 3
console.log(c.a)
// 還有種就是利用 call,apply,bind 改變 this,這個優(yōu)先級僅次于 new
async和await在于直接使用而言Promise,優(yōu)勢在于處理then的調(diào)用鏈,能夠更清晰準確的寫出代碼。一致在于重復await可能會導致性能問題,因為await會分離代碼,也許之后的編碼并不依賴于前者,但仍然需要等待前者完成,導致代碼丟失了并發(fā)性
下面來看一個使用await的代碼。
var a = 0 var b = async () => { a = a + await 10 console.log('2', a) // -> '2' 10 a = (await 10) + a console.log('3', a) // -> '3' 20 } b() a++ console.log('1', a) // -> '1' 1
首先函數(shù)b先執(zhí)行,在執(zhí)行到await 10之前變量a還是0,因為在await內(nèi)部實現(xiàn)了generators,generators會保留多個中東西,所以這時候a = 0被保存了下來
因為await是異步操作,遇到await就會立即返回一個pending狀態(tài)的Promise對象,暫時返回執(zhí)行代碼的控制權,導致函數(shù)外部的代碼可以繼續(xù)執(zhí)行,所以會先執(zhí)行console.log(‘1’, a)
這時候同步代碼執(zhí)行完畢,開始執(zhí)行異步代碼,將保存下來的值拿出來使用,這時候 a = 10
然后后面就是常規(guī)執(zhí)行代碼了
Generator是ES6中新增的語法,和Promise一樣,都可以用來初始化編程
// 使用 * 表示這是一個 Generator 函數(shù) // 內(nèi)部可以通過 yield 暫停代碼 // 通過調(diào)用 next 恢復執(zhí)行 function* test() { let a = 1 + 2; yield 2; yield 3; } let b = test(); console.log(b.next()); // > { value: 2, done: false } console.log(b.next()); // > { value: 3, done: false } console.log(b.next()); // > { value: undefined, done: true }
從以上代碼可以發(fā)現(xiàn),加上*的函數(shù)執(zhí)行后擁有了next函數(shù),然后函數(shù)執(zhí)行后返回了一個對象。每次調(diào)用next函數(shù)可以繼續(xù)執(zhí)行被暫停的代碼。以下是Generator函數(shù)的簡單實現(xiàn)
// cb 也就是編譯過的 test 函數(shù) function generator(cb) { return (function() { var object = { next: 0, stop: function() {} }; return { next: function() { var ret = cb(object); if (ret === undefined) return { value: undefined, done: true }; return { value: ret, done: false }; } }; })(); } // 如果你使用 babel 編譯后可以發(fā)現(xiàn) test 函數(shù)變成了這樣 function test() { var a; return generator(function(_context) { while (1) { switch ((_context.prev = _context.next)) { // 可以發(fā)現(xiàn)通過 yield 將代碼分割成幾塊 // 每次執(zhí)行 next 函數(shù)就執(zhí)行一塊代碼 // 并且表明下次需要執(zhí)行哪塊代碼 case 0: a = 1 + 2; _context.next = 4; return 2; case 4: _context.next = 6; return 3; // 執(zhí)行完畢 case 6: case "end": return _context.stop(); } } }); }
Promise是ES6補充的語法,解決了備選地獄的問題。
可以把Promise看成一個狀態(tài)機。初始是pending狀態(tài),可以通過函數(shù)resolve和reject,將狀態(tài)轉(zhuǎn)換為resolved或者rejected狀態(tài),狀態(tài)一旦改變就不能再次變化。
then函數(shù)會返回一個Promise實例,并且該返回值是一個新的實例而不是之前的實例。因為Promise規(guī)范規(guī)定pending已有狀態(tài),其他狀態(tài)是不可以改變的,如果返回的是一個相同實例的話,多個then調(diào)用就失去了意義了。對于then來說,本質(zhì)上可以把它看成是flatMap
// 三種狀態(tài) const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTED = "rejected"; // promise 接收一個函數(shù)參數(shù),該函數(shù)會立即執(zhí)行 function MyPromise(fn) { let _this = this; _this.currentState = PENDING; _this.value = undefined; // 用于保存 then 中的回調(diào),只有當 promise // 狀態(tài)為 pending 時才會緩存,并且每個實例至多緩存一個 _this.resolvedCallbacks = []; _this.rejectedCallbacks = []; _this.resolve = function (value) { if (value instanceof MyPromise) { // 如果 value 是個 Promise,遞歸執(zhí)行 return value.then(_this.resolve, _this.reject) } setTimeout(() => { // 異步執(zhí)行,保證執(zhí)行順序 if (_this.currentState === PENDING) { _this.currentState = RESOLVED; _this.value = value; _this.resolvedCallbacks.forEach(cb => cb()); } }) }; _this.reject = function (reason) { setTimeout(() => { // 異步執(zhí)行,保證執(zhí)行順序 if (_this.currentState === PENDING) { _this.currentState = REJECTED; _this.value = reason; _this.rejectedCallbacks.forEach(cb => cb()); } }) } // 用于解決以下問題 // new Promise(() => throw Error('error)) try { fn(_this.resolve, _this.reject); } catch (e) { _this.reject(e); } } MyPromise.prototype.then = function (onResolved, onRejected) { var self = this; // 規(guī)范 2.2.7,then 必須返回一個新的 promise var promise2; // 規(guī)范 2.2.onResolved 和 onRejected 都為可選參數(shù) // 如果類型不是函數(shù)需要忽略,同時也實現(xiàn)了透傳 // Promise.resolve(4).then().then((value) => console.log(value)) onResolved = typeof onResolved === 'function' ? onResolved : v => v; onRejected = typeof onRejected === 'function' ? onRejected : r => throw r; if (self.currentState === RESOLVED) { return (promise2 = new MyPromise(function (resolve, reject) { // 規(guī)范 2.2.4,保證 onFulfilled,onRjected 異步執(zhí)行 // 所以用了 setTimeout 包裹下 setTimeout(function () { try { var x = onResolved(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }); })); } if (self.currentState === REJECTED) { return (promise2 = new MyPromise(function (resolve, reject) { setTimeout(function () { // 異步執(zhí)行onRejected try { var x = onRejected(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }); })); } if (self.currentState === PENDING) { return (promise2 = new MyPromise(function (resolve, reject) { self.resolvedCallbacks.push(function () { // 考慮到可能會有報錯,所以使用 try/catch 包裹 try { var x = onResolved(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (r) { reject(r); } }); self.rejectedCallbacks.push(function () { try { var x = onRejected(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (r) { reject(r); } }); })); } }; // 規(guī)范 2.3 function resolutionProcedure(promise2, x, resolve, reject) { // 規(guī)范 2.3.1,x 不能和 promise2 相同,避免循環(huán)引用 if (promise2 === x) { return reject(new TypeError("Error")); } // 規(guī)范 2.3.2 // 如果 x 為 Promise,狀態(tài)為 pending 需要繼續(xù)等待否則執(zhí)行 if (x instanceof MyPromise) { if (x.currentState === PENDING) { x.then(function (value) { // 再次調(diào)用該函數(shù)是為了確認 x resolve 的 // 參數(shù)是什么類型,如果是基本類型就再次 resolve // 把值傳給下個 then resolutionProcedure(promise2, value, resolve, reject); }, reject); } else { x.then(resolve, reject); } return; } // 規(guī)范 2.3.3.3.3 // reject 或者 resolve 其中一個執(zhí)行過得話,忽略其他的 let called = false; // 規(guī)范 2.3.3,判斷 x 是否為對象或者函數(shù) if (x !== null && (typeof x === "object" || typeof x === "function")) { // 規(guī)范 2.3.3.2,如果不能取出 then,就 reject try { // 規(guī)范 2.3.3.1 let then = x.then; // 如果 then 是函數(shù),調(diào)用 x.then if (typeof then === "function") { // 規(guī)范 2.3.3.3 then.call( x, y => { if (called) return; called = true; // 規(guī)范 2.3.3.3.1 resolutionProcedure(promise2, y, resolve, reject); }, e => { if (called) return; called = true; reject(e); } ); } else { // 規(guī)范 2.3.3.4 resolve(x); } } catch (e) { if (called) return; called = true; reject(e); } } else { // 規(guī)范 2.3.4,x 為基本類型 resolve(x); } }
這里來解析一道[] == ![] // -> true譯文,下面是這個表達式為何為true的步驟
// [] 轉(zhuǎn)成 true,然后取反變成 false [] == false // 根據(jù)第 8 條得出 [] == ToNumber(false) [] == 0 // 根據(jù)第 10 條得出 ToPrimitive([]) == 0 // [].toString() -> '' '' == 0 // 根據(jù)第 6 條得出 0 == 0 // -> true ===在開發(fā)中,對于預先返回的code,可以通過==去判斷
前者存儲在棧上,另外存儲在堆上
如果JS是門多線程的語言話,我們在多個線程中處理DOM就可能會發(fā)生問題(一個線程),因為JS是門非雙向單線程語言,因為在最初是JS就是為了和瀏覽器交互而產(chǎn)生的。中新加二進制,另一個線程中刪除例程),當然可以約會讀寫鎖解決這個問題。
JS在執(zhí)行的過程中會產(chǎn)生執(zhí)行環(huán)境,這些執(zhí)行環(huán)境會被順序的加入到執(zhí)行棧中。如果遇到異步的代碼,會被掛起并加入到Task(有多種task)少量中。一旦執(zhí)行棧為空,則將會Event Loop從Task中間中拿出需要執(zhí)行的代碼并加入執(zhí)行棧中執(zhí)行,所以本質(zhì)上來說JS中的異步還是同步行為
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); console.log('script end');
代碼以上雖然setTimeout延時為0,其實還是異步。的英文這因為HTML5標準規(guī)定這個函數(shù)第二個參數(shù)不得小于4毫秒,不足會自動增加。所以setTimeout還是會在script end之后打印。
不同的任務源會被分配到不同的Task層次中,任務源可以分為微任務(microtask)和宏任務(macrotask)。在ES6規(guī)范中,microtask稱為工作,macrotask稱為task。
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); new Promise((resolve) => { console.log('Promise') resolve() }).then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end'); // script start => Promise => script end => promise1 => promise2 => setTimeout
以上代碼雖然setTimeout寫在Promise之前,但是因為Promise屬于微任務而setTimeout屬于宏任務,所以會有以上的打印。
微任務包括 process.nextTick,promise,Object.observe,MutationObserver
宏任務包括 script,setTimeout,setInterval,setImmediate,I/O,UI renderin
很多人有個誤區(qū),認為微任務快于宏任務,其實是錯誤的。因為宏任務中包括了script,瀏覽器會先執(zhí)行一個宏任務,然后有異步代碼的話就先執(zhí)行微任務
所以正確的一次Event loop順序是這樣的
執(zhí)行同步代碼,這屬于宏任務
執(zhí)行棧為空,查詢是否有微任務需要執(zhí)行
執(zhí)行所有微任務
必要的話渲染 UI
然后開始下一輪Event loop,執(zhí)行宏任務中的異步代碼
通過上述的 Event loop順序可知,如果宏任務中的異步代碼有大量的計算和需要操作DOM的話,為了更換的界面響應,我們可以把操作DOM放入微任務中
JS是單線程的,所以setTimeout的誤差實際上是無法被完全解決的,原因有很多,可能是某些中的,有可能是瀏覽器中的各種事件導致。這也是為什么頁面開久了,定時器會不準的原因,當然我們可以通過一定的辦法去減少這個誤差。
// 以下是一個相對準備的倒計時實現(xiàn) var period = 60 * 1000 * 60 * 2 var startTime = new Date().getTime(); var count = 0 var end = new Date().getTime() + period var interval = 1000 var currentInterval = interval function loop() { count++ var offset = new Date().getTime() - (startTime + count * interval); // 代碼執(zhí)行所消耗的時間 var diff = end - new Date().getTime() var h = Math.floor(diff / (60 * 1000 * 60)) var hdiff = diff % (60 * 1000 * 60) var m = Math.floor(hdiff / (60 * 1000)) var mdiff = hdiff % (60 * 1000) var s = mdiff / (1000) var sCeil = Math.ceil(s) var sFloor = Math.floor(s) currentInterval = interval - offset // 得到下一次循環(huán)所消耗的時間 console.log('時:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代碼執(zhí)行時間:'+offset, '下次循環(huán)間隔'+currentInterval) // 打印 時 分 秒 代碼執(zhí)行時間 下次循環(huán)間隔 setTimeout(loop, currentInterval) } setTimeout(loop, currentInterval)
[1, [2], 3].flatMap(v => v) // -> [1, 2, 3]
如果想將一個多維整數(shù)徹底的降維,可以這樣實現(xiàn)
const flattenDeep = (arr) => Array.isArray(arr) ? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , []) : [arr] flattenDeep([1, [[2], [3, [4]], 5]])
這個問題通??梢酝ㄟ^JSON.parse(JSON.stringify(object))來解決
let a = { age: 1, jobs: { first: 'FE' } } let b = JSON.parse(JSON.stringify(a)) a.jobs.first = 'native' console.log(b.jobs.first) // FE
但是該方法也是有局限性的:
會忽略 undefined
會忽略 symbol
不能序列化函數(shù)
不能解決循環(huán)引用的對象
let obj = { a: 1, b: { c: 2, d: 3, }, } obj.c = obj.b obj.e = obj.a obj.b.c = obj.c obj.b.d = obj.b obj.b.e = obj.b.c let newObj = JSON.parse(JSON.stringify(obj)) console.log(newObj)
在遇到函數(shù),undefined或者symbol的時候,該對象也不能正常的序列化
let a = { age: undefined, sex: Symbol('male'), jobs: function() {}, name: 'yck' } let b = JSON.parse(JSON.stringify(a)) console.log(b) // {name: "yck"}
但是在通常情況下,復雜數(shù)據(jù)都是可以序列化的,所以這個函數(shù)可以解決大部分問題,并且該函數(shù)是內(nèi)置函數(shù)中處理深拷貝性能移動的。當然如果你的數(shù)據(jù)中含有以上某種情況下,可以使用lodash的深拷貝函數(shù)
typeof對于基本類型,除了null都可以顯示正確的類型
typeof 1 // 'number' typeof '1' // 'string' typeof undefined // 'undefined' typeof true // 'boolean' typeof Symbol() // 'symbol' typeof b // b 沒有聲明,但是還會顯示 undefined
typeof 對于對象,除了函數(shù)都會顯示 object
typeof [] // 'object' typeof {} // 'object' typeof console.log // 'function'
對于null來說,雖然它是基本類型,但是會顯示object,這是一個存在很久了的Bug
typeof null // 'object'
instanceof 可以正確的判斷對象的類型,因為內(nèi)部機制是通過判斷對象的原型鏈中是不是能找到類型的 prototype
我們也可以試著實現(xiàn)一下 instanceof function instanceof(left, right) { // 獲得類型的原型 let prototype = right.prototype // 獲得對象的原型 left = left.__proto__ // 判斷對象的類型是否等于類型的原型 while (true) { if (left === null) return false if (prototype === left) return true left = left.__proto__ } }
到此,關于“分析web前端中的JS”的學習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。