如題: html
function A() {
B = function () {console.log(10)}
return this
};
A.B = function () {console.log(20)};
A.prototype.B = function () {console.log(30)}
var B = function () {console.log(40)}
function B() {console.log(50)}
A.B() // answer 20 【原型與原型鏈】
// 在`A`的原型對象中查找是否有`B`函數而且調用,這裏並未執行`A`函數。
// A.B = function () {console.log(20)};
// 在A的原型對象中添加了`B`函數,中止查找,因此答案爲 20
B() // answer 40 【函數表達式和函數聲明】
// var B = function () {console.log(40)}
// function B() {console.log(50)}
// 同名 -> 函數提高會 > 變量提高
// 換言之 同名的函數表達式和函數聲明同時存在時 老是執行表達式
A().B() // answer 10 【函數表達式和函數聲明】
// A() 執行函數A ==> 1.變量B從新賦值函數 2.返回this(window)
// .B() 執行全局下的B函數 已經被從新賦值 因此輸出10
B() // answer 10
// 上面的代碼執行過A函數了,此時全局下的B函數輸出10
new A.B() // answer 20【函數表達式和函數聲明】
// new 執行了 A.B = function () {console.log(20)};
new A().B() // answer 30
// new 執行構造函數 A ,全局變量 B 從新賦值函數10
// 此時A() 指針指向哪裏?
// 首先要知道 new 作了什麼事?
// ==> 建立空對象objA objA.__proto__ = A.prototype
// .B() 在A的原型對象中查找 B; A.prototype 指向函數的原型對象
// A.prototype.B = function () {console.log(30)} 輸出 30
複製代碼
每個函數都有一個
prototype
屬性。git
function Foo() {}
Foo.prototype; // {constructor,__proto__}
複製代碼
不管何時,只要建立了一個新函數,根據一組特定的規則爲該函數建立一個prototype 屬性,這個屬性指向函數的原型對象。github
那麼這個建立的原型對象是什麼呢?express
{
constructor: ƒ Foo(),
__proto__: Object
}
複製代碼
每個原型對象都有一個
constructor
屬性函數
建立了自定義的構造函數後,其原型對象只會默認取得 constructor
屬性。這個屬性解決了對象識別問題,便可以經過該屬性判斷出實例是由哪一個構造函數建立的。post
Foo.prototype.constructor === Foo; // true
複製代碼
前面說了,原型對象只會默認取得 constructor
屬性,那麼原型對象的其餘屬性(好比:__proto__
)是這麼來的呢,這就要說到 __proto__
指針了。ui
每個實例都有一個
__proto__
指針,指向構造函數的原型對象。this
var foo = new Foo();
foo.__proto__ === Foo.prototype; //true
複製代碼
上面提到的構造函數的原型對象它自己也是一個實例,因此在它內部會有一個__proto__
指針。spa
ECMAScript
中提供了構造函數來建立新對象。但構造函數自己就是一個函數,與普通函數沒有任何區別,只不過爲了區分,通常將其首字母大寫,但這並非必須的。prototype
函數被 new 關鍵字調用時就是構造函數。
function f(name) {
console.log("execute");
this.name = name;
}
var k = new f("k"); // execute =====> 調用new
console.log(k); // {name: "k"}
var h = f("h"); // execute =====> 未調用new
console.log(h); // undefined
複製代碼
從上面代碼能夠看出:
new
,函數的角色就成爲了構造函數,建立一個對象並返回。var obj = {}; // 建立一個空對象
obj.__proto__ = constructor.prototype;//添加__proto__屬性,並指向構造函數的prototype 屬性。
constructor.call(this); // 綁定this
return obj;
複製代碼
new 關鍵字的內部實現機制:
原型鏈的理論主要基於上述提到的構造函數、實例和原型的關係:
constructor
屬性__proto__
指針 其中最最重要的是第三條,依賴這條關係,層層遞進,就造成了實例與原型的鏈條。接着上面的探索,構造函數的原型的原型是由 Object
生成的,那麼 Object
的原型是由什麼生成?而原型鏈的終點又是在哪?
Object.prototype.__proto__ // null
null.__proto__; // Uncaught TypeError: Cannot read property '__proto__' of null
// game over
複製代碼
原型的終點是 null
,由於 null
沒有 proto
屬性。
最後以一個例子來理解上面所談到的原型與原型鏈
function Foo(){} // 構造函數 Foo
var foo = new Foo() // foo.__proto__ 指向 Foo.prototype
複製代碼
函數聲明是用指定的參數聲明一個函數。一個被函數聲明建立的函數是一個 Function
對象,具備 Function
對象的全部屬性、方法和行爲。
// 函數聲明語法
function name([param[, param[, ... param]]]) { statements }
複製代碼
在函數表達式中咱們能夠忽略函數名稱建立匿名函數,並將該匿名函數賦值給變量。
var add = function(a, b) {
return a + b;
};
add(2, 3) // 5
複製代碼
固然, 也能夠建立命名函數表達式 Named function expression:
var add = function func(a, b) {
return a + b;
};
add(2, 3) // 5
複製代碼
命名函數表達式中函數名稱只能做爲函數體做用域內的局部變量,外部不可訪問。
var a = function pp(v) {
v++;
if (v>3) {
return v;
} else {
return pp(v);
}
}
a(1); // 4
pp(1); // ReferenceError: pp is not defined
複製代碼
對於函數聲明建立的函數,能夠在本做用域內任意位置訪問。
a(); // 1
function a() {
return 1;
}
a(); // 1
複製代碼
而函數表達式不會。
console.log(a); // undefined (只是變量提高)
a(1); // TypeError: a is not a function
var a = function(v) {
console.log(v);
};
複製代碼
console.log(fn); // [Function: fn]
var fn = function () {
console.log(1);
}
function fn() {
console.log(2);
}
fn() // 1
複製代碼
提高過程
// 函數提高
function fn() {
console.log(2);
}
// 變量提高
var fn;
fn = function () {
console.log(1);
}
fn() //最終輸出1
複製代碼