1. 程式人生 > >javascript中面向物件中物件,屬性,原型鏈和一些擴充套件知識總結

javascript中面向物件中物件,屬性,原型鏈和一些擴充套件知識總結

面向物件和麵向物件程式設計

面向物件

就是找個工具,幫我完成一項工作,物件就像一個工具一樣,每個工具都可以幫我們實現某個功能,比如汽車可以實現運輸,我們只需要學會如何去開動汽車,而不需要知道汽車是如何實現開動的。面向物件的好處:我們所有的專案都是團隊作戰。通過物件實現任務分離。用物件的某一部分實現一項功能,可以實現團隊作戰。物件實質上就是一個包含多個工具的工具包,用物件來實現對功能的分類管理。

比如一個字串物件,有toString,substring,subStr,indexOf,lastIndexOf等函式,用來實現不同的功能。

在物件中可以包含屬性,方法,通過點語法來訪問物件的屬性和方法。

面向物件程式設計

相當於組裝一輛汽車。汽車是一個物件,我們需要讓汽車有移動,剎車等功能。我們先要定義這個物件,就是建構函式。然後給汽車新增輪胎,發動機等,就相當於新增屬性,我們讓汽車跑起來,剎車,就相當於新增方法。而一個剎車系統,相當於物件中的一個模組。我們通過模組化來管理整個物件。這就是面向物件程式設計。而在我們給別人使用這個物件的時候,只需要讓別人例項化這個物件,然後就可以訪問物件中的屬性和物件中的方法了。

物件的幾種不同表現形式

json物件
var person = {"name":"tom","age":"10"}
此處用json實現一個person物件,json物件是物件的一種字面量表示形式

擴充套件知識:

json物件和json字串之間的轉換:
通過JSON.parse(str),把字串轉換成json物件,通過JSON.stringify(obj)把物件轉換成字串。
json物件和json協議之間的區別:
json協議是一種國際標準,用於前端與後端進行交換資料的格式。是一種規範,按照這種規範就能實現資料的傳遞。json物件是物件的字面量表示形式,相當於一個例項化後的物件。 用建構函式實現的物件
function Person (name,age) {
  this.name = name;
  this.age = age;
}
var person = new Person('Tom',10); // 例項化物件
console.log(person.name); // 此處我們通過點語法來訪問物件的屬性
console.log(person.age);
通過建構函式和原型來實現物件
function Person (name,age) {
  this.name = name;
  this.age = age;
}
Person.prototype = {
  say:function() {
    console.log(this.name + ' is saying');
  }
}
var person = new Person('Tom',10);
person.say(); // Tom is saying
擴充套件知識:通常,我們都用此種方法來例項化物件,為了節省記憶體,我們把物件的方法都放在原型裡面。為什麼呢? 在我們通過new例項化物件的時候,在記憶體中,會自動拷貝建構函式中的所有屬性和方法,用來給例項物件賦值,而不管我們例項化多少次,原型裡面的屬性和方法只生成一次,所以會節省記憶體。我們在例項化new的時候,系統做了如下幾件事情:
a.在記憶體中建立了一個空的物件,就像是這樣 var p = {};
b.拷貝物件中的屬性和方法到空物件中。
c.自動生成一個__proto__屬性指向類的原型。
有關c我們可以通過如下來驗證。
console.log(person.__proto__ === Person.prototype); // true
另外,由此引出兩個方法 hasOwnProperty() 和 isPrototypeOf()方法
hasOwnProperty:用來判斷某個方法或者屬性是否屬於該物件,備註:只會去尋找物件建構函式或者json物件裡面的屬性和方法,還用person舉例:
// 用三個if來 測試 hasOwnProperty
if(person.hasOwnProperty('name')){
  console.log('name exsit'); // 輸出 name exsit
}
if(person.hasOwnProperty('say')){
  console.log('say exsit'); // 不輸出
}
if(Person.prototype.hasOwnProperty('say')){
  console.log('say exsit'); // 輸出 say exsit
}
hasOwnProperty 此方法不會檢查該物件的原型鏈,被檢查的屬性和方法必須是物件本身的一個成員。 比如第二if,它不會檢查原型裡的say方法,只會在建構函式中去找,所以不輸出。 比如第三個if中用Person.prototype中,有輸出,是因為Person.perototype本身就是一個物件,它在自己裡面能找到say方法。
isPrototypeOf:它是基類物件Object的一個方法:Object.prototype.isPrototypeOf()。
先做個示例:obj1.isPrototypeOf(obj2),它的意思是obj1是否被包含在obj2的原型鏈中,下面是示例程式碼:
// 先建立4個建構函式
function A() {}
function B() {}
function C() {}
function D() {}

B.prototype = new A(); // 把B的原型指向A例項化後的物件
C.prototype = new B(); // 把C的原型指向B例項化後的物件
D.prototype = new C(); // 把D的原型指向C例項化後的物件

// 下面開始來測試
var d = new D();
if (B.prototype.isPrototypeOf(d)) {
  console.log('haha'); // 輸出 haha
}
從這裡可以看出 isPrototypeOf 和 instanceof 的區別,我們開始詳細的說下:
instanceof : mdn 上的api詳解:
isPrototypeOf : mdn 上的api詳解: 簡要的說:instanceof運算子可以用來判斷某個建構函式的prototype屬性所指向的物件是否存在於另外一個要檢測物件的原型鏈上。
isPrototypeOf() 方法測試一個物件是否存在於另一個物件的原型鏈上。

function A() {}
function B() {}
function C() {}
function D() {}

B.prototype = new A(); // 把B的原型指向A例項化後的物件
C.prototype = new B(); // 把C的原型指向B例項化後的物件
D.prototype = new C(); // 把D的原型指向C例項化後的物件

var d = new D();
var c = new C();
var b = new B();
var a = new A();


console.log(A.prototype.isPrototypeOf(d)); // true 這裡A是處於頂層,表示 A.prototype處於d的原型鏈上
console.log(B.prototype.isPrototypeOf(d)); // true
console.log(C.prototype.isPrototypeOf(d)); // true
console.log(D.prototype.isPrototypeOf(d)); // true


console.log(d instanceof C); // true
console.log(d instanceof B); // true
console.log(d instanceof A); // true
object instanceof AFunction  檢測的是AFunction.prototype是否在object的原型鏈中,而不是檢測AFunction自身。
語法:object instanceof constructor

isPrototypeOf 方法允許你檢查一個物件是否存在於另一個物件的原型鏈上。
語法:prototype.isPrototypeOf(object)

屬性進階

萬物皆屬性

方法從某種角度來說,也可以看成一個屬性,方法的定義,使用,在記憶體中的儲存,都基本一致。
function Test(){
  this.name = 'test';
  this.test = function() {
    console.log('test');
  }
}

萬物皆變數

所有的資料都是通過變數來儲存(管理)的,變數都是在記憶體中儲存的
var str = 'javascript';
var json = {"name":"javascript"}
var fn = function() {}
擴充套件知識:函式宣告和函式表示式的區別  函式宣告會被提升,函式表示式不會被提升,請看下面程式碼:
function fn() {}
var func = function(){};
上面的程式碼實際解析的順序是這樣的:

屬性分類

公有屬性、私有屬性、公有靜態屬性、類方法、原型方法、原型屬性
function Car(type,price) {
  this.type = type; // 公有屬性
  this.price = price;
  var private = 1; // private 是私有屬性,外界或者原型裡都無法訪問,只在建構函式內部能訪問
  this.run = function() { // 物件方法
    console.log('run');
  }
}
Car.prototype = {
  color:'black',// 原型屬性
  drive:function() { // 原型方法
    console.log('drive');
  },
  test:function() {
    console.log(private);
  }
}
Car.Weight = 1000; // 公有靜態屬性,只能通過建構函式來訪問,首字母一般大寫
Car.Fly = function(){ // 類方法 ,只能通過建構函式來訪問,首字母一般大寫
  console.log('car can\'t fly');
};

// 呼叫測試
var c = new Car('small',10000);
console.log(c.run()); // run
console.log(c.color); // black
console.log(Car.prototype.color); // black
console.log(c.drive()); // drive
console.log(Car.Weight); // 1000
console.log(Car.Fly()); // car can't fly
get,set取值器和設定器,用於對某一屬性進行包裝,用於取值和設定,js原生自帶的兩種。因為這個比較複雜我找到了一篇api文件,詳細說明了這個,還有 Object.defineProperty() 方法的介紹。

物件例項進階

在進行這個話題之前,我們需要了解一下,例項化後的物件是如何在記憶體中儲存的。 當我們例項化的時候,會在記憶體裡面開闢兩段區域: 棧專門儲存簡單變數,堆專門用來儲存物件。 當我們例項化的時候,會在記憶體中開闢一段區域,然後自動拷貝建構函式中所有的屬性和方法。 雖然都是通過同一個物件new出來的,但是不同的例項,會被分配到不同的空間,當我們更改一個例項的屬性時,不會影響到另一個例項。 但是有一點,如果你更改的是在原型裡的引用型別的屬性,那麼這個引用型別屬性時共享的,更改一個,會影響其他例項。 原因是原型裡面的引用型別屬性是全部共享的,值型別不會共享,舉例如下:
function Classroom(id,num,teacher) {
  this.id = id;
  this.num = num;
  this.teacher = teacher;
}
Classroom.prototype = {
  property:{
    blackbord:"only one",
    chalk:"110pieces"
  },
  other:'hehe'
}
var cr = new Classroom(3,2,'Tom');
cr.property.blackbord = 'has two'; // 修改引用型別的property屬性
cr.other = 'crhehe'; // 修改值型別的other屬性
var cr2 = new Classroom(1,2,'Jack');
console.log(cr2.property.blackbord); // has two ,這裡其它例項受到影響了
console.log(cr2.other); // hehe, 這裡其它例項不受影響。
每個例項物件都有兩個隱藏的屬性一個是__proto__,一個是constructor。例項的__proto__屬性來自它的建構函式,例項的constructor來自它的建構函式的原型裡,下面用瀏覽器的 repl 環境下測試來驗證這一說法,截圖驗證: repl裡驗證兩個隱藏屬性

原型鏈進階

原型

在js中只要是一個物件都會有一個原型,也就是prototype,那麼js設計者為什麼會這樣設計呢,因為這樣設計有個好處,就是節省記憶體。那麼,我們再次來說下,例項化一個物件的時候,系統都做了哪些事情:

1.建立一個空的物件

2.拷貝建構函式中的屬性和方法放到空物件裡

3.自動生成一個__proto__屬性指向類的原型


因為物件的方法中大都是大片的程式碼塊,如果都放在建構函式裡面就會有大量的記憶體開支,因為每次我們new的時候,都會重複上面三個步驟,而prototype裡的東西只在第一次new的時候生成,不會每次都生成,所以我們需要把那些比較佔用記憶體的方法放到原型裡面。
原型物件的特性

1.不管例項化多少次,原型物件只生成一次。

2.原型物件中的屬性和方法可以被所有例項訪問。

3.原型物件中引用型別的屬性是共享的,值型別的屬性是各自擁有的。

4.物件例項化後實際上生成了兩個物件,一個是建構函式物件,一個是原型物件。


原型鏈

我們知道,類的建構函式和類的原型(prototype)之間有一個聯絡,就是類的建構函式中的一個隱藏屬性__proto__ ,它指向了這個類的prototype。 而類的prototype裡面也有一個隱藏屬性constructor,指向了這個類的建構函式。 另外類的prototype裡面還有一個隱藏屬性__proto__ 指向了這個類的父類,由此實現了鏈條關係。 還在瀏覽器的repl環境裡舉個例子:
    示例隱藏屬性__proto__ 和 constructor
內建物件的原型鏈
分析:上面str是一個String物件的一個例項,例項有兩個隱藏屬性 __proto__ 和constructor
str.constructor === String.prototype.constructor   為true表示,例項的隱藏屬性constructor 來自類的原型。 
str.__proto__ === String.prototype   為true ,表示例項的__proto__指向類的原型
str.constructor === String 和 String.prototype.constructor === String   都為true 表示constructor指向類的建構函式
String.prototype.__proto__ === Object.prototype   為true 表示了繼承關係,String類原型中的__proto__指向了父類Object的原型
屬性和方法的搜尋機制
例項化後的物件首先遍歷自身的屬性和方法,如果沒有找到,就通過類建構函式中的__proto__去類的原型裡去找,如果在類原型裡面還沒有找到,那麼按著類的原型中的__proto__這個鏈條到上一級的類的原型中找,一直按著這種關係,直到搜尋到Object.prototype,如果還沒找到返回undefined,在系統中最終的鏈條是null,Object.prototype.__proto__ === null ,所以說null 是JavaScript面向物件的源頭。
階段性總結一下:js通過__proto__ 這個鏈條將建構函式和原型以及一層一層的原型直到最終的null聯絡起來,這就是所謂的原型鏈
① Object物件的__proto__ :Object物件是Function物件的一個例項 如下所示:
console.log(Object.constructor === Function); // true  
console.log(Object.__proto__ === Function.prototype); // true 
②不止是Object,在js中所有的內建物件都是Function的一個例項,如下所示:
console.log(String.constructor === Function); // true  
console.log(String.__proto__ === Function.prototype); // true  
  
console.log(Array.constructor === Function); // true  
console.log(Array.__proto__ === Function.prototype); // true  
  
console.log(Image.constructor === Function); // true  
console.log(Image.__proto__ === Function.prototype); // true  
// ... and so on  
  
console.log(Number.__proto__ === Function.prototype); // true  
console.log(Boolean.__proto__ === Function.prototype); // true  
console.log(String.__proto__ === Function.prototype); // true  
console.log(Object.__proto__ === Function.prototype); // true  
console.log(Function.__proto__ === Function.prototype); // true  
console.log(Array.__proto__ === Function.prototype); // true  
console.log(RegExp.__proto__ === Function.prototype); // true  
console.log(Error.__proto__ === Function.prototype); // true  
console.log(Date.__proto__ === Function.prototype); // true  
console.log(Image.__proto__ === Function.prototype); // true
③ 而內建物件的例項,則各自指向各自的原型,如下所示:
var str = new String('hello');  
var arr = new Array(1,2,3);  
var img = new Image();  
console.log(str.__proto__ === String.prototype); // true;  
console.log(arr.__proto__ === Array.prototype); // true;  
console.log(img.__proto__ === Image.prototype); // true; 
④ 將隨便挑一個比如String物件把它單獨拿出來看,會有這麼一條關係鏈,如下:
var str = new String('hello');
console.log(str.__proto__ === String.prototype); // true
console.log(String.prototype.__proto__ === Object.prototype); // true
console.log(String.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
自定義物件的原型鏈
function A() {};
var a = new A();
console.log(a.__proto__ === A.prototype); // true
console.log(A.constructor === Function); // true
console.log(A.__proto__ === Function.prototype); // true
console.log(Function.constructor === Function); // true
console.log(Function.__proto__ === Function.prototype); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(A.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
總結null是鏈條的最終的源頭。 Object.prototype是一切鏈式查詢的最後一站。 Function.prototype是一切內建函式和自定義建構函式的原型物件。 而屬性搜尋機制的底層就是通過__proto__屬性連結起來的。所以__proto__才是面向物件的底層實現機制,是理解面向物件本質所在。