javascript對象模型
文章目錄
- javascript對象模型
- 定義類
- 1.字面式聲明方式(也稱為字面值建立對象。)
- 2.ES6之前----構造器構造類
- 3.ES6中的class關鍵字
- this的不同作用域
- 高階對象、高階類、或稱Mixin模式
- 繼承實作
- 高階對象實作
JavaScript是一種基于原型(Prototype)的面向對象語言,而不是基于類的面向對象語言。
C++、Java有類Class和執行個體Instance的概念,類是一類事物的抽象,而執行個體則是類的實體。
- JS是基于原型的語言,它隻有原型對象的概念。
- 原型對象就是一個模闆,新的對象從這個模闆建構進而擷取最初的 屬性。任何對象在運作時可以動态的增加屬性。而且,任何一個對象都可以作為另一個對象的原型,這樣後者就可 以共享前者的屬性。
定義類
1.字面式聲明方式(也稱為字面值建立對象。)
- js1.2開始支援
- 文法:
var obj = {
property_1:value_1,
property_2:value_2,
...,
"property n":value_n
}
- 示例:
var obj = {
x:1,
1:'abc',
'y':'123'
}
for (let s in obj){
console.log(s,typeof(s),obj[s])
}
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5iN4MTN1YWZkJGM0UmZ2YGNzYzXyUjMzETM5EzLcBTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.jpg)
2.ES6之前----構造器構造類
- 定義一個函數(構造器)對象,函數名首字母大寫
- 使用this定義屬性
- 使用new和構造器建立一個新對象
- 如果繼承,需要調用繼承對象的call方法,第一個參數傳入this來實作繼承。
//定義類,構造器
function Point(x,y){
this.x = x;
this.y = y;
this.show = ()=>{console.log(this,this.x,this.y)}
console.log('Point~~~~~~')
}
console.log(Point);
p1 = new Point(1,2);
console.log(p1)
console.log('--------------------');
//繼承
function Point3D(x,y,z){
Point.call(this,x,y); //"繼承Point,需要調用Point的call方法"
this.z = z;
console.log("Point3D~~~~~~~~~~~~~~");
}
console.log(Point3D)
p2 = new Point3D(14,15,16);
console.log(p2);
p2.show()
- new建構一個新的通用對象,new操作符會将新對象的this值傳遞給Point3D構造函數,函數為這個對象建立z屬性。
- 使用new後可以得到一個對象,使用這個對象的this來調用構造器。
- 使用Point3D對象的this來執行Point的構造器,是以使用call方法,傳入子類的this。
- 最後完成構造後,将對象指派給p2
- 注意:如果不使用new關鍵字,就是一次普通的函數調用,this不代表執行個體。
3.ES6中的class關鍵字
從ES6開始,新提供了class關鍵字,使得建立對象更加簡單、清晰。
- 類定義使用class關鍵字。建立的本質上還是函數,是一個特别的函數
- 一個類隻能擁有一個名為constructor的構造器方法。如果沒有顯式的定義一個構造方法,則會添加一個預設的constuctor方法。
- 繼承使用extends關鍵字
- 一個構造器可以使用super關鍵字來調用一個父類的構造函數
- 類沒有私有屬性
- 注意:js中沒有多繼承。
class Point{
constructor(x,y){
this.x = x;
this.y = y;
console.log("Point被初始化")
}
show(){ //show方法
console.log(this,this.x,this.y)
}
}
let p1 = new Point(10,11);
p1.show()
// 繼承
class Point3D extends Point{
constructor(x,y,z){
super(x,y);
this.z = z;
console.log("Point3D被初始化")
}
}
let p2 = new Point3D(20,21,22)
p2.show()
- 繼承中方法的重寫
- 子類中直接重寫父類的方法。
- 如果需要使用父類的方法,使用super.method()的方式調用
class Point{
constructor(x,y){
this.x = x;
this.y = y;
console.log("Point被初始化")
}
show(){ //show方法
console.log(this,this.x,this.y)
}
}
let p1 = new Point(10,11);
p1.show()
// 繼承
class Point3D extends Point{
constructor(x,y,z){
super(x,y);
this.z = z;
console.log("Point3D被初始化")
}
show(){ // 重寫
console.log(this,this.x,this.y,this.z)
}
}
let p2 = new Point3D(20,21,22)
p2.show()
- 對象中屬性和方法的通路順序
- 在對象中屬性的通路順序高于方法的通路順序。
- 如果一個對象中定義了個和方法相同的屬性名,并且該屬性指向一個函數對象,那麼該屬性的通路會優先與對象中方法的通路
- 注意:類在執行個體化時,會先為示例對象添加方法,在添加屬性。是以會導緻屬性覆寫方法。即屬性的通路順序優于方法的通路順序。
- 父類、子類使用同一種方式類定義屬性或者方法,子類覆寫父類。
- 通路同名屬性或方法時,優先使用屬性。
class Point{
constructor(x,y){
this.x = x;
this.y = y;
this.show = ()=> console.log('Point~~~~show',this) //會将類中的方法替換
console.log("Point被初始化")
}
show(){ //show方法
console.log(this,this.x,this.y)
}
}
let p1 = new Point(10,11);
p1.show()
// 繼承
class Point3D extends Point{
constructor(x,y,z){
super(x,y);
this.z = z;
console.log("Point3D被初始化")
}
//無法實作對屬性的重寫,需要重寫必須重新定義屬性
show(){ // 重寫
console.log(this,this.x,this.y,this.z)
}
}
let p2 = new Point3D(20,21,22)
p2.show() //是屬性的通路
console.log(p1) //實質上是替換了類中的方法
console.log(p2) //實質上是替換了類中的方法
- 靜态屬性
- 靜态屬性目前還沒有得到很好的支援。
- 靜态方法
- 在方法名上加上static,就是靜态方法了。類似于python中的類變量。
- 靜态方法中的this是類本身對象。而不是類執行個體的對象。
- 可以通過類名直接通路類中的靜态方法
- 執行個體對象通路靜态方法需要通過constructor屬性通路類中的靜态方法。
class Add{
constructor(x,y){
this.x = x;
this.y = y;
}
static print(){
console.log(this,typeof this,'--------------')
}
}
add = new Add(3,4);
console.log(Add,'=========');
Add.print();
// add.print(); // 執行個體不能直接通路靜态方法,和C++,Java一緻
add.constructor.print();//可以通過constructor通路靜态方法
this的不同作用域
- js和C++,Java一樣有this,但是js的表現是不通的。因為C++,Java是靜态編譯型語言,this是編譯期綁定,而js是動态語言,運作期綁定。
- 在js中函數執行時,會開啟新的執行上下文環境ExecutionContext。this屬性是什麼需要看函數是怎麼調用的了。
- 普通函數調用方式,this指向全局對象。全局對象是nodejs的global或者浏覽器中的window。
- js檔案根中的this。初始是一個空對象。即object.
- 通過執行個體對象調用執行個體方法,執行個體方法中的this指代的是該執行個體對象。
- call和apply方法調用,會傳遞一個this對象給調用的方法。
- 全局中的this不是global對象
a = 10;
var b = 100;
let c = 20;
const d = 30;
function add(){};
console.log(this);
console.log(this == global,this === global);
console.log(global);
]
- 方法中的this就是global對象
a = 10;
var b = 100;
let c = 20;
const d = 30;
function add(){
console.log('-------------');
console.log(this == global,this === global);
console.log(this)
};
add();
- 執行個體對象中的this是對象的執行個體本身
var school = {
name :'xdd',
getNameFunc : function (){ //由于是類對象的屬性方法,即方法中的this就是類對象,而不是global
console.log(this == global,this === global); // false,false
console.log(this,this.name);
console.log("---------------")
return function(...arrs){ // 沒有使用new關鍵字,即隻是一個普通的函數
console.log(this === global); //傳回的是一個函數,而不是執行個體對象的函數。即this激素global
console.log(this.name);
console.log(arrs)
}
}
}
console.log(school);
const fun = school.getNameFunc();
fun();
console.log('-------------使用call方法注入this對象-------------')
fun.call(school,1,2,3,4) //call方法傳參可以直接傳參
console.log('-------------使用apply方法注入this對象-------------')
fun.apply(school,[1,2,3,4]) // apply方法傳參需要使用數組
console.log('-------------使用bind方法為方法綁定this對象,傳回函數執行-------------')
fun.bind(school)(1,2,3,4)
school對象中函數傳回的方法中的this不是執行個體本身。它已經不是C++,java的指向執行個體本身了。this的問題,這是曆史遺留問題,為了相容,新版本保留了。而我們使用時,有時候需要明确的讓this必須是我們期望的對象。有如下解決方法
- 顯示傳入this對象,即在方法中接受一個that對象,讓調用者傳入this。
var school = {
name :'xdd',
getNameFunc : function (){ //由于是類對象的屬性方法,即方法中的this就是類對象,而不是global
console.log(this == global,this === global); // false,false
console.log(this,this.name);
console.log("---------------")
return function(that){ // 沒有使用new關鍵字,即隻是一個普通的函數
console.log(this === global); //傳回的是一個函數,而不是執行個體對象的函數。即this激素global
console.log(that.name);
}
}
}
school.getNameFunc()(school)
- ES3(ES-262第三版)引入了apply、call方法(上面例子已經展現了)
- apply、call方法都是函數對象的方法,第一參數都是傳入對象引入的。
- apply傳入其它參數需要數組。
- call傳入其它參數需要使用可變參數收集。
- ES5引入了bind方法
- bind方法來設定函數的this值。會傳回一個綁定了的函數。
- bind方法是為函數先綁定this,調用時直接使用。
- ES6引入支援this的箭頭函數
- ES6新技術,就不需要相容this問題。
var school = {
name :'xdd',
getNameFunc : function (){ //由于是類對象的屬性方法,即方法中的this就是類對象,而不是global
console.log(this == global,this === global); // false,false
console.log(this,this.name);
console.log("---------------")
return (...args)=>{ // 沒有使用new關鍵字,即隻是一個普通的函數
console.log(this === global); //傳回的是一個函數,而不是執行個體對象的函數。即this激素global
console.log(this.name);
console.log(args)
}
}
}
school.getNameFunc()(1,2,3,4)
- 以上解決this問題的方法,bind方法最常用。
高階對象、高階類、或稱Mixin模式
Mixin模式,混合模式。這是一種不用繼承就可以複用的技術。主要還是為了解決多重繼承的問題。多繼承的繼承路徑問題。
- js是基于對象的,類和對象都是對象模闆。
- 混合mixin,指的是将一個對象的全部或者部分拷貝到另外一個對象上去。其實就是屬性了。
- 可以将多個類或對象混合成一個類或對象。
繼承實作
class Serialization{
constructor(){
console.log("Serialization constructor---------");
if (typeof(this.stringify) !== 'function'){ //檢測順序是否是一個方法
throw new ReferenceError('should define stringify.')
}
}
}
class Point extends Serialization{
constructor(x,y){
console.log('Point Constructor--------------');
super();//調用父類的構造器
this.x = x;
this.y = y;
}
}
// s = new Serialization(); // 構造Serialization失敗。
// p = new Point(4,5);// 構造類對象時,調用父類構造器執行也會失敗。
- 上面例子中父類構造器函數中,要求具有數學是stringify的序列化函數,如果沒有則抛出異常。
- 完善代碼如下:
class Serialization{
constructor(){
console.log("Serialization constructor---------");
if (typeof(this.stringify) !== 'function'){ //檢測順序是否是一個方法
throw new ReferenceError('should define stringify.')
}
}
}
class Point extends Serialization{
constructor(x,y){
console.log('Point Constructor--------------');
super();//調用父類的構造器
this.x = x;
this.y = y;
}
stringify(){
return `<Point x=${this.x}, y=${this.y}>`;
}
}
class Point3D extends Point{
constructor(x,y,z){
super(x,y);
this.z = z;
}
stringify(){
return `<Point x=${this.x},y=${this.y},z=${this.z}`;
}
}
// s = new Serialization(); // 構造Serialization失敗。
p = new Point(4,5);// 構造類對象時,調用父類構造器執行也會失敗。
console.log(p.stringify())
p3d = new Point3D(9,8,7);
console.log(p3d.stringify())
高階對象實作
- 将類的繼承構造成箭頭函數
//Mixi類屬性檢測,檢查子類是否有特定屬性
const Serialization = Sup => class extends Sup{
constructor(...args){
console.log("Serialization constructor---------");
super(...args)
if (typeof(this.stringify) !== 'function'){ //檢測順序是否是一個方法
throw new ReferenceError('should define stringify.')
}
}
}
// 頂級父類
class Point{
constructor(x,y){
console.log('Point Constructor--------------');
this.x = x;
this.y = y;
}
}
// 強制限制了Point3D方法必須重寫stringif方法
class Point3D extends Serialization(Point){
constructor(x,y,z){
super(x,y);
this.z = z;
}
stringify(){
return `<Point x=${this.x},y=${this.y},z=${this.z} >`;
}
}
p3d = new Point3D(9,8,7);
console.log(p3d.stringify())
- 注意:
- Serialization(Point)這個一步實際上是一個匿名箭頭函數調用,傳回了一個新的類型。Point3D繼承自這個新的匿名類型,增強了功能。
- React架構大量使用了這種Mixin技術。