這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 函數創建與定義的過程 函數定義階段 在堆記憶體中開闢一段空間 把函數體內的代碼一模一樣的存儲在這段空間內 把空間賦值給棧記憶體的變數中 函數調用階段 按照變數名內的存儲地址找到堆記憶體中對應的存儲空間 在調用棧中開闢一個新的函數執行空間 在執行 ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
函數創建與定義的過程
- 函數定義階段
- 在堆記憶體中開闢一段空間
- 把函數體內的代碼一模一樣的存儲在這段空間內
- 把空間賦值給棧記憶體的變數中
- 函數調用階段
- 按照變數名內的存儲地址找到堆記憶體中對應的存儲空間
- 在調用棧中開闢一個新的函數執行空間
- 在執行空間中進行形參賦值
- 在執行空間中進行預解析
- 在執行空間中完整執行一遍函數內的代碼
- 銷毀在調用棧創建的執行空間
不會銷毀的函數執行空間
- 當函數內返回一個引用數據類型
- 並且函數外部有變數接收這個引用數據類型
- 函數執行完畢的執行空間不會銷毀
- 如果後續不需要這個空間了,只要讓變數指向別的位置即可
function fn() { const obj = { a: 1, b: 2 } return obj } const res = fn() console.log(res) // 如果後續不需要這個空間了, 只需要讓 res 指向別的位置即可 res = 100
閉包
- 需要一個不會被銷毀的執行空間
- 需要直接或間接返回一個函數
- 內部函數使用外部函數的私有變數
- 概念 : 函數里的函數
- 優點:
- 可以在函數外訪問函數內的變數
- 延長了變數的生命周期
- 缺點:
- 閉包函數不會銷毀空間,大量使用會造成記憶體溢出
function outer () { let a = 100 let b = 200 // 我們說 inner 是 outer 的閉包函數 function inner () { /** * 我使用了一個 a 變數, 但是 inner 自己沒有 * 所以我用的是 外部函數 outer 內部的變數 a */ // console.log(a) return a } return inner } // 我們說 res 是 outer 的閉包函數 let res = outer() let outerA = res() console.log(outerA)
沙箱模式
- 利用了函數內間接返回了一個函數
- 外部函數返回一個對象,對象內書寫多個函數
function outer () { let a = 100 let b = 200 // 創建一個 沙箱, "間接的返回一個函數" const obj = { getA: function () { return a }, getB: function () { return b }, setA: function (val) { a = val } } return obj } // 得到一個沙箱 const res1 = outer() console.log(res1.getA()) // 100 console.log(res1.getB()) // 200 res1.setA(999) console.log(res1.getA()) // 999 // 重新得到一個沙箱 const res2 = outer() console.log(res2.getA()) // 100
沙箱小案例
<button class="sub">-</button> <input class="inp" type="text" value="1"> <button class="add">+</button> <br> <button class="sub1">-</button> <input class="inp1" type="text" value="1"> <button class="add1">+</button>
// 準備一個沙箱 function outer() { let a = 1 return { getA() { return a }, setA(val) { a = val } } } // 0. 獲取元素 const subBtn = document.querySelector('.sub') const addBtn = document.querySelector('.add') const inp = document.querySelector('.inp') // 0. 準備變數 // let count = 1 let res = outer() subBtn.onclick = function () { let count = res.getA() res.setA(count - 1) inp.value = res.getA() } addBtn.onclick = function () { // count++ let count = res.getA() res.setA(count + 1) inp.value = res.getA() } // 0. 獲取元素 const subBtn1 = document.querySelector('.sub1') const addBtn1 = document.querySelector('.add1') const inp1 = document.querySelector('.inp1') // 0. 準備變數 let res1 = outer() subBtn1.onclick = function () { let count = res1.getA() res1.setA(count - 1) inp1.value = res1.getA() } addBtn1.onclick = function () { let count = res1.getA() res1.setA(count + 1) inp1.value = res1.getA() }
沙箱語法糖
- 儘可能的簡化沙箱模式的語法
- 利用 get 和 set 進行操作數據
- 語法糖:
- 在不影響功能的情況下提供一點更適合操作的語法
function outer() { let a = 100 let b = 200 return { get a() { return a }, get b() { return b }, set a(val) { a = val } } } let res = outer() console.log(res.a) console.log(res.b) res.a = 999 console.log(res.a) // 999
閉包面試題!!!!
function fun(n, o) { console.log(o) const obj = { fun: function (m) { return fun(m, n) } } return obj } var a = fun(0) // undefined a.fun(1) // 0 a.fun(2) // 0 a.fun(3) // 0 /** * var a = fun(0) * a.fun(1) * a.fun(2) * a.fun(3) * * 1. var a = fun(0) * 調用 fun(QF001) 函數(QF001) 傳遞一個 參數 0 * 全局函數 fun (QF001) 的 形參 n == 0 形參 o == undefined * 調用 fun 函數後, 會返回一個對象 存儲在 變數 a 中, 這個對象內部有一個屬性叫做 fun, 屬性值為 一個函數(QF002), * 所以我們可以通過 a.fun() 去調用這個函數 * * 2. a.fun(1) * 2.1 調用這個函數 會 return 一個函數 fun (為全局函數 QF001) 的調用結果, * 2.2 調用全局函數 fun(m, n) m 此時 傳遞的是 1, n 傳遞的是 0 * 2.3 執行全局函數 fun(m, n) 內部會輸出第二個形參 * * 3. a.fun(2) * 2.1 調用這個函數 會 return 一個函數 fun(為全局函數 QF001) 的調用結果 * 2.2 調用全局函數 fun(m, n) m 此時傳遞的是 2, n 傳遞的是 0 * 2.3 執行全局函數 fun(m, n) 內部會輸出第二個形參 * */
防抖與節流
防抖
- 解釋:在短時間內觸發一件事,每次都用上一次的時間替代,也就是只執行最後一次
box.oninput = ((timerID) => { return function (e) { clearInterval(timerID) timerID = setTimeout(() => { console.log('搜索了: ', e.target.value) }, 300) } })(0)
節流
- 解釋:短時間內快速觸發一件事,當一個事件處理函數開始執行的時候,不允許重覆執行(瀑布流)
box.oninput = ((flag) => { return function (e) { if (!flag) return flag = false setTimeout(() => { console.log('搜索了: ', e.target.value) flag = true }, 300) } })(true)
柯里化函數
- 定義:本質上還是一個函數,只不過將原本接收多個參數才能正常執行的函數拆分成多個只接收一個的函數
// 原本函數 function reg (reg, str) { return reg.test(str) } // 柯里化後 function reg(reg) { return (str) => { return reg.test(str) } } const res = reg(/^\w{3,5}$/) console.log(res('123asd')); // false console.log(res('123')); // true
封裝柯里化函數案例
/** * 函數柯里化封裝 * * fn 函數能夠幫助我們拼接一個 完整的網路地址 * a --- 傳輸協議: http https * b --- 功能變數名稱: localhost 127.0.0.1 * c --- 埠號: 0-65535 * d --- 地址: /index.html /a/b/c/index.html * * * 現在只有我們正確的傳遞了參數的數量才能夠實現最好的拼接, 如果傳遞的參數數量不夠也會運行函數, 但是字元串不太對 * * 需求: * 將當前函數處理成柯里化函數, 只有傳遞的參數數量足夠的時候, 在執行函數內容 */ // 功能函數 function fn(a, b, c, d) { return a + '://' + b + ':' + c + d } // 通過柯里化解決 function keli (callBack, ...args) { return function (..._args) { _args = [...args, ..._args] if (_args.length >= callBack.length) { return callBack(..._args) } else { return keli(callBack, ..._args) } } }
數據劫持(代理)
將來在框架中我們通常都是 數據驅動視圖 也就是說: 修改完畢數據, 視圖自動更新
- 數據劫持:以原始數據為基礎,對數據進行一份複製
- 複製出來的數據是不允許被修改的,值是從原始數據裡面獲取的
- 語法:
Object.defineproperty(哪一個對象,屬性名,{配置項})
- 配置項:
- value:該屬性對應值
- writable:該屬性確定是否允許被重寫,預設值是false
- emunerable:該屬性是否可被枚舉(遍歷), 預設是 false
- get:是一個函數,叫做getter獲取器,可以用來決定改屬性的屬性值
- get屬性的返回值就是當前屬性的屬性值
- set:是一個函數,叫做setter設置器,當修改屬性值的時候會觸發函數
- set和get不能和其他三個屬性一起用
<h1>姓名: <span class="name">預設值</span> </h1> <h1>年齡: <span class="age">預設值</span> </h1> <h1>性別: <span class="sex">預設值</span> </h1> 請輸入年齡:<input type="text" name="" id="name"> <br> 請輸入年齡:<input type="text" name="" id="age"> <br> 請輸入性別:<input type="text" name="" id="sex"> const nameEl = document.querySelector('.name') const ageEl = document.querySelector('.age') const sexEl = document.querySelector('.sex') const inp1 = document.querySelector('#name') const inp2 = document.querySelector('#age') const inp3 = document.querySelector('#sex') const obj = { name:'張三', age:18, sex:'男' } function bindHtml(res) { nameEl.innerHTML = res.name ageEl.innerHTML = res.age sexEl.innerHTML = res.sex } const app = observer(obj, bindHtml) inp1.oninput = function () { app.name = this.value } inp2.oninput = function () { app.age = this.value } inp3.oninput = function () { app.sex = this.value } function observer (origin, callBack) { const target = {} // 數據劫持 for (let key in origin) { Object.defineProperty(target, key, { get () { return origin[key] }, set (val) { origin[key] = val callBack(target) } }) } // 首次調用 callBack(target) return target }
數據劫持 + 渲染 (vue功能實現)
<div id="app"> <p>姓名:{{name}}</p> <p>年齡:{{age}}</p> <p>性別:{{sex}}</p> <p>用戶:{{id}}</p> </div> 用戶id:<input type="text" name="" id=""> <script src="js/vue.js"></script> <script> const app = createApp({ el:'#app', data:{ name:'張三', age:20, sex:'男', id:'0001' } }) const inp = document.querySelector('input') inp.oninput = function () { app.id = this.value } </script>
function createApp(options) { // 安全判斷(傳參判斷) // 1.1 el if (options.el === undefined) { throw new Error('el選項必須傳遞') } // 1.2 data if (Object.prototype.toString.call(options.data) !== '[object Object]') { throw new Error('data 屬性必須是個對象') } // 1.3 el 不能為空 const root = document.querySelector(options.el) if (root === null) throw new Error('el 必須傳入,且root為有效元素') // 2 數據劫持 const target = {} for (let key in options.data) { Object.defineProperty(target, key, { get() { return options.data[key] }, set(val) { options.data[key] = val // 每次修改數據調用渲染函數 bindHtml(root, target, rootStr) } }) } // 拿到根元素下麵的結構(字元串形式) const rootStr = root.innerHTML // 首次調用 bindHtml(root, target, rootStr) return target } function bindHtml(root, _data, _str) { // 定義一個正則拿到{{......}} const reg = /{{ *(\w+) *}}/g const arr = _str.match(reg) arr.forEach(item => { const key = /{{ *(\w+) *}}/.exec(item)[1] _str = _str.replace(item, _data[key]) }); root.innerHTML = _str }
數據劫持升級
自己劫持自己
- 語法:
Object.defineProperties(想要劫持得對象,配置項)
const obj = { username:'admin', password:'123456', id:'0001' } for (let key in obj) { Object.defineProperties(obj, { // 對象裡面預設把key當作字元串,通過[]語法實現將其當作變數 ['_' + key]:{ value:obj[key], writable:true }, [key]:{ get () { // 如果returnobj[key],每次return都會訪問, // 然後觸發get方法會形成死迴圈 return obj['_' + key] }, set (val) { obj['_' + key] = val } } }) }
數據代理(ES6)
- 通過內置構造函數代理
- 語法:
new Proxy(想要代理得對象,)
- 數據代理完成後,在向對象中添加屬性,也可以自動完成代理
const obj = { name:'zhangsan', age:18 } const res = new Proxy(obj, { get (target, property) { return target[property] }, set (target, property, val) { target[property] = val } }) res.age = 20 console.log(res.age); console.log(res.name); // 數據代理後添加的數據也可以被代理 res.abc = 123 console.log(res);