標籤:大寫 def zh-cn var 執行個體化 hello === 第一個 something
函數和對象的關係
函數可以有屬性,對象也可以有屬性,在函數名前使用 new 操作符即可返回一個函數的執行個體化對象
function fn () {}fn.a = ‘haha‘console.log(fn.a) //"haha"let obj = {}obj.a = ‘heihei‘console.log(obj.a) //"heihei"let newObj = new fn()
每個函數都有一個屬性(prototype)原型對象,發現有constructor屬性和 __poroto__屬性,constructor指向建立它的構造器函數,這裡要明確的是 函數也會有建構函式,而這個__poroto__ 與它的建構函式的 prototype 是同一個東西,見圖 虛線指向的CFp就是建構函式的prototype,小的cf 是通過CF建立的對象執行個體
// fn.prototype{ constructor: ? doSomething(), __proto__: { constructor: ? Object(), hasOwnProperty: ? hasOwnProperty(), isPrototypeOf: ? isPrototypeOf(), propertyIsEnumerable: ? propertyIsEnumerable(), toLocaleString: ? toLocaleString(), toString: ? toString(), valueOf: ? valueOf() }}
我們既可以在CF上添加屬性,也可以在prototype 添加
區分:函數上添加屬性,建構函式產生執行個體上添加、函數原型添加
function fn() { this.c = ‘c‘}fn.a = ‘a‘fn.prototype.b = ‘b‘let obj = new fn()obj.d = ‘d‘// fn.prototype 上定義 屬性b ,obj定義了屬性d,fn上添加了屬性 a,// fn函數內部有個this的屬性c, 那麼obj有幾個屬性呢? 答案是 c b d
下面就來分析下為什麼會這樣,首先圖中的CF上的P1、P2在對象執行個體中均存在,參考下面的代碼
function CF(){ this.p1 = ‘p1‘, this.p2 = ‘p2‘}let cf1 = new CF()
見
可以看到屬性均出現在了對象執行個體上,現在就來說一下構建執行個體的發生了什麼
- 建立新對象cf1
- 建構函式的範圍賦給新對象,this指向新對象
- 執行建構函式中的代碼,即注入屬性
- 返回新對象
不過this上的屬性建構函式是不具有的,而在函數上直接定義的屬性它當然是有的,通過prototyoe設定的是在圖的CFp位置的,執行個體繼承它,也就是對象執行個體會得到這些屬性,對象執行個體在添加prototype屬性前建立依然有效
函數和對象的關係也就清楚了,通過建構函式方式可以建立對象,且可以得到函數原型上的屬性和方法,也可以在建構函式裡通過this為其設定一些不是公有的屬性方法等
擴充:
使用this添加的屬性具有一定“私人”性,但是還有些需要注意的地方,見下面代碼
function fn() { let value = ‘a‘ this.a = ‘a‘ this.privateValues = function (){ return value } }let newObj1 = new fn()let newObj2 = new fn()console.log(newObj1.a === newObj2.a) //true,基本類型的比較只比較值console.log(newObj1.privateValues === newObj2.privateValues) // false,每次進入對象都不相同
使用函數將其包裹起來返回具有更好的“私人”性
使用對象重寫原型對象
function fn() {}fn.prototype = { name: ‘kangkang‘, sayName: { console.log(this.name) }}
使用對象重寫會導致建立一個新的prototype對象, 對象裡的 constructor不再指向建構函式,這時需要重新指定下
function fn() {}fn.prototype = { constructor: fn name: ‘kangkang‘, sayName: { console.log(this.name) }}
繼承
這裡的繼承想要有個較為深刻的理解,首先需要介紹下原型鏈:
每個執行個體對象(object )都有一個私人屬性(稱之為 proto)指向它的原型對象(prototype)。該原型對象也有一個自己的原型對象 ,層層向上直到一個對象的原型對象為 null。根據定義,null 沒有原型,並作為這個原型鏈中的最後一個環節
參考MDN
由於每個執行個體都會繼承原型鏈上的原型對象上的屬性或方法,當你使用某個執行個體上所沒有的屬性時,會依著原型鏈一層一層尋找,那麼繼承這個概念也就很清楚了,我們將原型對象給需要繼承的對象不就行了嗎?其實之間會有些問題
function father() {}function son() {}father.prototype.sayHello = function() { console.log(‘hello‘)}son.prototype = father.prototype let obj = new son()obj.sayHello() // hello// father.prototype.sayHello = function() { console.log(‘haha‘)} obj.sayHello() // haha son.prototype.sayHello = function() {console.log(‘ddd‘)}father.prototype.sayHello() //ddd
上面這種“繼承”是有問題的,不僅互相影響而且當father修改原型時會直接影響到son,進行改進
function father() {}function son() {} father.prototype.sayHello = function() { console.log(‘hello‘)} function wa() { } wa.prototype = father.prototype son.prototype = new wa()son.prototype.constructor = sonson.prototype.sayHello = function() {console.log(‘sss‘)} father.prototype.sayHello() //hello
ECMAScript 5 中引入了一個新方法:Object.create(). 可以調用這個方法來建立一個新對象。新對象的原型就是調用 create 方法時傳入的第一個參數
var a = {a: 1}; // a ---> Object.prototype ---> null var b = Object.create(a);// b ---> a ---> Object.prototype ---> nullconsole.log(b.a); // 1 (繼承而來) var c = Object.create(b);// c ---> b ---> a ---> Object.prototype ---> null var d = Object.create(null);// d ---> nullconsole.log(d.hasOwnProperty); // undefined, 因為d沒有繼承Object.prototype
上面還是有點缺點,當 原型對象上有參考型別值得屬性時
function a () { this.obj = { name: ‘kangkang‘, age: 3 }}function b() { this.b = ‘b‘} b.prototype = new a()var c = new b()var d = new b()c.obj.name=‘xixi‘console.log(d.obj.name) //xixi
這時建立的執行個體在每次獲得建構函式的引用值屬性時,獲得的值相同,值儲存的是參考型別的地址,自然是同一個引用值了,所以互相影響,解決方案
看下面代碼
function fn (name) { this.score = [90,98,99] this.name = name}function a () { fn.call(this,‘kangkang‘) this.age = 3}var c = new a()var d = new a()c.score[0] = 100console.log(c.score) //[100, 98, 99]console.log(d.score) //[90, 98, 99]
這種方法又缺少了原型上的繼承,所以結合起來就是
function fn (name) { this.score = [90,98,99] this.name = name}fn.prototype.sayName = function() { console.log(this.name) }function a () { fn.call(this,‘kangkang‘) this.age = 3}a.prototype = new fn()a.prototype.constructor = avar c = new a()var d = new a()c.score[0] = 100console.log(c.score) //[100, 98, 99]console.log(d.score) //[90, 98, 99]console.log(c.sayName()) // kangkang
類
ES6 實現了 class,其中有 class constructor static extends super 這些關鍵字完整的實現“類”的功能, 它是文法糖,是基於上面所說的原型的概念實現的
- 定義類名 首字母大寫
- constructor方法是一個特殊的方法,其用於建立和初始化使用class建立的一個對象,只能有一個
- 使用 super 關鍵字來調用一個父類的建構函式
- extends 關鍵字在類聲明或類運算式中用於建立一個類作為另一個類的一個子類。
- static 關鍵字用來定義一個類的一個靜態方法。調用靜態方法不需要執行個體化該類,但不能通過一個類執行個體調用靜態方法。靜態方法通常用於為一個應用程式建立工具函數。
class Polygon { constructor(height, width) { this.height = height; this.width = width; }}class Square extends Polygon { constructor(sideLength) { super(sideLength, sideLength); } get area() { return this.height * this.width; } set sideLength(newLength) { this.height = newLength; this.width = newLength; }}var square = new Square(2);
static用法
class Point { constructor(x, y) { this.x = x; this.y = y; } static distance(a, b) { const dx = a.x - b.x; const dy = a.y - b.y; return Math.hypot(dx, dy); }}const p1 = new Point(5, 5);const p2 = new Point(10, 10);console.log(Point.distance(p1, p2)); //7.0710678118654755// 注意此處不是從執行個體中調用它的
拓展:
ES6屬性簡寫和方法簡寫 參考阮一峰老師的部落格
const o = { method() { return "Hello!"; }};// 等同於const o = { method: function() { return "Hello!"; }};function f(x, y) { return {x, y};}// 等同於function f(x, y) { return {x: x, y: y};}f(1, 2) // Object {x: 1, y: 2}
以上為查閱資料總結而得,如要詳細準確還請自行查閱方能辨析有所得
JavaScript中函數、對象、類別關係 記錄