1、定義一個函數,JavaScript內部各做了哪些事情
定義一個函數時,JavaScript內部執行了以下步驟:
-
解析函數聲明:
當你定義一個函數時,JavaScript的解析器會首先解析函數聲明。這意味著它會檢查函數聲明的語法是否正確,包括函數名、參數列表、函數體等。 -
創建函數對象:
一旦函數聲明被解析通過,JavaScript會在內存中創建一個函數對象。這個函數對象包含了函數的定義、參數信息、函數體以及其它與函數相關的元數據。 -
函數作用域和閉包:
在函數創建的過程中,JavaScript會確定函數的作用域。這包括確定函數內部可以訪問的變量和函數。如果函數內部引用了外部作用域的變量,那么這些變量會被“封閉”在函數內部,形成閉包。閉包允許函數在執行完畢后依然能夠訪問其定義時的詞法環境。 -
將函數對象賦值給變量:
如果你使用了變量來定義函數(例如var myFunction = function() { ... };
),那么JavaScript會將新創建的函數對象賦值給相應的變量。這樣,你就可以通過變量來引用和調用這個函數了。 -
函數原型和
prototype
屬性:
每個函數對象都有一個prototype
屬性,它指向一個原型對象。這個原型對象包含了可以被函數的所有實例共享的屬性和方法。當你使用new
關鍵字創建函數的新實例時,新實例的內部[[Prototype]]
鏈接會指向這個原型對象,從而可以訪問原型上的屬性和方法。
函數對象的原型對象(prototype)的默認值是一個空的Object對象。
換句話說,當你定義一個函數時,JavaScript會自動為其添加一個prototype屬性,這個屬性的默認值是一個空的對象(即不包含任何屬性和方法的對象)。這個原型對象主要用于實現基于原型的繼承和屬性查找。在JavaScript中,每個構造函數都有一個prototype屬性,這個屬性是一個指針,指向一個對象,該對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。按照慣例,這個prototype對象會包含一個名為constructor的屬性,該屬性是一個指向prototype屬性所在函數的指針。這樣,構造函數就能夠識別哪些對象是其實例。
需要注意的是,雖然prototype對象的默認值是一個空對象,但你可以根據需要向其中添加屬性和方法,以便在構造函數的實例中共享這些屬性和方法。此外,你也可以通過修改prototype對象來修改已有實例的行為,但這通常是不推薦的,因為這可能會導致代碼難以理解和維護。
下面是一個簡單的例子,展示了如何查看一個函數對象的prototype屬性的默認值:
function MyFunction() {// 函數體 }console.log(MyFunction.prototype); // 輸出: {}
在這個例子中,
MyFunction
是一個空函數,我們打印出它的prototype屬性,可以看到它的默認值是一個空的Object對象。
- 函數屬性和方法:
函數對象自身也有一些屬性和方法,例如name
(函數名)、length
(參數個數)、call
、apply
、bind
等。這些屬性和方法允許你操作函數或獲取函數的元數據信息。
下面是一個簡單的函數定義示例:
function myFunction(arg1, arg2) {// 函數體console.log('Hello, world!');
}
在這個例子中,JavaScript會執行以下步驟:
- 解析函數聲明
myFunction
,檢查其語法是否正確。 - 創建一個新的函數對象,包含
myFunction
的定義、參數arg1
和arg2
、函數體以及其它元數據。 - 確定函數的作用域,并檢查是否有閉包形成。
- 將這個函數對象賦值給變量
myFunction
。 - 為這個函數對象創建一個
prototype
屬性,并設置其默認的constructor
屬性指向函數對象本身。
最終,你可以通過myFunction
變量來調用這個函數,并執行其函數體中的代碼。
2、new 創建一個函數實例時,JavaScript內部做了哪些事情
當使用 new
關鍵字創建一個函數的實例時,JavaScript 內部執行以下步驟:
-
創建新對象:
JavaScript 首先會創建一個新的空對象。這個新對象將用作新創建的實例。 -
設置原型鏈:
新創建的對象的內部[[Prototype]]
鏈接(這不是一個真正的屬性,而是一個內部鏈接)會被設置為構造函數的prototype
對象。這意味著新創建的對象可以繼承構造函數原型上的屬性和方法。 -
構造函數調用:
接下來,構造函數會被調用,并將新創建的對象作為this
上下文。這意味著在構造函數內部,你可以使用this
關鍵字來訪問和修改新創建的對象。 -
執行構造函數體:
構造函數的代碼(函數體)會被執行。在這個過程中,你可以定義新對象的屬性、方法,以及執行其他任何初始化代碼。 -
返回新對象:
如果構造函數沒有顯式地返回一個非原始值(即非null
、undefined
、數字、字符串或布爾值),那么new
表達式的結果就是新創建的對象。如果構造函數返回了一個對象,那么這個返回的對象將替代新創建的對象,并作為new
表達式的結果。 -
實例屬性和方法:
在構造函數中定義的任何屬性和方法都將成為新創建對象的實例屬性和方法。這些屬性和方法僅對當前實例可見,每個實例都會有自己的一套屬性和方法的副本。
下面是一個使用 new
關鍵字創建函數實例的示例:
function Person(name, age) {this.name = name;this.age = age;this.introduce = function() {console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);}
}var john = new Person('John', 30);
john.introduce(); // 輸出: Hello, my name is John and I am 30 years old.
在這個例子中:
Person
是一個構造函數。- 使用
new Person('John', 30)
創建了一個新的Person
實例。 - 新對象的
[[Prototype]]
鏈接被設置為Person.prototype
。 - 構造函數被調用,并設置了新對象的
name
和age
屬性,以及introduce
方法。 introduce
方法是定義在新對象上的實例方法,每個實例都會有自己的方法副本。john
變量引用了新創建的Person
實例,并可以調用其introduce
方法。