前端知識總結--2 js部分

1。 JavaScript的『預解釋』與『變量提高』javascript

先看如下代碼輸出啥?css

var a= 1;
function f() {
  console.log(a);
  var a = 2;
}
f();

首先答案是:undefined;html

JavaScript在瀏覽器中運行的過程分爲兩個階段預解釋階段、執行階段;

  1. 讀取var a後,在當前做用域中查找是否有相同聲明,若是沒有就在當前做用域集合中建立一個名爲a的變量,不然忽略此聲明繼續進行解析;
  2. 接下來,V8引擎會處理a = 2的賦值操做,首先會詢問當前做用域中是否有名爲a的變量,若是有進行賦值,不然繼續向上級做用域詢問

因此該題目中:在函數fn的做用域中,首先提取變量聲明:var a;由於該做用域中有對a的賦值,因此不會繼續查找上級做用域。且在賦值前打印,因此是undefined;vue

相似的看下題:java

var a= 1;
function f() {
  console.log(a);
}
f(); // 1
var a= 1;
function f() {
  console.log(a);
  var a;
}
f(); //undefined

函數聲明與函數表達式

咱們看到,在編譯器處理階段,除了被var聲明的變量會有變量提高這一特性以外,函數也會產生這一特性,可是函數聲明與函數表達式兩種範式建立的函數卻表現出不一樣的結果.node

f();
g();
//函數聲明
function f() {
    console.log('f');
}
//函數表達式
var g = function() {
    console.log('g');
};

//fes6

//報錯:VM693:2 Uncaught TypeError: g is not a functionsegmentfault

f() 好理解屬於函數聲明提高;可是對於函數表達式g,被賦予undefined,undefeated沒法被執行而報錯。瀏覽器

衝突處理

變量之間衝突app

var a = 3;
var a = 4;
console.log(a); //4

函數衝突

f();
function f() {
    console.log('f');
}

function f () {
    console.log('g');
};
// g

3.函數與變量之間衝突

console.log(f);

function f() {
    console.log('f');
}
var f ='g';

ƒ f() {
console.log('f');
}

說明函數覆蓋了變量;

相似的let,存在暫時性死區:

 

function f() {
  console.log(a);
  let a = 2;
}
f(); 

 

報錯: //ReferenceError: a is not defined

這段代碼直接報錯顯示未定義,letconst擁有相似的特性,阻止了變量提高,當代碼執行到console.log(a)時,執行換將中a還從未被定義,所以產生了錯誤

======================

JS的執行機制:

淺析JS中的堆內存與棧內存 

淺析javascript調用棧

https://www.cxymsg.com/guide/mechanism.html#javascript%E7%9A%84%E6%89%A7%E8%A1%8C%E7%8E%AF%E5%A2%83

 

談談你對原型鏈的理解?✨

這個問題關鍵在於兩個點,一個是原型對象是什麼,另外一個是原型鏈是如何造成的

#原型對象

絕大部分的函數(少數內建函數除外)都有一個prototype屬性,這個屬性是原型對象用來建立新對象實例,而全部被建立的對象都會共享原型對象,所以這些對象即可以訪問原型對象的屬性。

例如hasOwnProperty()方法存在於Obejct原型對象中,它即可以被任何對象當作本身的方法使用.

用法:object.hasOwnProperty( propertyName )

hasOwnProperty()函數的返回值爲Boolean類型。若是對象object具備名稱爲propertyName的屬性,則返回true,不然返回false

var person = {
    name: "Messi",
    age: 29,
    profession: "football player"
  };
console.log(person.hasOwnProperty("name")); //true
console.log(person.hasOwnProperty("hasOwnProperty")); //false
console.log(Object.prototype.hasOwnProperty("hasOwnProperty")); //true

由以上代碼可知,hasOwnProperty()並不存在於person對象中,可是person依然能夠擁有此方法.

因此person對象是如何找到Object對象中的方法的呢?靠的是原型鏈。]

 

JavaScript的參數是按照什麼方式傳遞的?

基本類型傳遞方式

因爲js中存在複雜類型和基本類型,對於基本類型而言,是按值傳遞的.

var a = 1;
function test(x) {
  x = 10;
  console.log(x);
}
test(a); 
console.log(a); 

結果是: 10---1

雖然在函數testa被修改,並無有影響到 外部a的值,基本類型是按值傳遞的.

#複雜類型按引用傳遞?

咱們將外部a做爲一個對象傳入test函數.

var a = {
  a: 1,
  b: 2
};
function test(x) {
  x.a = 10;
  console.log(x);
}
test(a); 
console.log(a); 
// { a: 10, b: 2 }
// { a: 10, b: 2 }

能夠看到,在函數體內被修改的a對象也同時影響到了外部的a對象,可見覆雜類型是按引用傳遞的.

但是若是再作一個實驗:

var a = {
  a: 1,
  b: 2
};
function test(x) {
  x = 10;
  console.log(x);
}
test(a); 
console.log(a); 

結果是: 

  //10

// { a: 1, b: 2 }

外部的a並無被修改,若是是按引用傳遞的話,因爲共享同一個堆內存,a在外部也會表現爲10纔對. 此時的複雜類型同時表現出了按值傳遞按引用傳遞的特性.

#按共享傳遞

複雜類型之因此會產生這種特性,緣由就是在傳遞過程當中,對象a先產生了一個副本a,這個副本a並非深克隆獲得的副本a,副本a地址一樣指向對象a指向的堆內存.

所以在函數體中修改x=10只是修改了副本a,a對象沒有變化. 可是若是修改了x.a=10是修改了二者指向的同一堆內存,此時對象a也會受到影響.

有人講這種特性叫作傳遞引用,也有一種說法叫作按共享傳遞

 

簡述JavaScript的垃圾回收機制

引用計次

當聲明瞭一個變量並將一個引用類型值賦給該變量時,則這個值的引用次數就是1。若是同一個值又被賦給另外一個變量,則該值的引用次數加1。相反,若是包含對這個值引用的變量又取得了另一個值,則這個值的引用次數減1

聲明一個對象A,每多一個引用,A引用次數+1,每少一個引用,A的引用次數-1

缺點:相互引用的沒法消除

標記清除

當變量進入環境(例如,在函數中聲明一個變量)時,就將這個變量標記爲「進入環境」。內存不能釋放。
當變量離開環境時,則將其標記爲「離開環境」。釋放變量,回收內存。

相似於,函數執行順序中,各個變量和函數執行在函數調用棧中,此時有標記,當執行完畢以後,退出調用棧,則消除標記,因此垃圾回收機制在必定時間內回收沒有標記的變量

淺析javascript調用棧

 ==========================

瀏覽器如何解析css選擇器

 

瀏覽器會『從右往左』解析CSS選擇器。

咱們知道DOM Tree與Style Rules合成爲 Render Tree,其實是須要將Style Rules附着到DOM Tree上,所以須要根據選擇器提供的信息對DOM Tree進行遍歷,才能將樣式附着到對應的DOM元素上。

如下這段css爲例

.mod-nav h3 span {font-size: 16px;}

咱們對應的DOM Tree 以下

 

 

若從左向右的匹配,過程是:

  1. 從 .mod-nav 開始,遍歷子節點 header 和子節點 div
  2. 而後各自向子節點遍歷。在右側 div 的分支中
  3. 最後遍歷到葉子節點 a ,發現不符合規則,須要回溯到 ul 節點,再遍歷下一個 li-a,一顆DOM樹的節點動不動上千,這種效率很低。

若是從右至左的匹配:

  1. 先找到全部的最右節點 span,對於每個 span,向上尋找節點 h3
  2. 由 h3再向上尋找 class="mod-nav" 的節點
  3. 最後找到根元素 html 則結束這個分支的遍歷。

後者匹配性能更好,是由於從右向左的匹配在第一步就篩選掉了大量的不符合條件的最右節點(葉子節點);而從左向右的匹配規則的性能都浪費在了失敗的查找上面

瀏覽器重繪與重排的區別?

  • 重排: 部分渲染樹(或者整個渲染樹)須要從新分析而且節點尺寸須要從新計算,表現爲從新生成佈局,從新排列元素
  • 重繪: 因爲節點的幾何屬性發生改變或者因爲樣式發生改變,例如改變元素背景色時,屏幕上的部份內容須要更新,表現爲某些元素的外觀被改變

『重繪』不必定會出現『重排』,『重排』必然會出現『重繪』

即大小,位置等變化會帶來重排;顏色等變化會致使重繪;

如何觸發重排和重繪?

任何改變用來構建渲染樹的信息都會致使一次重排或重繪:

  • 添加、刪除、更新DOM節點
  • 經過display: none隱藏一個DOM節點-觸發重排和重繪
  • 經過visibility: hidden隱藏一個DOM節點-只觸發重繪,由於沒有幾何變化
  • 移動或者給頁面中的DOM節點添加動畫
  • 添加一個樣式表,調整樣式屬性
  • 用戶行爲,例如調整窗口大小,改變字號,或者滾動。

#如何避免重繪或者重排?

#集中改變樣式

咱們每每經過改變class的方式來集中改變樣式

 

// 判斷是不是黑色系樣式
const theme = isDark ? 'dark' : 'light'

// 根據判斷來設置不一樣的class
ele.setAttribute('className', theme)

使用DocumentFragment

咱們能夠經過createDocumentFragment建立一個遊離於DOM樹以外的節點,而後在此節點上批量操做,最後插入DOM樹中,所以只觸發一次重排

var fragment = document.createDocumentFragment();

for (let i = 0;i<10;i++){
  let node = document.createElement("p");
  node.innerHTML = i;
  fragment.appendChild(node);
}

document.body.appendChild(fragment);

DOM的事件流是什麼

事件冒泡(event bubbling),即事件開始時由最具體的元素(文檔中嵌套層次最深的那個節點)接收,而後逐級向上傳播到較爲不具體的節點。

<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<body>
<div></div>
</body>    
</html>

若是單擊了頁面中的<div>元素,那麼這個click事件沿DOM樹向上傳播,在每一級節點上都會發生,按照以下順序傳播:

  1. div
  2. body
  3. html
  4. document

#事件捕獲

事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。事件捕獲的用意在於在事件到達預約目標以前就捕獲它。

仍是以上一節的html結構爲例:

在事件捕獲過程當中,document對象首先接收到click事件,而後事件沿DOM樹依次向下,一直傳播到事件的實際目標,即<div>元素

  1. document
  2. html
  3. body
  4. div

事件流又稱爲事件傳播,DOM2級事件規定的事件流包括三個階段:事件捕獲階段(capture phase)、處於目標階段(target phase)和事件冒泡階段(bubbling phase)。

2019-06-23-03-06-09

觸發順序一般爲

  1. 進行事件捕獲,爲截獲事件提供了機會
  2. 實際的目標接收到事件
  3. 冒泡階段,能夠在這個階段對事件作出響應

什麼是事件委託

事件委託就是利用事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件.

在綁定大量事件的時候每每選擇事件委託。

<ul id="parent">
  <li class="child">one</li>
  <li class="child">two</li>
  <li class="child">three</li>
  ...
</ul>

<script type="text/javascript">
  //父元素
  var dom= document.getElementById('parent');

  //父元素綁定事件,代理子元素的點擊事件
  dom.onclick= function(event) {
    var event= event || window.event;
    var curTarget= event.target || event.srcElement;

    if (curTarget.tagName.toLowerCase() == 'li') {
      //事件處理
    }
  }
</script>

優勢:

  • 節省內存佔用,減小事件註冊
  • 新增子對象時無需再次對其綁定事件,適合動態添加元素

侷限性:

  • focus、blur 之類的事件自己沒有事件冒泡機制,因此沒法委託
  • mousemove、mouseout 這樣的事件,雖然有事件冒泡,可是隻能不斷經過位置去計算定位,對性能消耗高,不適合事件委託

vue中央事件總線eventBus的簡單理解和使用

 

實現instanceOf

// 模擬 instanceof
function instance_of(L, R) {
  //L 表示左表達式,R 表示右表達式
  var O = R.prototype; // 取 R 的顯示原型
  L = L.__proto__; // 取 L 的隱式原型
  while (true) {
    if (L === null) return false;
    if (O === L)
      // 這裏重點:當 O 嚴格等於 L 時,返回 true
      return true;
    L = L.__proto__; //L在上面已經等於了其隱式原型,即父級的顯示原型,因此這裏至關於L往上走了一級
  }
}

 

js中new一個對象的過程

function Person(name, age) { 
this.name = name; 
this.age = age; 
} 
var person = new Person("Alice", 23); 

new一個對象的四個過程:

一、建立一個空對象  var obj = new Object(); 

二、讓Person中的this指向obj,並執行Person的函數體  var result = Person.call(obj); 

三、設置原型鏈,將obj的__proto__成員指向了Person函數對象的prototype成員對象  obj.__proto__ = Person.prototype; 

四、判斷Person的返回值類型,若是是值類型,返回obj。若是是引用類型,就返回這個引用類型的對象。

if (typeof(result) == "object") 
person = result; 
else
person = obj;

 es5和es6實現類的繼承    : http://www.javashuo.com/article/p-mcbrfimx-cx.html

 


如何實現一個Event

React/Vue不一樣組件之間是怎麼通訊的?

首先看一下es6中規定的Map的用法:

1,js建立map對象

var map = new Map();

2.將鍵值對放入map對象

map.set("key",value)

map.set("key1",value1)

map.set("key2",value2)

3.根據key獲取map值

map.get(key)

4.刪除map指定對象

delete map[key]

5.循環遍歷map

map.

forEach(function(key){
  console.log("key",key)  //輸出的是map中的value值

})

------------------

Vue

  1. 父子組件用Props通訊
  2. 非父子組件用Event Bus通訊
  3. 若是項目夠複雜,可能須要Vuex等全局狀態管理庫通訊
  4. $dispatch(已經廢除)和$broadcast(已經廢除)

React

  1. 父子組件,父->子直接用Props,子->父用callback回調
  2. 非父子組件,用發佈訂閱模式的Event模塊
  3. 項目複雜的話用Redux、Mobx等全局狀態管理管庫
  4. 用新的Context Api
<!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>
</head>

<body>
    <div class="bottom">bottom</div>
</body>
<script>
    class EventEmeitter {
        constructor() {
            this._events = this._events || new Map(); // 儲存事件/回調鍵值對
            this._maxListeners = this._maxListeners || 10; // 設立監聽上限
        }
    }
    // 觸發名爲type的事件
    EventEmeitter.prototype.emit = function (type, ...args) {
        //type--arson
        //...args---low-end
        let handler;
        // 從儲存事件鍵值對的this._events中獲取對應事件回調函數
        handler = this._events.get(type);
        //console.log(handler);
        if (args.length > 0) {
            handler.apply(this, args);
        } else {
            handler.call(this);
        }
        return true;
    };

    // 監聽名爲type的事件
    EventEmeitter.prototype.addListener = function (type, fn) {
        // 將type事件以及對應的fn函數放入this._events中儲存
        if (!this._events.get(type)) {
            this._events.set(type, fn);
        }
    };
    const emitter = new EventEmeitter();

    // 監聽一個名爲arson的事件對應一個回調函數
    emitter.addListener('arson', man => {
        console.log(`expel ${man}`);
    });
    var bottom = document.querySelector('.bottom');
    bottom.addEventListener('click', function () {
        // 咱們觸發arson事件,發現回調成功執行
        emitter.emit('arson', 'low-end');
    })
</script>
</html>
相關文章
相關標籤/搜索