[TOC]
# 1. 創建對象(普通寫法)
<mark>1. 寫法1</mark>:浪費內存。
```js
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = function () {
console.log('hello ' + this.name)
}
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
// 上面的type、sayHello是實現的功能是永遠不變的,但是每new一次Person,都會重復創建這個兩個屬性
// 沒有必要,且浪費內存
console.log(p1.sayHello === p2.sayHello) // => false
```
<mark>2. 寫法2</mark>:將共享的屬性定義在對象外部
```js
function sayHello = function () {
console.log('hello ' + this.name)
}
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = sayHello
// 對于不需要傳參的函數,調用時可以去掉()
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // => true
// 存在的問題是:會造成全局命名空間沖突
```
<mark>3. 寫法3</mark>:將共享的屬性定義在另一個對象中
```js
var fns = {
sayHello: function () {
console.log('hello ' + this.name)
},
sayAge: function () {
console.log(this.age)
}
}
function Person (name, age) {
this.name = name
this.age = age
this.type = 'human'
this.sayHello = fns.sayHello
this.sayAge = fns.sayAge
}
var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)
console.log(p1.sayHello === p2.sayHello) // => true
console.log(p1.sayAge === p2.sayAge) // => true
```
# 2. prototype創建對象
<mark>4. 寫法4</mark>:使用`prototype`屬性
Javascript 規定,每一個構造函數都有一個 `prototype` 屬性,指向```prototype```對象(prototype是一個屬性也是一個對象)的內存地址。```prototype```對象的所有屬性和方法,都會被構造函數的實例繼承。
這也就意味著,我們可以把所有對象實例需要共享的屬性和方法直接定義在 `prototype` 對象上。
```js
function Person (name, age) {
this.name = name
this.age = age
}
console.log(Person.prototype)
Person.prototype.type = 'human'
Person.prototype.sayName = function () {
console.log(this.name)
}
var person1 = new Person(...)
var person2 = new Person(...)
console.log(person1.sayName === person2.sayName) // => true
// 這時所有實例的 type 屬性和 sayName() 方法, 其實都是同一個內存地址,指向 prototype 對象,因此就提高了運行效率。
```
或
```js
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype = {
constructor: Person, // 將Person寫在這里的原因是:為了防止prototype對象丟失constructor構造器
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '歲了')
}
}
```
## 3.1 屬性成員的搜索原則:原型鏈
在我們調用 `person1.sayName()` 的時候,會先后執行兩次搜索:
* 首先,解析器會問:“實例 person1 有 sayName 屬性嗎?”答:“沒有。
* 然后,它繼續搜索,再問:“ person1 的原型有 sayName 屬性嗎?”答:“有。
* 于是,它就讀取那個保存在原型對象中的函數。
* 當我們調用 person2.sayName() 時,將會重現相同的搜索過程,得到相同的結果。
* 如果一直到原型鏈的末端還沒有找到,則返回 `undefined`
## 3.2 構造函數、實例、原型三者之間的關系**

* 任何構造函數都具有一個 `prototype` 屬性,該屬性是一個對象。
* 構造函數的 `prototype` 對象默認都有一個 `constructor` 屬性,指向 `prototype` 對象所在函數。
* 通過構造函數得到的實例對象內部會包含一個指向構造函數的 `prototype` 對象的指針
* 所有實例都直接或間接繼承了原型對象的成員
`__proto__`。
```js
function F () { // 構造函數
// 任何構造函數/對象都有一個prototype屬性
// 任何構造函數/對象的prototype屬性都有一個constructor屬性,指向構造函數
// 任何構造函數/對象的實例對象都有一個指向prototype的指針__proto__
}
console.log(F.prototype) // => object
console.log(F.prototype.constructor === F) // => true
var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true
F.prototype.sayHi = function() {}
instance.sayHi() // 實例對象可以直接訪問原型對象成員。
```
## 3.3 實例對象讀寫原型對象成員
讀取:
- 先在自己身上找,找到即返回
- 自己身上找不到,則沿著原型鏈向上查找,找到即返回
- 如果一直到原型鏈的末端還沒有找到,則返回 `undefined`
值類型成員寫入(`實例對象.值類型成員 = xx`):
- 當實例期望重寫原型對象中的某個普通數據成員時實際上會把該成員添加到自己身上
- 也就是說該行為實際上會屏蔽掉對原型對象成員的訪問
引用類型成員寫入(`實例對象.引用類型成員 = xx`):
- 當實例期望重寫原型對象中的某個普通數據成員時實際上會把該成員添加到自己身上
- 也就是說該行為實際上會屏蔽掉對原型對象成員的訪問
復雜類型修改(`實例對象.成員.xx = xx`):
- 同樣會先在自己身上找該成員,如果自己身上找到則直接修改
- 如果自己身上找不到,則沿著原型鏈繼續查找,如果找到則修改
- 如果一直到原型鏈的末端還沒有找到該成員,則報錯(`實例對象.undefined.xx = xx`)
## 3.4 原型對象使用建議
* 共享數組
* 共享對象
如果真的希望可以被實例對象之間共享和修改這些共享數據那就不是問題。但是如果不希望實例之間共享和修改這些共享數據則就是問題。
一個更好的建議是,最好不要讓實例之間互相共享這些數組或者對象成員,一旦修改的話會導致數據的走向很不明確而且難以維護。所以建議如下:
* 私有成員(一般就是非函數成員)放到構造函數中
* 共享成員(一般就是函數)放到原型對象中
* 如果重置了 `prototype` 記得修正 `constructor` 的指向
- js應用場景
- js組成
- js書寫位置
- 浮點數精度問題
- undefined與null的區別
- 數據類型轉換
- 運算符優先級
- 代碼調試
- 函數
- 函數的定義和調用
- 函數的return細節
- 函數是一種數據類型
- this的指向
- 函數成員
- 函數閉包
- 作用域
- 預解析
- js對象
- 對象的創建與調用
- new關鍵字
- this關鍵字
- 構造函數創建對象
- 事件
- 數據類型
- 繼承
- 雜項
- 如何阻止標簽的默認行為
- 為一個標簽綁定或移除任何一個事件
- 如何阻止事件的冒泡行為
- 事件的三個階段
- 移動元素的條件
- 勻速動畫函數封裝
- 變速動畫函數封裝
- 獲取元素的css屬性值
- 數據類型判斷方法
- 創建對象的7種寫法
- 如何繼承
- 為js內置對象添加原型函數
- 將局部變量轉換為全局變量
- call函數的用法
- 沙箱
- 淺拷貝
- 深拷貝
- 對象賦值會改變對象
- 解析URL中的字符串
- 格式化日期
- 獲取當前瀏覽器類型
- Vue3.x
- 調式工具Vue Devtools