北京MedPeer涼經

一、請說出三種不同含義的this使用場景

  1.作為對象方法調用

    在 javascript 中,函數也是對象,因此函數可以作為一個對象的屬性,此時該函數被稱為該對象的方法,在使用這種調用方式時,this 被自然綁定到該對象

var test = {
    a:0,
    b:0
    get:function(){
        return this.a;
    }
}

  2.作為全局變數使用

    函數也可以直接被調用,此時 this 綁定到全局對象。在瀏覽器中,window 就是該全局對象。比如下面的例子:函數被調用時,this 被綁定到全局對象,接下來執行賦值語句,相當於隱式的聲明了一個全局變數。

var x = 10;
function foo() {
  console.log(this); //Window
  console.log(this.x); //10
}
foo();

  3.作為構造函數調用

   javascript 支援面向對象式編程,與主流的面向對象式程式語言不同,javascript 並沒有類(class)的概念,而是使用基於原型(prototype)的繼承方式。相應的,javascript 中的構造函數也很特殊,如果不使用 new 調用,則和普通函數一樣。作為又一項約定俗成的準則,構造函數以大寫字母開頭,提醒調用者使用正確的方式調用。如果調用正確,this 綁定到新創建的對象上。

function Foo() {
  this.x = 10;
  console.log(this); //Foo {x:10}
}
var foo = new Foo();
console.log(foo.x); //10

  4.在call、apply、bind中調用

    當一個函數被 call、apply 或者 bind 調用時,this 的值就取傳入的對象的值。

var obj = {
  x: 10,
};
function foo() {
  console.log(this); //{x: 10}
  console.log(this.x); //10
}
foo.call(obj);
foo.apply(obj);
foo.bind(obj)();

  5.作為DOM節點

    在一個 html dom 事件處理程式里,this 始終指向這個處理程式所綁定的 html dom 節點

function Listener() {
  document.getElementById('foo').addEventListener('click', this.handleClick); //這裡的 this 指向 Listener 這個對象。不是強調的是這裡的 this
}
Listener.prototype.handleClick = function(event) {
  console.log(this); //<div id="foo"></div>
};
var listener = new Listener();
document.getElementById('foo').click();

  6.箭頭函數中的this

    箭頭函數內部的 this 是詞法作用域,由上下文確定。

var obj = {
  x: 10,
  foo: function() {
    var fn = () => {
      return () => {
        return () => {
          console.log(this); //Object {x: 10}
          console.log(this.x); //10
        };
      };
    };
    fn()()();
  },
};
obj.foo();

  以上是六種this的使用環境,原文鏈接

二、編程題

  實現一個獲取當前頁面鏈接search值的函數。(如下例子中,輸入key1輸出value1)例:』//test.com/index?key1=value1&key2=value2』

  思路:需要根據key找到value,首先將?後面拼接的參數提取出來,然後將其轉為對象形式,再按鍵取值

  實現:使用DOM中location對象的屬性+substr+split方法獲取鍵值對數組,再遍曆數組將裡面的key1=value1通過等號分開,再存入對象中

var arr = window.location.search.substr(1).split("&")
var obj={}
for(var i=0;i<arr.length;i++){
	var key = arr[i].split('=')[0]
	var value = arr[i].split('=')[1]
	obj[key]=value
}
var str = prompt('請輸入鍵')
console.log(obj[str])

三、說一下你對原型鏈的理解、應用場景、以及下面程式碼報錯原因

function  Dog(name)  {  
this.name  =  name
}
Dog.bark  =  function()  {
    console.log(this.name  +  '  says  woof')
}	
let  fido  =  new  Dog('fido');
fido.bark()

  理解:在javascript裡面,每個對象都有一個prototype屬性,指向它的原型對象.這個原型對象裡面同時還有自己的原型,原型一環扣一環,直到某個對象的原型為null,這一級一級的鏈結構就是原型鏈。

  應用場景:js通過原型鏈實現函數或對象的繼承

  報錯原因:fido這個對象並沒有bark方法。程式碼中雖然給dog對象綁定了bark方法,但是綁定的是靜態方法,調用形式只能通過dog.bark去調用。Fido並沒有繼承這個方法,所以會報錯。

  Ps:如果需要fido.bark()不報錯。可以在dog函數的內部寫this.bark=function(){}或者在綁定bark方法時,使用Dog.prototype.bark=function(){}

四、什麼是事件委託、簡述它的作用和原理

  概念:事件委託就是事件目標(子元素)不處理事件,把事件委託給父元素去處理。

       作用:1、減少頁面綁定事件的數量,提高頁面的性能

                  2、可以靈活的處理子節點動態變化的場景,無論子節點增加還是減少,事件都無需重新綁定

       原理:利用事件冒泡,將事件加在父元素或者祖先元素上,觸發該事件。

 五、下面的程式碼執行結果是什麼,請說明理由

function  greet(person)  {
    if  (person  ==  {  name:  'amy'  })  {
        return  'hey  amy'
    }  else  {
        return  'hey  arnold'
    }
}
greet({  name:  'amy'  })

    執行結果在控制台列印出’hey  arnold’。greet方法傳入一個對象時,與內部的對象{  name:  ‘amy’  }進行==判斷,儘管這兩個對象的結構、鍵值相同,但是他們的地址不同,所以返回flase,執行了else語句裡面的程式碼,返回了’hey  arnold’

六、寫出下面程式碼執行後正確順序的輸出結果以及原因解析

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

  原因:首先執行fun1()函數,因為已經有全局變數a=1,所以列印出1,然後執行fun2()函數,函數內部存在變數提升,函數內部實際執行順序:

var a;
console.log(a)//undefined;
a=2;
conosle.log(a)//2

  所以列印出undefined和2,最後在執行console.log(a),這裡仍然列印出的是全局變數a的值,所以是1

七、寫出下面程式碼執行後正確順序的輸出結果以及原因解析

var a=1;
console.log(1);
setTimeout(function () {
    console.log(2);
},0);
function fun() {
    console.log(3);
    setTimeout(function () {
        console.log(4);
    },0);
    for (var i=0;i<10000;i++){
        a++;
    }
    console.log(5);
    return a;
};
console.log(6);
console.log(fun());

  正確輸出順序1 6 3 5 10001 2 4

  原因:

         按照順序執行程式碼,先列印出1,然後遇到第一個setTimeout,會將其存放到非同步隊列,等待主執行緒執行完成之後才會執行。然後列印6,再執行fun函數。Fun函數內部首先列印出3,然後遇到第二個setTimeout,也將其放入非同步隊列。,再執行for循環,for循環內部a的最終值是10001。然後列印5,再返回a,列印a的值為10001。此時主執行緒執行完畢,執行先前存放的setTimeout。又因為隊列是先進先出,所以先列印2再列印4。

八、JS的基本數據類型有哪些

  7種:Boolean Null Undefined Number String Symbol(ES6) BigInt (ES10)

九、vue的雙向數據綁定原理

  首先要對數據進行劫持監聽,vue設置了一個監聽器observer,用來監聽所有屬性。如果屬性發上變化了,就需要告訴訂閱者watcher看是否需要更新。因為訂閱者是有很多個,vue設置了一個消息訂閱器dep來專門收集這些訂閱者,然後在監聽器observer和訂閱者watcher之間進行統一管理的。接著,我們還需要有一個指令解析器compile,對每個節點元素進行掃描和解析,將相關指令對應初始化成一個訂閱者watcher,並替換模板數據或者綁定相應的函數,此時當訂閱者watcher接收到相應屬性的變化,就會執行對應的更新函數,從而更新視圖。(後面複習vue的時候我會單獨寫一篇文章解釋,這個就先放到這裡,不全面!)

 十、原生js如何實現雙向數據綁定

   這篇文章非常詳細了,傳送門

十一、如何監聽多個promise的執行狀態以及promise的應用場景

  使用promise.all()方法,promise.all(iterable) 方法返回一個 promise 實例,此實例在 iterable 參數內所有的 promise 都「完成(resolved)」或參數中不包含 promise 時回調完成(resolve);如果參數中 promise 有一個失敗(rejected),此實例回調失敗(reject),失敗原因的是第一個失敗 promise 的結果

  應用場景:js非同步編程的新方案,解決了以往的回調嵌套回調出現回調地獄的問題

十二、DOM節點的增刪改查,getElementBy和querySelector獲取DOM節點的區別

  DOM的增刪改查就不再贅述了,傳送門。詳細的說一下getElementBy和querySelector的區別

  1.W3C標準

    queryselectorall 屬於 w3c 中的 selectors api 規範 。而 getelementsby 系列則屬於 w3c 的 dom 規範 。(了解)

  2.瀏覽器兼容

    queryselectorall 已被 ie 8+、ff 3.5+、safari 3.1+、chrome 和 opera 10+ 良好支援 。getelementsby 系列,以最遲添加到規範中的 getelementsbyclassname 為例,ie 9+、ff 3 +、safari 3.1+、chrome 和 opera 9+ 都已經支援該方法了。

  3.接收參數

    querySelectorAll 方法接收的參數是一個 CSS 選擇符。而 getElementsBy 系列接收的參數只能是單一的className、tagName 和 name。程式碼如下:

var c1 = document.querySelectorAll('.b1 .c');
var c2 = document.getElementsByClassName('c');
var c3 = document.getElementsByClassName('b2')[0].getElementsByClassName('c');

    需要注意的是,queryselectorall 所接收的參數是必須嚴格符合 css 選擇符規範的。所以下面這種寫法,將會拋出異常。(css 選擇器中的元素名,類和 id 均不能以數字為開頭。)程式碼如下:

try {
  var e1 = document.getElementsByClassName('1a2b3c');
  var e2 = document.querySelectorAll('.1a2b3c');
} catch (e) {
  console.error(e.message);
}
console.log(e1 && e1[0].className);
console.log(e2 && e2[0].className);

  4.返回值(獲取的結點)

    大部分人都知道,queryselectorall 返回的是一個static node list,而 getelementsby 系列的返回的是一個Live Node List。

// Demo 1
var ul = document.querySelectorAll('ul')[0],
    lis = ul.querySelectorAll("li");
for(var i = 0; i < lis.length ; i++){
    ul.appendChild(document.createElement("li"));
}

// Demo 2
var ul = document.getElementsByTagName('ul')[0], 
    lis = ul.getElementsByTagName("li"); 
for(var i = 0; i < lis.length ; i++){
    ul.appendChild(document.createElement("li")); 
}

    因為 demo 2 中的 lis 是一個動態的 node list, 每一次調用 lis 都會重新對文檔進行查詢,導致無限循環的問題。 而 demo 1 中的 lis 是一個靜態的 node list,是一個 li 集合的快照,對文檔的任何操作都不會對其產生影響。

    至於為什麼queryselectorall拿到的是一個快照,源自W3C的規定【the nodelist object returned by the queryselectorall() method must be static ([dom], section 8)】

  ps:queryselectorall 的返回值是一個靜態的 nodelist 對象,而 getelementsby 系列的返回值實際上是一個 htmlcollection 對象 。nodelist 對象會包含文檔中的所有節點,如 element、text 和 comment 等。 htmlcollection 對象只會包含文檔中的 element 節點。

  5.性能

    因為queryselectorall返回一個靜態的nodelist(深克隆) getelementby系列返回一個動態的實時變化的nodelist(htmlcollection)(淺克隆,每次都返回一個指針) 所以queryselectorall會降低性能

十三、總結

  由於一直沒來得及複習,js還沒開始,不過老本已經不夠吃了,需要加油啊,很多基礎的東西都忘記了。還有一部分問題,我打算放到其他複習文章裡面,就不在這裡說了