在JavaScript
中,對象是一組無序的相關屬性和方法的集合,所有的事物都是對象,例如字符串、數值、數組、函數等。
對象是由屬性和方法組成的。
- 屬性:事物的特徵,在對象中用屬性來表示(常用名詞)
- 方法:事物的行爲,在對象中用方法來表示(常用動詞)
0. 創建對象的三種方式
0.1 使用對象字面量創建對象:
就是花括號 { } 裏面包含了表達這個具體事物(對象)的屬性和方法;{ } 裏面採取鍵值對的形式表示
var star = {
name : 'Jon',
age : 18,
sex : '男',
sayHi : function(){
alert('我好帥');
}
};
0.2 利用 new Object 創建對象
// 創建空對象
var andy = new Obect();
通過內置構造函數Object創建對象,此時andy變量已經保存了創建出來的空對象。
// 通過對象操作屬性和方法的方式,來爲對象增加屬性和方法
andy.name = '張三';
andy.age = 18;
andy.sex = '男';
andy.sayHi = function(){
alert('我好帥~');
}
注意:Object()
:第一個字母大寫
0.3 利用構造函數創建對象
構造函數:是一種特殊的函數,主要用來初始化對象,即爲對象成員變量賦初始值,它總與 new 運算符一起使用。我們可以把對象中一些公共的屬性和方法抽取出來,然後封裝到這個函數裏面。
構造函數的封裝格式:
function 構造函數名(形參1,形參2,形參3) {
this.屬性名1 = 參數1;
this.屬性名2 = 參數2;
this.屬性名3 = 參數3;
this.方法名 = 函數體;
}
構造函數的調用格式:
var obj = new 構造函數名(實參1,實參2,實參3)
以上代碼中,obj即接收到構造函數創建出來的對象。
注意事項:
- 構造函數約定首字母大寫(建議這樣操作)。
- 函數內的屬性和方法前面需要添加 this ,表示當前對象的屬性和方法。
- 構造函數中不需要 return 返回結果。
- 當我們創建對象的時候,必須用 new 來調用構造函數。
使用構造函數創建一個對象的過程:
-
在構造函數代碼開始執行之前,創建一個空對象;
-
修改this的指向,把this指向創建出來的空對象;
-
執行函數的代碼
-
在函數完成之後,返回創建出來的對象
1. 變量、屬性、函數、方法的區別
1.1 屬性和變量的區別:
屬性是對象的一部分,而變量不是對象的一部分,變量是單獨存儲數據的容器
變量:單獨聲明賦值,單獨存在
屬性:對象裏面的變量稱爲屬性,不需要聲明,用來描述該對象的特徵
1.2 方法和函數的區別:
方法是對象的一部分,函數不是對象的一部分,函數是單獨封裝操作的容器
函數:單獨存在的,通過“函數名()
”的方式就可以調用
方法:對象裏面的函數稱爲方法,方法不需要聲明,使用“對象.方法名()
”的方式就可以調用,方法用來描述該對象的行爲和功能。
2. 靜態成員和實例成員
我們知道,JS
中的一切皆是對象,那麼構造函數其實也是個對象,同時通過new 構造函數
也可以創造出對象,我們一般也稱這樣的對象是實例。
function Cat(n, m){
this.name = n;
this.age = m;
this.climb = function(){
console.log('跑的真快');
}
}
var c1 = new Cat('貓', 3);
// 使用instanceof可以檢測一個對象是否是另一個對象的實例
console.log(c1 instanceof Cat);
像上面的代碼中,構造函數內使用this
開頭設置的成員,在獲得實例的時候,自動成爲實例的成員。
// Cat 的構造函數
function Cat(n, m){
this.name = n;
this.age = m;
this.climb = function(){
console.log('跑的真快');
}
}
// 爲構造函數對象動態添加成員(靜態成員)
Cat.jump = function(){
console.log('我會跳');
}
var c1 = new Cat('貓', 3);
// 此時調用c1.jump()會報錯
// c1.jump();
// 實例成員不能被構造函數對象調用
// Cat.climb();
// 爲實例動態添加成員
c1.jump = function(){
console.log('跳的真高');
}
c1.jump();
Cat.jump();
// 對於一個實例動態添加的成員並不會被其它實例共有
var c2 = new Cat('黑貓', 2)
// c2.jump();
// Tiger 的構造函數
function Tiger(){
this.say = function(){
console.log('我是森林之王');
}
}
// 使用Cat的實例作爲原型
Tiger.prototype = c2;
var t1 = new Tiger();
// 原型中有的成員也是實例成員
t1.climb();
通過上面的代碼我們可以看出:
- 實例成員:構造函數中this上添加的成員和後期動態添加的成員
- 靜態成員:構造函數本身添加的成員或者後期動態添加的成員
- 對於一個實例動態添加的成員並不會被其它實例共有
- 如果構造函數A有原型,那麼構造函數A原型中的成員也是通過該構造函數A獲取的實例的實例成員
通俗來講:
實例成員:由構造函數創建出來的實例能直接訪問的屬性和方法,包括:實例本身的成員 以及 原型中的所有的屬性和方法 和 後期動態添加的成員
靜態成員:由構造函數直接訪問到的屬性和方法。
3. __proto__(原型)
、constructor(構造器)
和 prototype(原型類型)
在JS
中, __proto__
、constructor
和 prototype
三者關係是比較難搞懂的,爲了搞懂它們,我們必須記住3個基本點:
prototype
屬性是函數所獨有的,其它對象沒有。__proto__
和constructor
屬性是對象獨有,不過函數也是對象,因此函數也有這兩個屬性。__proto__
並不是JS
標準中規定的屬性,不同的瀏覽器解釋器實現的名字可能不一樣,在ES標準中它叫[[Prototype]]
。
function Cat(){
}
var c1 = new Cat();
var o1 = {};
console.dir(Cat);
console.dir(c1);
console.dir(o1);
3.1 __proto__
屬性
__proto__
屬性 都是從一個對象指向一個對象,即指向它們的原型對象(可以理解爲父對象)。它的作用就是當訪問一個對象的屬性時,如果該對象內部不存在這個屬性,那麼就會去它的__proto__
屬性所指向的那個對象(父對象)裏找,如果父對象也不存在這個屬性,則繼續往父對象的__proto__
屬性所指向的那個對象(爺爺對象)裏找,依次類推,一直到最頂級對象,如果還找不到,那麼就會報錯。這個從當前對象一直到最頂級對象,通過__proto__
屬性連起來的一條鏈,就是我們說的原型鏈。
3.2 prototype
屬性
prototype
屬性是函數獨有的屬性,它是從一個函數指向一個對象,表示函數的原型對象,通過new
這個函數獲得到的實例對象的__proto__
屬性都指向這個對象。
function Cat(){
this.describe = '貓';
}
var c1 = new Cat();
function Tiger(){
}
// 指定原型
Tiger.prototype = c1;
var t1 = new Tiger();
// 他們兩個完全一樣,就是同一個對象
console.log(t1.__proto__ === Tiger.prototype);
var t2 = new Tiger();
// 他們兩個完全一樣,就是同一個對象
console.log(t2.__proto__ === Tiger.prototype);
由上面的代碼我們可以看出,其實prototype
屬性其實主要是爲了 共享(重用)成員(屬性和方法),通過該函數得到實例,都能共享(重用)到這些成員。試想一下,如果每一次繼承,都在內存中創建一份成員方法,那是多麼的消耗內存啊。
3.3 constructor
屬性
constructor
屬性也是對象才擁有的(其實是__proto__
指向的對象或者prototype
指向的對象擁有的),它是從一個對象指向一個函數,含義就是指向該對象的構造函數,
,有一些特殊的屬性的構造函數就是它自己本身。
// Cat
function Cat(){
this.describe = '貓';
}
var c1 = new Cat();
console.log(c1);
console.log(c1.constructor === Cat)
// Tiger
function Tiger(){
this.name = '老虎';
}
// 指定原型
Tiger.prototype = c1;
var t1 = new Tiger();
console.log(t1);
// 原型對象發生了覆蓋
console.log(t1.constructor === Cat)
// NortheastTiger
function NortheastTiger(){
this.xxx = '東北虎';
}
// 指定原型
NortheastTiger.prototype = t1;
var n1 = new NortheastTiger();
console.log(n1)
// 原型對象發生了覆蓋
console.log(n1.constructor === Cat)
每個對象都可以找到其對應的constructor
,因爲創建對象的前提是需要有constructor
,而這個constructor
可能是對象自己本身顯式定義的或者通過__proto__
在原型鏈中找到的,故通過函數創建的對象即使自己沒有constructor
屬性,它也能通過__proto__
找到對應的constructor
。
constructor
修正
上面的代碼,可能很多人會在後面發生迷惑,我們看一下下面的代碼:
// Cat
function Cat() {
this.describe = '貓';
}
var c1 = new Cat();
// Tiger
function Tiger() {
this.name = '老虎';
}
// Tiger.prototype 指向的對象的 constructor 指向構造函數本身,也就是Tiger
console.log(Tiger.prototype);
如果我們改變了Tiger.prototype
的指向,會造成原有的constructor
丟失:
// Cat
function Cat() {
this.describe = '貓';
}
var c1 = new Cat();
// Tiger
function Tiger() {
this.name = '老虎';
}
// Tiger.prototype 指向的對象的 constructor 指向構造函數本身,也就是Tiger構造函數
console.log(Tiger.prototype);
Tiger.prototype = c1;
// 此時 Tiger.prototype 指向的對象 是 c1 , c1的constructor執行的是自己的構造函數,也就是Cat構造函數
// 因此通過new Tiger() 獲取到的對象的 constructor指向也爲Cat構造函數
console.log(Tiger.prototype);
// t1的constructor指向Cat構造函數
var t1 = new Tiger();
console.log(t1.constructor);
很明顯,這是有偏差的,我們可以手動將其修正:
function Cat() {
this.describe = '貓';
}
var c1 = new Cat();
// Tiger
function Tiger() {
this.name = '老虎';
}
// 從這裏開始出現偏差
Tiger.prototype = c1;
// 修正一下
Tiger.prototype.constructor = Tiger
// 再獲取的對象的constructor指向就正確了
var t2 = new Tiger();
console.log(t2.constructor);