Javascript是一种面向对象的语言,Javascript中有一个非常经典的词,即一切都是对象,因为它是面向对象的,所以面向对象的主要有三个特性:封装、继承、多态。

Javascript的继承不是C++的继承一样,对C++的继承是基于类的,而Javascript是基于原型的继承。

现在问题来了。

原型是什么原型可以在C++的类,还保留了对象的属性和方法。例如,我们写一个简单的对象

复制代码代码如下所示:

功能动物(名称){

this.name =名称;

}

animal.prototype.setname =函数(名){

this.name =名称;

}

VaR动物=新的动物(旺旺);



我们可以看到,这是一种动物,其中有一个属性名和方法集名称。请注意,一旦修改原型,如增加一个方法,对象将分享这种方法的实例,所有实例



复制代码代码如下所示:

功能动物(名称){

this.name =名称;

}

VaR动物=新的动物(旺旺);



在这一点上,动物只有name属性,如果我们添加一个句子,

复制代码代码如下所示:

animal.prototype.setname =函数(名){

this.name =名称;

}



在这一点上,动物也将有一个setName方法。



继承复制--从空的对象,我们知道一个JS的基本类型为对象,其最基本的例子是空的对象,即它直接调用新对象生成实例(),或是通过文字{ }声明。空清理对象的对象,只预定义的属性和方法,而所有其他的对象从空对象继承,那么所有的物体都有这些预定义的属性和方法的原型也是一个对象实例。原型的意义是,如果构造函数有一个原型对象的构造函数,创建实例绑定到复制的作为的实例是对象的复制、实例会继承所有的属性,方法,和其他那么,复制是如何实现的呢方法1:建立各施工实例,本实例的原型,和新的实例和原型占据相同的内存空间。这使得obj1和obj2相同的原型,但是也很不合算-内存空间的消费将迅速增加。图:





方法二:写这个策略的时间复制来自一致的欺骗技术:写时复制。这个骗局的一个典型例子是动态链接库(DLL)的操作系统,这始终是复制时存储区写入。图:





我们只是指出obj1和obj2的系统相当于他们的原型,所以在阅读的时候,你只需要跟着读原型的指令。当你需要写一个对象的属性(如obj2),我们复制图像的原型和指向未来的图像运行图:





这种方法的优点是在创建实例和读取属性时不需要大量内存开销。我们只分配一些内存的一些代码,当我们第一次写,并把一些代码和内存开销。但这成本不再是以来,因为效率访问图像和访问原型是一致的。然而,这种方法并不比系统经常写操作之前的方法更经济。方法三:读遍历方法变换复制的粒度从原型成员。该方法的特点是只有一个实例的成员写的,该成员的信息复制到实例图像。当对象的属性是书面的,如(obj2。值= 10),是产生一个指定的值的属性值,这是摆在OB成员列表J2的对象。看图片:



可以发现,obj2仍然是一个参考的原型,并没有对象实例创建大小为原型相同的操作过程。这样,写操作不会导致大量的内存分配,所以使用的内存似乎是经济的。不同的是,obj2(和所有对象实例)需要保持成员列表。这个成员名单如下两个规则:确保它是在读取时首先访问。如果在指定的对象没有属性,然后遍历整个原型链直到原型是空的或找到的属性。原型链将告诉后来者。显然,在这三种方法,读取遍历是最好的性能。因此,Javascript的原型继承构造函数读穿越。一位熟悉C++将由顶级对象的代码迷惑。没课的关键词是很好的理解,毕竟,有一个功能关键词,关键是不一样的。但什么构造函数事实上,Javascript也是一个类似的构造函数,就像构造函数一样,当使用新操作符时,构造函数实际上被调用,而这个构造函数绑定到对象,例如,我们使用下面的代码

复制代码代码如下所示:

VaR动物=动物(旺旺);



动物会被定义。有人会说,没有返回值当然是未定义的。如果你改变了动物的对象定义,



复制代码代码如下所示:

功能动物(名称){

this.name =名称;

返回此;

}



猜猜现在是什么动物

此时,动物变成了窗口,不同的是窗口被扩展了,所以窗口有一个name属性。这是因为这不是指定的,是窗口的默认点,也就是顶层变量。只有调用新的关键字才能正确地调用构造函数。那么,如何避免使用新关键字呢我们可以稍微改变一下:

复制代码代码如下所示:

功能动物(名称){

如果(!(这是动物)){

返回新动物(名称);

}

this.name =名称;

}



这都是一样的。构造函数也用来表示对象的实例所属。我们可以使用instanceof来判断,但是返回真的祖先对象,在继承遗产时的真正对象,所以它不适合。当构造函数被调用的,默认是针对当前对象。

复制代码代码如下所示:

console.log(animal.prototype.constructor =动物); / /真的



我们可以改变我们的想法:原型在函数开始时基本上是无价值的,实现可能是以下逻辑

设置 / / __proto__是内置函数的一个成员,get_prototyoe()的方法

复制代码代码如下所示:

无功__proto__ = null;

功能get_prototype(){

如果(!__proto__){

__proto__ =新的对象();

__proto__构造函数=这;

}

返回__proto__;

}





这样做的好处是避免每个声明创建一个对象实例并保存成本。构造函数可以被修改,稍后会提到它。基于原型的继承相信每个人都知道它不是智商的下限。

js有几种继承方式。这里有两种继承

1。方法是最常用的方法,而且安全性也比较好。

复制代码代码如下所示:

功能动物(名称){

this.name =名称;

}

功能犬(年龄){

this.age =年龄;

}

var狗=新狗(2);



构建继承非常简单,子对象的原型直接指向父对象的实例(注意是一个实例,而不是对象)。

复制代码代码如下所示:

dog.prototype =新的动物(旺旺);



在这一点上,狗将有两个属性,名字和年龄。如果instanceof运算符用于狗

复制代码代码如下所示:

console.log(狗是动物); / /真的

console.log(狗是狗); / /假



这样继承就实现了,但有一个小问题。

复制代码代码如下所示:

console.log(dog.prototype.constructor =动物); / /真的

console.log(dog.prototype.constructor =狗); / /假



我们可以看到构造函数所指向的对象发生了变化,因此它与我们的目的不一致,我们无法判断我们的新成员是谁,所以我们可以添加一个词:

复制代码代码如下所示:

dog.prototype.constructor =狗;



再看一遍。



复制代码代码如下所示:

console.log(狗是动物); / /假

console.log(狗是狗); / /真的



完成。这个方法是原型链维护的一部分,下面将详细描述。2。方法和两种方法各有优缺点,但弊大于利,先看代码。

复制代码代码如下所示:

功能动物(名称){

this.name =名称;

}

animal.prototype.setname =函数(名){

this.name =名称;

}

功能犬(年龄){

this.age =年龄;

}

dog.prototype = animal.prototype;



这将复制原型。

这种方法的优点是不需要实例化对象(与方法相比),节省资源,缺点也很明显。除了像上面一样的问题,即指向父对象的构造函数,它只能复制的属性和方法与父对象的原型声明。也就是说,在上面的代码中,动物的属性对象名称不能被复制,但它能复制集名称方法。最要命的是,孩子的任何修改都会影响对象的原型,是父对象,原型,由两个对象实例声明将受到影响。因此,这种方法是不推荐的。

原型链

编写继承的人们知道继承可以是多层次的。在JS,这是一个原型链。原型链在上一篇文章中也提到过几次,那么原型链是什么至少有一个实例应该将原型属性指向原型,它是Javascript中对象系统的基础。但是,这个属性是不可见的。我们称之为内部原型链,它可以从构造函数的原型构建的构造函数原型链中分离出来,我们首先根据上述代码构造一个简单的继承关系:

复制代码代码如下所示:

功能动物(名称){

this.name =名称;

}

功能犬(年龄){

this.age =年龄;

}

VaR动物=新的动物(旺旺);

dog.prototype =动物;

var狗=新狗(2);



作为提醒,前一篇文章说所有对象都继承了空对象,所以我们构建了一个原型链:





我们可以看到子对象的原型指向父对象的实例,并形成构造函数原型链。子实例的内部原型对象也指向父对象的实例,形成内部原型链。

复制代码代码如下所示:

功能getattrfromobj(ATTR,obj){

如果(typeof(obj)=对象){

var obj原=;

当(原){

如果(proto.hasownproperty(ATTR)){

Return proto{attr};

}

原__proto__ =原;

}

}

返回未定义;

}



在这种情况下,如果我们看狗的名字属性,它将被搜索的狗的成员列表,当然,它也不会被发现,因为现在的成员名单只狗年龄。然后将按照原型链,由原,以实例。继续寻找,那就是动物,名称属性找到并返回。如果你正在寻找一个不存在的属性,如果你不能在动物找到它,它会继续跟进。普罗托找到空对象。在找到它后,它将继续跟踪。普罗托和空对象的原型将指向null并寻找出口。



维护原型链时,我们谈到原型继承时提出了一个问题。当使用结构继承的方法时,子对象实例的构造函数引用父对象,其优点是可以通过构造函数属性访问原型链,缺点是显而易见的,

复制代码代码如下所示:

(新)obj(。prototype.constructor = obj);



然后,当我们重写原型,由对象产生的实例构造函数不指向自己!它违背了构造函数的初衷,上面提到了一个解决方案:

复制代码代码如下所示:

dog.prototype =新的动物(旺旺);

dog.prototype.constructor =狗;



这似乎不是一个问题。但事实上,这带来了一个新的问题,因为我们会发现我们不能追溯到原型链,因为我们找不到父对象,而内原型链原属性是不可访问的。所以SpiderMonkey提供一种改进的解决方法:添加一个属性命名__proto__任何创建的对象,它总是指向由构造函数的原型。这样,任何构造函数的修改不会影响__proto__价值,维修、维护方便的构造函数。

但有两个问题:

__proto__可以改写,这意味着它仍然是危险的使用它

__proto__是一个特殊的处理,在其他引擎SpiderMonkey,是无法使用的,如Jscript。

另一种方法是保留原型的构造函数属性,并初始化子构造函数函数中实例的构造函数属性。

代码如下:重写基类

复制代码代码如下所示:

功能犬(年龄){

this.constructor = arguments.callee;

this.age =年龄;

}

dog.prototype =新的动物(旺旺);



在这种方式中,所有的子对象实例的构造函数是正确地指出了对象,与原型的构造函数指向父对象。虽然这种方法的效率是比较低的,因为每一个施工实例重写构造函数属性,但毫无疑问的是,这种方法可以有效地解决以前的contradictions.es5以这种情况考虑和解决的问题。我们可以用Object.getPrototypeOf()在任何时间得到物体的真实原型,而无需访问构造函数或维护外部原型链。因此,作为对象属性的搜索上一节,我们可以改写如下:

复制代码代码如下所示:

功能getattrfromobj(ATTR,obj){

如果(typeof(obj)=对象){

{做

VaR原= object.getprototypeof(狗);

如果(原{属性}){

返回原{属性};

}

}

而(原);

}

返回未定义;

}



当然,这个方法只能用于浏览器支持ES5。为了向后兼容,我们还需要考虑最后的方法。该方法更适用于这两种方法集成包,读者是很好的,没有竞争。