搜尋
首頁web前端js教程JavaScript中的prototype原型學習指南_基礎知識

原型是什麼

Function 類型有一個屬性 prototype,直接翻譯過來就是原型。這個屬性就是一個指針,指向一個對象,這個對象包含一些屬性和方法,這些屬性和方法會被目前函數產生的所有實例(對象)所共享。

這句話根據前面所說的,細細琢磨下來,就可以得到下面程式碼:

function Person(){
 ...
}
Person.prototype = {
 country : 'china',
 sayName : function(){
  ...
 }
}

先建立了一個 Function 類型的實例 person,然後 person 的方法 prototype 是一個對象,就宣告指向了一個對象。這個物件裡面的屬性和方法,會被目前 person 函數產生的實例所共用。也就是說:

person1 = new Person();
person2 = new Person();

person1 和person2 都是透過Person 這個Function 類型實例,再次產生的實例,它們兩個都有共同的屬性country 和方法sayName,因為它們都有某個指標(__proto__),直接指向Person.prototype 所指向的對象。不過要注意 __proto__ 這個指標是不標準的,只有 Chrome 和 Firefox 等瀏覽器自己定義的,實際中,也不會用到這個屬性,只是作為理解 prototype 來用:

201659145452510.png (668×270)

關於原型等用法,後面會更具體的講到。

建立物件的模式

下面,我們就來看下創建物件的方法和常用模式,以及它們之間的優缺點。

1.工廠模式

就像工廠一樣,抽象了創建具體物件的過程,用函數來封裝以特定介面建立物件的細節。透過使用函數取代部分重複工作,程式碼如下:

function createPerson(name, age, job){
 var o = new Object();
 o.name = name;
 o.age = age;
 o.job = job;
 o.sayName = function(){
  alert(this.name);
 };
 return o;
}
var person1 = createPerson("jiangshui","22","engineer");

這樣就創建出來了一個人,工廠模式解決了多個相似對象重複創建問題,但是沒有解決對象識別問題。只是單純的創造了一個對象,而不管這個對像是從人類模版還是動物模版創建的,無法區分這個對象的類型。

2.建構子模式

建立一個自訂的建構函數,從而定義自訂物件類型的屬性和方法。

function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = jpb;
 this.sayName = function(){
  alert(this.name);
 };
};
var person1 = new Person(...);

3.建構函式模式與工廠模式差異:

  • 沒有明確的創建物件。
  • 直接將屬性和方法賦值 this 物件。
  • 沒有 return 語句。

Person 是Function 類型的對象,new 之後,會繼續產生一個對象,但這個新產生的對象,由於在函數中傳遞進去參數,並賦值給了this 指針,那麼傳遞進去的內容,就變成了新產生物件的屬性或方法。

建構子預設習慣是首字母大寫,上面程式碼執行經歷了下面幾個步驟:

  • 建立一個新物件
  • 將建構子作用域賦值給新物件
  • 執行建構函式中的程式碼
  • 回傳新物件

這樣產生的實例中,都預設包含一個 constructor 屬性指向建構函數,例如:

alert(person1.constructor == Person);

所以用建構函式模式,有型別的區分,可以將它的實例標識為一種特定的型別。

此外,建構函數就是普通的函數,因為要回授得到新對象,所以用 new 呼叫。如果不用的話,直接執行就跟普通函數一樣,例如上面,執行 Person.sayName() 會彈出 window.name,因為函數在 window 下面執行,所以 this 指向 window。

建構函式模式也是有缺陷的,建構函式模式裡面的方法,在每個實例上都重新創建了一遍,因此不同實例上的同名函數是不相等的。例如:

person1.sayName == person2.sayName; //false

也就是說,由建構函式產生的每個物件實例,屬性和方法都是獨有的,都是複製了一遍。屬性獨有是必須的,因為這正是物件之間不同的地方,但是很多方法功能和程式碼都是一樣的,重複複製多次,顯然就會浪費資源。

所以我們可以把函數放在外面,然後在構造函數裡面,用指針指向這個函數,那么生成的實例中,方法存儲的就是一個指向某函數的指針,也就共用一個函數了:

function Person(name, age){
 this.name = name;
 this.age = age;
 this.sayName = sayName;
}
function sayName(){
 alert(this.name);
}

但是这样,这个函数就变成了全局函数,而且与 Person 构造函数关联性不强,没有封装性可言。

下面有请原型模式登场。

原型模式

前面已经介绍了一部分关于原型的基础知识。简单的说,就是每个函数都有一个 prototype 属性,指向一个对象(原型对象),这个对象里面可以放一些属性或者方法。然后这个函数生成的实例,会有一个不规范的属性(__proto__)指向原型。

由此来看,你应该可以理解:prototype 产生的属性和方法是所有实例共享的。

这样正好解决了上面构造函数模式中,实例中函数的共用问题。例如下面代码:

function Person(){
 ....
}
Person.prototype.name = "jiangshui";
Person.prototype.sayName = function(){
 alert(this.name);
};

var person1 = new Person();
person1.sayName(); //jiangshui

或者

Person.prototype = {
 constructor : Person,
 name : "jiangshui",
 sayName : function(){
  alert(this.name);
 }
};

第二种方法覆盖了整个 prototype 对象,所以需要手动指定 constructor 属性,指向构造函数否则会指向 Object。

梳理一下它们的关系:

  • Person.prototype -》 原型对象,可以定义一些属性或者参数,被所有实例共用。
  • Person.prototype.constructor == Person —》 原型对象有个默认的属性 constructor 指向该原型对象所属的构造函数(注意另一种写法会覆盖掉这个属性,需要重新指定)。
  • person1 = new Person() -》构造函数生成实例,实例包含了构造函数的内容和原型对象的内容。
  • person1.__proto__ -》指向创建这个实例的原型对象(不规范,不要用)。

使用 isPrototypeOf() 可以确定对象之间的关系。例如:

Person.prototype.isPrototypeOf(person1);

当代码读取某个对象的某个属性,会执行搜索。先从当前对象开始,如果没有,则搜索指针指向的原型对象,而不会搜索构造函数。对象实例可以访问但是不能重写原型对象的值。如果实例中设置了与原型对象同名的属性,则搜索过程,在实例中结束而不会访问原型对象,所以达到覆盖的目的。因此即使这个属性设置为 null,也表示在实例中已经存在该属性,而不会取消掉这个属性,从而可以访问原型对应属性。

所以需要使用 delete 操作符,完全删除实例属性,从而可以重新访问原型。

原型是动态的,对原型对象所做的任何修改,都能立即从实例上反映出来。原因是实例与原型之间的松散链接关系,每次调用实例的属性方法,都会进行一次查询,如果原型变了,查询结果也就变了。

了解原型之后,我们也可以对原生对象添加新方法或属性。Object、Array、String 等原生引用类型,与上面构造函数类似,我们可以用 prototype 扩充它们的方法。例如:

String.prototype.startsWith = function(text){
 return this.indexOf(text) == 0;
};
var msg = "Hello World";
msg.startsWith("Hello");

这段代码为 String 这个原生引用类型,增加了一个 startsWith 方法,功能就是传递进去一个参数,看看要测试的字符串是否以参数开始。由于原型的动态性,所以只要执行一下,所有字符串类型的变量全都获得了这个方法。

但是不推荐使用这个方法,如果用的太多,代码太多,会导致维护困难、代码混乱等情况。一般情况下,会先继承某个原生引用类型,然后再在新自定义的类型上创建。关于继承,后面会再总结。

原型模式也不是万能的,原型中的所有属性和方法是被所有实例共享的,所以对于函数之类非常合适,而对于包含引用类型的属性来说,就会产生一些冲突。例如:

function Person(){}
Person.prototype = {
 constructor : Person,
 friends : ["greg","jack"]
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("tom");
console.log(person2.friends);

你会在 console 中看到,person2 的 friends 多了一个 tom,这并不是我想要的,但是对 person1 定义他的朋友时,的确影响到了实例 person2。

所以我们要结合原型模式和构造函数模式来使用。

组合使用构造函数模式和原型模式

这就是最常用的模式,构造函数用来定义实例属性,通过传递参数实现自定义;原型用来定义方法或者需要所有实例共享的属性。这样,既实现了自定义,又保证了共用,还避免了问题。

function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.friends = ["greg","jack"];
}
Person.prototype = {
 constructor : Person,
 sayName : function(){
  alert(this.name);
 }
};
var jiangshui = new Person("jiangshui","22","engineer");

实际应用示例

OK,到了这里,你可能会看懂原型是啥,以及如何创建对象,可是,这些又有什么用?确实,我之前的工作,一直也就是用 jQuery 写一些代码就可以了,根本用不到封装然后生成对象实现功能等。那这些究竟有什么用?

这种开发方式主要用于模块化和组建化的开发。比如你常用的弹窗功能,你当然可以把弹窗有关代码,每次都粘贴复制,然后修改一下就可以用在项目里面了。更好的选择是把你的弹窗功能代码,抽象封装成这样的一个组件,这样当你需要用弹窗的时候,只需要传递参数生成一个弹窗实例,就可以调用了。

原型对象和原型链

在Javascript中,万物皆对象,但对象也有区别,大致可以分为两类,即:普通对象(Object)和函数对象(Function)。

一般而言,通过new Function产生的对象是函数对象,其他对象都是普通对象。

举例说明:

function f1(){
  //todo
}
var f2 = function(){
  //todo
};
var f3 = new Function('x','console.log(x)');
 
var o1 = {};
var o2 = new Object();
var o3 = new f1();
 
console.log(
  typeof f1,//function
  typeof f2,//function
  typeof f3,//function
  typeof o1,//object
  typeof o2,//object
  typeof o3 //object
);
>> function function function object object object

f1属于函数的声明,最常见的函数定义方式,f2实际上是一个匿名函数,把这个匿名函数赋值给了f2,属于函数表达式,f3不常见,但也是一种函数对象。

Function是JS自带的对象,f1,f2在创建的时候,JS会自动通过new Function()的方式来构建这些对象,因此,这三个对象都是通过new Function()创建的。

在Javascript中创建对象有两种方式:对象字面量和使用new表达式,o1和o2的创建恰好对应了这两种方式,重点讲一下o3, 如果用Java和C#的思路来理解的话,o3是f1的实例对象,o3和f1是同一类型,至少我以前这么认为,其实不然…

那么怎么理解呢? 很简单,看o3是不是通过new Function产生的, 显然不是,既然不是函数对象,那就是普通对象 。

通过对函数对象和普通对象的简单理解之后,我们再来了解一下Javascript中的原型和原型链:

在JS中,每当创建一个函数对象f1 时,该对象中都会内置一些属性,其中包括prototype和__proto__,  prototype即原型对象,它记录着f1的一些属性和方法。

需要注意的是,prototype 对f1是不可见的,也就是说,f1不会查找prototype中的属性和方法。

function f(){}
f.prototype.foo = "abc";
console.log(f.foo); //undefined

那么,prototype有什么用呢? 其实prototype的主要作用就是继承。 通俗一点讲,prototype中定义的属性和方法都是留给自己的“后代”用的,因此,子类完全可以访问prototype中的属性和方法。

想要知道f1是如何把prototype留给“后代”,我们需要了解一下JS中的原型链,此时,JS中的 __proto__ 入场了,这哥们长的很奇特,隐藏的也很深,以致于你经常见不到它,但它在普通对象和函数对象中都存在, 它的作用就是保存父类的prototype对象,JS在通过new 表达式创建一个对象的时候,通常会把父类的prototype赋值给新对象的__proto__属性,这样,就形成了一代代传承…

function f(){}
f.prototype.foo = "abc";
var obj = new f();
console.log(obj.foo); //abc

现在我们知道,obj中__proto__保存的是f的prototype, 那么f的prototype中的__proto__中保存的是什么呢? 看下图:

201659150626664.png (515×122)

如图所示,f.prototype的__proto__中保存的是Object.prototype,Object.prototype对象中也有__proto__,而从输出结果看,Object.prototype.__proto__ 是null,表示obj对象原型链的终结。如下图所示:

201659150641500.png (607×148)

obj对象拥有这样一个原型链以后,当obj.foo执行时,obj会先查找自身是否有该属性,但不会查找自己的prototype,当找不到foo时,obj就沿着原型链依次去查找…

在上面的例子中,我们在f的prototype上定义了foo属性,这时obj就会在原型链上找到这个属性并执行。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Python vs. JavaScript:開發人員的比較分析Python vs. JavaScript:開發人員的比較分析May 09, 2025 am 12:22 AM

Python和JavaScript的主要區別在於類型系統和應用場景。 1.Python使用動態類型,適合科學計算和數據分析。 2.JavaScript採用弱類型,廣泛用於前端和全棧開發。兩者在異步編程和性能優化上各有優勢,選擇時應根據項目需求決定。

Python vs. JavaScript:選擇合適的工具Python vs. JavaScript:選擇合適的工具May 08, 2025 am 12:10 AM

選擇Python還是JavaScript取決於項目類型:1)數據科學和自動化任務選擇Python;2)前端和全棧開發選擇JavaScript。 Python因其在數據處理和自動化方面的強大庫而備受青睞,而JavaScript則因其在網頁交互和全棧開發中的優勢而不可或缺。

Python和JavaScript:了解每個的優勢Python和JavaScript:了解每個的優勢May 06, 2025 am 12:15 AM

Python和JavaScript各有優勢,選擇取決於項目需求和個人偏好。 1.Python易學,語法簡潔,適用於數據科學和後端開發,但執行速度較慢。 2.JavaScript在前端開發中無處不在,異步編程能力強,Node.js使其適用於全棧開發,但語法可能複雜且易出錯。

JavaScript的核心:它是在C還是C上構建的?JavaScript的核心:它是在C還是C上構建的?May 05, 2025 am 12:07 AM

javascriptisnotbuiltoncorc; sanInterpretedlanguagethatrunsonenginesoftenwritteninc.1)JavascriptwasdesignedAsignedAsalightWeight,drackendedlanguageforwebbrowsers.2)Enginesevolvedfromsimpleterterpretpretpretpretpreterterpretpretpretpretpretpretpretpretpretcompilerers,典型地,替代品。

JavaScript應用程序:從前端到後端JavaScript應用程序:從前端到後端May 04, 2025 am 12:12 AM

JavaScript可用於前端和後端開發。前端通過DOM操作增強用戶體驗,後端通過Node.js處理服務器任務。 1.前端示例:改變網頁文本內容。 2.後端示例:創建Node.js服務器。

Python vs. JavaScript:您應該學到哪種語言?Python vs. JavaScript:您應該學到哪種語言?May 03, 2025 am 12:10 AM

選擇Python還是JavaScript應基於職業發展、學習曲線和生態系統:1)職業發展:Python適合數據科學和後端開發,JavaScript適合前端和全棧開發。 2)學習曲線:Python語法簡潔,適合初學者;JavaScript語法靈活。 3)生態系統:Python有豐富的科學計算庫,JavaScript有強大的前端框架。

JavaScript框架:為現代網絡開發提供動力JavaScript框架:為現代網絡開發提供動力May 02, 2025 am 12:04 AM

JavaScript框架的強大之處在於簡化開發、提升用戶體驗和應用性能。選擇框架時應考慮:1.項目規模和復雜度,2.團隊經驗,3.生態系統和社區支持。

JavaScript,C和瀏覽器之間的關係JavaScript,C和瀏覽器之間的關係May 01, 2025 am 12:06 AM

引言我知道你可能會覺得奇怪,JavaScript、C 和瀏覽器之間到底有什麼關係?它們之間看似毫無關聯,但實際上,它們在現代網絡開發中扮演著非常重要的角色。今天我們就來深入探討一下這三者之間的緊密聯繫。通過這篇文章,你將了解到JavaScript如何在瀏覽器中運行,C 在瀏覽器引擎中的作用,以及它們如何共同推動網頁的渲染和交互。 JavaScript與瀏覽器的關係我們都知道,JavaScript是前端開發的核心語言,它直接在瀏覽器中運行,讓網頁變得生動有趣。你是否曾經想過,為什麼JavaScr

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。