知識點漏缺總結
Proxy 來替換原本的 Object.defineProperty 來實現資料響應式。 Proxy 是 ES6 中新增的功能,它可以用來自定義物件中的操作。
let p = new Proxy(target, handler) 複製程式碼
target 代表需要新增代理的物件,handler 用來自定義物件中的操作,比如可以用來自定義 set 或者 get 函式。
接下來我們通過 Proxy 來實現一個數據響應式
let onWatch = (obj, setBind, getLogger) => { let handler = { get(target, property, receiver) { getLogger(target, property) return Reflect.get(target, property, receiver) }, set(target, property, value, receiver) { setBind(value, property) return Reflect.set(target, property, value) } } return new Proxy(obj, handler) } let obj = { a: 1 } let p = onWatch( obj, (v, property) => { console.log(`監聽到屬性${property}改變為${v}`) }, (target, property) => { console.log(`'${property}' = ${target[property]}`) } ) p.a = 2 // 監聽到屬性a改變 p.a // 'a' = 2 複製程式碼
在上述程式碼中,我們通過自定義 set 和 get 函式的方式,在原本的邏輯中插入了我們的函式邏輯,實現了在對物件任何屬性進行讀寫時發出通知。
當然這是簡單版的響應式實現,如果需要實現一個 Vue 中的響應式,需要我們在 get 中收集依賴,在 set 派發更新,之所以 Vue3.0 要使用 Proxy 替換原本的 API 原因在於 Proxy 無需一層層遞迴為每個屬性新增代理,一次即可完成以上操作,效能上更好,並且原本的實現有一些資料更新不能監聽到,但是 Proxy 可以完美監聽到任何方式的資料改變,唯一缺陷可能就是瀏覽器的相容性不好了。
reduce
const arr = [1, 2, 3] const sum = arr.reduce((acc, current) => acc + current, 0) console.log(sum) 複製程式碼
對於 reduce 來說,它接受兩個引數,分別是回撥函式和初始值 ,接下來我們來分解上述程式碼中 reduce 的過程
首先初始值為 0,該值會在執行第一次回撥函式時作為第一個引數傳入 回撥函式接受四個引數,分別為累計值、當前元素、當前索引、原陣列
在一次執行回撥函式時,當前值和初始值相加得出結果1,
該結果會在第二次執行回撥函式時當做第一個引數傳入。
所以在第二次執行回撥函式時,相加的值就分別是 1 和2,以此類推,迴圈結束後得到結果 6
就通過 reduce 來實現 map和filter 函式
const numbers = [10, 20, 30, 40]; const go = numbers.reduce((finalList, num) => { //實現map方法,對映的作用 num = num * 2; if (num > 50) { //實現filter方法過濾 finalList.push(num); } return finalList; }, []); alert(go); // [60, 80] 複製程式碼
setInterval
其實這個函式作用和 setTimeout 基本一致,只是該函式是每隔一段時間執行一次回撥函式 。
通常來說不建議使用 setInterval。第一,它和 setTimeout 一樣,不能保證在預期的時間執行任務。第二,它存在執行累積的問題 ,請看以下虛擬碼
function demo() { setInterval(function(){ console.log(2) },1000) sleep(2000) } demo() 複製程式碼
以上程式碼在瀏覽器環境中,如果定時器執行過程中出現了耗時操作,多個回撥函式會在耗時操作結束以後同時執行,這樣可能就會帶來效能上的問題。
如果你有迴圈定時器的需求 ,其實完全可以通過requestAnimationFrame 來實現
function setInterval(callback, interval) { let timer const now = Date.now let startTime = now() let endTime = startTime const loop = () => { timer = window.requestAnimationFrame(loop) endTime = now() if (endTime - startTime >= interval) { startTime = endTime = now() callback(timer) } } timer = window.requestAnimationFrame(loop) return timer } let a = 0 setInterval(timer => { console.log(1) a++ if (a === 3) cancelAnimationFrame(timer) }, 1000) 複製程式碼
首先 requestAnimationFrame 自帶函式節流功能,基本可以保證在 16.6 毫秒內只執行一次(不掉幀的情況下),並且該函式的延時效果是精確的,沒有其他定時器時間不準的問題,當然你也可以通過該函式來實現 setTimeout。
程序與執行緒
程序和執行緒都是一個時間段的描述,是CPU工作時間段的描述。放在應用上來說就代表了一個程式。執行緒是程序中的更小單位,描述了執行一段指令所需的時間。
把這些概念拿到瀏覽器中來說,當你開啟一個 Tab 頁時,其實就是建立了一個程序,一個程序中可以有多個執行緒,比如渲染執行緒、JS 引擎執行緒、HTTP 請求執行緒等等。當你發起一個請求時,其實就是建立了一個執行緒,當請求結束後,該執行緒可能就會被銷燬。
bind
和call很相似,第一個引數是this的指向,從第二個引數開始是接收的引數列表。區別在於bind方法返回值是函式以及bind接收的引數列表的使用。
bind返回值是函式
var obj = { name: 'Dot' } function printName() { console.log(this.name) } var dot = printName.bind(obj) console.log(dot) // function () { … } dot()// Dot 複製程式碼
bind 方法不會立即執行,而是返回一個改變了上下文 this 後的函式。 而原函式 printName 中的 this 並沒有被改變,依舊指向全域性物件 window。
引數的使用
function fn(a, b, c) { console.log(a, b, c); } var fn1 = fn.bind(null, 'Dot'); fn('A', 'B', 'C');// A B C fn1('A', 'B', 'C');// Dot A B fn1('B', 'C');// Dot B C fn.call(null, 'Dot');// Dot undefined undefined 複製程式碼
call 是把第二個及以後的引數作為 fn 方法的實參傳進去,而 fn1 方法的實參實則是在 bind 中引數的基礎上再往後排。
有時候我們也用bind方法實現函式珂里化 ,以下是一個簡單的示例:
var add = function(x) { return function(y) { return x + y; }; }; var increment = add(1); var addTen = add(10); increment(2); // 3 addTen(2); // 12 複製程式碼
在低版本瀏覽器沒有 bind 方法,我們也可以自己實現一個。
if (!Function.prototype.bind) { Function.prototype.bind = function () { var self = this,// 儲存原函式 context = [].shift.call(arguments), // 儲存需要繫結的this上下文 args = [].slice.call(arguments);// 剩餘的引數轉為陣列 return function () {// 返回一個新函式 self.apply(context, [].concat.call(args, [].slice.call(arguments))); } } } 複製程式碼
call apply bind應用場景
求陣列中的最大和最小值
var arr = [1,2,3,89,46] var max = Math.max.apply(null,arr)//89 var min = Math.min.apply(null,arr)//1 複製程式碼
將類陣列轉化為陣列
var trueArr = Array.prototype.slice.call(arrayLike) 複製程式碼
陣列追加
var arr1 = [1,2,3]; var arr2 = [4,5,6]; var total = [].push.apply(arr1, arr2);//6 // arr1 [1, 2, 3, 4, 5, 6] // arr2 [4,5,6] 複製程式碼
判斷變數型別
function isArray(obj){ return Object.prototype.toString.call(obj) == '[object Array]'; } isArray([]) // true isArray('dot') // false 複製程式碼
利用call和apply做繼承
function Person(name,age){ // 這裡的this都指向例項 this.name = name this.age = age this.sayAge = function(){ console.log(this.age) } } function Female(){ Person.apply(this,arguments)//將父元素所有方法在這裡執行一遍就繼承了 } var dot = new Female('Dot',2) 複製程式碼
使用 log 代理 console.log
function log(){ console.log.apply(console, arguments); } // 當然也有更方便的 var log = console.log() 複製程式碼
總結
call、apply和bind函式存在的區別:
bind返回對應函式, 便於稍後呼叫; apply, call則是立即呼叫。 除此外, 在 ES6 的箭頭函式下, call 和 apply 將失效,對於箭頭函式來說:
箭頭函式體內的 this 物件, 就是定義時所在的物件,而不是使用時所在的物件;所以不需要類似於var _this = this這種醜陋的寫法
箭頭函式不可以當作建構函式,也就是說不可以使用 new 命令, 否則會丟擲一個錯誤
箭頭函式不可以使用 arguments 物件,,該物件在函式體內不存在. 如果要用, 可以用 Rest 引數代替
不可以使用 yield 命令, 因此箭頭函式不能用作 Generator 函式
為什麼 0.1 + 0.2 != 0.3
因為 JS 採用 IEEE 754 雙精度版本(64位),0.1 在二進位制中是無限迴圈的一些數字,其實不只是 0.1,其實很多十進位制小數用二進位制表示都是無限迴圈的。 JS 採用的浮點數標準卻會裁剪掉我們的數字。 那麼這些迴圈的數字被裁剪了,就會出現精度丟失的問題,也就造成了 0.1 不再是 0.1 了,而是變成了 0.100000000000000002
解決的辦法有很多,這裡我們選用原生提供的方式來最簡單的解決問題
parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true 複製程式碼