<script type="text/javascript"> function createObject(name, age) { //集中實例化的函數 var obj = new Object(); obj.name = name; obj.age = age; obj.run = function () { return this.name + this.age + '運行中...'; }; return obj; } var box1 = createObject('Lee', 100); //第一個實例 var box2 = createObject('Jack', 200); //第二個實例 console.log(box1.run()); console.log(box2.run()); </script>構造函數
解決了重復實例化的問題,又解決了對象識別的問題<script type="text/Javascript"> function Box(name, age) { //構造函數模式 this.name = name; this.age = age; this.run = function () { return this.name + this.age + '運行中...'; }; } var box1 = new Box('Lee', 100); //new Box()即可 var box2 = new Box('Jack', 200); console.log(box1.run()); console.log(box1 instanceof Box); //很清晰的識別他從屬于Box </script>構造函數的方法有一些規范:1.函數名和實例化構造名相同且大寫,(PS:非強制,但這么寫有助于區分構造函數和普通函數);2.通過構造函數創建對象,必須使用new運算符。構造函數實例化對象過程:1.當使用了構造函數,并且new 構造函數(),那么就后臺執行了new Object();2.將構造函數的作用域給新對象,(即new Object()創建出的對象),而函數體內的this就代表new Object()出來的對象。3.執行構造函數內的代碼;4.返回新對象(后臺直接返回)。構造函數和普通函數的唯一區別,就是他們調用的方式不同。只不過,構造函數也是函數,必須用new運算符來調用,否則就是普通函數。var box = new Box('Lee', 100); //構造模式調用alert(box.run());Box('Lee', 20); //普通模式調用,無效var o = new Object(); Box.call(o, 'Jack', 200) //對象冒充調用alert(o.run()); 函數屬性引用問題:var box1 = new Box('Lee', 100); //傳遞一致var box2 = new Box('Lee', 100); //同上alert(box1.name == box2.name); //true,屬性的值相等alert(box1.run == box2.run); //false,方法其實也是一種引用地址為了解決函數引用地址不一致的問題,可以使用外部函數:function Box(name, age) { this.name = name; this.age = age; this.run = run;}function run() { //通過外面調用,保證引用地址一致 return this.name + this.age + '運行中...';}雖然使用了全局的函數run()來解決了保證引用地址一致的問題,但這種方式又帶來了一個新的問題,全局中的this在對象調用的時候是Box本身,而當作普通函數調用的時候,this又代表window。原型
我們創建的每個函數都有一個PRototype(原型)屬性,這個屬性是一個對象,它的用途是包含可以由特定類型的所有實例共享的屬性和方法,邏輯上可以這么理解:prototype是通過調用構造函數而創建的原型對象。簡單的說就是靜態屬性和方法。<script type="text/javascript"> function Box() {};//聲明一個構造函數 Box.prototype.name = 'Lee';//在原型里添加屬性 Box.prototype.age = 100; Box.prototype.run = function () {//在原型里添加方法 return this.name + this.age + '運行中...'; }; console.log("實例化之前:"); console.log(Box.prototype); var box1 = new Box(); console.log("實例化之后:"); console.log(Box.prototype); console.log(Box.prototype == box1.__proto__); //true,證明Box的原型對象在實例化之前就已經存在 var box2 = new Box(); console.log(box1.run == box2.run);//true,方法的引用地址保持一致 </script>運行結果:原型對象中的constructor是一個函數,代表構造函數本身為了更進一步了解構造函數的聲明方式和原型模式的聲明方式,我們通過圖示來了解一下:構造函數模式:
原型模式:
在原型模式聲明中,多了兩個屬性,這兩個屬性都是創建對象時自動生成的。__proto__屬性是實例指向原型對象的一個指針。
<script type="text/javascript"> function Box() {} //聲明一個構造函數 Box.prototype.name = 'Lee'; //在原型里添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型里添加方法 return this.name + this.age + '運行中...'; }; var box = new Box(); console.log(box.__proto__); var obj = new Object(); console.log(obj.__proto__);</script>在瀏覽器里查看打印的結果:可以看到,Box實例對象的__proto__屬性指向的Box構造函數的原型對象,原型對象里的constructor是原型本身,Box構造函數的原型對象默認是繼承自Object。
<script type="text/javascript"> function Box() {} //聲明一個構造函數 Box.prototype = new Number(2); var box = new Box(); console.log(box.__proto__); console.log(box.__proto__.__proto__); var obj = new Number(); console.log(obj); console.log(obj.__proto__);</script>查看輸出結果:從結果可以看出,Number的原型對象重寫了toString和valueOf方法,原型對象繼承在Object判斷一個對象是否指向了該構造函數的原型對象,可以使用isPrototypeOf()方法來測試。
alert(Box.prototype.isPrototypeOf(box));isPrototypeOf()函數執行流程:1.先查找構造函數實例里的屬性或方法,如果有,立刻返回;如果構造函數實例里沒有,則去它的原型對象里找,如果有,就返回對象屬性和方法的訪問順序:1.先查找構造函數實例里的屬性或方法,如果有,立刻返回;如果構造函數實例里沒有,則去它的原型對象里找,如果有,就返回;<script type="text/javascript"> function Box() {};//聲明一個構造函數 Box.prototype.name = 'Lee';//在原型里添加屬性 var box = new Box(); box.name = "li";//li console.log(box.name);//覆蓋原型中的屬性 delete box.name;//刪除構造函數里的屬性 console.log(box.name);//Lee </script>hasOwnProperty():判斷對象實例中是否有某個屬性。in操作符:屬性是否存在于實例或者原型中。<script type="text/javascript"> function Box(){ this.name = "lisong"; } Box.prototype.age = 26; function isProperty(object, property) { //判斷原型中是否存在屬性 return !object.hasOwnProperty(property) && (property in object); } var box = new Box(); console.log(isProperty(box, 'name')) //false console.log(isProperty(box, 'age')) //true</script>使用字面量創建原型對象:<script type="text/javascript"> function Box(){} console.log(Box.prototype); Box.prototype = { age:26, name:"lisong" } console.log(Box.prototype); var box = new Box(); console.log(box.name);//lisong console.log(Box.prototype.constructor == Box);//false console.log(Box.prototype.constructor == Object);//true</script>運行結果:從結果可以看出,用字面量創建的原型對象,其constructor屬性會指向Object的構造函數,而不會指向Box的構造函數
組合構造函數+原型模式
function Box(name, age) { //不共享的使用構造函數 this.name = name; this.age = age; this. family = ['父親', '母親', '妹妹'];};Box.prototype = { //共享的使用原型模式 constructor : Box, run : function () { return this.name + this.age + this.family; }};這種混合模式很好的解決了傳參和引用共享的大難題。是創建對象比較好的方法動態原型模式
原型模式,不管你是否調用了原型中的共享方法,它都會初始化原型中的方法,并且在聲明一個對象時,構造函數+原型部分讓人感覺又很怪異,最好就是把構造函數和原型封裝到一起。function Box(name ,age) { //將所有信息封裝到函數體內 this.name = name; this.age = age; if (typeof this.run != 'function') { //僅在第一次調用的初始化 Box.prototype.run = function () { return this.name + this.age + '運行中...'; }; }}var box = new Box('Lee', 100);alert(box.run());寄生構造函數
寄生構造函數,其實就是工廠模式+構造函數模式。這種模式比較通用,但不能確定對象關系。function Box(name, age) { var obj = new Object(); obj.name = name; obj.age = age; obj.run = function () { return this.name + this.age + '運行中...'; }; return obj;}假設要創建一個具有額外方法的引用類型。由于之前說明不建議直接String.prototype.addstring,可以通過寄生構造的方式添加。function myString(string) { var str = new String(string); str.addstring = function () { return this + ',被添加了!'; }; return str;}var box = new myString('Lee'); //比直接在引用原型添加要繁瑣好多alert(box.addstring());原型鏈繼承
<script type="text/javascript"> function Box() { //Box構造 this.name = 'Lee'; } function Desk() { //Desk構造 this.age = 100; } Desk.prototype = new Box(); //Desc繼承了Box,通過原型,形成鏈條 var desk = new Desk(); console.log(desk.age); console.log(desk.name); //得到被繼承的屬性 function Table() { //Table構造 this.level = 'AAAAA'; } Table.prototype = new Desk(); //繼續原型鏈繼承 var table = new Table(); console.log(table.name); //繼承了Box和Desk console.log(table instanceof Object); //true console.log(desk instanceof Table); //false,desk是table的超類 console.log(table instanceof Desk); //true console.log(table instanceof Box); //true</script>對象冒充繼承(借用構造函數)
對象冒充繼承主要解決超類型無法傳參的問題<script type="text/javascript"> function Box(age) { this.name = ['Lee', 'Jack', 'Hello'] this.age = age; } function Desk(age) { Box.call(this, age);//對象冒充,給超類型傳參 } var desk = new Desk(200); console.log(desk.age); console.log(desk.name);</script>組合繼承(原型鏈+對象冒充)
借用構造函數雖然解決了剛才兩種問題,但沒有原型,復用則無從談起。所以,我們需要原型鏈+借用構造函數的模式,這種模式成為組合繼承。<script type="text/javascript"> function Box(age) { this.name = ['Lee', 'Jack', 'Hello'] this.age = age; } Box.prototype.run = function () { return this.name + this.age; }; function Desk(age) { Box.call(this, age); //對象冒充,第二次調用超類Box } Desk.prototype = new Box(); //原型鏈繼承,第一次調用超類Box var desk = new Desk(100); console.log(desk.run()); function isPropertyToProto(object, property) { //判斷原型中是否存在屬性 return !object.hasOwnProperty(property) && (property in object); } console.log(isPropertyToProto(desk,"run"));//true console.log(desk.hasOwnProperty("age"));//true console.log(desk.__proto__.hasOwnProperty("age"));//true</script>組合繼承缺點:某些屬性在構造函數里和原型對象里都有,重復了,還有即使超類被調用了兩次原型式繼承
借助原型并基于已有的對象創建新對象,同時還不必因此創建自定義類型。<script type="text/javascript"> function obj(o) { //傳遞一個字面量函數 function F() {} //創建一個構造函數 F.prototype = o; //把字面量函數賦值給構造函數的原型 return new F(); //最終返回出實例化的構造函數 } var box = { //字面量對象 name : 'Lee', arr : ['哥哥','妹妹','姐姐'] }; var box1 = obj(box); //傳遞</script>寄生式繼承(原型式+工廠模式)
原型式+工廠模式結合而來,目的是為了封裝創建對象的過程。<script type="text/javascript"> function obj(o) { //傳遞一個字面量函數 function F() {} //創建一個構造函數 F.prototype = o; //把字面量函數賦值給構造函數的原型 return new F(); //最終返回出實例化的構造函數 } var box = { //字面量對象 name : 'Lee', arr : ['哥哥','妹妹','姐姐'] }; function create(o) { //封裝創建過程 var f= obj(o); f.run = function () { return this.arr; //同樣,會共享引用 }; return f; } var box1 = create(box); console.log(box1.run());//["哥哥", "妹妹", "姐姐"]</script>上面的代碼主要是為了封裝額外的方法和屬性寄生組合繼承(寄生+組合)
解決組合繼承超類調用兩次的問題<script type="text/javascript"> function Box(name) { this.name = name; this.arr = ['哥哥','妹妹','父母']; } Box.prototype.run = function () { return this.name; }; function Desk(name, age) { Box.call(this, name);//對象冒充 this.age = age; } function obj(o) { function F() {} F.prototype = o; return new F(); } function create(box, desk) {//原型繼承 var f = obj(box.prototype); f.constructor = desk; desk.prototype = f; /* desk.prototype = box.prototype; //會同時修改掉box的constructor,所以不能使用desk.prototype = box.prototype desk.prototype.constructor = desk; */ } create(Box,Desk); var d = new Desk("lisong",26); console.log(d.run());//lisong </script>
新聞熱點
疑難解答