摘要:對象在中,除了數字字符串布爾值這幾個簡單類型外,其他的都是對象。那么在函數對象中,這兩個屬性的有什么區別呢表示該函數對象的原型表示使用來執行該函數時這種函數一般成為構造函數,后面會講解,新創建的對象的原型。這時的函數通常稱為構造函數。。
本文原發于我的個人博客,經多次修改后發到sf上。本文仍在不斷修改中,最新版請訪問個人博客。
最近工作一直在用nodejs做開發,有了nodejs,前端、后端、腳本全都可以用javascript搞定,很是方便。但是javascript的很多語法,比如對象,就和我們常用的面向對象的編程語言不同;看某個javascript開源項目,也經常會看到使用this關鍵字,而這個this關鍵字在javascript中因上下文不同而意義不同;還有讓人奇怪的原型鏈。這些零零碎碎的東西加起來就很容易讓人不知所措,所以,有必要對javascript這門語言進行一下深入了解。
我這篇文章主要想說說如何在javascript中進行面向對象的編程,同時會講一些javascript這門語言在設計之初的理念。下面讓我們開始吧。
首先強調一下,我們現在廣泛使用的javascript都是遵循了ECMAScript 5.1標準的,正在制定中的版本為6.0,這個版本變化很大,增加了很多新的語法與函數,大家可以去Mozilla Developer Network上查看。
設計理念javascript1.0 最初是由網景公司的Brendan Eich在1995年5月花了十天搞出來的,Eich的目標是設計出一種即輕量又強大的語言,所以Eich充分借鑒了其他編程語言的特性,比如Java的語法(syntax)、Scheme的函數(function)、Self的原型繼承(prototypal inheritance)、Perl的正則表達式等。
其中值得一提的是,為什么繼承借鑒了Self語言的原型機制而不是Java的類機制?首先我們要知道:
Self的原型機制是靠運行時的語義
Java的類機制是靠編譯時的類語法
Javascript1.0的功能相對簡單,為了在今后不斷豐富javascript本身功能的同時保持舊代碼的兼容性,javascript通過改變運行時的支持來增加新功能,而不是通過修改javascript的語法,這就保證了舊代碼的兼容性。這也就是javascript選擇基于運行時的原型機制的原因。
wikipedia這樣描述到:JavaScript is classified as a prototype-based scripting language with dynamic typing and first-class functions。這些特性使得javascript是一種多范式的解釋性編程語言,支持面向對象、命令式(imperative)、函數式(functional)編程風格。
對象在javascript中,除了數字、字符串、布爾值(true/false)、undefined這幾個簡單類型外,其他的都是對象。
數字、字符串、布爾值這些簡單類型都是不可變量,對象是可變的鍵值對的集合(mutable keyed conllections),對象包括數組Array、正則表達式RegExp、函數Function,當然對象Object也是對象。
對象在javascript中說白了就是一系列的鍵值對。鍵可以是任何字符串,包括空串;值可以是除了undefined以外的任何值。在javascript中是沒有類的概念(class-free)的,但是它有一個原型鏈(prototype linkage)。javascript對象通過這個鏈來實現繼承關系。
javascript中有一些預定義對象,像是Object、Function、Date、Number、String、Array等。
字面量(literal)javascript中的每種類型的對象都可以采用字面量(literal)的方式創建。
對于Object對象,可以使用對象字面量(Object literal)來創建,例如:
var empty_object = {};//創建了一個空對象 //創建了一個有兩個屬性的對象 var stooge = { "first-name": "Jerome", "last-name": "Howard" };
當然,也可以用new Object()或Object.create()的方式來創建對象。
對于Function、Array對象都有其相應的字面量形式,后面會講到,這里不再贅述。
原型鏈(prototype linkage)javascript中的每個對象都隱式含有一個[[prototype]]屬性,這是ECMAScript中的記法,目前各大瀏覽器廠商在實現自己的javascript解釋器時,采用的記法是__proto__,也就是說每個對象都隱式包含一個__proto__屬性。舉個例子:
var foo = { x: 10, y: 20 };
foo這個對象在內存中的存儲結構大致是這樣的:
當有多個對象時,通過__proto__屬性就能夠形成一條原型鏈。看下面的例子:
var a = { x: 10, calculate: function (z) { return this.x + this.y + z; } }; var b = { y: 20, __proto__: a }; var c = { y: 30, __proto__: a }; // call the inherited method b.calculate(30); // 60 c.calculate(40); // 80
上面的代碼在聲明對象b、c時,指明了它們的原型為對象a(a的原型默認指向Object.prototye,Object.prototype這個對象的原型指向null),這幾個對象在內存中的結構大致是這樣的:
這里需要說明一點,我們如果想在聲明對象時指定它的原型,一般采用Object.create()方法,這樣效率更高。
除了我們這里說的__proto__屬性,相信大家平常更常見的是prototype屬性。比如,Date對象中沒有加幾天的函數,那么我們可以這么做:
Date.prototype.addDays = function(n) { this.setDate(this.getDate() + n); }
那么以后所有的Date對象都擁有addDays方法了(后面講解繼承是會解釋為什么)。那么__proto__屬性與prototype屬性有什么區別呢?
javascript的每個對象都有__proto__屬性,但是只有函數對象有prototype屬性。
那么在函數對象中, 這兩個屬性的有什么區別呢?
__proto__表示該函數對象的原型
prototype表示使用new來執行該函數時(這種函數一般成為構造函數,后面會講解),新創建的對象的原型。例如:
var d = new Date(); d.__proto__ === Date.prototype; //這里為true
看到這里,希望大家能夠理解這兩個屬性的區別了。
在javascript,原型和函數是最重要的兩個概念,上面說完了原型,下面說說函數對象。
函數對象Function首先,函數在javascript中無非也是個對象,可以作為value賦值給某個變量,唯一不同的是函數能夠被執行。
使用對象字面量方式創建的對象的__proto__屬性指向Object.prototype(Object.prototype的__proto__屬性指向null);使用函數字面量創建的對象的__proto__屬性指向Function.prototype(Function.prototype對象的__proto__屬性指向Object.prototype)。
函數對象除了__proto__這個隱式屬性外,還有兩個隱式的屬性:
函數的上下文(function’s context)
實現函數的代碼(the code that implements the function’s behavior)
和對象字面量一樣,我們可以使用函數字面量(function literal)來創建函數。類似于下面的方式:
//使用字面量方式創建一個函數,并賦值給add變量 var add = function (a, b) { return a + b; };
一個函數字面量有四個部分:
function關鍵字,必選項。
函數名,可選項。上面的示例中就省略了函數名。
由圓括號括起來的一系列參數,必選項。
由花括號括起來的一系列語句,必選項。該函數執行時將會執行這些語句。
函數調用與this一個函數在被調用時,除了聲明的參數外,還會隱式傳遞兩個額外的參數:this與arguments。
this在OOP中很重要,this的值隨著調用方式的不同而不同。javascript中共有四種調用方式:
method invocation pattern。當函數作為某對象一個屬性調用時,this指向這個對象。this賦值過程發生在函數調用時(也就是運行時),這叫做late binding
function invocation pattern。當函數不作為屬性調用時,this指向全局對象,這是個設計上的錯誤,正確的話,內部函數的this應該指向外部函數。可以通過在函數中定義一個變量來解決這個問題。
var add = function(a, b) {return a+b;} var obj = { value: 3, double: function() { var self = this;//把this賦值給了self this.value = add(self.value, self.value); } } obj.double(); //obj.value現在為6
construct invocation pattern。javascript是一門原型繼承語言,這也就意味著對象可以直接從其他對象中繼承屬性,沒有類的概念。這和java中的繼承不一樣。但是javascript提供了一種類似與java創建對象的語法。當一個函數用new來調用時,this指向新創建的對象。這時的函數通常稱為構造函數。
apply invocation pattern。使用函數對象的apply方法來執行時,this指向apply的第一個參數。
除了this外,函數在調用是額外傳入的另一個參數是arguments。它是函數內部的一個變量,包含函數調用處的所有參數,甚至包含函數定義時沒有的參數。
var sum = function () { var i, sum = 0; for (i = 0; i < arguments.length; i += 1) { sum += arguments[i]; } return sum; }; sum(4, 8, 15, 16, 23, 42); // 108
需要注意的是,這里的arguments不是一個數組,它只是一個有length屬性的類數組對象(Array-like),它并不擁有數組的其他方法。
關于對象,最后說一下數組,javascript中的數組和平常編程中的數組不大一樣。
數組對象Array數組是一種在內存中線性分配的數據結構,通過下標計算出元素偏移量,從而取出元素。數組應該是一個快速存取的數據結構,但是在javascript中,數組不具備這種特性。
數組在javascript中一個具有傳統數組特性的對象,這種對象能夠把數組下標轉為字符串,然后把這個字符串作為對象的key,最后對取出對應該key的value(這又一次說明了對象在javascript中就是一系列鍵值對)。
雖然javascript中的數組沒有傳統語言中的數組那么快,但是由于javascript是弱類型的語言,所以javascript中的數組可以存放任何值。此外Array有很多實用的方法,大家可以去MDN Array查看。
javascript也為數組提供了很方便的字面量(Array Literal)定義方式:
var arr = [1,2,3]
通過數組字面量創建的數組對象的__proto__指向Array.prototype。
繼承Inheritance在Java中,對象是某個類的實例,一個類可以從另一個類中繼承。但是在基于原型鏈的javascript中,對象可以直接從另一個對象創建。
在上面講解對象時,我們知道了在創建一個對象時,該對象會自動賦予一個__proto__屬性,使用各種類型的字面量(Literal)時,javascript解釋器自動為__proto__進行了賦值。當我們在javascript執行使用new操作符創建對象時,javascript解釋器在構造函數時,同時會執行類似于下面的語句
this.__proto__ = {constructor: this};
新創建的對象都會有一個__proto__屬性,這個屬性有一個constructor屬性,并且這個屬性指向這個新對象。舉個例子:
var d = new Date() d.__proto__.constructor === Date //這里為true
如果new不是一個操作符,而是一個函數的話,它的實現類似于下面的代碼:
Function.prototype.new = function () { // Create a new object that inherits from the constructor"s prototype. var that = Object.create(this.prototype); // Invoke the constructor, binding –this- to the new object. var other = this.apply(that, arguments); // If its return value isn"t an object, substitute the new object. return (typeof other === "object" && other) || that; };
之前也說了,基于原型的繼承機制是根據運行時的語義決定的,這就給我們提供了很大的便利。比如,我們想為所有的Array添加一個map函數,那么我們可以這么做:
Array.prototype.map = function(f) { var newArr = []; for(i=0; i因為所有的數組對象的__proto__都指向Array.prototype對象,所以我們為這個對象增加方法,那么所有的數組對象就都擁有了這個方法。
javascript解釋器會順著原型鏈查看某個方法或屬性。如果想查看某對象的是否有某個屬性,可以使用Object.prototype.hasOwnProperty方法。
總結通過上面多次講解,希望大家對對象在javascript中就是一系列的鍵值對、原型與函數這三個概念有更加深刻的認識,使用javascript來寫前端、后端與腳本。在React.js 2015大會上,Facebook公布了即將開源的React Native,這意味著今后我們可以用javascript來寫IOS、Android的原生應用了,這可真是learn-once, write-anywhere。相信隨著ECMAScript 6的發布,javascript這門語言還會有一系列翻天覆地的變化,Stay Tuned。:-)
參考JavaScript. The core
《Javascript: The Good Parts》強烈建議大家去看這個書。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85531.html
摘要:所支持的面向對象編程包括原型繼承。發明于年的就是首批支持函數式編程的語言之一,而演算則可以說是孕育了這門語言。即使在今天,這個家族的編程語言應用范圍依然很廣。 1. 能說出來兩種對于 JavaScript 工程師很重要的編程范式么? JavaScript 是一門多范式(multi-paradigm)的編程語言,它既支持命令式(imperative)/面向過程(procedural)編程...
摘要:所支持的面向對象編程包括原型繼承。發明于年的就是首批支持函數式編程的語言之一,而演算則可以說是孕育了這門語言。即使在今天,這個家族的編程語言應用范圍依然很廣。 1. 能說出來兩種對于 JavaScript 工程師很重要的編程范式么? JavaScript 是一門多范式(multi-paradigm)的編程語言,它既支持命令式(imperative)/面向過程(procedural)編程...
摘要:所支持的面向對象編程包括原型繼承。發明于年的就是首批支持函數式編程的語言之一,而演算則可以說是孕育了這門語言。即使在今天,這個家族的編程語言應用范圍依然很廣。 1. 能說出來兩種對于 JavaScript 工程師很重要的編程范式么? JavaScript 是一門多范式(multi-paradigm)的編程語言,它既支持命令式(imperative)/面向過程(procedural)編程...
摘要:對象重新認識面向對象面向對象從設計模式上看,對象是計算機抽象現實世界的一種方式。除了字面式聲明方式之外,允許通過構造器創建對象。每個構造器實際上是一個函數對象該函數對象含有一個屬性用于實現基于原型的繼承和共享屬性。 title: JS對象(1)重新認識面向對象 date: 2016-10-05 tags: JavaScript 0x00 面向對象 從設計模式上看,對象是...
閱讀 1223·2021-11-25 09:43
閱讀 1336·2021-09-26 09:55
閱讀 2330·2021-09-10 11:20
閱讀 3364·2019-08-30 15:55
閱讀 1441·2019-08-29 13:58
閱讀 1163·2019-08-29 12:36
閱讀 2337·2019-08-29 11:18
閱讀 3406·2019-08-26 11:47