👇 內容速覽 👇javascript
call
/apply
/bind
ES5
/ES6
實現雙向綁定instanceof
原理與實現來看下call
的原生表現形式:html
function test(arg1, arg2) { console.log(arg1, arg2) console.log(this.a, this.b) } run.call({ a: 'a', b: 'b' }, 1, 2)
好了,開始手動實現咱們的call2
。在實現的過程有個關鍵:前端
若是一個函數做爲一個對象的屬性,那麼經過對象的.
運算符調用此函數,this
就是此對象vue
let obj = { a: 'a', b: 'b', test: function (arg1, arg2) { console.log(arg1, arg2) // this.a 就是 a; this.b 就是 b console.log(this.a, this.b) } } obj.test(1, 2)
知道了實現關鍵,下面就是咱們模擬的call
:java
Function.prototype.call2 = function(context) { if(typeof this !== 'function') { throw new TypeError('Error') } // 默認上下文是window context = context || window // 保存默認的fn const { fn } = context // 前面講的關鍵,將函數自己做爲對象context的屬性調用,自動綁定this context.fn = this const args = [...arguments].slice(1) const result = context.fn(...args) // 恢復默認的fn context.fn = fn return result } // 如下是測試代碼 function test(arg1, arg2) { console.log(arg1, arg2) console.log(this.a, this.b) } test.call2({ a: 'a', b: 'b' }, 1, 2)
apply
和call
實現相似,只是傳入的參數形式是數組形式,而不是逗號分隔的參數序列。webpack
所以,藉助es6提供的...
運算符,就能夠很方便的實現數組和參數序列的轉化。css3
Function.prototype.apply2 = function(context) { if(typeof this !== 'function') { throw new TypeError('Error') } context = context || window const { fn } = context context.fn = this let result if(Array.isArray(arguments[1])) { // 經過...運算符將數組轉換爲用逗號分隔的參數序列 result = context.fn(...arguments[1]) } else { result = context.fn() } context.fn = fn return result } /** * 如下是測試代碼 */ function test(arg1, arg2) { console.log(arg1, arg2) console.log(this.a, this.b) } test.apply2({ a: 'a', b: 'b' }, [1, 2])
bind
的實現有點意思,它有兩個特色:git
new
的狀況Function.prototype.bind2 = function(context) { if(typeof this !== 'function') { throw new TypeError('Error') } const that = this // 保留以前的參數,爲了下面的參數拼接 const args = [...arguments].slice(1) return function F() { // 若是被new建立實例,不會被改變上下文! if(this instanceof F) { return new that(...args, ...arguments) } // args.concat(...arguments): 拼接以前和如今的參數 // 注意:arguments是個類Array的Object, 用解構運算符..., 直接拿值拼接 return that.apply(context, args.concat(...arguments)) } } /** * 如下是測試代碼 */ function test(arg1, arg2) { console.log(arg1, arg2) console.log(this.a, this.b) } const test2 = test.bind2({ a: 'a', b: 'b' }, 1) // 參數 1 test2(2) // 參數 2
實現一個對象的深拷貝函數,須要考慮對象的元素類型以及對應的解決方案:es6
/** * 數組的深拷貝函數 * @param {Array} src * @param {Array} target */ function cloneArr(src, target) { for(let item of src) { if(Array.isArray(item)) { target.push(cloneArr(item, [])) } else if (typeof item === 'object') { target.push(deepClone(item, {})) } else { target.push(item) } } return target } /** * 對象的深拷貝實現 * @param {Object} src * @param {Object} target * @return {Object} */ function deepClone(src, target) { const keys = Reflect.ownKeys(src) let value = null for(let key of keys) { value = src[key] if(Array.isArray(value)) { target[key] = cloneArr(value, []) } else if (typeof value === 'object') { // 若是是對象並且不是數組, 那麼遞歸調用深拷貝 target[key] = deepClone(value, {}) } else { target[key] = value } } return target }
這段代碼是否是比網上看到的多了不少?由於考慮很周全,請看下面的測試用例:
// 這個對象a是一個囊括以上全部狀況的對象 let a = { age: 1, jobs: { first: "FE" }, schools: [ { name: 'shenda' }, { name: 'shiyan' } ], arr: [ [ { value: '1' } ], [ { value: '2' } ], ] }; let b = {} deepClone(a, b) a.jobs.first = 'native' a.schools[0].name = 'SZU' a.arr[0][0].value = '100' console.log(a.jobs.first, b.jobs.first) // output: native FE console.log(a.schools[0], b.schools[0]) // output: { name: 'SZU' } { name: 'shenda' } console.log(a.arr[0][0].value, b.arr[0][0].value) // output: 100 1 console.log(Array.isArray(a.arr[0])) // output: true
看到測試用例,應該會有人奇怪爲何最後要輸出Array.isArray(a.arr[0])
。這主要是由於網上不少實現方法沒有針對array作處理,直接將其當成object,這樣拷貝後雖然值沒問題,可是array的元素會被轉化爲object。這顯然是錯誤的作法。
而上面所說的深拷貝函數就解決了這個問題。
要想實現,就要先看看什麼是「雙向數據綁定」,它和「單向數據綁定」有什麼區別?這樣才能知道要實現什麼效果嘛。
雙向綁定:視圖(View)的變化能實時讓數據模型(Model)發生變化,而數據的變化也能實時更新到視圖層。
單向數據綁定:只有從數據到視圖這一方向的關係。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script> const obj = { value: '' } function onKeyUp(event) { obj.value = event.target.value } // 對 obj.value 進行攔截 Object.defineProperty(obj, 'value', { get: function() { return value }, set: function(newValue) { value = newValue document.querySelector('#value').innerHTML = newValue // 更新視圖層 document.querySelector('input').value = newValue // 數據模型改變 } }) </script> </head> <body> <p> 值是:<span id="value"></span> </p> <input type="text" onkeyup="onKeyUp(event)"> </body> </html>
隨着,vue3.0放棄支持了IE瀏覽器。並且Proxy
兼容性愈來愈好,能支持13種劫持操做。
所以,vue3.0選擇使用Proxy
來實現雙向數據綁定,而再也不使用Object.defineProperty
。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script> const obj = {} const newObj = new Proxy(obj, { get: function(target, key, receiver) { return Reflect.get(target, key, receiver) }, set: function(target, key, value, receiver) { if(key === 'value') { document.querySelector('#value').innerHTML = value document.querySelector('input').value = value } return Reflect.set(target, key, value, receiver) } }) function onKeyUp(event) { newObj.value = event.target.value } </script> </head> <body> <p> 值是:<span id="value"></span> </p> <input type="text" onkeyup="onKeyUp(event)"> </body> </html>
instanceof
是經過原型鏈來進行判斷的,因此只要不斷地經過訪問__proto__
,就能夠拿到構造函數的原型prototype
。直到null
中止。
/** * 判斷left是否是right類型的對象 * @param {*} left * @param {*} right * @return {Boolean} */ function instanceof2(left, right) { let prototype = right.prototype; // 沿着left的原型鏈, 看看是否有何prototype相等的節點 left = left.__proto__; while(1) { if(left === null || left === undefined) { return false; } if(left === prototype) { return true; } left = left.__proto__; } } /** * 測試代碼 */ console.log(instanceof2([], Array)) // output: true function Test(){} let test = new Test() console.log(instanceof2(test, Test)) // output: true
《前端知識體系》
《設計模式手冊》
《Webpack4漸進式教程》