前端面試準備--基礎篇之js

本文參照 大神文章,也是本身複習準備面試的小筆記總結
本文主要內容:
1.原型/構造函數/實例/原型鏈
2.做用域/做用域鏈複製代碼

做用域/做用域鏈

在講做用域概念以前,先了解一個概念,執行上下文環境(Execution Context)
  • 執行上下文環境(Execution Context)
包含三部分:
  1. 變量對象:執行上下文的一部分,能夠抽象爲一種數據做用域,存儲該執行上下文中的全部變量和函數聲明(不包含函數表達式)。
活動對象(AO):當變量對象處於active EC的時候,稱其爲活動對象。 說到活動對象,又要講一下js的詞法執行順序: 每一個函數執行的瞬間,就會生成一個對象,即活動對象AO 1. 先分析參數: 函數先接收形參,將這些形參賦值給AO的屬性,初始值都爲undefined 而後接受實參,將實參賦值給屬性; 2.分析變量的聲明: 若是AO裏邊沒有這個屬性,則新增該屬性;若存在,則不做任何處理; 3.分析函數 若是AO屬性裏邊沒有該屬性,則新增屬性;若存在,則被新值覆蓋;
function t(age) {
    console.log(age);//1
    var age = 99;
    console.log(age);//2
    function age() {
        
    }
    console.log(age);//3
}
t(5);複製代碼
結果:
1:function age(){}
2: 99
3: 99

分析:
這時候還涉及到一個變量提高的問題;在同一做用域,變量和函數優先聲明,可是賦值不優先;
按照上邊的過程一步步分析:
1. 在t函數被調用的瞬間會生成一個活動變量AO,分析形參,將age付給AO的一個屬性,而且賦值爲5;
2. 在函數內部分析變量,age變量和函數的聲明都提高到函數的最上邊,也就是1的上邊;
當聲明變量的時候,發現AO的屬性裏有age這個屬性,不作任何操做;
而後聲明函數,聲明函數的時候,發現有該屬性,則把函數賦值給該屬性;
因此在1打印的時候AO.age爲age函數
3.執行到age = 99的時候,又將AO.age從新賦值給99
因此2打印爲99,2-3之間沒有作任何改變值的操做,因此3處也爲99;複製代碼
2. 做用域鏈
當使用變量時,會在執行上下文訪問到
3. this指向
類型:
  1. 全局執行上下文
  2. 函數執行上下文
  3. eval執行上下文
代碼執行過程:
  1. 建立全局上下文(global EC)
  2. 全局執行上下文(caller)逐行自上而下執行,遇到函數時,函數執行上下文被push到執行棧的頂層
  3. 函數執行上下文被激活,成爲active EC,開始執行函數中的代碼,caller被掛起
  4. 函數執行完,callee被pop移除出執行棧,控制權交換給全局上下文(caller),繼續執行
  • 做用域是什麼?
含義:該上下文中聲明的變量和函數聲明的做用範圍。
分類:塊級做用域和函數做用域
這時候
var a = 1; 
function b() {
  console.log(a);
}
function c() {
  var a = 3; 
  b();
}
c();複製代碼
  • 閉包
能夠理解爲:父函數被銷燬的狀況下,返回出的子函數的[[scope]]中仍然保留着父級的單變量對象和做用域鏈,所以能夠繼續訪問到父級的變量對象,這樣的函數稱爲閉包。
閉包因爲存在局部變量中一直不能被釋放,因此對性能有負面影響,因此能不用閉包儘可能不用。
function test(){
 for(var i = 0;i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  },10)
 }
}

test();複製代碼
輸出:5,5,5,5,5
由於setTimeout 是一個異步操做,當settimeout的回調被執行的時候,i已經爲5了
爲了解決這種問題,能夠選擇閉包
function test(){
 for(var i = 0;i < 5; i++) {
  (function(val){
    setTimeout(() => {
      console.log(i);
    },10)
  })(i);
 }
}

test();複製代碼
另外舉一個閉包的例子
function count() {
 var count = 0;
 var add = function() {
   count++;
   console.log(count);
 }
 return add;
}

var add = count();
add();// 1
add();// 2複製代碼

柯里化

簡單來講,就是一個函數原本須要傳多個參數,經過柯里化的實現,每次只須要傳部分參數。
舉例:
// say anyword to anyone
// 正常狀況是
function Hello(word, name) {
 console.log(name + word);
}
Hello('hi','andy');複製代碼
柯里化以後的代碼:
function Hello(word) {
 return function(name) {
   console.log(name + word);
 }
}

var sayHi = Hello('hi');
sayHi('Jack');複製代碼

深克隆,淺克隆

淺克隆和深克隆的區別就是,若是屬性值爲引用數據類型的時候,沒有改變指針;
一個簡單的深度克隆代碼:
function deepClone(obj) {
	let copy;

	// Handle the 3 simple types, and null or undefined
	if (null == obj || "object" != typeof obj) return obj;

	// Handle Date
	if (obj instanceof Date) {
		copy = new Date();
		copy.setTime(obj.getTime());
		return copy;
	}

	// Handle Array
	if (obj instanceof Array) {
		copy = [];
		for (let i = 0, len = obj.length; i < len; i++) {
			copy[i] = deepClone(obj[i]);
		}
		return copy;
	}

	// Handle Object
	if (obj instanceof Object) {
		copy = {};
		for (let attr in obj) {
			if (obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr]);
		}
		return copy;
	}

	throw new Error("Unable to copy obj! Its type isn't supported.");
}複製代碼

JS數據類型

js的數據類型分爲基本數據類型和引用數據類型
基本數據類型:number String Boolean undefined null
引用數據類型:Object function Array Date RegExp
  • 數據類型判斷
  1. typeof方法能夠檢測number String Boolean undefined 這四種基本數據類型,還能夠檢測出object和function兩種引用數據類型
  2. null, String(null)
  3. 檢測object的方法,Object.prototype.toString.call(obj).slice(8,-1).toLocaleLowerCase()
  • 數據類型轉換
  1. -、*、/、%:一概轉換成數值型再計算
  2. +: 數字 + 字符串 = 字符串
var w = 1 + 'aaa' // "1aaa"複製代碼
  1. . 數字 + 對象
var w = 2 + {a: 1}
// "2[object Object]"複製代碼
數字 + Boolean/null
w = 1 + false
//1
w = 1 + true
//2
w = 1 + null
//1複製代碼
數字 + undefined
w = 1 + undefined
//NaN複製代碼
其餘類型的數據轉換
[1,2,3].toString()
//"1,2,3"
Object.prototype.toString.call({x:'a'})
//"[object Object]"
NaN !== NaN //true複製代碼

一些讀代碼的題目

分析下邊代碼,寫出執行結果:
var t = true;
window.setTimeout(function (){
    t = false;
},1000);
while (t){}
alert('end');複製代碼
while(true){} 爲死循環,因此永遠沒有alert, 也不會執行setTimeout
2.
var city = "Rome"+function() {
  console.log(city);
  var city = "Paris";
}();複製代碼
//undefined 執行console.log的時候,只有city的生命,尚未賦值;因此打印結果爲undefined 所謂變量的提高,是指變量聲明的提高,而變量賦值並無提高。
3.
var name = "The Window";
var object = {
    name: "My Object",
    getNameFunc: function () {
        return function () {
            return this.name;
        };
    }
};
alert(object.getNameFunc()());複製代碼
// The Window
4.當點擊button#2的時候,console中會打印什麼?
var nodes = document.getElementsByTagName('button');
// assume there is totally 5 nodes
for (var i = 0; i < nodes.length; i++) {
    nodes[i].addEventListener('click', function() {
        console.log('You clicked element #' + i);
    });
}複製代碼
結果爲'you clicked element #5'。
怎樣解決這種問題呢?
最簡單的方式是將var 改爲let生成塊級做用域。
生成局部做用域的方式還有一種就是封裝函數
var parentClassEle = document.getElementsByClassName('parent');
for(var i = 0; i < parentClassEle.length; i++) {
   addClick(i);
}
        
function addClick(i) {
   var j = i;
   parentClassEle[i].addEventListener('click', function() {
     console.log(i)
   })
}複製代碼
5.考察原型原型鏈
function Mammal(name) {
    this.name = name;
    this.offspring = [];
}
Mammal.prototype.haveABaby = function () {
    var newBaby = new Mammal("Baby " + this.name);
    this.offspring.push(newBaby);
    return newBaby;
}

Mammal.prototype.toString = function () {
    return '[Mammal "' + this.name + '"]';
};     // 到目前爲止,這  是一個Mammal對象的實現。

 

// 將Employee的原型指向Person的一個實例

// 由於Person的實例能夠調用Person原型中的方法, 因此Employee的實例也能夠調用Person原型中的全部屬性。
Cat.prototype = new Mammal();

//修正constructor
Cat.prototype.constructor = Cat;
function Cat(name) {
    this.name = name;
}

Cat.prototype.toString = function () {
    return '[Cat "' + this.name + '"]';
}  // 到目前爲止,這是Mammal的一個子類Cat。


var someAnimal = new Mammal('Mr. Biggles');
var myPet = new Cat('Felix');
alert(someAnimal);   
alert(myPet);        

myPet.haveABaby();
alert(myPet.offspring.length);      
alert(myPet.offspring[0]);複製代碼

一些手寫代碼題

1.寫一個方法,測試數組的每一個元素是否是比2大?
function ArrayItemBig2(arr) {
   arr.every(val => {
      return val > 2;
   })
}複製代碼
獲得一個新的數組,獲得比2大的元素
function ArrayFilterBiger2(arr) {
   var newArr = [];
   arr.forEach(element => {
     if(Number(element) > 2) {
        newArr.push(element);
      }
   });
   return newArr;
}複製代碼

和對象相關的題目:

  • 生成對象的方式有幾種?
1. 字面量
var obj = {}
2. new的方式
var obj = new Object()
3. 定義原型對象
var obj = Object.create({a: 1})
//obj.__proto__ = {a: 1}複製代碼
  • 寫一個構造函數
function Person(name) {
 this.name = name;
}

Person.prototype = {
 getName: function() {
  console.log(thit.name)
 }
}複製代碼
  • 什麼是prototype?和__proto__有什麼區別
prototype是構造函數的一個屬性, 只有函數纔有prototype屬性;
__proto__是對象的一個屬性,每個Object都有__proto__屬性;
原型鏈是取決於__proto__的,而非prototype。複製代碼
  • new一個對象都發生了哪些操做?
例:
var child = new Person();
1.先建立一個空對象child = {}
2.將child.__proto__ = Person.prototype
3.將this指向改成child複製代碼
  • constructor是什麼?
constructor就是原型的構造函數
下邊能夠經過這個圖深入理解一下prototype、__proto__、 constructor、之間的關係
  • instanceof
例:
child instanceof Person
爲了檢測構造函數的prototype屬性是否出如今該對象的原型鏈中複製代碼

做用域相關的問題:

做用域分爲局部做用域和全局做用域。
相關文章
相關標籤/搜索