资讯专栏INFORMATION COLUMN

Javascript面向对象的程序设计

lbool / 1135人阅读

摘要:原型模式就是通过调用构造函数而创建的那个对象实例的原型对象。我们来看下面一个例子来理解这句话与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的。原型对象的问题首先,它省略了为构造函数传递初始化参数这一环节。

最近因为在给一个小同学做学习计划,所以也记录一些知识点,便于后面的同学的学习交流。这篇文章是关于Javascript的面向对象的程序设计,主要从三个方面来介绍,1. 理解对象属性; 2. 理解并创建对象; 3. 理解继承

一、理解对象属性
首先我们来理解Javascript对象是什么?在Javascript中,万物皆对象。其中创建自定义对象的最简单的方式就是创建一个Object的实例,如下:

</>复制代码

  1. var person = new Object();
  2. person.age = 29;
  3. // 对象字面量的形式:
  4. var person = {
  5. age: 29
  6. };

ECMAScript中有两种属性:数据属性和访问器属性。

数据属性:
其中数据属性有四个描述其行为的特性:
Configurable: 表示能都通过delete删除属性从而重新定义属性。
Enumerable: 表示能否通过for in 循环返回属性。
Writable: 表示能否修改属性的值。
Value: 包含这个属性的数据值。
要修改属性默认的配置,必须使用Object.defineProperty(), 这个方法接收三个参数:属性所在的对象,属性的名字和一个描述性对象。
比如:

</>复制代码

  1. var person = {};
  2. Object.defineProperty(person, ’name’, {
  3. writable: false,
  4. value: ’Nicholas"
  5. });
  6. alert(person.name); //Nicholas
  7. person.name = ‘Greg’;
  8. alert(person.name); //Nicholas

访问器属性:
访问器属性包含一对setter和getter函数。包含如下4个特性:
Configurable:能否被delete删除属性重新定义。默认值:true
Enumerable:能否被for-in枚举。默认值:true
Get:读取属性值。默认值:undefined
Set:写入属性值。默认值:undefined

</>复制代码

  1. var dog = {
  2. _age: 2,
  3. weight: 10
  4. }
  5. Object.defineProperty(dog, "age", {
  6. get: function () {
  7. return this._age
  8. },
  9. set: function (newVal) {
  10. this._age = newVal
  11. this.weight += 1
  12. }
  13. })

知道了对象的属性,那么我们创建对象的方式是什么呢?

二、创建对象的方式

创建对象的方式通常有下面几种方式:
1、工厂模式
我们举个例子:

</>复制代码

  1. function createPerson(name, age, job) {
  2. var o = new Object();
  3. o.name = name;
  4. o.age = age;
  5. o.job = job;
  6. o.sayName = function () {
  7. alert(this.name);
  8. };
  9. Return o;
  10. }
  11. var person = createPerson(‘Greg’, 27, ‘Doctor’);

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决问题识别的问题(即怎样知道一个对象的类型)

2、构造函数模式
我们举个例子:
**function Person(name, age, job) {

</>复制代码

  1. this.name = name;
  2. this.age = age;
  3. this.job = job;
  4. this.sayName = function () {
  5. alert(this.name);

};
}
var person = new Person(‘Greg’, 27, ‘Doctor’);**

构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。这里要提的一个属性是Constructor, 每个new 出来的实例都有一个Constructor(构造函数)属性,该属性指向构造函数。
对象的Constructor属性最初是用来标识对象类型的。但是,提到检测对象类型,还是instanceof操作符更好可靠一些。
alert(person1 instanceof Object); //true

构造函数的问题问题就是,每个方法都要在每个实例上重新创建一遍,当然,可以把函数定义转移到构造函数外部来解决这个问题,如下实例:

**function Person(name, age, job) {

</>复制代码

  1. this.name = name;
  2. this.age = age;
  3. this.job = job;
  4. this.sayName = sayName;

}
function sayName() {

</>复制代码

  1. alert(this.name);

}
var person = new Person(‘Greg’, 27, ‘Doctor’);**

那么这里的问题就是:在全局作用域中定义的函数实际上被只能被某个对象调用,这让全局作用域有点名不副实。而更让人无法接受的是:如果独享需要定义很多方法,那么就要定义很多个全局函数。下面我们来看看原型模式是不能解决这个问题。

3、原型模式
prototype就是通过调用构造函数而创建的那个对象实例的原型对象。我们来看下面一个例子来理解这句话:

</>复制代码

  1. function Person() {
  2. }
  3. Person.prototype.name = “Nicholas”;
  4. Person.prototype.age = 29;
  5. Person.prototype.job = “Software Engineer”;
  6. Person.prototype.sayName = function() {
  7. alert(this.name);
  8. };
  9. var person1 = new Person();
  10. person1.sayName(); // “Nicholas”

与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的。
针对这个特性,我们要注意的几个点就是。
第一,如果实例的属性与方法与原型的属性和方法同名,那谁的优先级高呢?
当然是创建出来的实例的属性和方法的优先级高。
第二,实例的属性与方法的修改会影响原型同名的属性和方法吗?
不会
这里提一下hasOwnProperty(), 使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在原型中。
第三,如何判断某个属性是存在原型中?

</>复制代码

  1. function hasPrototypeProperty(object, name) {
  2. return !Object.hasOwnProperty(name) && (name in object);
  3. }

hasOwnProperty()只在属性存在于实例中才返回true, 因此只要in操作符返回true而hasOwnProperty()返回false, 就可以确定属性是原型中的属性、这里说下Constructor, 每创建一个函数,就会同时创建它的Prototype对象,这个对象也会自动获得Constructor属性。
我们来看一个例子:

</>复制代码

  1. function Person() {
  2. }
  3. Person.prototype = {
  4. constructor: Person,
  5. name: “Nichloas”,
  6. age: 29,
  7. job: “Software Engineer”,
  8. sayName: function () {
  9. alert(this.name);
  10. }
  11. };

以上代码特意包含了一个Constructor属性,并将它的值设置为Person, 从而确保了通过该属性能够访问到适当的值。但是,以这种方式重设Constructor属性会导致它的Enumerable特性被设置为true, 默认情况下原生的Constructor属性是不可枚举的。

</>复制代码

  1. Object.defineProperty(Person.prototype, “Constructor”, {
  2. enumerable: false,
  3. value: Person
  4. });

原型对象的问题:
首先,它省略了为构造函数传递初始化参数这一环节。
然后,原型中所有属性是被很多实例共享的,对于包含引用类型值的属性来说,问题比较突出。所以,使用最多的方式使用构造函数和原型模式

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

</>复制代码

  1. function Person(name, age, job) {
  2. this.name = name;
  3. this.age = age;
  4. this.job = job;
  5. this.friends = [“Shelby”, “Court”];
  6. }
  7. Person.prototype = {
  8. constructor: Person,
  9. sayName: function () {
  10. alert(this.name);
  11. }
  12. };
  13. var person = new Person(‘Greg’, 27, ‘Doctor’);

知道了创建对象的方式,那么在Javascript中我们如何来继承对象呢?

三、继承
1、原型链
构造函数,原型和实例的关系:每一个构造函数都也有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的原型对象将包含一个指向另一个原型的指针,相应的,另一个原型中也包含也包含着一个指向一个构造函数的指针,假如另一个原型又是另一个类型的实例,那么上诉关系依然成立,我们来看下面一个例子.

</>复制代码

  1. function SuperType() {
  2. this.property = true;
  3. }
  4. SuerType.prototype.getSuperValue = function () {
  5. return this.property;
  6. };
  7. function SubType() {
  8. This.subProperty = false;
  9. }
  10. // 继承了SuperType
  11. SubType.prototype = new SuperType();
  12. SubType.prototype.getSubValue = function () {
  13. return this.subProperty();
  14. };

var instance = new SubType();
alert(instance.getSuperValue); // true
这个例子中,SubType继承了SuperType,而SuperType继承了Object。如果要确定原型和实例的关系,可以用instanceOf操作符与isPropertyOF()方法。
如 instance instanceOf object; // true
Object.prototype isPropertype(instance); // true

但是通过原型链实现继承时,不能使用对象字面量创建原型方法。

</>复制代码

  1. function SuperType() {
  2. this.property = true;
  3. }
  4. SuerType.prototype.getSuperValue = function () {
  5. return this.property;
  6. };
  7. function SubType() {
  8. This.subProperty = false;
  9. }
  10. // 继承了SuperType
  11. SubType.prototype = new SuperType();
  12. // 使用字面量添加新方法,会导致上一行代码无效
  13. SubType.prototype = {
  14. getSubValue: function () {
  15. return this.subproperty;
  16. },
  17. someOtherMethod: function () {
  18. return false;
  19. }
  20. };
  21. var instance = new SubType();
  22. alert(instance.getSuperValue); // error

接下来,我们来说下原型链继承的问题:
第一,最主要的问题来自包含引用类型值的原型。包含引用类型值的原型属性会被所有实例共享,而这也正是为什么要在构造函数中,而不是在原型对象中定义属性的原因。
第二,在创建子类型的实例时候,不能向超类型的构造函数中传递参数。

2、借用构造函数

</>复制代码

  1. function SuperType(name) {
  2. this.name = name;
  3. }
  4. function SubType() {
  5. // 继承了SuperType, 同时还传递了参数
  6. SuperType.call(this, “Nicholas”);
  7. this.age = 29;
  8. }
  9. var instance = new SubType();
  10. alert(instance.name); // “Nicholas”

借用构造函数的问题,方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对于子类型而言也是不可见的,结果所有类型都只能使用构造函数模式,考虑到这些问题,借用构造函数的技术也是很少多带带使用的。

3、组合继承
组合继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和放大的绩效,而通过借用构造函数来实现对实例属性的继承。

</>复制代码

  1. function SuperType(name) {
  2. this.name = name;
  3. this.colors = [“red”, “blue”, “green"]
  4. }
  5. SuperType.prototype.sayName = function () {
  6. alert[this.name];
  7. };
  8. function SubType(name, age) {
  9. // 继承属性
  10. SuperType.call(this, name);
  11. this.age = age;
  12. }
  13. // 继承方法
  14. SubType.prototype = new SuperType();
  15. SubType.prototype.sayAge = function () {
  16. alert(this.age);
  17. };
  18. var instance = new SubType(“Nicholas”, 29);
  19. instance.colors.push(“black”);
  20. alert(instance.colors); // red, blue, green, black

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为Javascript中最常见的继承模式。

4、原型式继承

</>复制代码

  1. function object(o) {
  2. function F() {}
  3. F.prototype = o;
  4. return new F();
  5. ]
  6. var person = {
  7. Name: “Nicholas”,
  8. friends: [“Shelby”, “Court”, “Van"]
  9. };
  10. var anotherPerson = object(person);
  11. anotherPerson.name = “Greg”;

ECMAScipt5通过新增Object.create()方法规范化了原型式继承。
接着上面的例子:

</>复制代码

  1. var anotherPerson = Object.create(person);
  2. anotherPerson.name = “Greg”;

5、寄生式继承
寄生式继承是与原型式继承紧密相关的一种思路,它与工厂模式类似,即创建一个仅用于封装过程的函数,该函数在内部以某种方法来增强对象,最后再像真地是它做了所有工作一样返回对象。
例如:

</>复制代码

  1. function createAnother(original) {
  2. var clone = object(original);
  3. clone.sayHi = function () {
  4. alert(‘hi’);
  5. };
  6. return clone;
  7. }
  8. var person = {
  9. name: ’Nochplas’,
  10. friends: [’Shelby’, ‘Court’, ‘Van"]
  11. };
  12. var anotherPerson = createAnother(person);

6、组合寄生式继承

组合继承是Javascript最常用的继承模式,不过,组合继承最大的问题就是无论什么情况下,都会调用两次超类型构建函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。

</>复制代码

  1. function SuperType(name) {
  2. this.name = name;
  3. this.colors = [“red”, “blue”, “green"]
  4. }
  5. SuperType.prototype.sayName = function () {
  6. alert[this.name];
  7. };
  8. function SubType(name, age) {.
  9. // 继承属性
  10. SuperType.call(this, name); // 第二次调用 SuperType()
  11. this.age = age;
  12. }
  13. // 继承方法
  14. SubType.prototype = new SuperType(); // 第一次调用 SuperType()
  15. SubType.prototype.sayAge = function () {
  16. alert(this.age);
  17. };

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。不必为了制定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果制定给子类型的原型。如下:

</>复制代码

  1. function inheritPrototype (subType, superType) {
  2. var prototype = object (superType.prototype); // 前面的原型式继承object方法
  3. prototype.constructor = subtype;
  4. subtype.prototype = prototype;
  5. }
  6. function SuperType(name) {
  7. this.name = name;
  8. this.colors = [“red”, “blue”, “green"]
  9. }
  10. SuperType.prototype.sayName = function () {
  11. alert[this.name];
  12. };
  13. function SubType(name, age) {.
  14. // 继承属性
  15. SuperType.call(this, name);
  16. this.age = age;
  17. }
  18. inheritPrototype(SubType, SuperType);
  19. SubType.prototype.sayAge = function () {
  20. alert(this.age);
  21. };

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109243.html

相关文章

  • SegmentFault 技术周刊 Vol.32 - 七夕将至,你对象”还好吗?

    摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...

    李昌杰 评论0 收藏0
  • SegmentFault 技术周刊 Vol.32 - 七夕将至,你对象”还好吗?

    摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...

    Lyux 评论0 收藏0
  • SegmentFault 技术周刊 Vol.32 - 七夕将至,你对象”还好吗?

    摘要:很多情况下,通常一个人类,即创建了一个具体的对象。对象就是数据,对象本身不包含方法。类是相似对象的描述,称为类的定义,是该类对象的蓝图或原型。在中,对象通过对类的实体化形成的对象。一类的对象抽取出来。注意中,对象一定是通过类的实例化来的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 马上就要到七夕了,离年底老妈老爸...

    AaronYuan 评论0 收藏0
  • 面向对象 JavaScript

    摘要:是完全的面向对象语言,它们通过类的形式组织函数和变量,使之不能脱离对象存在。而在基于原型的面向对象方式中,对象则是依靠构造器利用原型构造出来的。 JavaScript 函数式脚本语言特性以及其看似随意的编写风格,导致长期以来人们对这一门语言的误解,即认为 JavaScript 不是一门面向对象的语言,或者只是部分具备一些面向对象的特征。本文将回归面向对象本意,从对语言感悟的角度阐述为什...

    novo 评论0 收藏0
  • javascript 面向对象版块之理解对象

    摘要:用代码可以这样描述安全到达国外面向过程既然说了面向对象,那么与之对应的就是面向过程。小结在这篇文章中,介绍了什么是面向对象和面向过程,以及中对象的含义。 这是 javascript 面向对象版块的第一篇文章,主要讲解对面向对象思想的一个理解。先说说什么是对象,其实这个还真的不好说。我们可以把自己当成一个对象,或者过年的时候相亲,找对象,那么你未来的老婆也是一个对象。我们就要一些属性,比...

    lovXin 评论0 收藏0
  • JS对象(1)重新认识面向对象

    摘要:对象重新认识面向对象面向对象从设计模式上看,对象是计算机抽象现实世界的一种方式。除了字面式声明方式之外,允许通过构造器创建对象。每个构造器实际上是一个函数对象该函数对象含有一个属性用于实现基于原型的继承和共享属性。 title: JS对象(1)重新认识面向对象 date: 2016-10-05 tags: JavaScript 0x00 面向对象 从设计模式上看,对象是...

    superw 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<