视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
JavaScript面向对象的程序设计(犯迷糊的小羊)
2020-11-27 22:13:53 责编:小采
文档

下面以一个实例对象直接讲解这两个特性:

//数据特性;
var teren = {};
Object.defineProperty(teren,{
 value:"teren",
 writable:false,
 enumerable:true,
 configurable:true
})

//访问器特性;
//html
<div id="name"></div>
//js
var obj = Object.defineProperty({},"name",{
set:function(name){
 document.getElementById('name').innerHTML=name
},
get:function(){
 console.log( document.getElementById('name').innerHTML
 ) 
 },
})
obj.name = "hello world"
obj.name

[demo]

Object.defineProperties可以一次性配置对象的多个属性;

2. 创建对象的方式

上一节我们对面向对象的程序设计思想和对象有了初步理解,这一节我们深入探讨一下对象的创建方式及其优缺点;

创建对象的不同方式也可以简单的称作设计模式,不同的设计模式在实际编程应用中起到不同的作用;

2.1 单例模式

单例模式就是产生一个类的唯一实例对象,它能够确保您只有一个对象实例能够实际派上用场;

单例模式下,创建对象方式如下:

var singleton = {
 attr:1,
 method:function(){
 return this.attr
 }
}
var ex1 = singleton;
var ex2 = singleton;
ex1 === ex2//true

上述创建单例的方式:

优点:使用非常简捷;
缺点:缺乏封装,成员暴露,初始化时占用资源;

可以使用闭包方式解决这一问题:

var substance = (function(){
 var unique; 
 function init(){
 var type; 
 return {
 setType:function(t){
 return type = t;
 }
 }
 }
 return {
 getInstance:function(){
 if(!unique){
 unique = init();
 }
 return unique;
 } 
 }
})();

var Adam = substance.getInstance();
var Eve = substance.getInstance();
Adam === Eve
Adam.setType('Man')//Man

2.2 工厂模式

单例模式只能创作单个实例对象,也就是说如果将该实例对象赋予多个变量时,会存在对象的引用问题,即修改其中一个变量会影响到另一个变量;
有时我们需要创造多个结构相似的对象,只有部分属性有所区别,这时候工厂模式派上用场;

工厂模式的设计思想就是能够像工厂一样批量生产出相似属性和方法的对象,使用工厂模式能解决多个相似的问题,例如创造多个弹窗(只是标题不同);

function person(name,age){
 var obj = new Object();
 obj.name = name;
 obj.age = age;
 obj.greet = function(){
 return "hello "+this.name;
 };
 return obj
}
var Adam = person("Adam",18);
var Eve = person("Eve",20);

上述工厂模式:

优点:能批量生产结构类似的对象;封装创建对象的细节;
缺点:未能解决对象的类型,即由哪个构造函数创建的;

2.3 构造函数模式

构造函数可以创建特定类型的对象,类似之前的Array、RegExp等原生对象都能创造特定类型的实例对象;

function Person(name,age){
 this.name = name;
 this.age = age;
 this.greet = function(){
 return "hello "+this.name;
 }
}
var p1 = new Person('Adam',18);
var p2 = new Person('Eve',20);

使用构造函数模式就能够解决实例对象由谁创建的问题;

上述代码和工厂模式的区别在于:

1.没有显示创建新对象;
2.直接将属性和方法赋给this对象;
3.没有return语句;
4.函数名开头大写以区别普通函数;
5.使用new操作符去创建对象实例;

new操作符的原理

使用new操作符去调用函数和直接调用函数不同,其new操作符的运行函数的过程为:

创建一个新对象;
将构造函数的作用域赋给新对象并执行构造函
内的代码;
返回新对象;
使用代码表示如下:

function Person(name,age){
 this.name = name;
 this.age = age;
 this.greet = function(){
 return "hello "+this.name;
 }
}
function createPerson(name,age){
 var o = new Object();
 Person.call(o,name,age);
 return o;
}
var p1 = createPerson('Adam',18);
var p2 = createPerson('Eve',20);

使用构造函数模式创建对象的优缺点在于:

优点:能够识别对象属于的构造函数;
缺点:如果存在不同实例对象共享的属性和方法,使用构造函数模式则会浪费内存;

【注】
关于this关键字的更多知识点可以参见【what's this】;
构造函数如果不用new操作符调用和普通函数是一样的;

2.4 原型模式

每个函数都有一个prototype原型属性,这个原型属性可以部署特定类型的实例共享的属性和方法;

function Person(){}
}
Person.prototype.greet = function(){
 return "hello "+this.name;

将原来的greet函数部署在Person函数的prototype原型属性上,这样p1和p2可以共享该方法,而不像构造函数模式每创建一个实例就增加一个greet方法浪费内存;

【注】
关于原型对象的更多理解详见下一节——JavaScript的继承机制;

使用原型模式创建对象的优缺点在于:

优点:对于每个实例的共享属性和方法能够较好实现;
缺点:单独采用原型模式将无法区分不同实例的私有属性;

2.5 混合模式

混合模式,就是综合构造函数模式和原型模式的优缺点,构造函数模式部署实例的私有属性,原型模式部署实例的公有属性;

function Person(name,age){
 this.name = name;
 this.age = age;
}
Person.prototype.greet = function(){
 return "hello "+this.name;
}
var p1 = new Person("Adam",18);
var p2 = new Person("Eve",20);

混合模式是目前使用最广泛、认同度最高的一种创建自定义类型(类)的设计模式;

【注】
当然,设计模式不仅仅上述所提到,还有更加精深可以参考《设计模式》一书以及之前小羊写的一篇文章《设计模式梗概》;

3.JavaScript的继承机制

上一节我们通过创建对象的不同模式,隐式引出了原型对象的概念,这一节中我们将详细了解一下原型对象、原型链及其实现的继承机制;

前面,我们从数据特征上看,知道对象是无序属性(键值对)的集合;
现在,我们可以从面向对象的角度看,任何对象都是更为抽象的对象的实例,可以理解为类的概念;
从这个角度理解,我们现在可以重新定义一下对象和类的含义:
对象可以说是对现实事物的抽象,对象封装了属性和方法,属性值指的是对象的状态,方法指的是对象的行为;
类是提供一种模板的‘对象',它是对象的抽象;
举个简单的栗子:

function Person(name,age){
 this.name = name;
 this.age = age;
}
Person.prototype.greet = function(){
 return "hello "+this.name;
}
var p1 = new Person("Adam",18);
var p2 = new Person("Eve",20);

上述代码表明,p1和p2两个实例是对现实Adam和Eve的抽象,而“类”Person又是对2个实例的抽象,为创建相似结构的人提供标准的模板;
[注]ES6之前JavaScript中没有类,在ES6中定义了类;

3.1 原型对象

在上一节的原型模式中,我们提到每个函数都有一个prototype属性,这个属性指向函数的原型对象,可以部署特定类型的实例共享的属性和方法;

更为深入理解prototype原型对象,prototype原型对象不仅可以部署特定类型的实例共享的属性和方法,而且还是实现JavaScript继承的关键;

只要创建一个新函数就会为该函数创建一个prototype属性,每个prototype属性自动获得一个constructor属性,该属性指向prototype属性所在的函数指针;

当使用构造函数创建一个实例时,该实例内部包含一个内部属性__proto__指向构造函数的原型对象;

由此,一个简单的继承便产生了;
以下面代码为例:

function Person(name,age){
 this.name = name;
 this.age = age;
}
Person.prototype.greet = function(){
 return "hello "+this.name;
}
var p1 = new Person("Adam",18);
var p2 = new Person("Eve",20);

构造函数创建之后,自动创建一个prototype属性,prototype原型对象下有一个默认的constructor属性指向prototype属性所在的函数Person中;
在prototype原型对象上部署greet方法,实例p1的内部属性__proto__指向构造函数Person.prototype,由此继承了构造函数的原型对象上的greet方法;

【注意】

  • 实例的__proto__指向构造函数的prototype原型对象实现继承,这种联系存在于实例与构造函数的原型对象之间而不是构造函数之间;
  • 当js引擎解析对象的属性时,先会搜索对象本身的属性,如果没有则会去搜索__proto__指向的原型对象上的属性,直到找到为止,如果在对象本身定义的属性和原型对象上的具有相同属性名,则在读取该属性时,自身属性会屏蔽原型对象上的属性;
  • function Person(name,age){
     this.name = name;
     this.age = age;
    }
    Person.prototype.greet = function(){
     return "hello "+this.name;
    }
    var p1 = new Person("Adam",18);
    p1.greet()//hello Adam;
    p1.greet = function(){
     return "hello world"
    }
    修改构造函数的原型对象可以直接使用点操作,效果是直接在原来的原型对象上增加属性,有时需要增加的属性太多是,点操作就显得太麻烦,可以采用重置原型对象的方法:
    function Person(name,age){
     this.name = name;
     this.age = age;
    }
    Person.prototype = {
     constructor:Person,
     greet1:function(){},
     greet2:function(){},
     greet3:function(){}
    };
    var p1 = new Person("Adam",18);
    Person.prototype.constructor.name//"Object"

    需要注意的是,重置原型对象后,要重新为原型对象的constructor的属性指回Person构造函数;
    如果不重置constructor的话,那么此时的Person.prototype是由字面量创建的对象,字面量创建的对象默认的构造函数是Object;

    3.2 原型链

    上面我们只定义一个构造函数,实现一次继承;如果存在多个构造函数,它们之间也存在继承关系,那么就会形成一条关于继承的原型链;

    function SuperType(name,age){
     this.name = name;
     this.age = age
    }
    SuperType.prototype.greet = function(){
     return "hello "+this.name
    }
    function SubType(name,age,height){
     SuperType.call(this,name,age);
     this.height = height;
    }
    SubType.prototype = Object.create(SuperType.prototype);
    SubType.prototype.constructor = SubType;
    SubType.prototype.method = function(){return 1;}
    
    var instance = new SubType('teren',18,180)

    上面就是一个最为常用的实现多个类间继承的设计模式;
    使用Object.create(SuperType.prototype)的优缺点在于:

    优点:能够创建一个新的SuperType.prototype对象赋给SubType.prototype,修改SubType.prototype这个而不影响原来构造函数SuperType.prototype;
    缺点:虽然拥有子类的prototype和父类的prototype值是相同的,但内存不同,从而切断子类和父类之间的类型;

    还可以使用SubType.prototype =new SuperType()实现相同效果,其优缺点在于:

    优点:能够体现子类和父类的继承关系;
    缺点:子类具有父类的私有属性;

    所以,一般在实际实现原型链时使用Object.create()方法,而理解原型链时使用new SuperType()方法;

    3.3 与原型对象相关的方法

    遍历对象属性方法
    Object.keys()Object.getOwnPropertyNames()用于遍历对象自身而不是继承的属性名,返回一个数组,其中Object.keys()只返回可枚举属性;

    in用于检查一个对象是否具有某个属性。它不区分该属性是对象自身的属性,还是继承的属性;
    for...in用于遍历对象的所有可枚举属性(不管是自身的还是继承的)

    如果只遍历自身的属性,可以使用如下代码:

    for (var key in instance){
     if(instance.hasOwnProperty(key)){
     console.log(key)
     }
    }

    判断属性是否为自身的方法

    Object.prototype.hasOwnProperty()返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上;

    设置和获取实例对象的原型对象的方法

    Object.getPropertyOf()返回一个实例对象的原型对象;
    Object.setPropertyOf(obj,prototype)可传两个参数,第1个为现有参数,第2个为原型对象;

    判断一个对象是否为另一个对象的原型对象

    Object.prototype.isPrototypeOf()用于判断一个对象是否是另一个对象的原型;

    小结

    通读本文,我们可以知道:

  • 面向对象编程时一种思维模式,它把世界的一切看做是对象的集合,世界的运作是一个个对象分工合作的结果;映射到程序设计中,就是编写各个具有特定功能的对象(模块),并将它们有机整合使程序得以运作;
  • 对象从数据特征角度看,是无序键值对的集合,对象的属性具有两种特性——数据特性和访问器特性;
  • 创建对象的不同方式可以称为设计模式,本文简单讲解了单例模式、工厂模式、构造函数模式、原型模式、混合模式等;
  • 从面向对象角度看,对象可以说是对现实事物的抽象,类是对对象的抽象;
  • 每个函数都有一个原型对象prototype,既可以部署特定类型实例的共享属性和方法,也是JavaScript实现继承的关键;
  • prototype原型对象有一个constructor属性,该属性指向prototype所在的函数指针;
  • 每当使用构造函数创建一个实例时,该实例内部包含一个内部属性__proto__指向构造函数的原型对象,由此实现简单的继承;
  • 当A构造函数是B构造函数的实例时,由此就会形成一条原型链,即
    A构造函数的实例对象C的__proto__指向A构造函数的原型对象prototype,A构造函数prototype的__proto__指向B构造函数的原型对象prototype,B构造函数prototype的__proto__指向Function构造函数的prototype,Function的prototype的__proto__指向Object的prototype;
  • 与原型对象的相关方法包括:Object.keys()和Object.getPropertyNames()、for...in方法,Object.getPrototypeOf()和Object.setPrototypeOf()方法,Object.prototype.hasOwnProperty()和Object.prototype.isPrototypeOf()方法;
  • 参考资料

    《JavaScript高级程序设计(第3版)》
    《JavaScript标准参考教程》——阮一峰

    下载本文
    显示全文
    专题