1. 程式人生 > >理解JS的原型物件,建構函式,物件之間的關係

理解JS的原型物件,建構函式,物件之間的關係

第一次看原型的時候感覺沒咋看懂,這周又重新把原型物件,建構函式,物件看了一遍,理解頗多。在理解這三者及這三者的關係之前必須先熟悉的掌握一些前提知識,不然容易混淆。

前提補充:

  1. 物件(鍵值對的集合)的動態特性:
    物件創建出來為物件新增新的屬性和方法/訪問物件的方法和屬性:
  • 點語法 :使用點語法進行賦值的時候,如果物件中存在該屬性,則是修改操作;如果物件中不存在該屬性,則是給物件新增此屬性並且賦值
  • [ ] 法 :物件名[屬性名], 這裡的屬性名是字串
  1. 建立物件的方式
  • 物件字面量:
var obj={key: value,key: value,key: value};
  • 使用內建建構函式
var obj=new
Object();
  • 工廠函式
function createObj(name,age){
            var o =new Object();
            o.name = name;
            o.age = age;

            o.sayName = function () {
                console.log("我的名字是:"+name);
            }
            return o;
        }

        var obj = createObj("張三",18);
  1. this和new: new建立一個物件例項,這個物件是使用者自定義的,也可以是一些帶建構函式的系統自帶的物件,new關鍵字讓this不再指向window,而是指向new的那個物件(new改變this指向的物件)。
function fun() {   
    alert(this);
}
fun();// 這個this 指向的是window(視窗)


function fn() {
    alert(this);
}
new fn();  
// 添加了new 的函式 this 指向的是 新的物件
  1. 建構函式:建構函式也是函式,只是通常用來初始化物件,並且和new關鍵字一起出現。建構函式首字母務必是大寫。
  1. 建構函式的返回值:
  • 如果不寫返回值,預設返回的是新創建出來的物件 (一般都不寫return語句)
  • 如果寫return語句 return的是空值(return;),或者是基本型別的值 或者null,都會預設返回新創建出來的物件
  • 如果返回的是object型別的值,將不會返回剛才新建立的物件,而是返回return後面的值
  • 一個簡單建構函式的例子:
function Person(name,age) {  //記得首字母大寫
    this.name = name;
    this.age = age;
    this.sayHello = function () {
        console.log("Hey");
    }
    //此處一般不寫return
}

var p = new Person("張三",18);   //建構函式預設返回新建立的物件
//new 使this指向新創建出來的物件
console.log(p);
p.sayHello();
  • 也可以這樣寫:
//此處是方法
function sayNameMe(){
    console.log("我叫"+ this.name);
}

function Person(name,age) {
    this.name = name;
    this.age = age;
    this.sayName = sayNameMe;
    //把方法寫在外面
}

var zs = new Person("張三",age);
zs.sayName();

這樣寫的原因:如果在建構函式中定義函式(新增方法),那麼每次建立物件,都會重新建立該函式。但是函式內部程式碼完全相同,就造成了資源浪費。為了處理這個問題,我們要讓所有的物件共用一個方法,在建構函式的外部定義好該函式,將該函式賦值給建構函式內的方法。但是使用這種方法也會導致全域性變數增多(每一個方法對應的函式都是一個新的全域性變數)。

  • 例項化和示例:例項化就是用建構函式new出來的新物件的過程,而一個個新物件就是一個個示例。
上面的補充主要是為了大家可以更好的掌握原型,那麼這時候你肯定想說原型到底是什麼?為什麼要用原型?下來我們就開始介紹原型啦。為什麼要用原型?現在我們已經知道了,如果單純的使用建構函式(把方法放到函式的外面)會使全域性變數增多,造成汙染,而解決這個問題的辦法就是使用原型。

原型prototype


  1. 什麼是原型:其實原型是一個物件。是在建構函式建立的時候,系統會自動的給這個建構函式關聯一個物件,這個物件就是原型。
  2. 原型的作用:原型中的屬性可以被它的建構函式建立的每一個物件使用。所以我們可以將建構函式中需要建立的函式,放到原型物件中儲存,解決上面提到的問題。
function Person(name,age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        console.log("我叫"+this.name);
    }
}
var zs = new Person("張三",18);
  1. 訪問原型物件的方法:

1.通過建構函式訪問原型物件:建構函式.prototype

console.log(Person.prototype);

2.通過物件訪問原型物件:物件.--proto--

--proto--是一個非標準屬性,原型物件中的屬性。可以使用 物件.--proto-- 去訪問原型物件

  1. new物件 訪問 原型裡的屬性或者方法:
  1. 點語法:物件 . 原型裡的屬性

注意:用點語法訪問屬性和方法時,會首先在物件自己內部查詢,如果找到了就直接只用,如果沒有找到才去原型中查詢,如果找到了直接使用,如果原型中也沒有的話屬性會Undefined,方法會報錯。

2.建構函式.prototype[屬性] 3.物件.--prototype--.屬性

  1. 給原型物件新增屬性/方法:

1.點語法:建構函式 . prototype . 新的屬性/方法

Person.prototype.sayAge = function () {
    console.log("我"+this.age+"歲");
}

2.[ ]法:建構函式 . prototype [新的屬性/方法]

Person.prototype["sayAge"]=fuction () {
    console.log("我"+this.age+"歲");
}

3.直接替換原型物件:注意在替換原型之前建構函式 創造的物件的原型和替換後建構函式建立的物件的原型不同

function Person(name,age){
	this.name=name;
	this.age=age;
}
Person.prototype.sayName=function(){
	console.log("我叫"+this.name);
}
var p1=new Person("張三",18);
//直接替換原型物件
Person.prototype={
	askName: function(){
		console.log("我的名字是"+this.name);
	}
}
var p2= new Person("張三",18);
//	p2.sayName();  替換後和之前的原型不一樣 不能再使用
p2.askName();

6.使用原型的注意事項:

  • 使用物件訪問屬性時,如果在本身內找不到就會在原型中找,但是使用點語法給賦值(注意是賦值)時,並不會去原型中查詢。點語法賦值時,如果物件沒有該屬性,就會給物件新增該屬性並賦值,如果有該屬性則是修改屬性值,而不會修改原型中的東西。意思就是點語法賦值的時候不會影響原型中的屬性。
//如下:
function Person(){
}
Person.prototype.name = "張三";
Person.prototype.age = 18;

var p = new Person();
console.log(p.name);//張三

p.name = "李四";
console.log(p.name);//李四

var p1 = new Person();
console.log(p1.name);//張三
  • 當原型中的屬性是引用型別的屬性時,那麼所有的物件共享該屬性,並且一個物件修改了該引用型別屬性的成員時,其他物件也會受到影響。(這裡你需要知道引用型別和值型別的不同處)。
//如下:
function Person(){

}
var x = {
    name:"張三",
    age:18
};
Person.prototype.peo =x;//原型中的屬性是引用型別
var p = new Person();
console.log(p.peo.name);//張三

Person.prototype.peo = {
    name:"李四"
};
var p1 =new Person();
console.log(p1.peo.name);//李四
console.log(p.peo.name);//李四
  • 一般情況下不會將屬性放到原型物件中,一般情況下原型中只會放需要共享的方法。
  1. constructor屬性:
    原型物件在構造出來時就會預設有一個constructor屬性,指向對應的建構函式,當然該建構函式構造出來的物件也可以使用該屬性(物件可以使用它的建構函式的原型物件中所有得屬性和方法)
如下:
function Person(name, age){
    this.name = name;
    this.age = age;
}
var p = new Person("張三",18);
var p1 = new p.constructor("張三1",19);
//相當於直接使用 new Person()

console.log(p1);

注意:在使用新的物件替換預設的原型物件時,原型物件中的constructor屬性會變為Object,所有當你需要為了保證 建構函式--原型物件--物件之間關係的合理性,應該在替換原型物件時,給新的物件新增constructor屬性(經常:建構函式.prototype.constructor=建構函式)。


  1. 將屬性寫在原型裡與將屬性寫在建構函式裡有什麼不同:
  • 把方法寫在原型中比寫在建構函式中消耗的記憶體更小,因為在記憶體中一個類的原型只有一個,寫在原型中的行為可以被所有例項共享,例項化的時候並不會在例項的記憶體中再複製一份 而寫在類中的方法,例項化的時候會在每個例項中再複製一份,所以消耗的記憶體更高 所以沒有特殊原因,我們一般把屬性寫到類中,而行為寫到原型中
  • 建構函式中定義的屬性和方法要比原型中定義的屬性和方法的優先順序高,如果定義了同名稱的屬性和方法,建構函式中的將會覆蓋原型中的
  1. hasOwnProperty:
    用來判斷一個物件是否有你給出名稱的屬性或物件,返回值為Boolean型別。不過需要注意的是,此方法無法檢查該物件的原型鏈中是否具有該屬性,該屬性必須是物件本身的一個成員。
function Person(){

}
var p = new Person();
p.name="李四";
console.log(p.hasOwnProperty("name"));//true
console.log(p.hasOwnProperty("__proto__"));//false
  1. isPrototypeOf:
    用來判斷指定物件object1是否存在於另一個物件object2的原型鏈中,是則返回true,否則返回false。
// object1.isPrototypeOf(object2); 
// object1是一個物件的例項; 
// object2是另一個將要檢查其原型鏈的物件。
  1. propertyIsEnumerable:
    用來檢測屬性是否屬於某個物件的,如果檢測到了,返回true,否則返回false.
  • 這個屬性必須屬於例項的,並且不屬於原型.
  • 這個屬性必須是可列舉的,也就是自定義的屬性,可以通過for..in迴圈出來的.
  • ps:建構函式裡面的屬性也是例項的屬性
  • 所以可以用來判斷 :1.屬性是否屬於物件本身 2.判斷屬性是否可以被遍歷
  1. Object.defineProperty():
  • 語法:Object.defineProperty(obj, prop, descriptor)
  • 引數說明: obj:必需,目標物件;
    prop:必需,需定義或修改的屬性的名字;
    descriptor:必需,目標屬性所擁有的特性;
  • 返回值:傳入函式的物件。即第一個引數obj
  • 我們可以給這個屬性設定一些特性,比如是否只讀不可以寫;是否可以被for..in或Object.keys()遍歷。
  1. toString 和 toLocaleString
ar o = {};
// console.log(o.toString());
// console.log(o.toLocaleString());
//
// var now = new Date();
// console.log(now.toString());
// console.log(now.toLocaleString());
  1. valuOf():用於返回指定物件的原始值。該方法屬於Object物件,由於所有的物件都"繼承"了Object的物件例項,因此幾乎所有的例項物件都可以使用該方法。
  • 語法:object.valueOf()
  • 在物件參與運算的時候,預設的會先去呼叫物件的valueOf方法,如果valueOf獲取到的值,無法進行運算 ,就去去呼叫toString方法最終做的就是字串拼接的工作。
function Person(){
	this.valueOf=function(){
		return 1;
	}
}
var p = new Person();
console.log( 1 + p); //2   計算
function Person(){
	
}
var p = new Person();
console.log( 1 + p); //1[object Object]  拼接