js 閉包 基礎 示例 高階
瀏覽器垃圾回收機制
/**
- GC:瀏覽器垃圾回收機制
- 【棧記憶體:EC】
- 全域性執行上下文:在瀏覽器載入頁面的時候形成,然後在頁面關閉的時候釋放(頁面重新整理:先釋放,重新載入中後再形成)
- 私有上下文:函式執行會形成一個私有上下文(程式碼塊中let/const也會形成私有上下文)
-
+ 一般情況下程式碼執行完就會出棧釋放
-
+ 然後當前上下文中的某個內容(一般是一個堆(物件、函式))被上下文以外的事物佔用了,則當前上下文不能被釋放
*【堆記憶體:HEAP】
-
以谷歌瀏覽器為例
-
按照是否被引用來決定是否釋放,瀏覽器會定期間隔一段時間查詢所有堆記憶體是否被佔用了,如果沒有被佔用則釋放
-
+ 我們可以手動賦值為null,達到記憶體釋放的目的
-
IE瀏覽器按照引用計數方式實現垃圾回收
-
+這種方式會出現計數混亂的問題,會導致記憶體洩漏
閉包定義
- 函式執行,會形成一個私有的上下文,這個私有上下文會保護裡面的私有變數不受外界的干擾,我們把函式的這種保護機制成為閉包
- 另一種說法:函式執行形成一個不被釋放的上下文,一方面可以保護裡面的私有變數不受外界干擾,還可以將這些變數儲存下來,這種方式才是閉包
- 閉包機制會消耗記憶體(合理使用)
- 閉包作用:儲存 保護
- 示例1
- 正常情況下函式執行完後所形成的上下文都會出棧釋放,私有上下文中的一切東西都銷燬,優化棧記憶體控制元件
- 如果函式執行所形成的的上下文中有一個東西(一般是引用型別的地址)被當前上下文以外的事務所佔用,
- 則當前上下文將不會釋放,將導致棧記憶體變大
- 函式每次執行都會形成全新的私有上下文
*/
let x= 5;
function fn(x) {//形參變數也屬於私有變數
return function (y) {
console.log(y + (++x));
}
}
let f = fn(6);
f(7); //14
fn(8)(9) //18
f(10); //18
console.log(x);//5
let x= 5;
function fn() {
return function (y) {
console.log(y + (++x));
}
}
let f = fn(6);
f(7); //13
fn(8)(9) //16
f(10); //18
console.log(x);//8
let x= 5;
function fn() {
return function (y) {
console.log(y + (++x));
}
}
let f = fn();
f(7); //13
fn()(9) //16
f(10); //18
console.log(x);//8
//閉包的常見方式
//方式1.
function fun() {
var x = 0;
return function () {
return ++x;
}
}
//方式2.
function fn() {
return {
name:“xxx”
};
}
var obj = fn();
//方式3.
function fn() {
let x = 0;
document.body.onclick = function () {
console.log(x++);
}
}
/**
- 示例1
*/
let a= 10,b=9;
function A(a) {
A = function (b) {
alert(a+b++);
};
alert(a++);
}
A(1); //1 A(1)執行完後,原來的A a上下文並不會被釋放
A(2); // 4 alert(a+b++); 中的a是原來A a的變數
//----------問題-------------
var fnA= [];
for(var i = 0; i<5;i++){
fnA[i] = function () {
return i;
}
}
fnA0;//5
fnA4;//5
閉包解決迴圈賦值的問題
//-------閉包解決迴圈賦值的問題 方案1-1----------
var fnA= [];
for(var i = 0; i<5;i++){
fnA[i] = function (i) {
return function () {
return i;
};
}(i);
}
fnA0;//0
fnA4;//4
//-------閉包解決迴圈賦值的問題 方案1-2----------
var fnA= [];
for(var i = 0; i<5;i++){
(function(i) {
fnA[i] = function () {
return i;
};
})(i);
}
fnA0;//0
fnA4;//4
//-------閉包解決迴圈賦值的問題 方案1-3 let----------優於以上的程式碼
//還是基於閉包的的機制,但是不是自己去執行函式建立,而是利用es6中let產生私有上下文實現
var fnA= [];
for(let i = 0; i<5;i++){ //此處會產生塊級上下文,每輪迴圈都會產生一個私有的上下文,迴圈結束父級上線文將會被銷燬
//首先for let會產生 父級上下文生成變數i
//for迴圈體會產生私有上下文併產生自己的變數i,每次迴圈完會將父級上下文釋放,因為i獨立,私有上下文會影響父類中的i,但是每輪迴圈結束後私有上下文中的變數不受父級影響
fnA[i] = function () {
return i;
}
}
fnA0;//0
fnA4;//4
//-----------自定義屬性解決迴圈賦值的問題 方案2------不會產生閉包,但是還是會迴圈產生堆記憶體-------
//事先把資訊儲存到屬性身上,後期在其他操作上需要用到直接獲取即可
//例如button迴圈繫結事件時,給每個button新增自定義屬性
for (let i = 0; i < buttonList.length; i++) {
buttonList[i].myIndex = i;
buttonList[i].οnclick= function () {
console.log(this.myIndex);
}
}
//-----------事件代理機制 自定義標籤 解決迴圈賦值的問題 方案3 效能會提高>=40%-------
// button
document.body.onclick = function (ev) {
let target = ev.target;
if(target.tagName === “BUTTON”){
let index = + target.getAttribute(“data-index”); // + 轉數字
console.log(index);
}
}
//…
var a= 9;
function fn() {
a =0;
return function (b) {
return b + a++;
}
}
var f= fn();
console.log(f(5)); // 5;
console.log(fn()(5)); // 5
console.log(f(5));//6
console.log(a); // 2
套娃
//----------------------------
function fun(n,o) {
console.log(o);
return {
fun: function (m) {
return fun(m,n);//注意此處會執行後返回
}
};
}
var c = fun(0).fun(1); // undefined 0
c.fun(2); // 1
c.fun(3); // 1
var b =10;
(function b() { //匿名函式具名化,名字只能在內部使用,且預設無法被重新賦值,除非這個函式名在函式體中被重新宣告過
b =20;
console.log(b); //function b(){ … }
})();
console.log(b);
var b =10;
(function b() { //匿名函式具名化,名字只能在內部使用,且預設無法被重新賦值,除非這個函式名在函式體中被重新宣告過
console.log(b);// undefined
var b =20;
console.log(b); //20
})();
console.log(b);
模組化
//沒有物件和函式的情況下,編寫程式碼經常出現‘全域性變數汙染’
//…怎麼使用…
/**
*模組化/單例模式設計 (利用閉包的保護和物件的分組特徵一起實現)
*每一個物件都是Object的單獨例項
-
1.let obj = {}; //字面量方式
-
2.let obj = new Object();//建構函式方式,後臺語言只有這種方式
-
AModule 物件名且是名稱空間,分組特徵
*/
let AModule = (function () {
let step = 0;
function fn() {}
function query() {
}
function privateFn(){ //私有方法}
//我們想把私有的東西暴露出去供外面呼叫、
// 1. // 瑕疵:不能給外部暴露太多方法,否則出現‘全域性變數汙染’
window.query = query;// 2. 基於物件(分組)的方法,把需要暴露的方法,都放置到一個空間下 好的方案
return {
fn, query,step,
init() {
//控制業務板塊中我們先執行誰,再執行誰
}
}
})()
function f1() {
AModule.query();
}
//…jquery 模組化實現…
//https://code.jquery.com/jquery-3.5.1.js
var A = typeof window !== “undefined” ? window : this; //根據執行環境返回全域性物件
//利用暫時性死區:一個未被宣告的變數在typeof檢測時不會報錯,只是返回“undefined”
//檢測window是否存在
// + JS在瀏覽器中執行:是存在window的
// + JS在node中只執行:不存在window,全域性物件是global
var B = function( window, noGlobal ) {
//如果是在瀏覽器環境22中執行的js程式碼
// + window就是window noGlobla為undefined
//如果是node下執行 則 window是global ,noGlobal 為true
“use strict”;
var jquery = function (select, context) {
return new jQuery.fn.init(select, context);
};
jQuery.fn = jQuery.prototype = {
}
//瀏覽器環境下:暴露給全域性變數兩個變數,只都是jquery
if (typeof noGlobal === "undefined" ) {
window.jQuery = window.$ = jQuery;
}
return jquery;
};
(function (global, factory) {
“use strict”;
// 嚴格模式:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
//驗證是否支援CommonJS/ES6Module規範
if(typeof module === “object” && typeof module.exports === “object”){
// 程式碼是基於node環境下的或者是基於webpack打包的專案
module.exports = global.document ? factory(global, true) :function (w) {
if(!w.document){
throw new Error(“jquery requires a window with a document”);
}
return factory(w);
};
} else {
// 執行在瀏覽器或者webview中
factory(global);
}
})(A, B);
//…類庫、外掛、ui元件、框架…
/**
- 在我們的專案中編寫自己的類庫、外掛、ui元件、框架的時候,我們需要基於閉包的機制進行’私有化’處理
-
- 能夠在瀏覽器中執行
-
- 也支援CommonJS、ES6Module規範(node\webpack)
*/
(function () {
function Banner() {
- 也支援CommonJS、ES6Module規範(node\webpack)
}
//瀏覽器環境
if(typeof window !== "undefined"){
window.Banner = Banner;
}
//支援CommonJS\ESModule
if(typeof module !== "undefined" && typeof module.exports != "undefined"){
module.exports = Banner;
}
})();
惰性函式…函式重構
//…惰性函式…函式重構…
/**
- 懶 能執行一次絕不執行二次
*/
//這樣寫每次都需要判斷window.getComputedStyle,沒必要,第一次執行已經知道相容性了
function getCss(element, attr) {
if(window.getComputedStyle){
return window.getComputedStyle(element)[attr];
}
//IE678
return element.currentStyle[attr];
}
console.log(getCss(document.body, “width”));
console.log(getCss(document.body, “padding”));
//…
let isCompatible = ‘getComputedStyle’ in window;
function getCss(element, attr) {
if(isCompatible){
return window.getComputedStyle(element)[attr];
}
//IE678
return element.currentStyle[attr];
}
console.log(getCss(document.body, “width”));
console.log(getCss(document.body, “padding”));
//…惰性思想…
function getCss(element, attr) {
if(window.getComputedStyle){
getCss = function (element, attr) {
return window.getComputedStyle(element)[attr];
}
} else{
//IE678
getCss = function (element, attr) {
return element.currentStyle[attr];
}
}
return getCss(element, attr);
}
console.log(getCss(document.body, “width”));
console.log(getCss(document.body, “padding”));
閉包柯里化函式…預處理思想
//…閉包柯里化函式…預處理思想…
let res = fn(1,2)(3);
console.log(res); //實現 1 + 2 + 3 =>6
function fn(…outterArgs) {
return function anonymous(…innerArgs) {
let arr = outterArgs.concat(innerArgs);
return arr.reduce(function (total, item) {
return total + item;
})
}
}
//自定義reduce
function reduce(arr, callback, init) {
let arrindex = 0;
if(init === “undefined”){
init = arr[0];
arrindex++;
}
for (let i = arrindex; i < arr.length; i++) {
init = callback(init, arr[i], i);
}
return init;
}
let arr = [10, 20, 30, 40, 50];
let result = reduce(arr, function (result, item, index) {
return result + item;
}, 0);
console.log(result);
result = reduce(arr, function (result, item, index) {
return result + item;
}, 100);
console.log(result);
Compose函式
//…Compose函式…
const add1 = (x) => x+1;
const mul3 = (x) => x*3;
const div2 = (x) => x/2;
console.log(div2(mul3(add1(add1(0)))));
//這樣寫可讀性太差,我們可以構建一個compose函式,他接受任意多個函式作為引數(這些函式都只能接收一個引數),然後compose返回的也是一個函式,達到以下效果
const opera = compose(div2,mul3,add1, add1);
console.log(opera(0));//相當於div2(mul3(add1(add1(0))))
console.log(opera(2));//相當於div2(mul3(add1(add1(2))))
console.log(opera());
//方式1
function compose(…funcs) {
return function opera(x) {
if(funcs.length === 0){
return x;
}
if(funcs.length === 1){
return funcs0;
}
return funcs.reduceRight(function (result, item, index) {
if(index === funcs.length-2){
return item(result(x));
} else {
return item(result);
}
});
}
}
//方式2
const add1 = (x) => x+1;
const mul3 = (x) => x*3;
const div2 = (x) => x/2;
console.log(div2(mul3(add1(add1(0)))));
//這樣寫可讀性太差,我們可以構建一個compose函式,他接受任意多個函式作為引數(這些函式都只能接收一個引數),然後compose返回的也是一個函式,達到以下效果
const opera = compose(div2,mul3,add1, add1);
console.log(opera(0));//相當於div2(mul3(add1(add1(0))))
console.log(opera(2));//相當於div2(mul3(add1(add1(2))))
console.log(opera());
function compose(…funcs) {
if(funcs.length === 0){
return arg =>{
return arg;
}
}
if(funcs.length === 1){
return funcs[0];
}
return funcs.reduce((a,b)=> {
return (...args) => {
a(b(...args));
}
});
}
防抖 節流
//…防抖動防抖:對於頻繁觸發某個操作,我們只識別一次(只觸發一次函式) …
//點選做啥事情,一般都是防抖為主
// 防抖函式 點選做啥事情,一般都是防抖為主
// func[function]:最後要觸發執行的函式
// wait[number]: 頻繁的設定界限 多少時間點選算頻繁
// immediate[boolean]: = true 觸發第一次點選的 =false最後一次點選的 預設是最後一次點選的
// return 可以被呼叫執行的函式
function debounce(func,wait = 300,immediate = false){
console.log(‘debounce’,this) // debounce在click執行之前執行 是debounce的執行結果給了click 這個時候 debounce裡面的this 應該是window
let timer = null
return function anonymous(...params){
console.log('ANY',this) // 這個是click在頁面中點選執行的 this指向元素本身
let now = immediate && timer === null;
// 如果是立即執行,
now ? func.call(this,...params) : null;
// 每次點選都把之前設定的定時器清除
clearTimeout(timer)
// 重新設定一個新的定時器監聽wait時間內是否觸發第二次
timer = setTimeout(()=>{
// 手動恢復初始狀態
timer = null;
// this 是當前的元素
// wait這麼久的等待中,沒有觸發第二次
!immediate ? func.call(this,...params) : null;
},wait)
}
}
function handle(){
setTimeout(()=>{
console.log(‘OK’)
},1000)
}
//document.body.onclick = handle; //如果瘋狂點選submit,handle會觸發很多次,那麼一秒後會輸出很多OK。
//body.onclick = function(){
// // 在匿名函式中 我們控制handle 只執行一次
//}
document.body.onclick = debounce(handle,500,true);
//…節流:在一段頻繁操作中,可以觸發多次,但是觸發的頻率由自己制定…
//滾動scroll,文字框輸入過程中的模糊匹配keydown都用節流
//例如:每次滾動過程中,瀏覽器都有最快反應時間(谷歌一般是5-6ms,ie一般是13-17ms),只要反應過來就會觸發一次函式,也就是說谷歌每5ms就會觸發一次,過於頻繁
function throttle(func, wait = 300) {
let timer=null,
previous = 0;//上一次觸發時間
return function anonymous(…params) {
let now = new Date(),
remaining = wait - (now - previous) // 還差多長時間 達到一次觸發頻率
if(remaining<=0){
// 兩次操作間隔時間已經超過wait了 可以觸發了
window.clearTimeout(timer)
previous = now
timer = null
func.call(this,…params)
}else if(!timer){
// 不符合觸發的頻率 設定定時器等待
timer = setTimeout(()=>{
timer = null
previous = new Date()
func.call(this,…params)
},remaining)
}
}
}
function handle() {
console.log(ok);
}
// window.scroll = handle; // 谷歌每5ms就會觸發一次,過於頻繁
window.scroll = throttle(handle);//每300ms觸發一次
相關文章
- 【JS基礎】作用域和閉包JS
- Python高階--閉包Python
- JS學習理解之閉包和高階函式JS函式
- JS基礎總結(3)——作用域和閉包JS
- python基礎知識之函式初階——閉包Python函式
- 還搞不懂閉包算我輸(JS 示例)JS
- 面試-JS基礎知識-作用域和閉包、this面試JS
- python高階-閉包-裝飾器Python
- 2019 JS經典面試題(基礎+高階)JS面試題
- 《JavaScript設計模式與開發實踐》基礎篇(2)—— 閉包和高階函式JavaScript設計模式函式
- Python高階 -- 11 閉包、裝飾器Python
- javascript 基礎(作用域和閉包)JavaScript
- Python基礎之閉包函式Python函式
- 【重溫基礎】19.閉包
- 【JS基礎】從零開始帶你理解JavaScript閉包--我是如何徹底搞明白閉包的JSJavaScript
- [JavaScript閉包]Javascript閉包的判別,作用和示例JavaScript
- JS閉包ClosureJS
- 【重溫基礎】JS中的常用高階函式介紹JS函式
- Swift開發基礎06-閉包Swift
- JavaScript夯實基礎系列(二):閉包JavaScript
- 設定模式基礎 之 3閉包模式
- 閉包詳解二:JavaScript中的高階函式JavaScript函式
- js閉包的理解JS
- js函式閉包JS函式
- js中的閉包JS
- 淺談js閉包JS
- 零基礎學習 Python 之閉包Python
- js高階JS
- python進階(12)閉包Python
- JS基礎教程——正規表示式示例(推薦)JS
- js閉包及閉包的經典使用場景JS
- Python基礎(五)——閉包與lambda的結合Python
- JS作用域與閉包JS
- JS閉包作用域解析JS
- [JS]什麼是閉包?JS
- 對JS閉包的理解JS
- JS中的 閉包(Closure)JS
- JS進擊之路:閉包JS