每日一題 (二)

本博客轉(zhuǎn)自:「作者:若愚鏈接:https://zhuanlan.zhihu.com/p/22361337來源:知乎著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

1、什么是異步?

什么樣的代碼是異步代碼?
我們先不深入異步概念,先從「表象」來看看怎么樣的代碼是異步代碼:
書寫順序與執(zhí)行順序不同的代碼,是異步代碼。(只是從表象上來說,這并不是異步的定義

console.log(1)
setTimeout(function(){
  console.log(2)
},0)
console.log(3)
Paste_Image.png

上面代碼的書寫順序是 1 -> 2 -> 3;
但是執(zhí)行順序是 1 -> 3 -> 2。
中間的 console.log(2) 就是異步執(zhí)行的。
你現(xiàn)在知道了「代碼的書寫順序和執(zhí)行順序居然可以不同!」

什么是異步?
同步:一定要等任務(wù)執(zhí)行完了,得到結(jié)果,才執(zhí)行下一個任務(wù)。

var taskSync = function(){
 return '同步任務(wù)的返回值'
}
var result = taskSync() // 那么 result 就是同步任務(wù)的結(jié)果
otherTask() // 然后執(zhí)行下一個任務(wù)

異步:不等任務(wù)執(zhí)行完,直接執(zhí)行下一個任務(wù)。

var taskAsync = function(){
  var result = setTimeout(function(){
  console.log('異步任務(wù)的結(jié)果')
 }, 3000)
  return result
}
var result = taskAsync() // result 不是異步任務(wù)的結(jié)果,而是一個 timer id
otherTask() // 立即執(zhí)行其他任務(wù),不等異步任務(wù)結(jié)束

Paste_Image.png

聰明的你可能會發(fā)現(xiàn),我們拿到的 result 不是異步執(zhí)行的結(jié)果,而是一個 timer id,那么要怎么拿到異步任務(wù)的結(jié)果呢?

用回調(diào)。

改下代碼如下:

Paste_Image.png

所以「回調(diào)」經(jīng)常用于獲取「異步任務(wù)」的結(jié)果。

什么情況下需要用到異步?
現(xiàn)在有三個函數(shù),taskA()、taskB() 和 taskC(),三個任務(wù)互不影響。taskA 和 taskC 執(zhí)行得很快,但是 taskB 執(zhí)行需要 10 秒鐘。

// 同步的寫法
function taskB(){
 var response = $.ajax({
  url:"/data.json",
  async: false // 注意這里 async 為 false,表示是同步
 })
 return response // 十秒鐘后,返回 response
}
taskA()
taskB()
taskC()

taskC 一定要等 taskB 執(zhí)行完了才能執(zhí)行,這就是同步。
執(zhí)行順序為:

A -> B -> AJAX 請求 -> C ---------------------------

現(xiàn)在換成異步:

// 異步的寫法
function taskB(){
 var result = $.ajax({

  url:"/data.json",

async: true // 異步
  })
  return result // 一定要注意,現(xiàn)在的 result 不是上面的 response
}
taskA()
taskB()
taskC()

這樣寫之后,執(zhí)行順序就是

A -> B -> C ---------------------------------------
    -> AJAX 請求 --------------------------------

就是說 AJAX 請求和任務(wù)C 同時執(zhí)行。但是請注意執(zhí)行的主體。AJAX 請求是由瀏覽器的網(wǎng)絡(luò)請求模塊執(zhí)行的,taskC 是由 JS 引擎執(zhí)行的。

綜上,如果幾個任務(wù)互相獨立,其中一個執(zhí)行時間較長,那么一般就用異步地方式做這件事。

JS 引擎不能同時做兩件事

有些人說異步是同時做兩件事,但其實 JS 引擎不會這樣。以 setTimeout 為例,setTimeout 里面的代碼一定會在當(dāng)前環(huán)境中的任務(wù)執(zhí)行完了「之后」才執(zhí)行。異步意味著不等待任務(wù)結(jié)束,并沒有強(qiáng)制要求兩個任務(wù)「同時」進(jìn)行。但是 AJAX 請求是可以與 JS 代碼同時進(jìn)行的,因為這個請求不是由 JS 引擎負(fù)責(zé),而是由瀏覽器網(wǎng)絡(luò)模塊負(fù)責(zé)。

以上,就是異步的簡介。

2、Callback(回調(diào))是什么?

Callback 是什么?
 callback 是一種特殊的函數(shù),這個函數(shù)被作為參數(shù)傳給另一個參數(shù)去調(diào)用。這樣的函數(shù)就是回調(diào)函數(shù)。
 callback 拆開,就是 call back,在英語里面就是「回?fù)茈娫挕沟囊馑?。那我們就用打電話為例子來說明一下 callback:

  • 1、「我打電話給某某」(I call somebody),那么「打電話」的人就是「我」。
  • 2、「我」在電話里說:你辦完某事后,回?fù)茈娫捊o「我」。
  • 3、某某做完事后,就會「回?fù)茈娫?/strong>給我」(calls back to me),那么「打電話」的人就是「某某」。

用編程來解釋的話,是這樣的:

  • 1、「我調(diào)用一個函數(shù) f」(I call a function),那么「調(diào)用函數(shù)」的人是「我」。代碼是 f(c)。
  • 2、「我」讓這個函數(shù) f 在執(zhí)行完后,調(diào)用我傳給它的另一個函數(shù) c。
  • 3、f 執(zhí)行完的時候,就會「調(diào)用 c」,也叫做「回調(diào) c」(call c back),調(diào)用 c 的人是 f。

好了,解釋完了:callback 就是(傳給另一個函數(shù)調(diào)用的)函數(shù)。把括號里面的內(nèi)容去掉,簡化成:callback 就是一種函數(shù)。

Callback 很常見

  $button.on('click', function(){})

click 后面的 function 就是一個回調(diào),因為「我」沒有調(diào)用過這個函數(shù),是 jQuery 在用戶點擊 button 時調(diào)用的。

  div.addEventListener('click', function(){})

click 后面的 function 也是一個回調(diào),因為「我」沒有調(diào)用過這個函數(shù),是瀏覽器在用戶點擊 button 時調(diào)用的。
一般來說,只要參數(shù)是一個函數(shù),那么這個函數(shù)就是回調(diào)。

Callback 有點反直覺
  很多初學(xué)者不明白 callback 的用法,因為 callback 有一點「反直覺」。比如說我們用代碼做一件事情,分為兩步:step1( ) 和 step2( )。符合人類直覺的代碼是:

step1()
step2()

callback 的寫法卻是這樣的:

step1(step2)

為什么要這樣寫?或者說在什么情況下應(yīng)該用這個「反直覺」的寫法?
一般(注意我說了一般),在 step1 是一個異步任務(wù)的時候,就會使用 callback。

3、什么是 HTML 5?

其實這個題目的意圖是想知道你「會不會搜索」。人人都在說 HTML 5,你卻不知道 HTML 5 是什么。為什么會這樣?因為你不知道哪里的前端知識是靠譜的,你只能聽別人說。
  今天介紹一個靠譜的前端知識來源——MDN(Mozilla Developer Network)。
谷歌搜索「HTML 5 MDN」即可搜到權(quán)威介紹。

HTML 5 概覽

HTML 5 是新版 Web 技術(shù)的集合,包含以下八個部分:

  1. 語義升級
  • HTML 5 加了很多新的標(biāo)簽,使 HTML 更富有語義。
  • 升級了 iframe 標(biāo)簽,使其更安全。
  • 新增 MathML,是數(shù)學(xué)公式可以在 Web 中展現(xiàn)。
  1. 服務(wù)器增強(qiáng)新增 Web Sockets
  • 新增 EventSource API
  • 新增 WebRTC
  1. 離線儲存
  • 新增 AppCache
  • online 與 offline 事件
  • localStorage 和 sessionStorage
  • IndexedDB
  • File API
  1. 多媒體
  • Web 原生支持音視頻播放
  • Camera API 可控制攝像頭
  1. 圖像繪制
  • Canvas 可繪制圖像和文本
  • WebGL 可渲染 3D 影像
  • SVG 可制作矢量圖形
  1. 更多集成
  • Web Workers 能夠把 JavaScript 計算委托給后臺線程
  • XMLHttpRequest 升級
  • History API 允許對瀏覽器歷史記錄進(jìn)行操作
  • 新增 conentEditable 屬性
  • 拖放 API、全屏 API、指針鎖定 API
  • 可以使用 navigator.registerProtocolHandler() 方法把 web 應(yīng)用程序注冊成一個協(xié)議處理程序。
  • requestAnimationFrame 允許控制動畫渲染以獲得更優(yōu)性能。
  1. 設(shè)備相關(guān) API
  • 你現(xiàn)在可以用 JS 來處理攝像頭、觸控屏幕、地理位置等設(shè)備相關(guān)功能了。
  1. 樣式
  • CSS 全面升級。

H5 是什么?

  • H5 就是微信里面長得像 PPT、可以一頁一頁滑動的網(wǎng)頁。
    以上,就是 MDN 用法的介紹——獲取靠譜知識。
4、你是如何做性能優(yōu)化的?

為什么要做性能優(yōu)化?
有些人看到這個題目,一上來就說「減少請求,添加緩存」之類的。不是說你錯了,而是說你回答問題的時候沒有思路。

首先你要明白一點:做任何事情都是有「目的」的。

吃飯喝水是為了生存,那么做性能優(yōu)化的「目的」是什么?

想過這個問題么?如果沒想過,今后就要刻意問問自己了。

優(yōu)化的目的可以是:

1、 增強(qiáng)用戶體驗。但是這樣說很虛,具體來說可以是:
 1.1. 加快頁面展示速度(慢)
 1.2. 加快頁面運行速度(卡)
 
2、節(jié)約服務(wù)器帶寬流量

3、減少服務(wù)器壓力

什么時候做性能優(yōu)化?
你有目的了,不代表你馬上就要去采取行動。

首先,你應(yīng)該完成了網(wǎng)頁的基本功能后再優(yōu)化。如果你在前期就花時間優(yōu)化,那么后期有可能沒時間做其他功能。

其次,在沒有找到性能瓶頸之前,不要優(yōu)化!

一個網(wǎng)頁的性能到底跟哪幾方面有關(guān)?你優(yōu)化的地方屬于哪一方面?這是需要首先搞清楚的。

一個網(wǎng)頁的大概流程包括:

  • 1、DNS 查詢
  • 2、發(fā)送請求
  • 3、等待服務(wù)器響應(yīng)
  • 4、下載服務(wù)器響應(yīng)內(nèi)容
  • 5、解析 HTML、CSS、JS 等
  • 6、渲染 HTML、CSS、JS 和圖片等
  • 7、響應(yīng)用戶的點擊事件等

如果你的性能瓶頸在「等待服務(wù)器響應(yīng)」這一步,那么你怎么優(yōu)化 JS、CSS 都沒用。
所以再說一遍:在沒有找到性能瓶頸之前,不要優(yōu)化!
怎么優(yōu)化?
等你找到了瓶頸所在,就可以「對癥下藥」了。

  • 1、DNS 查詢——減少網(wǎng)頁所用的域名個數(shù),可可以減少 DNS 查詢的時間
  • 2、發(fā)送請求——添加緩存、合并文件,都可以減少請求數(shù)量
  • 3、等待服務(wù)器響應(yīng)——這一步的優(yōu)化只能是在 MySQL 和后臺方面做考慮了
  • 4、下載服務(wù)器響應(yīng)內(nèi)容——添加 Etag、Expires 響應(yīng)頭,得到 304 響應(yīng),可以降低下載量
  • 5、解析 HTML、CSS、JS 等——去掉無用的 HTML、CSS 和 JS 即可減少解析時間
  • 6、渲染 HTML、CSS、JS 和圖片等——避免使用低效的 HTML、CSS 和 JS 即可
  • 7、響應(yīng)用戶的點擊事件等——盡量不在前端做復(fù)雜的運算等……

整體思路

  • 1、為什么要做?
  • 2、什么時候做?
  • 3、怎么做?
5、Promise 是什么?

window.Promise 已經(jīng)是 JS 的一個內(nèi)置對象了。

  1. Promise 有規(guī)格文檔嗎?
  2. 你一般如何使用 Promise。

目前的 Promise 都遵循 Promises/A+ 規(guī)范。
英文規(guī)范:https://promisesaplus.com/**
中文翻譯:圖靈社區(qū) : 閱讀 : 【翻譯】Promises/A+規(guī)范
看完規(guī)范你可以了解 Promise 的全貌,本文主要講講 Promise 的用途。

Promise 之前的時代——回調(diào)時代

假設(shè)我們用 getUser 來說去用戶數(shù)據(jù),它接收兩個回調(diào) sucessCallback 和 errorCallback:

  function getUser(successCallback, errorCallback){ 
    $.ajax({ 
      url:'/user',
      success: function(response){
         successCallback(response) 
      },
      error: function(xhr){ 
        errorCallback(xhr) 
      }
    })
   }

看起來還不算復(fù)雜。

如果我們獲取用戶數(shù)據(jù)之后還要獲取分組數(shù)組、分組詳情等,代碼就會是這樣:

  getUser(function(response){
    getGroup(response.id, function(group){
      getDetails(groupd.id, function(details){
        console.log(details)
      },function(){
        alert('獲取分組詳情失敗')
      })
    }, function(){
      alert('獲取分組失敗')
    })
    }, function(){
      alert('獲取用戶信息失敗')
  })

三層回調(diào),如果再多一點嵌套,就是「回調(diào)地獄」了。

Promise 來了

Promise 的思路呢,就是 getUser 返回一個對象,你往這個對象上掛回調(diào):

var promise = getUser()
promise.then(successCallback, errorCallback)

當(dāng)用戶信息加載完畢,successCallback 和 errorCallback 之一就會被執(zhí)行。
把上面兩句話合并成一句就是這樣的:

getUser().then(successCallback, errorCallback)

如果你想在用戶信息獲取結(jié)束后做更多事,可以繼續(xù) .then:

getUser().then(success1).then(success2).then(success3)

請求成功后,會依次執(zhí)行 success1、success2 和 success3。
如果要獲取分組信息:

 getUser().then(function(response){
  getGroup(response.id).then(function(group){
    getDetails(group.id).then(function(){ 

    },error3)
  },error2)
 }, error1)

這種 Promise 寫法跟前面的回調(diào)看起來其實變化不大。真的,Promise 并不能消滅回調(diào)地獄,但是它可以使回調(diào)變得可控。你對比下面兩個寫法就知道了。

getGroup(response.id, success2, error2)
getGroup(response.id).then(success2, error2)

用 Promise 之前,你不能確定 success2 是第幾個參數(shù);
用 Promise 之后,所有的回調(diào)都是

.then(success, error) 

這樣的形式。
以上是 Promise 的簡介,想完整了解 Promise,請參考下面的自學(xué)鏈接。
Promise對象 -- JavaScript 標(biāo)準(zhǔn)參考教程(alpha)

6、Babel 是什么?

Babel 作為一個工具,其實只要跟著它官網(wǎng)文檔過一遍就知道它怎么用了。
一句話,Babel 能把你寫的 JS 變成其他版本的 JS。
這樣一來,你就可以寫 IE 不支持的 JS 語法了,因為最終會被翻譯成 IE 支持的語法。
比如你寫 ES6

// src/index.js
[1,2,3].map(n => n + 1);

Babel 可以把它翻譯成 ES5

// lib/index.js
[1,2,3].map(function(n) {
  return n + 1;
});

如何安裝
進(jìn)入你的項目目錄,用這句話安裝 Babel:

npm install --save-dev babel-cli babel-preset-latest

然后新建一個文件,命名為 .babelrc,文件內(nèi)容如下:

{
  "presets": ["es2015"]
}

然后在 package.json 里面添加一個 script:

"scripts": {
"build": "babel src -d lib"
},

然后運行命令

  npm run build

那么 src/index.js 就會被翻譯成 lib/index.js。

如何實時翻譯
怎么能做到我每次改 src/index.js ,lib/index.js 就自動變化呢?
只需要在上面的 script 里面加一個 --watch 選項即可:

這是 package.json 文件
{
  ...
  "scripts": {
    "build": "babel --watch src -d lib"
  },
  ...
}
7、什么是響應(yīng)式頁面?

前幾年火的一個概念:響應(yīng)式頁面。

  1. 什么樣的頁面是響應(yīng)式頁面?
  2. 響應(yīng)式頁面用到哪些技術(shù)?
  3. 響應(yīng)式頁面和自適應(yīng)頁面有什么區(qū)別?

什么是響應(yīng)式頁面?
首先你要理解什么是「響應(yīng)」。

  • 悟空拿著寶瓶,對金角大王叫了「金角大王」,金角大王應(yīng)了一聲。這就是「響應(yīng)」。
  • 武昌起義成功之后,各地紛紛也開始革命。這也是「響應(yīng)」。

「響應(yīng)」就是「你動,我也動」。
「響應(yīng)式頁面」就是「隨著設(shè)備屬性(如寬高)的變化,網(wǎng)頁也隨著變化?!?/p>

Paste_Image.png

如上圖,左邊是 PC 上頁面的樣子,右邊是手機(jī)上頁面的樣子。

響應(yīng)式頁面用到哪些技術(shù)?

  • 多使用 max-width、min-width,不寫死寬度
  • 使用 media 查詢來響應(yīng)不同分辨率
  • 使用動態(tài) REM 方案保證手機(jī)端的顯示效果

響應(yīng)式頁面和自適應(yīng)頁面的區(qū)別

自適應(yīng)頁面(流體布局、fluid layout)指的是頁面寬度不固定。跟響應(yīng)式頁面沒有什么關(guān)系。
自適應(yīng)頁面強(qiáng)調(diào)「不寫死寬度」;響應(yīng)式頁面強(qiáng)調(diào)「響應(yīng)」。
自適應(yīng)頁面可以是響應(yīng)式的,也可以不是響應(yīng)式的。
響應(yīng)式頁面可以是自適應(yīng)的,也可以是不自適應(yīng)的(也就是定寬的)。

8、簡述瀏覽器緩存是如何控制的

1. 情形1,無緩存
瀏覽器向服務(wù)器請求資源 a.jpg,服務(wù)器找到對應(yīng)資源把內(nèi)容返回給瀏覽器。當(dāng)瀏覽器再次向服務(wù)器請求資源a.jpg時,服務(wù)器重新發(fā)送完整的數(shù)據(jù)文件給瀏覽器。

  • 優(yōu)點:簡單,啥都不用做
  • 缺點:每次請求都查找并返回原始文件,浪費帶寬

2. 情形2,有緩存無更新
瀏覽器第一次請求a.jpg 時服務(wù)器會發(fā)送完整的文件,瀏覽器可以把這個文件存到本地(緩存),下次再需要這個文件時直接從本地獲取就行了,這樣就能省下帶寬了。

  • 優(yōu)點: 省帶寬
  • 缺點: 如果服務(wù)器上a.jpg的文件內(nèi)容變了,瀏覽器每次都從緩存讀取無法獲取最新文件

3. 情形3, 緩存+更新機(jī)制
瀏覽器第一次請求a.jpg 時服務(wù)器會發(fā)送完整的文件,服務(wù)器在發(fā)送文件的時候還附帶發(fā)送一些額外信息——過期時間,如 Expires: Mon,10 Dec 1990 02:25:22GMT。瀏覽器可以把這個文件和額外信息存到本地。當(dāng)再次需要a.jpg的時候瀏覽器用當(dāng)前瀏覽器時間和Expires做個比較,如果當(dāng)前時間在過期時間以內(nèi),就直接使用緩存文件(狀態(tài)為304);如果在過期時間以外就重新向服務(wù)器發(fā)送請求要資源(200)。 服務(wù)器在每次給資源的時候都會發(fā)送新的過期時間

  • 優(yōu)點:緩存可控制
  • 缺點:控制的功能太單一;這種格式的時間很容易寫錯

4. 情形4, 緩存+更新機(jī)制升級版
比如:瀏覽器第一次請求a.jpg 時,服務(wù)器會發(fā)送完整的文件并附帶額外信息

Cach-Control: max-age=300;

瀏覽器把文件和附帶信息保存起來。當(dāng)再次需要a.jpg 時,如果是在300秒以內(nèi)發(fā)起的請求則直接使用緩存(304),否則重新發(fā)起網(wǎng)絡(luò)請求(200)。下面是Cache-Control常見的幾個值:

  • Public表示響應(yīng)可被任何中間節(jié)點緩存,如 Browser <-- proxy1 <-- proxy2 <-- Server,中間的proxy可以緩存資源,比如下次再請求同一資源proxy1直接把自己緩存的東西給 Browser 而不再向proxy2要。
  • Private表示中間節(jié)點不允許緩存,對于Browser <-- proxy1 <-- proxy2 <-- Server,proxy 會老老實實把Server 返回的數(shù)據(jù)發(fā)送給proxy1,自己不緩存任何數(shù)據(jù)。當(dāng)下次Browser再次請求時proxy會做好請求轉(zhuǎn)發(fā)而不是自作主張給自己緩存的數(shù)據(jù)。
  • no-cache表示不使用 Cache-Control的緩存控制方式做前置驗證,而是使用 Etag 或者Last-Modified字段來控制緩存
  • no-store ,真正的不緩存任何東西。瀏覽器會直接向服務(wù)器請求原始文件,并且請求中不附帶 Etag 參數(shù)(服務(wù)器認(rèn)為是新請求)。
  • max-age,表示當(dāng)前資源的有效時間,單位為秒。

優(yōu)點:緩存控制功能更強(qiáng)大

缺點:假如瀏覽器再次請求資源a.jpg的時間間隔超過了max-age,這時候向服務(wù)器發(fā)送請求服務(wù)器應(yīng)該會重新返回a.jpg的完整文件。但如果 a.jpg 在服務(wù)器上未做任何修改,發(fā)送a.jpg的完整文件就太浪費帶寬了,其實只要發(fā)送一個「a.jpg未被更改」的短消息標(biāo)示就好了。

5. 情形5, 緩存+更新機(jī)制終極版
比如:瀏覽器第一次請求a.jpg 時,服務(wù)器會發(fā)送完整的文件并附帶額外信息,其中Etag 是 對a.jpg文件的編碼,如果a.jpg在服務(wù)端未被修改,這個值就不會變

Cache-Control: max-age=300;
ETag:W/"e-cbxLFQW5zapn79tQwb/g6Q"

瀏覽器把a(bǔ).jpg和額外信息保存到本地。假如瀏覽器在300秒以內(nèi)再次需要獲取a.jpg時,瀏覽器直接從緩存讀取a.jpg(304)。假如瀏覽器在300秒之后再次需要獲取a.jpg時,瀏覽器發(fā)現(xiàn)該緩存的文件已經(jīng)不新鮮了,于是就向服務(wù)器發(fā)送請求 重新獲取a.jpg, 在發(fā)送請求的時候附帶剛剛保存的a.jpg的ETag ( If-None-Match:W/"e-cbxLFQW5zapn79tQwb/g6Q")。 服務(wù)器在接收到請求后拿瀏覽器請求的 Etag 和當(dāng)前文件重新計算后端 Etag 做個比較,如果二者相等表示文件在未修改則發(fā)送個短消息(響應(yīng)頭,不包含圖片內(nèi)容),如果二者不等則發(fā)送新文件和新的 ETag,瀏覽器獲取新文件并更新該文件的 Etag。

與 ETag 類似功能的是Last-Modified/If-Modified-Since。當(dāng)資源過期時(max-age超時),發(fā)現(xiàn)資源具有Last-Modified聲明,則再次向web服務(wù)器請求時帶上頭 If-Modified-Since,表示請求時間。web服務(wù)器收到請求后發(fā)現(xiàn)有頭If-Modified-Since 則與被請求資源的最后修改時間進(jìn)行比對。若最后修改時間較新,說明資源又被改動過,則響應(yīng)整片資源內(nèi)容(200);若最后修改時間較舊,說明資源無新修改,則響應(yīng)HTTP 304 ,告知瀏覽器繼續(xù)使用所保存的cache。

9、什么是JS原型鏈?

我們知道 JS 有對象,比如:

var obj = { name: 'obj' }

我們可以對 obj 進(jìn)行一些操作,包括

  • 「讀」屬性
  • 「新增」屬性
  • 「更新」屬性
  • 「刪除」屬性

下面我們主要來看一下「讀」和「新增」屬性。
為什么有 valueOf / toString 屬性呢?
在我們沒有對 obj 進(jìn)行任何其他操作之前,發(fā)現(xiàn) obj 已經(jīng)有幾個屬性(方法)了:

Paste_Image.png

那么問題來了:valueOf / toString / constructor 是怎么來?我們并沒有給 obj.valueOf 賦值呀。
要搞清楚 valueOf / toString / constructor 是怎么來的,就要用到 console.dir 了。

Paste_Image.png

上面這個圖有點難懂,我手畫一個示意圖:

Paste_Image.png

我們發(fā)現(xiàn) console.dir(obj) 打出來的結(jié)果是:

  1. obj 本身有一個屬性 name(這是我們給它加的)

  2. obj 還有一個屬性叫做 proto(它是一個對象)

  3. obj.proto 有很多屬性,包括 valueOf、toString、constructor 等

  4. obj.proto 其實也有一個叫做 proto 的屬性(console.log 沒有顯示),值為 null

現(xiàn)在回到我們的問題:obj 為什么會擁有 valueOf / toString / constructor 這幾個屬性?

答案:
這跟 proto 有關(guān)。當(dāng)我們「讀取」 obj.toString 時,JS 引擎會做下面的事情:

  1. 看看 obj 對象本身有沒有 toString 屬性。沒有就走到下一步。
  2. 看看 obj.proto 對象有沒有 toString 屬性,發(fā)現(xiàn) obj.proto 有 toString 屬性,于是找到了
    所以 obj.toString 實際上就是第 2 步中找到的 obj.proto.toString。
    可以想象,
  3. 如果 obj.proto 沒有,那么瀏覽器會繼續(xù)查看 obj.proto.proto
  4. 如果 obj.proto.proto 也沒有,那么瀏覽器會繼續(xù)查看 obj.proto.proto.proto__
  5. 直到找到 toString 或者 proto 為 null。

上面的過程,就是「讀」屬性的「搜索過程」。而這個「搜索過程」,是連著由 proto 組成的鏈子一直走的。
這個鏈子,就叫做「原型鏈」。

共享原型鏈
現(xiàn)在我們有另一個對象

var obj2 = { name: 'obj2' }

Paste_Image.png

那么 obj.toString 和 obj2.toString 其實是同一個東西,也就是 obj2.proto.toString。

這有什么意義呢?

如果我們改寫 obj2.proto.toString,那么 obj.toString 其實也會變!

這樣 obj 和 obj2 就是具有某些相同行為的對象,這就是意義所在。

差異化
如果我們想讓 obj.toString 和 obj2.toString 的行為不同怎么做呢?直接賦值就好了:

obj.toString = function(){ return '新的 toString 方法' }

Paste_Image.png

總結(jié):
「讀」屬性時會沿著原型鏈搜索。
「新增」屬性時不會去看原型鏈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容