JavaScript创建对象、原型与继承

JavaScript是一门极其灵活的语言,烂七八糟的设计是它最大的优点。不同于其他严格类型的语言例如java,学习曲线比较友好。JavaScript个人感觉上手基本不用费劲,要想上高度那就是一个悲催而且毁三观的故事。特别是有面向对象语言基础的人来说,JavaScript真像一个噩梦。JavaScript更加的零碎,封装的不是很好。你必须理清脉络深入理解了,才能写出来高大上的优雅的代码。在下尽量的用简练易懂的语言,简单的阐述一下我对JavaScript面向对象的一点粗浅的理解。

1,要想面向对象先得创建对象

a,原始模式

var object = new Object();
object.name="huazi";
object.age="22";
object.sayHi = function(){
       console.log("hi"+this.name);
}

object.sayHi(); //hi huazi

首先通过一个名称为object的对象,为其添加name和age属性以及一个sayHi的方法。this.name将会被解析成object.name。这种方式的缺点显而易见:使用同一个接口(Object)创建对象,产生大量的冗余代码。所以一般不会这么使用。

b,工厂模式

function createObject(name,age){
      var obj = new Object();
      obj.name=name;
      obj.age=age;

      obj.sayHi=function(){
            console.log("hi "+this.name);
      }
      return obj;
}

var obj = createObject("huazi",22);
console.log(obj.sayHi(); //hi huazi

此种方式创建对象叫做工厂模式,你给我属性,我给你个包含这些属性和方法的对象.这种模式创建的对象也有很大的问题:得到的对象属于什么类型的是不确定的.instanceof发现只能匹配到Object,不能匹配到createObject。

c,构造器模式

function createObject(name,age){
    this.name=name;
    this.age=age;
    this.sayHi=function(){
        console.log("hi "+this.name);
    }
}
var obj = new createObject("huazi",22);
obj.sayHi(); //hi huazi
obj instanceof createObject;//true

构造器模式创建对象的过程分为四步:1,创建一个Object类型对象.2,将执行环境交给这个对象(this指向).3,执行构造很熟.4,返回对象.此种方式解决了类型不确定的问题.但是缺点是,在每次创建对象的过程中,都会重新创建类如sayHi的对象.而这明显是不必要的.修改如下:

function createObject(name,age){
    this.name=name;
    this.age=age;
    this.sayHi=sayHi;
}
function sayHi(){
    console.log("hi "+this.name);
}
var obj = new createObject("huazi",22);
obj.sayHi(); //hi huazi
obj instanceof createObject;//true

很简单,只需要把函数放到全局作用域即可。可是问题又来了,全局作用域中的函数只能被某一个对象调用。这在逻辑上实在有点牵强。更严重的情况是,往往我们需要定义很多方法来实现一个对象。所以就会出现大量的全局函数,并且全局函数不能在其他对象上使用。

d,原型模式

function createObject(){};
createObject.prototype.name = "huazi";
createObject.prototype.age=22;
createObject.prototype.sayHi=function(){
    console.log("hi "+this.name);
}

var obj1 = new createObject();
var obj2 = new createObject();
obj1.sayHi(); //hi huazi
obj1.sayHi===obj2.sayHi; //true

好了,现在好像解决了刚才的问题,把属性和方法都加到了原型中。这样就不会出现全局属性和重复函数对象了。这种模式的缺点也显而易见:构造不能传参,也就是说所有对象将长的一模一样。还有就是内存共享的问题。属性要是引用类型比如Array那么就热闹了。牵一发动全身。

e,组合模式(构造+原型)

function createObject(name,age){
    this.name=name;
    this.age=age;
    this.array = [1,2,3];
}
createObject.prototype.sayHi = function(){
    console.log("hi "+this.name);
}

var obj1 = new createObject("huazi",22);
var obj2 = new createObject("huazi",22);
obj1.sayHi(); //hi huazi
obj1.array===obj2.array;//false

除了此种写法之外,在给原型加方法的时候还可以使用字面量的方式添加。但是需要注意的是使用字面量添加等于重写了prototype了,所以需要显示的申明,constructor的指向。

createObject.prototype={
    constructor:createObject,
    sayHi:function(){
        console.log("hi "+this.name);
    }
}

还有需要注意的一点是,在使用字面量之前不能创建对象。前面说过此种方式等于是重写了prototype。所以之前创建的对象实例,不会更新新的属性和方法。为啥?自行脑补一下。

f,动态原型模式

function createObject(name,age){
    this.name=name;
    this.age=age;
    if(typeof this.sayHi != "function"){  //不存在的情况下添加方法
        createObject.prototype.sayHi = function(){
            console.log("hi "+this.name);
        }
    }
}

var obj1 = new createObject("huazi",22);
var obj2 = new createObject("saint",22);
obj1.sayHi(); //hi huazi
obj2.sayHi(); //hi saint

这种方式就算比较完美的了。但是还还需要注意,在构造内部不能使用字面量的方式去添加原型属性。回溯到构造创建对象过程,我们知道第一步就已经创建好对象了。所以使用字面量也会发生意外的事情。

g,寄生构造模式和稳妥模式

寄生构造模式的思路是:创建一个函数,这个函数的职责就是封装一个对象所需要的所有属性和方法,然后返回对象。从样子上来看,此种方式几乎与工厂模式无异。只是早创建对象的时候方式有些变化。就是使用new关键字来创建。

function createObject(name,age){
    var obj = new Object();
    obj.name=name;
    obj.age=age;
    obj.sayHi=function(){
        console.log("hi "+this.name);
    }
    return obj;
}
var o  = new createObject("huazi",22);
o.sayHi();

再回忆一下构造模式创建对象的最后一步,返回对象。寄生模式其实是覆盖了返回对象的那一步。同时此种模式也没有摆脱类型不能确定的问题。那么此种模式在什么时候可以用到呢?

function createObject(){
    var array = new Array();
    //Array构造中创建对象是使用push的
    array.push.apply(array,arguments);
    //添加新的方法
    array.toPipeString=function(){
        return this.join("|");
    }
    return array;
}
var o  = new createObject("huazi",22);
o.toPipeString(); //huazi|22

为Array添加了一个方法,同时没有修改Array的构造和原型。实际上就还对Array的二次包装,添加新的方法。这种方式比较靠谱。安全性也比较好,够封闭。

还有一种非常安全的创建对象模式叫做稳妥模式。与寄生模式非常的类似

function createObject(name,age){
    var obj = new Object();
    obj.sayHi=function(){
        console.log("hi "+name);
    }
    return obj;
}
var o  = new createObject("huazi",22);
o.sayHi(); //hi huazi

可以发现,name值只能在sayHi中访问,在构造中包装的数据是绝对安全可靠的。这就有点private的意思了。sayHi是暴露给外部的接口。和寄生模式一样采用稳妥模式创建的对象,类型是无法确定的。

2,原型

无论什么时候只要是创建了一个新函数,随之就会根据一组特定的规则为该函数创建一个prototype属性。在默认情况下prototype属性会自动获得一个constructor(构造函数)属性,这个函数非常的特殊。包含一个指向prototype属相所在函数的指针。

function createObject(name,age){}
var o  = new createObject("huazi",22);
console.log(createObject.prototype.constructor);//function createObject(name,age) {}
console.log(createObject.prototype)//createObject(name,age) {}
//判断对象o是否是原型对象的派生。
console.log(createObject.prototype.isPrototypeOf(o));//true

实际上是这样的。每个对象都会有一个_proto_属性,这个属性指向的是函数原型createObject.prototype。而crateObject.prototype中存在一个constructor属性,此属性指向了createObject构造函数。等于指来指去,指出了一个回路。isPrototypeOf函数的参数是函数对象实例,作用是判断该实例的归属,是否是该原型对象的派生。使用hasOwnProperty()可以检测一个方法是存在于原型中还是存在于实例中,当然前提是确定可以访问这个方法或者这个方法存在才能确定。使用delete操作符可以删除掉实例中的方法或者属性。在原型中的属性方法默认是不能被delete的。还有一个坑就是delete不存在的属性或者方法也会返回true所以在使用delete的时候需要小心一些。

在给prototype添加属性之前创建了一个对象,那么这个对象是否可以引用新添加的原型上的属性呢?答案是可以的。在访问属性的时候首先会查看实例对象是否存在此属性,不然就去原型找。而_proto_就是指向原型对象的。没错是指向,所以不管何时更新原型属性都是ok的。

3,继承

ECMAScript中描述了原型链的概念,并说明原型链将作为实现继承的主要方法。原型链顾名思义,就是讲原型链接起来实现继承。子类的prototype指向父类的实例对象就是很简单的一种实现。

function superClass(){
    this.name="huazi";
}
superClass.prototype.getName=function(){
    console.log(this.name);
}
function childClass(){
    this.name="bob";
}
childClass.prototype = new superClass();

childClass.prototype.getName = function(){
    console.log(this.name+"v");
}

var instance = new childClass();
//重写了父类方法和属性
instance.getName();    //bobv
console.log(instance instanceof Object); //true
console.log(instance instanceof childClass);//true
console.log(instance instanceof superClass);//true

console.log(Object.prototype.isPrototypeOf(instance));//true
console.log(superClass.prototype.isPrototypeOf(instance));//true
console.log(childClass.prototype.isPrototypeOf(instance));//true

和其他的情况一样,一旦使用到prototype,就不建议使用字面量的方式为原型添加属性了。脑补即可。

同时此种不加区分的继承方式,任然保留内存共享的问题(引用类型属性的问题)。同原型模式创建对象的问题是一样的。当然解决这个问题的办法和解决创建对象时的方法也是一样的,那就是混合使用构造和原型来实现继承。

function superClass(name){
    this.name=name;
    this.number=[1,2,4];
}
superClass.prototype.getName=function(){
    console.log(this.name);
}
function childClass(name,age){
    //继承属性
    superClass.call(this,name);
    this.age=age;
}
childClass.prototype = new superClass();

childClass.prototype.getName = function(){
    console.log(this.name+"v");
}

var instance = new childClass("huazi",22);
//重写了父类方法和属性
instance.getName();    //huaziv
instance.number.push("1");
instance.number===new childClass().number;  //false
instance.number//[1,2,4,‘1‘]

此种方法的原则就是属性靠构造,方法靠原型。可以简单地这么理解一下。此种模式也有个问题就是效率没有达到极致,因为多次调用了父类构造。还有一个让人不舒服的地方就是原型和实例上都包括有name和age属性。这是不能接受的。

最后放一个大招,叫做寄生组合模式。其实也就是避免了上面这个方式的缺点,即绕过父类构造来继承父类原型。

/**
 * 1,找到父类原型对象
 * 2,修改构造的指向
 * 3,父类原型
 */
function inheritPrototype(superClass,childClass){
    var _prototype = new Object(superClass.prototype);
    _prototype.constructor = childClass;
    childClass.prototype=_prototype;
}

function superClass(name){
    this.name=name;
    this.number=[1,2,4];
}
superClass.prototype.getName=function(){
    console.log(this.name);
}
function childClass(name,age){
    //继承属性
    superClass.call(this,name);
    this.age=age;
}
//不同点就再这
inheritPrototype(superClass,childClass);

childClass.prototype.getName = function(){
    console.log(this.name+"v");
}

var instance = new childClass("huazi",22);
//重写了父类方法和属性
instance.getName();    //huaziv
instance.number.push("1");
instance.number===new childClass().number;  //false
instance.number//[1,2,4,‘1‘]

恶心死我了,终于写完了!

时间: 2024-10-12 18:03:11

JavaScript创建对象、原型与继承的相关文章

深入浅出JavaScript之原型链&继承

Javascript语言的继承机制,它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇特的"原型链"(prototype chain)模式,来实现继承. 这部分知识也是JavaScript里的核心重点之一,同时也是一个难点.我把学习笔记整理了一下,方便大家学习,同时自己也加深印象.这部分代码的细节很多,需要反复推敲.那我们就开始吧. 小试身手 原型链

JavaScript基于原型的继承

在一个纯粹的原型模式中,我们会摒弃类,转而专注于对象,基于原型的继承相比基于类的继承的概念上更为简单 if( typeof Object.beget !== 'function') { Object.beget = function(o) { var F = function() {}; F.prototype = o; return new F(); } } var myMammal = { name : 'Herb the Mammal', get_name : function() { r

JavaScript的原型与继承

首先,什么是原型: JavaScript里所有函数,变量方法都是对象,而对象对应的就是原型(prototype). 所以以此来看,JS里任何的对象都有一个原型对象,而默认的原型对象就处在原型链的最顶端. 现在说到了一个新的概念,什么是原型链? 在JavaScript中,一共有两种类型的值,原始值和对象值.每个对象都有一个内部属性[[prototype]],我们通常称之为原型.原型的值可以是一个对象,也可以是null.如果它的值是一个对象,则这个对象也一定有自己的原型.这样就形成了一条线性的链,我

JavaScript中原型与继承(简单例子)

利用原型prototype创建自定义对象Person: function Person(name,sex){ this.name = name; this.sex = sex; } Person.prototype = { getName:function(){return this.name}, getSex:function(){return this.sex} } var liu = new Person("lcy","female"); //创建一个空白对象

JavaScript教程——对象的继承

面向对象编程很重要的一个方面,就是对象的继承.A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法.这对于代码的复用是非常有用的. 大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承.传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现,本章介绍 JavaScript 的原型链继承. ES6 引入了 class 语法,基于 class 的继承不在这个教程介绍,请参阅<ES6 标准入门>一书的相关章节. 原型对

JavaScript构造函数+原型创建对象,原型链+借用构造函数模式继承父类练习

虽然经常说是做前端开发的,但常常使用的技术反而是JQuery比较多一点.在JavaScript的使用上相对而言少些.尤其是在创建对象使用原型链继承上面,在项目开发中很少用到.所以今天做个demo练习一下,以后忘记了也可以照搬一下. 说明一下: 1. Demo中使用的是构造函数+原型模式创建的对象.构造函数中存储对象实例使用的属性,原型模式增加实例使用的方法. 2. Demo中的继承分为两个方面.一个是属性继承,使用的是借用构造函数模式 call()方法.另一个是方法继承,这个就是使用原型方式继承

JavaScript之基础-16 JavaScript 原型与继承

一.JavaScript 原型 原型的概念 - 在JavaScript中,函数本身也是一个包含了方法和属性的对象 - 每个函数中都有一个prototype属性,该属性引用的就是原型对象 - 原型对象是保存共享属性值和共享方法的对象 为对象扩展属性 - 扩展单个对象的成员 - 扩展共享的属性值 - 内存图描述 删除属性 - 可以使用delete关键字删除对象的属性 自由属性与原型属性 - 自有属性:通过对象的引用添加的属性;其它对象可能无此属性;即使有,也是彼此独立的属性 emp1.job = '

对Javascript的原型,原型链和继承的个人理解

继承是OO语言中一个最为人津津乐道的概念,也是初接触Javascript的初学者难理解的概念=.=继承主要分为两种:一种是接口继承,另一种是实现继承.而在ECMAScript中只支持实现继承,所以我们今天来讨论讨论实现继承.实现继承就是继承实际的方法,主要依靠原型链来实现.讲到这里我们就需要讨论讨论什么是原型链. 1.什么是原型 要理解原型链我们首先要知道什么是原型.我们知道每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象包含所有实例共享的属性和方法.所以我个人

javascript中创建对象和实现继承

# oo ##创建对象 1. 原型.构造函数.实例之间的关系 * 原型的construct->构造函数:调用isPrototypeOf(obj)方法可以判定和实例的关系:  * 构造函数的prototype->原型:  * 实例的__proto__ ->原型(仅在chrome,safari,Firefox中存在,而共有的是[[prototype]]):用getPrototypeOf()方法可以返回[[prototype]]的值:  * 构造函数后构造函数的‘子类’new产生一个实例,可以

JavaScript中的原型和继承

请在此暂时忘记之前学到的面向对象的一切知识.这里只需要考虑赛车的情况.是的,就是赛车. 最近我正在观看 24 Hours of Le Mans ,这是法国流行的一项赛事.最快的车被称为 Le Mans 原型车.这些车虽然是由"奥迪"或"标致"这些厂商制造的,可它们并不是你在街上或速公路上所见到的那类汽车.它们是专为参加高速耐力赛事而制造出来的. 厂家投入巨额资金,用于研发.设计.制造这些原型车,而工程师们总是努力尝试将这项工程做到极致.他们在合金.生物燃料.制动技术