JavaScript面試自我補充

知識點大綱:

  1. 數據類型
  2. 形參傳值與傳址
  3. 預解析、作用域和作用域鏈
  4. Object.freeze()凍結對象屬性變量
  5. 嚴格模式
  6. IIFE
  7. 閉包(這個弄詳細點!)

本文是根據自己的情況,總結/補充的易於“入坑”面試考點,部分較新穎。對大家來說,可能內容介紹不是很全面,請見諒!

1.數值類型

NaN

NaN和任何值都不相等,即使本身也不相等!

NaN==NaN
<false
10=='10'
<true

所以
ES5
檢測NaN,用不等於!=檢測
ES6
檢測NaN,用isNaN檢測

Symbol(獨一無二的簡單數據類型)

Symbol是ES6新增的一個基本數據類型,表示獨一無二的值。
使用場景:

  1. 對象的屬性名,確保對象中不會出現相同的屬性名;
  2. 防止不小心被修改或覆蓋
//獨一無二
let a = Symbol('name');
let b = Symbol('name');
console.log(a==b);		//false
//解決相同屬性名問題(ES6之前屬性名設置name1、name2、name3......)
let a = Symbol('name');
let b = Symbol('name');
let obj = {
  name: '張三',
  name: '李四',
  name: '王五'
}
console.log(obj);       //{name: "王五"}
console.log(obj.name);  //王五
let a = Symbol('name');
let b = Symbol('name');
let obj = {
  [a]: '張三',
  [b]: '李四',
  [Symbol('name')]: '王五'
}
console.log(obj);       //{Symbol(name): "張三", Symbol(name): "李四", Symbol(name): "王五"}
console.log(obj[a]);  //張三

還有幾個需要了解的方法:

  • Symbol.for
    • 根據描述獲取Symbol,如果不存在則新建一個Symbol(在全局被登記)
  • Symbol.keyFor
    • 根據使用Symbol.for登記的Symbol返回描述,如果找不到返回undefined 。
let a= Symbol.for("name");
let b= Symbol.for("name");
console.log(a== b); //true
console.log(Symbol.keyFor(a));	//name

2.形參傳值與傳址

變量在參數傳遞方式上,有所不同(理解棧和堆)
       函數的參數如果是簡單類型,會將一個值類型的數值副本傳到函數內部,函數內部不影響函數外部傳遞的參數變量

       如果是一個參數是引用類型,會將引用類型的地址值複製給傳入函數的參數,函數內部修改會影響傳遞參數的引用對象。
1.形參傳值的誤區:
變量和形參
在這裏插入圖片描述

// 引用類型
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a) // {x: 100, y: 200}
console.log(b) // {x: 100, y: 200}

3.作用域、作用域鏈

作用域

全局作用域:

window.a = 'hello';

//非嚴格模式下
b = 10;		//容易造成全局污染(影響其他文件的值)

函數級作用域:
var(函數級作用域,在函數中,只有當前函數內部訪問;在塊中,父級和塊內都可以訪問到)
let(塊級作用域)-----沒有預解析(推薦)
const(常量-塊級作用域)-----沒有預解析

for(var i = 0; i < 5; i++) {
    var num = 10;
}
console.log(num);		//10

for(var j = 0; j < 5; j++) {
    let num2 = 100;
}
console.log(num2);		//<VM232 helloword.js:18 Uncaught ReferenceError: num2 is not defined
function show(){
    var a = "hello";
}
show(); 	//調用執行完成,show被釋放(出棧)
console.log(a); //helloword.js:5 Uncaught ReferenceError: a is not defined
let a = 10;
function show(){
    console.log(a);        //10
}
show();
let a = 10;
function show(){
    console.log(a);        //Uncaught ReferenceError: Cannot access 'a' before initialization(初始化之前不能訪問‘a’)
    let a = 100;
}
show();
案例
var a = 666;
function show() {
    var a = 12;
    show2();
}
function show2(){
    console.log(a);
}
show();				//666
var a = 666
function show3() {
    var a = 12;
    function show4(){
    	console.log(a);
	}
	show4();
}
show3();		//12

4.Object.freeze()凍結對象屬性變量

const stu = {
  "id": 'st02023',
  "name": '王磊'
};
stu.name = '張三';
console.log(stu);   //{id: "st02023", name: "張三"}
const stu = {
  "id": 'st02023',
  "name": '王磊'
};
Object.freeze(stu);
stu.name = '張三';
console.log(stu);   //{id: "st02023", name: "王磊"}

5.嚴格模式-“use strict”——避免全局污染(作用之一)

"use strict"
var a = 15;
b = 25;     //helloword.js:3 Uncaught ReferenceError: b is not defined

6.IIFE(普通話口語“亦菲~”)——自調用函數

作用:

  1. 防止全局變量污染
  2. 函數和變量,在執行完都被釋放
var a = 100;
var a = 200;
console.log(a);     //200
(function(){
    var a = 100;  
    console.log(a);     //100  
})();

(function(){
    var a = 200;    
    console.log(a);     //200
})();

console.log(a);     //helloword.js:9 Uncaught ReferenceError: a is not defined

7.閉包

//案例1
function show() {
  let a = 100;
  return function() {
    return a;
  }
}

let show2 = show();
console.log(show());    //ƒ () { return a; }	//show返回的匿名函數
console.log(show2());   //100
show2 = null;

閉包官方定義:閉包就是能夠讀取其他函數內部變量的函數。(包括其他語言)
在JavaScript中,
閉包定義:函數內部的子函數就是閉包(比如:上面代碼的匿名函數)
閉包本質:是連接函數內部和函數外部的橋樑(比如:上面匿名函數是連接show和show2的橋樑)
閉包現象:能夠讀取其他函數內部變量(比如:show2訪問show的變量a)
最大特點:閉包記住了閉包函數誕生的生產環境,可以直接使用生產環境的變量(比如:上面代碼的匿名函數可以訪問show的所有變量,甚至是show的父環境的變量)
閉包作用:函數可以訪問其他函數內部變量
滿足閉包的三個條件:

  • 函數嵌套(函數內部有子函數(閉包函數))
  • 閉包訪問所在的作用域
  • 所在的作用域被調用

閉包使用場景:框架、(我在迭代事件添加監聽使用過沙箱式閉包),具體十種場景,後面案例介紹!

理解閉包的緩存和手動釋放閉包占用資源

//案例2
//用閉包實現點贊
function click_like() {
  let num = 0;
  return function() {
    return ++num;
  }
}

let show2 = click_like();
console.log(show2());    //1
console.log(show2());    //2
console.log(show2());    //3
console.log(show2());    //4
console.log(click_like());   //ƒ () { return num++; }
console.log(click_like());   //ƒ () { return num++; }
console.log(click_like());   //ƒ () { return num++; }
console.log(click_like());  //ƒ () { return num++; }
console.log(typeof click_like());	//function
console.log(click_like()());//1
console.log(click_like()());//1
console.log(click_like()());//1
show2 = null;

根據代碼來幫助理解緩存沒有被釋放(個人理解):因爲全局引用變量show2依賴函數click_like(我們知道全局變量是不會自動釋放的),show2不能被回收,那被佔用的函數click_like也不能回收,接着就是被佔用的function匿名函數(閉包)的佔用的參數都不能回收。
再換個對象討論
let show2 = click_like(); 調用click_like(返回的是匿名函數ƒ () { return num++; }),click_like調用完成就被釋放掉,所以結果總是console.log(click_like()());//1;
這就理解了閉包的另一個作用了吧!——緩存數據
回收設置:直接將全局引用變量賦爲null。
注意:閉包最後一定要手動回收,避免網頁加載緩慢(可能造成網頁崩潰)

理解了原理,咱們再寫一個使用IIFE的閉包(沙箱式閉包)…看看是不是跟容易懂了!

//案例3
//使用包含IIFE的閉包實現點贊
let show2 = (function() {
  let num = 0;
  return function() {
    return ++num;
  }
})();
console.log(show2()); //1
console.log(show2()); //2
console.log(show2()); //3
console.log(show2()); //4
show2 = null;

看一個稍微複雜的閉包:

//案例4
function show() {
  let arr = [];
  for(var i = 0; i < 10; i++) {
    arr[i] = function() {
      return i;
    }
  }
  return arr;
}

let show2 = show();   //調用show,返回函數數組:arr[0]=f(){...}, arr[1]=f(){...}, arr[3]=f(){...} .........
console.log(show2[0]()); //10   //for循環執行完成,返回函數還沒被執行,i的值最終爲10
console.log(show2[0]()); //10
console.log(show2[1]()); //10
console.log(show2[1]()); //10
show2 = null;

結果都是10,怎麼改進呢?(沙箱式閉包)

//案例5
function show() {
  let arr = [];
  for(var i = 0; i < 10; i++) {
    arr[i] = (function(x) {
      return x;
    })(i);
  }
  return arr;
}

let show2 = show();   //調用show,IIFE執行,調用完成返回arr=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(show2); //(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
show2 = null;

第二種改進方式:(直接將循環迭代的聲明變量i設置爲塊作用域)

//案例6
function show() {
  let arr = [];
  for(let i = 0; i < 10; i++) {
    arr[i] = function() {
      return i;
    }
  }
  return arr;
}

let show2 = show();   
console.log(show2[0]()); //0
console.log(show2[0]()); //0
console.log(show2[1]()); //1
console.log(show2[2]()); //2
console.log(show2[3]()); //3
console.log(show2[4]()); //4
show2 = null;

閉包十種場景:

  1. 返回值(最常見)——前面案例1、2
  2. 函數賦值——前面案例3
  3. 函數參數——將閉包函數當做參數
  4. IIFE——前面案例3、5
  5. 循環賦值——前面案例4、5
  6. getter和setter——設置getter和setter的閉包函數
  7. 迭代器——前面的“點贊”
  8. 區分首次
let show = (function() {
  let arr = [];
  return function(id) {
    if(arr.indexOf(id) >= 0) {
      return false;
    }
    else {
      arr.push(id);
      return true;
    }
  }
})();

console.log(show(10));  //true
console.log(show(10));  //true
  1. 緩存機制
    10.image圖片對象上報
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章