let
和const
是ES6
新增的命令,用於聲明變量,這兩個命令跟ES5
的var
有許多不一樣,而且let
和const
也有一些細微的不一樣javascript
var
和let
/const
的區別css
const
命令兩個注意點:html
ES5只有全局做用域和函數做用域,沒有塊級做用域。前端
這帶來不少不合理的場景:vue
var tmp = new Date();
function f() {
console.log(tmp); // 想打印外層的時間做用域
if (false) {
var tmp = 'hello world'; // 這裏聲明的做用域爲整個函數
}
}
f(); // undefined
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]); // i應該爲這次for循環使用的變量
}
console.log(i); // 5 全局範圍均可以讀到
複製代碼
function f1() {
let n = 5;
if (true) {
let n = 10;
console.log(n); // 10 內層的n
}
console.log(n); // 5 當前層的n
}
複製代碼
{{{{
{let insane = 'Hello World'}
console.log(insane); // 報錯 讀不到子做用域的變量
}}}};
複製代碼
{
let a = ...;
...
}
{
let a = ...;
...
}
複製代碼
以上形式,能夠用於測試一些想法,不用擔憂變量重名,也不用擔憂外界干擾java
塊級做用域聲明函數:node
在塊級做用域聲明函數,由於瀏覽器的要兼容老代碼,會產生一些問題!react
在塊級做用域聲明函數,最好使用匿名函數的形式。css3
if(true){
let a = function () {}; // 做用域爲塊級 令聲明的函數做用域範圍更清晰
}
複製代碼
ES6 的塊級做用域容許聲明函數的規則,只在使用大括號的狀況下成立,若是沒有使用大括號,就會報錯。web
// 報錯
'use strict';
if (true)
function f() {} // 咱們須要給if加個{}
複製代碼
變量提高的現象:在同一做用域下,變量能夠在聲明以前使用,值爲undefined
ES5 時使用var
聲明變量,常常會出現變量提高的現象。
// var 的狀況
console.log(foo); // 輸出undefined
var foo = 2;
// let 的狀況
console.log(bar); // 報錯ReferenceError
let bar = 2;
複製代碼
只要一進入當前做用域,所要使用的變量就已經存在了,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量
var tmp = 123; // 聲明
if (true) {
tmp = 'abc'; // 報錯 由於本區域有tmp聲明變量
let tmp; // 綁定if這個塊級的做用域 不能出現tmp變量
}
複製代碼
暫時性死區和不能變量提高的意義在於:
在測試時出現這種狀況:
var a= '聲明';const a '不報錯'
,這種狀況是由於babel
在轉化的時候,作了一些處理,在瀏覽器的控制檯中測試,就成功報錯
let
、const
不容許在相同做用域內,重複聲明同一個變量
function func(arg) {
let arg; // 報錯
}
function func(arg) {
{
let arg; // 不報錯
}
}
複製代碼
var a = 1;
// 若是在 Node環境,能夠寫成 global.a
// 或者採用通用方法,寫成 this.a
window.a // 1
let b = 1;
window.b // undefined
複製代碼
let p; var p1; // 不報錯
const p3 = '立刻賦值'
const p3; // 報錯 沒有賦值
複製代碼
簡單類型:不能改動
const p = '不能改變';
p = '報錯'
複製代碼
複雜類型:變量指針不能變
考慮以下狀況:
const p = ['不能改動']
const p2 = {
name: 'OBKoro1'
}
p[0] = '不報錯'
p2.name = '不報錯'
p = ['報錯']
p2 = {
name: '報錯'
}
複製代碼
const所說的一旦聲明值就不能改變,實際上指的是:變量指向的那個內存地址所保存的數據不得改動
push
、shift
、splice
等方法也是容許的,你就是把值一個一個全都刪光了都不會報錯。複雜類型還有函數,正則等,這點也要注意一下。
優勢:
缺點:
優勢:
缺點:
總結:若是動畫只是簡單的狀態切換,不須要中間過程控制,在這種狀況下,css動畫是優選方案。它可讓你將動畫邏輯放在樣式文件裏面,而不會讓你的頁面充斥 Javascript 庫。然而若是你在設計很複雜的富客戶端界面或者在開發一個有着複雜UI狀態的 APP。那麼你應該使用js動畫,這樣你的動畫能夠保持高效,而且你的工做流也更可控。因此,在實現一些小的交互動效的時候,就多考慮考慮CSS動畫。對於一些複雜控制的動畫,使用javascript比較可靠。
共同點:都是保存在瀏覽器端,且同源的
區別:
跨域概念解釋:當前發起請求的域與該請求指向的資源所在的域不同。這裏的域指的是這樣的一個概念:咱們認爲若協議 + 域名 + 端口號均相同,那麼就是同域。 以下表
利用script
標籤沒有跨域限制的漏洞,網頁能夠獲得從其餘來源動態產生的 JSON 數據。JSONP請求必定須要對方的服務器作支持才能夠
JSONP和AJAX相同,都是客戶端向服務器端發送請求,從服務器端獲取數據的方式。但AJAX屬於同源策略,JSONP屬於非同源策略(跨域請求)
優勢:
缺點:只支持get請求具備侷限性,,不安全可能會遭受XSS攻擊
// index.html
function jsonp({ url, params, callback }) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
window[callback] = function(data) {
resolve(data)
document.body.removeChild(script)
}
params = { ...params, callback } // wd=b&callback=show
let arrs = []
for (let key in params) {
arrs.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arrs.join('&')}`
document.body.appendChild(script)
})
}
jsonp({
url: 'http://localhost:3000/say',
params: { wd: 'Iloveyou' },
callback: 'show'
}).then(data => {
console.log(data)
})
複製代碼
上面這段代碼至關於向http://localhost:3000/say?wd=Iloveyou&callback=show
這個地址請求數據,而後後臺返回show('我不愛你')
,最後會運行show()
這個函數,打印出'我不愛你'
// server.js
let express = require('express')
let app = express()
app.get('/say', function(req, res) {
let { wd, callback } = req.query
console.log(wd) // Iloveyou
console.log(callback) // show
res.end(`${callback}('我不愛你')`)
})
app.listen(3000)
複製代碼
互聯網有一項著名的8秒原則。用戶在訪問Web網頁時,若是時間超過8秒就會感到不耐煩,若是加載須要太長時間,他們就會放棄訪問。
主要包括這些方面:html壓縮、css 壓縮、js的壓縮和混亂和文件合併。
資源壓縮能夠從文件中去掉多餘的字符,好比回車、空格。你在編輯器中寫代碼的時候,會使用縮進和註釋,這些方法無疑會讓你的代碼簡潔並且易讀,但它們也會在文檔中添加多餘的字節。
一、異步加載的方式 異步加載的三種方式——async
和defer
、動態腳本建立
① async方式
<script type="text/javascript" src="xxx.js" async="async"></script>
複製代碼
② defer方式
<script type="text/javascript" src="xxx.js" defer></script>
複製代碼
③動態建立script標籤
在還沒定義defer和async前,異步加載的方式是動態建立script,經過window.onload方法確保頁面加載完畢再將script標籤插入到DOM中,具體代碼以下:
function addScriptTag(src){
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function(){
addScriptTag("js/index.js");
}
複製代碼
二、異步加載的區別
其中藍色線表明網絡讀取,紅色線表明執行時間,這倆都是針對腳本的;綠色線表明 HTML 解析。
經過將靜態資源(例如javascript,css,圖片等等)緩存到離用戶很近的相同網絡運營商的CDN節點上,不但能提高用戶的訪問速度,還能節省服務器的帶寬消耗,下降負載。
資源預加載是另外一個性能優化技術,咱們可使用該技術來預先告知瀏覽器某些資源可能在未來會被使用到。
經過 DNS 預解析來告訴瀏覽器將來咱們可能從某個特定的 URL 獲取資源,當瀏覽器真正使用到該域中的某個資源時就能夠儘快地完成 DNS 解析。例如,咱們未來可從 example.com 獲取圖片或音頻資源,那麼能夠在文檔頂部的 標籤中加入如下內容:
<link rel="dns-prefetch" href="//example.com">
複製代碼
當咱們從該 URL 請求一個資源時,就再也不須要等待 DNS 的解析過程。該技術對使用第三方資源特別有用。經過簡單的一行代碼就能夠告知那些兼容的瀏覽器進行 DNS 預解析,這意味着當瀏覽器真正請求該域中的某個資源時,DNS 的解析就已經完成了,從而節省了寶貴的時間。 另外須要注意的是,瀏覽器會對a標籤的href自動啓用DNS Prefetching,因此a標籤裏包含的域名不須要在head中手動設置link。可是在HTTPS下不起做用,須要meta來強制開啓功能。這個限制的緣由是防止竊聽者根據DNS Prefetching推斷顯示在HTTPS頁面中超連接的主機名。下面這句話做用是強制打開a標籤域名解析
<meta http-equiv="x-dns-prefetch-control" content="on">
複製代碼
內存泄漏的常見場景
文章前言部分就有說到,JS 開發者喜歡用對象的鍵值對來緩存函數的計算結果,可是緩存中存儲的鍵越多,長期存活的對象也就越多,這將致使垃圾回收在進行掃描和整理時,對這些對象作無用功。
var leakArray = [];
exports.leak = function () {
leakArray.push("leak" + Math.random());
}
複製代碼
以上代碼,模塊在編譯執行後造成的做用域由於模塊緩存的緣由,不被釋放,每次調用 leak 方法,都會致使局部變量 leakArray 不停增長且不被釋放。
閉包能夠維持函數內部變量駐留內存,使其得不到釋放。
聲明過多的全局變量,會致使變量常駐內存,要直到進程結束纔可以釋放內存。
//dom still exist
function click(){
// 可是 button 變量的引用仍然在內存當中。
const button = document.getElementById('button');
button.click();
}
複製代碼
// vue 的 mounted 或 react 的 componentDidMount
componentDidMount() {
setInterval(function () {
// ...do something
}, 1000)
}
複製代碼
vue 或 react 的頁面生命週期初始化時,定義了定時器,可是在離開頁面後,未清除定時器,就會致使內存泄漏。
componentDidMount() {
window.addEventListener("scroll", function () {
// do something...
});
}
複製代碼
在頁面生命週期初始化時,綁定了事件監聽器,但在離開頁面後,未清除事件監聽器,一樣也會致使內存泄漏。
內存泄漏優化
確保佔用最少的內存可讓頁面得到更好的性能。而優化內存佔用的最佳方式,就是爲執行中的代碼只保存必要的數據。一旦數據再也不有用,最好經過將其值設置爲 null 來釋放其引用——這個作法叫作解除引用(dereferencing)
function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var globalPerson = createPerson("Nicholas");
// 手動解除 globalPerson 的引用
globalPerson = null;
複製代碼
解除一個值的引用並不意味着自動回收該值所佔用的內存。解除引用的真正做用是讓值脫離執行環境,以便垃圾收集器下次運行時將其回收。
var leakArray = [];
exports.clear = function () {
leakArray = [];
}
複製代碼
首先,明確gzip
是一種壓縮格式,須要瀏覽器支持纔有效(不過通常如今瀏覽器都支持),並且gzip
壓縮效率很好(高達70%左右)。而後gzip通常是由apache
、tomcat
等web服務器開啓。
固然服務器除了gzip
外,也還會有其它壓縮格式(如 deflate
,沒有gzip高效,且不流行),因此通常只須要在服務器上開啓了gzip壓縮,而後以後的請求就都是基於gzip壓縮格式的,很是方便
對於web應用來講,緩存是提高頁面性能同時減小服務器壓力的利器。
緩存能夠簡單的劃分紅兩種類型: 強緩存(200fromcache
)與 協商緩存(304
)
區別簡述以下:
200fromcache
)時,瀏覽器若是判斷本地緩存未過時,就直接使用,無需發起http請求304
)時,瀏覽器會向服務端發起http請求,而後服務端告訴瀏覽器文件未改變,讓瀏覽器使用本地緩存對於協商緩存,使用 Ctrl+F5強制刷新可使得緩存無效。可是對於強緩存,在未過時時,必須更新資源路徑才能發起新的請求(更改了路徑至關因而另外一個資源了,這也是前端工程化中經常使用到的技巧)。
屬於強緩存控制的:
(http1.1) Cache-Control(瀏覽器)/Max-Age(服務端)
(http1.0)Pragma(瀏覽器)/Expires(服務端)
複製代碼
注意: Max-Age
不是一個頭部,它是Cache-Control
頭部的值。
屬於協商緩存控制的:
(http1.1) If-None-Match(瀏覽器)/E-tag(服務端)
(http1.0) If-Modified-Since(瀏覽器)/Last-Modified(服務端)
複製代碼
能夠看到,上述有提到http1.1
和http1.0
,這些不一樣的頭部是屬於不一樣http時期的。
再提一點,其實HTML頁面中也有一個meta標籤能夠控制緩存方案- Pragma。
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
複製代碼
不過,這種方案仍是比較少用到,由於支持狀況不佳,譬如緩存代理服務器確定不支持,因此不推薦。
頭部的區別
首先明確,http的發展是從http1.0到http1.1,而在http1.1中,出了一些新內容,彌補了http1.0的不足。
http1.0中的緩存控制:
Pragma
:嚴格來講,它不屬於專門的緩存控制頭部,可是它設置 no-cache
時可讓本地強緩存失效(屬於編譯控制,來實現特定的指令,主要是由於兼容http1.0,因此之前又被大量應用)Expires
:服務端配置的,屬於強緩存,用來控制在規定的時間以前,瀏覽器不會發出請求,而是直接使用本地緩存,注意,Expires通常對應服務器端時間,如Expires:Fri,30Oct 1998 14:19:41
If-Modified-Since/Last-Modified
:這兩個是成對出現的,屬於協商緩存的內容,其中瀏覽器的頭部是 If-Modified-Since
,而服務端的是Last-Modified
,它的做用是,在發起請求時,若是If-Modified-Since
和 Last-Modified
匹配,那麼表明服務器資源並未改變,所以服務端不會返回資源實體,而是隻返回頭部,通知瀏覽器可使用本地緩存。Last-Modified
,顧名思義,指的是文件最後的修改時間,並且只能精確到 1s之內http1.1中的緩存控制:
Cache-Control
:緩存控制頭部,是瀏覽器的頭部,有no-cache
、max-age
等多種取值Max-Age
:服務端配置的,用來控制強緩存,在規定的時間以內,瀏覽器無需發出請求,直接使用本地緩存,注意,Max-Age
是Cache-Control
頭部的值,不是獨立的頭部,譬如 Cache-Control:max-age=3600
,並且它值得是絕對時間,由瀏覽器本身計算If-None-Match/E-tag
:這兩個是成對出現的,屬於協商緩存的內容,其中瀏覽器的頭部是 If-None-Match
,而服務端的是E-tag
,一樣,發出請求後,若是If-None-Match
和 E-tag
匹配,則表明內容未變,通知瀏覽器使用本地緩存,和Last-Modified
不一樣,E-tag
更精確,它是相似於指紋同樣的東西,基於FileEtagINodeMtimeSize
生成,也就是說,只要文件變,指紋就會變,並且沒有1s精確度的限制。Max-Age相比Expires?
Expires
使用的是服務器端的時間,可是有時候會有這樣一種狀況-客戶端時間和服務端不一樣步。那這樣,可能就會出問題了,形成了瀏覽器本地的緩存無用或者一直沒法過時,因此通常http1.1後不推薦使用Expires
。而 Max-Age
使用的是客戶端本地時間的計算,所以不會有這個問題,所以推薦使用 Max-Age
。
注意,若是同時啓用了Cache-Control
與Expires
,Cache-Control
優先級高。
E-tag相比Last-Modified?
Last-Modified
:
而E-tag
:
若是同時帶有E-tag
和Last-Modified
,服務端會優先檢查E-tag
。
各大緩存頭部的總體關係以下圖:
// apply
let arr = [0,245,7,986];
let result = Math.max.apply(null,arr)
console.log(result) // 986
// call
let arr2 = [0,245,7,986];
let result2 = Math.max.call(null,[...arr])
console.log(result2) // 986
複製代碼
// 第一種最方便
let arr = [1,2,3,4,5,1,2,1]
let result = Array.from(new Set(arr))
console.log(result) // [1,2,3,4,5]
// 第二種利用對象的屬性不能相同的特色進行去重(兼容性好)
let arr = [1,2,3,4,5,1,2,1];
let array = [];
var obj = {};
for (var i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
obj[arr[i]] = true;
array.push(arr[i]);
}
}
console.log(array); //[1,2,3,4,5]
// 第三種雙層循環 (兼容性好)
var arr = [1,2,3,4,5,1,2,1];
var array = [];
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < array.length; j++) {
if (arr[i] == array[j]) {
break;
}
}
//若是這兩個數相等說明循環完了,沒有相等的元素
if (j == array.length) {
array.push(arr[i]);
}
}
console.log(array); //[1,2,3,4,5]
// 第四種利用 indexOf()
var arr = [1,2,3,4,5,1,2,1];
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array.indexOf(arr[i]) == -1) {
array.push(arr[i]);
}
}
console.log(array); //[1,2,3,4,5]
// 第五種利用 forEach 和 indexOf()
var arr = [1,2,3,4,5,1,2,1];
var array = [];
arr.forEach(function(item, index) {
if (array.indexOf(item) == -1) {
array.push(item);
}
});
console.log(array); //[1,2,3,4,5]
// 第六種利用 filter()和 indexOf()
var arr = [1,2,3,4,5,1,2,1];
var array = arr.filter(function(item, index) {
return arr.indexOf(item) == index;
});
console.log(array); //[1,2,3,4,5]
// 第7種ES6 的 includes 實現去重
var arr = [1,2,3,4,5,1,2,1];
var array = [];
arr.forEach(function(item, index) {
if (!array.includes(item)) {
array.push(item);
}
});
console.log(array); //[1,2,3,4,5]
複製代碼
createDocumentFragment() //建立一個DOM片斷
createElement() //建立一個具體的元素
createTextNode() //建立一個文本節點
複製代碼
appendChild() // 添加
removeChild() // 移除
replaceChild() // 替換
insertBefore() // 插入
複製代碼
getElementsByTagName() //經過標籤名稱
getElementsByName() //經過元素的Name屬性的值
getElementById() //經過元素Id,惟一性
querySelector() // 經過選擇器獲取一個元素
querySelectorAll() // 經過選擇器獲取一組元素
getElementsByClassName() // 經過類名
document.documentElement // 獲取html的方法
document.body // 獲取body的方法
複製代碼
(1) function Foo () {
getName = function () { alert(1) };
return this;
}
(2) Foo.getName = function () { alert(2) };
(3) Foo.prototype.getName = function () { alert(3) };
(4) var getName = function () { alert(4) };
(5) function getName () { alert(5) };
//輸出的值
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); 3
複製代碼
此題涉及的知識點衆多,包括變量定義提高、this指針指向、運算符優先級、原型、繼承、全局變量污染、對象屬性及原型屬性優先級等等
此題包含7小問,分別說下。
第一問
先看此題的上半部分作了什麼,首先定義了一個叫Foo的函數,以後爲Foo建立了一個叫getName的靜態屬性存儲了一個匿名函數,以後爲Foo的原型對象新建立了一個叫getName的匿名函數。以後又經過函數變量表達式建立了一個getName的函數,最後再聲明一個叫getName函數。
第一問的 Foo.getName 天然是訪問Foo函數上存儲的靜態屬性,天然是2,沒什麼可說的
第二問
直接調用 getName 函數。既然是直接調用那麼就是訪問當前上文做用域內的叫getName的函數,因此跟1 2 3都沒什麼關係。此題有無數面試者回答爲5。此處有兩個坑,一是變量聲明提高,二是函數表達式。
變量聲明提高
即全部聲明變量或聲明函數都會被提高到當前函數的頂部。
備註:
例以下代碼:
console.log('x' in window); // true
var x;
x = 0;
複製代碼
代碼執行時js引擎會將聲明語句提高至代碼最上方,變爲:
var x;
console.log('x' in window); // true
x = 0;
複製代碼
函數表達式var getName
與 function getName
都是聲明語句,區別在於 var getName
是函數表達式,而 function getName
是函數聲明。
函數表達式最大的問題,在於js會將此代碼拆分爲兩行代碼分別執行。
例以下代碼:
console.log(x); // 輸出:function x(){} ?
function x(){}
var x=1;
function x(){}
複製代碼
實際執行的代碼爲,先將 var x=1 拆分爲 var x; 和 x = 1; 兩行,再將 var x; 和 function x(){} 兩行提高至最上方變成:
function x(){}
var x;
console.log(x); // function x(){}
x=1;
複製代碼
因此最終函數聲明的x覆蓋了變量聲明的x,log輸出爲x函數
。
同理,原題中代碼最終執行時的是:
function Foo() {
getName = function () { alert (1); };
return this;
}
function getName() { alert (5);}//提高函數聲明,覆蓋var的聲明
var getName;//只提高聲明不提高初始化
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
getName = function () { alert (4);};//最終的賦值再次覆蓋function getName聲明
----------------------------------------------------------------------------
getName();//最終輸出4
複製代碼
第三問
第三問的 Foo().getName(); 先執行了Foo函數,而後調用Foo函數的返回值對象的getName屬性函數。Foo函數的第一句 getName = function () { alert (1); };
是一句函數賦值語句,注意它沒有var聲明,因此先向當前Foo函數做用域內尋找getName變量,沒有。再向當前函數做用域上層,即外層做用域內尋找是否含有getName變量,找到了,也就是第二問中的alert(4)函數,將此變量的值賦值爲 function(){alert(1)}。
此處其實是將外層做用域內的getName函數修改了。
**注意:**此處若依然沒有找到會一直向上查找到window對象,若window對象中也沒有getName屬性,就在window對象中建立一個getName變量。以後Foo函數的返回值是this,而JS的this問題各大文章中已經有很是多的文章介紹,這裏再也不多說。
簡單的講,this的指向是由所在函數的調用方式決定的。而此處的直接調用方式,this指向window對象。
遂Foo函數返回的是window對象,至關於執行 window.getName() ,而window中的getName已經被修改成alert(1),因此最終會輸出1
此處考察了兩個知識點,一個是變量做用域問題,一個是this指向問題。
第四問
直接調用getName函數,至關於 window.getName() ,由於這個變量已經被Foo函數執行時修改了,遂結果與第三問相同,爲1
第五問
第五問 new Foo.getName(); ,此處考察的是js的運算符優先級問題。
經過查文檔能夠得知點(.)的優先級高於new(無參數列表)操做,遂至關因而:
new (Foo.getName)();
複製代碼
因此實際上將getName函數做爲了構造函數來執行,遂彈出2。
第六問
第六問 new Foo().getName() ,首先看運算符優先級括號高於new,實際執行爲
(new Foo()).getName()
複製代碼
遂先執行Foo函數,而Foo此時做爲構造函數卻有返回值,因此這裏須要說明下js中的構造函數返回值問題。
構造函數的返回值
在傳統語言中,構造函數不該該有返回值,實際執行的返回值就是此構造函數的實例化對象。
而在js中構造函數能夠有返回值也能夠沒有。
一、沒有返回值則按照其餘語言同樣返回實例化對象。
二、如有返回值則檢查其返回值是否爲引用類型。若是是非引用類型,如基本類型
(string,number,boolean,null,undefined)則與無返回值相同,實際返回其實例化對象。
三、若返回值是引用類型,則實際返回值爲這個引用類型。
原題中,返回的是this,而this在構造函數中原本就表明當前實例化對象,遂最終Foo函數返回實例化對象。
以後調用實例化對象的getName函數,由於在Foo構造函數中沒有爲實例化對象添加任何屬性,遂到當前對象的原型對象(prototype)中尋找getName,找到了。
遂最終輸出3。
第七問
第七問, new new Foo().getName(); 一樣是運算符優先級問題。
最終實際執行爲:
new ((new Foo()).getName)();
複製代碼
先初始化Foo的實例化對象,而後將其原型上的getName函數做爲構造函數再次new。
遂最終結果爲3