JavaScript是支援物件導向的;物件導向程式設計是用抽象方式建立基於現實世界模型的一種程式模式,物件導向使用先前建立的範例,包括模組化、多型和封裝幾種技術,包括JavaScript在內的許多流行程式語言都支援物件導向程式設計。
本教學操作環境:windows10系統、javascript1.8.5版、Dell G3電腦。
JavaScript 的核心是支援物件導向的,同時它也提供了強大且靈活的 OOP 語言能力。本文從物件導向程式設計的介紹開始,帶您探索 JavaScript 的物件模型,最後描述 JavaScript 當中物件導向程式設計的一些概念。
物件導向程式設計是用抽象方式創造基於現實世界模型的一種程式模式。它使用先前建立的範例,包括模組化,多態和封裝幾種技術。今天,許多流行的程式語言(如Java,JavaScript,C#,C ,Python,PHP,Ruby 和 Objective-C)都支援物件導向程式設計(OOP)。
相對於「一個程式只是一些函數的集合,或簡單的電腦指令清單。」的傳統軟體設計觀念而言,物件導向程式設計可以看作是使用一系列物件相互協作的軟體設計。 在 OOP 中,每個物件能夠接收訊息,處理資料和傳送訊息給其他物件。每個物件都可以被視為一個擁有清晰角色或責任的獨立小機器。
物件導向程式設計的目的是在程式設計中促進更好的靈活性和可維護性,在大型軟體工程中廣為流行。憑藉其對模組化的重視,物件導向的程式碼開發更簡單,更容易理解,相較於非模組化程式設計方法, 它能更直接地分析, 編碼和理解複雜的情況和過程。
Namespace
命名空間讓開發人員在一個獨特,應用相關的名字的名稱下捆綁所有功能的容器。
Class 類別
定義物件的特徵。它是物件的屬性和方法的模板定義。
Object 物件
類別的一個實例。
Property 屬性
物件的特徵,例如顏色。
Method 方法
物件的能力,例如行走。
Constructor 建構函數
物件初始化的瞬間,被呼叫的方法。通常它的名字與包含它的類別一致。
Inheritance 繼承
一個類別可以繼承另一個類別的特徵。
Encapsulation 封裝
一種把資料和相關的方法綁在一起使用的方法。
Abstraction 抽象
結合複雜的繼承,方法,屬性的物件能夠模擬現實的模型。
Polymorphism 多態
多意為「許多」,態意為「形態」。不同類別可以定義相同的方法或屬性。
基於原型的程式設計不是物件導向程式設計中體現的風格,且行為重複使用(在基於類別的語言中也稱為繼承)是透過裝飾它作為原型的現有物件的過程實現的。這種模式也被稱為弱類化,原型化,或基於實例的程式設計。
原始的(也是最典型的)基於原型語言的例子是由大衛·安格爾和蘭德爾·史密斯開發的。然而,弱類化的程式設計風格近來變得越來越流行,並已被諸如JavaScript,Cecil,NewtonScript,IO,MOO,REBOL,Kevo,Squeak(使用框架操縱Morphic元件),和其他幾種程式語言採用。
命名空間
命名空間是一個容器,它允許開發人員在一個獨特的,特定於應用程式的名稱下捆綁所有的功能。 在JavaScript中,命名空間只是另一個包含方法,屬性,物件的物件。
注意:需要認識到重要的一點是:與其他物件導向程式語言不同的是,Javascript中的普通物件和命名空間在語言層面上沒有區別。這點可能會讓JavaScript初學者感到困惑。
創造的JavaScript命名空間背後的想法很簡單:一個全域物件被創建,所有的變量,方法和功能成為該物件的屬性。使用命名空間也最大程度地減少應用程式的名稱衝突的可能性。
我們來建立一個全域變數叫做 MYAPP
// 全局命名空间 var MYAPP = MYAPP || {};
在上面的程式碼範例中,我們先檢查MYAPP是否已經被定義(是否在同一檔文件中或在另一檔)。如果是的話,那麼使用現有的MYAPP全域對象,否則,建立一個名為MYAPP的空物件用來封裝方法,函數,變數和物件。
我們也可以建立子命名空間:
// 子命名空间 MYAPP.event = {};
下面是用來建立命名空間和新增變量,函數和方法的程式碼寫法:
// 给普通方法和属性创建一个叫做MYAPP.commonMethod的容器 MYAPP.commonMethod = { regExForName: "", // 定义名字的正则验证 regExForPhone: "", // 定义电话的正则验证 validateName: function(name){ // 对名字name做些操作,你可以通过使用“this.regExForname” // 访问regExForName变量 }, validatePhoneNo: function(phoneNo){ // 对电话号码做操作 } } // 对象和方法一起申明 MYAPP.event = { addListener: function(el, type, fn) { // 代码 }, removeListener: function(el, type, fn) { // 代码 }, getEvent: function(e) { // 代码 } // 还可以添加其他的属性和方法 } //使用addListener方法的写法: MYAPP.event.addListener("yourel", "type", callback);
標準內建物件
JavaScript有包括在其核心的几个对象,例如,Math,Object,Array和String对象。下面的例子演示了如何使用Math对象的random()方法来获得一个随机数。
console.log(Math.random());
注意:这里和接下来的例子都假设名为 console.log 的方法全局有定义。console.log 实际上不是 JavaScript 自带的。
查看 JavaScript 参考:全局对象 了解 JavaScript 内置对象的列表。
JavaScript 中的每个对象都是 Object 对象的实例且继承它所有的属性和方法。
自定义对象
类
JavaScript 是一种基于原型的语言,它没类的声明语句,比如 C+ + 或 Java 中用的。这有时会对习惯使用有类申明语句语言的程序员产生困扰。相反,JavaScript可用方法作类。定义一个类跟定义一个函数一样简单。在下面的例子中,我们定义了一个新类Person。
function Person() { } // 或 var Person = function(){ }
对象(类的实例)
我们使用 new obj 创建对象 obj 的新实例, 将结果(obj 类型)赋值给一个变量方便稍后调用。
在下面的示例中,我们定义了一个名为Person的类,然后我们创建了两个Person的实例(person1 and person2)。
function Person() { } var person1 = new Person(); var person2 = new Person();
注意:有一种新增的创建未初始化实例的实例化方法,请参考 Object.create 。
构造器
在实例化时构造器被调用 (也就是对象实例被创建时)。构造器是对象中的一个方法。 在JavaScript中函数就可以作为构造器使用,因此不需要特别地定义一个构造器方法,每个声明的函数都可以在实例化后被调用执行。
构造器常用于给对象的属性赋值或者为调用函数做准备。 在本文的后面描述了类中方法既可以在定义时添加,也可以在使用前添加。
在下面的示例中, Person类实例化时构造器调用一个 alert函数。
function Person() { alert('Person instantiated'); } var person1 = new Person(); var person2 = new Person();
属性 (对象属性)
属性就是 类中包含的变量;每一个对象实例有若干个属性. 为了正确的继承,属性应该被定义在类的原型属性 (函数)中。
可以使用 关键字 this调用类中的属性, this是对当前对象的引用。 从外部存取(读/写)其属性的语法是: InstanceName.Property; 这与C++,Java或者许多其他语言中的语法是一样的 (在类中语法 this.Property 常用于set和get属性值)
在下面的示例中,我们为定义Person类定义了一个属性 firstName 并在实例化时赋初值。
function Person(firstName) { this.firstName = firstName; alert('Person instantiated'); } var person1 = new Person('Alice'); var person2 = new Person('Bob'); // Show the firstName properties of the objects alert('person1 is ' + person1.firstName); // alerts "person1 is Alice" alert('person2 is ' + person2.firstName); // alerts "person2 is Bob"
方法(对象属性)
方法与属性很相似, 不同的是:一个是函数,另一个可以被定义为函数。 调用方法很像存取一个属性, 不同的是add () 在方法名后面很可能带着参数。为定义一个方法, 需要将一个函数赋值给类的 prototype 属性; 这个赋值给函数的名称就是用来给对象在外部调用它使用的。
在下面的示例中,我们给Person类定义了方法 sayHello(),并调用了它。
function Person(firstName) { this.firstName = firstName; } Person.prototype.sayHello = function() { alert("Hello, I'm " + this.firstName); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); // call the Person sayHello method. person1.sayHello(); // alerts "Hello, I'm Alice" person2.sayHello(); // alerts "Hello, I'm Bob"
在JavaScript中方法通常是一个绑定到对象中的普通函数, 这意味着方法可以在其所在context之外被调用。 思考下面示例中的代码:
function Person(firstName) { this.firstName = firstName; } Person.prototype.sayHello = function() { alert("Hello, I'm " + this.firstName); }; var person1 = new Person("Alice"); var person2 = new Person("Bob"); var helloFunction = person1.sayHello; person1.sayHello(); // alerts "Hello, I'm Alice" person2.sayHello(); // alerts "Hello, I'm Bob" helloFunction(); // alerts "Hello, I'm undefined" (or fails with a TypeError in strict mode) console.log(helloFunction === person1.sayHello); // logs true console.log(helloFunction === Person.prototype.sayHello); // logs true helloFunction.call(person1); // logs "Hello, I'm Alice"
如上例所示, 所有指向sayHello函数的引用 ,包括 person1, Person.prototype, 和 helloFunction 等, 均引用了相同的函数.
在调用函数的过程中,this的值取决于我们怎么样调用函数. 在通常情况下,我们通过一个表达式person1.sayHello()来调用函数:即从一个对象的属性中得到所调用的函数。此时this被设置为我们取得函数的对象(即person1)。这就是为什么person1.sayHello() 使用了姓名“Alice”而person2.sayHello()使用了姓名“bob”的原因。
然而我们使用不同的调用方法时, this的值也就不同了。当从变量 helloFunction()中调用的时候, this就被设置成了全局对象 (在浏览器中即window)。由于该对象 (非常可能地) 没有firstName 属性, 我们得到的结果便是"Hello, I'm undefined". (这是松散模式下的结果, 在 严格模式中,结果将不同(此时会产生一个error)。 但是为了避免混淆,我们在这里不涉及细节) 。另外,我们可以像上例末尾那样,使用Function#call (或者Function#apply)显式的设置this的值。
更多有关信息请参考 Function#call and Function#apply
继承
创建一个或多个类的专门版本类方式称为继承(Javascript只支持单继承)。 创建的专门版本的类通常叫做子类,另外的类通常叫做父类。 在Javascript中,继承通过赋予子类一个父类的实例并专门化子类来实现。在现代浏览器中你可以使用 Object.create 实现继承。
JavaScript 并不检测子类的 prototype.constructor (见 Object.prototype), 所以我们必须手动申明它。
在下面的例子中, 我们定义了 Student类作为 Person类的子类。 之后我们重定义了sayHello() 方法并添加了 sayGoodBye() 方法。
// 定义Person构造器 function Person(firstName) { this.firstName = firstName; } // 在Person.prototype中加入方法 Person.prototype.walk = function(){ alert("I am walking!"); }; Person.prototype.sayHello = function(){ alert("Hello, I'm " + this.firstName); }; // 定义Student构造器 function Student(firstName, subject) { // 调用父类构造器, 确保(使用Function#call)"this" 在调用过程中设置正确 Person.call(this, firstName); // 初始化Student类特有属性 this.subject = subject; }; // 建立一个由Person.prototype继承而来的Student.prototype对象. // 注意: 常见的错误是使用 "new Person()"来建立Student.prototype. // 这样做的错误之处有很多, 最重要的一点是我们在实例化时 // 不能赋予Person类任何的FirstName参数 // 调用Person的正确位置如下,我们从Student中来调用它 Student.prototype = Object.create(Person.prototype); // See note below // 设置"constructor" 属性指向Student Student.prototype.constructor = Student; // 更换"sayHello" 方法 Student.prototype.sayHello = function(){ console.log("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + "."); }; // 加入"sayGoodBye" 方法 Student.prototype.sayGoodBye = function(){ console.log("Goodbye!"); }; // 测试实例: var student1 = new Student("Janet", "Applied Physics"); student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics." student1.walk(); // "I am walking!" student1.sayGoodBye(); // "Goodbye!" // Check that instanceof works correctly console.log(student1 instanceof Person); // true console.log(student1 instanceof Student); // true
对于 “ Student.prototype = Object.create(Person.prototype); ” 这一行,在不支持 Object.create 方法的老 JavaScript 引擎中,可以使用一个 "polyfill"(又名"shim",查看文章链接),或者使用一个 function 来获得相同的返回值,就像下面:
function createObject(proto) { function ctor() { } ctor.prototype = proto; return new ctor(); } // Usage: Student.prototype = createObject(Person.prototype);
更多相关信息请参考 Object.create,连接中还有一个老JavaScript引擎的兼容方案(shim)。
封装
在上一个例子中,Student类虽然不需要知道Person类的walk()方法是如何实现的,但是仍然可以使用这个方法;Student类不需要明确地定义这个方法,除非我们想改变它。 这就叫做封装,对于所有继承自父类的方法,只需要在子类中定义那些你想改变的即可。
抽象
抽象是允许模拟工作问题中通用部分的一种机制。这可以通过继承(具体化)或组合来实现。
JavaScript通过继承实现具体化,通过让类的实例是其他对象的属性值来实现组合。
JavaScript Function 类继承自Object类(这是典型的具体化) 。Function.prototype的属性是一个Object实例(这是典型的组合)。
var foo = function(){}; console.log( 'foo is a Function: ' + (foo instanceof Function) ); // logs "foo is a Function: true" console.log( 'foo.prototype is an Object: ' + (foo.prototype instanceof Object) ); // logs "foo.prototype is an Object: true"
多态
就像所有定义在原型属性内部的方法和属性一样,不同的类可以定义具有相同名称的方法;方法是作用于所在的类中。并且这仅在两个类不是父子关系时成立(继承链中,一个类不是继承自其他类)。
相关推荐:javascript学习教程
以上是javascript支援物件導向嗎的詳細內容。更多資訊請關注PHP中文網其他相關文章!