再谈javascript原型继承,浓密之创立对象的种种措施以及优缺点

by admin on 2019年2月26日

JavaScript 深远之继续的各种办法和优缺点

2017/05/28 · JavaScript
· 继承

初稿出处: 冴羽   

JavaScript 深远之创制对象的各样方式以及优缺点

2017/05/28 · JavaScript
· 对象

原稿出处: 冴羽   

诚然含义上来说Javascript并不是一门面向对象的语言,没有提供守旧的接二连三形式,可是它提供了一种原型继承的法子,利用本身提供的原型属性来落到实处持续。

再谈javascript原型继承,javascript原型继承

确实含义上来说Javascript并不是一门面向对象的语言,没有提供守旧的一而再格局,不过它提供了一种原型继承的方法,利用本人提供的原型属性来贯彻持续。

原型与原型链

说原型继承之前照旧要先说说原型和原型链,终归那是落到实处原型继承的底子。
在Javascript中,每一种函数都有三个原型属性prototype指向自己的原型,而由这么些函数创制的对象也有三个__proto__质量指向这一个原型,而函数的原型是二个对象,所以那几个指标也会有叁个__proto__本着自身的原型,那样逐层深远直到Object对象的原型,那样就形成了原型链。上面那张图很好的诠释了Javascript中的原型和原型链的关系。

必发88 1

每一个函数都以Function函数创造的靶子,所以每一种函数也有3个__proto__质量指向Function函数的原型。那里需求建议的是,真正形成原型链的是各样对象的__proto__质量,而不是函数的prototype属性,那是很主要的。

原型继承

基本情势

复制代码 代码如下:

var Parent = function(){
    this.name = ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(){
    this.name = ‘child’ ;
} ;
Child.prototype = new Parent() ;

var parent = new Parent() ;
var child = new Child() ;

console.log(parent.getName()) ; //parent
console.log(child.getName()) ; //child

那种是最简便完毕原型继承的不二法门,直接把父类的对象赋值给子类构造函数的原型,那样子类的指标就能够访问到父类以及父类构造函数的prototype中的属性。
那种措施的原型继承图如下:

必发88 2

那种办法的长处很肯定,完结丰盛简短,不需求其余特殊的操作;同时缺点也很引人侧目,固然子类要求做跟父类构造函数中相同的开头化动作,那么就得在子类构造函数中再重新三次父类中的操作:

复制代码 代码如下:

var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    this.name = name || ‘child’ ;
} ;
Child.prototype = new Parent() ;

var parent = new Parent(‘myParent’) ;
var child = new Child(‘myChild’) ;

console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

上边那种情景还只是亟需初步化name属性,借使开始化学工业作持续充实,那种格局是很不方便人民群众的。因而就有了上边一种创新的艺术。

再谈javascript原型继承,浓密之创立对象的种种措施以及优缺点。借用构造函数

复制代码 代码如下:

var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
Child.prototype = new Parent() ;

var parent = new Parent(‘myParent’) ;
var child = new Child(‘myChild’) ;

console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

上面那种格局在子类构造函数中经过apply调用父类的构造函数来进行同样的起初化学工业作,那样无论父类中做了有点发轫化学工业作,子类也能够实施同一的开头化学工业作。可是上面那种达成还留存1个难题,父类构造函数被实践了三次,二遍是在子类构造函数中,3遍在赋值子类原型时,那是很多余的,所以大家还亟需做二个创新:

复制代码 代码如下:

var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
Child.prototype = Parent.prototype ;

var parent = new Parent(‘myParent’) ;
var child = new Child(‘myChild’) ;

console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

诸如此类大家就只需求在子类构造函数中履行三回父类的构造函数,同时又能够一而再父类原型中的属性,那也正如相符原型的初衷,即是把须求复用的始末放在原型中,大家也只是继承了原型中可复用的情节。下面那种办法的原型图如下:

必发88 3

近期构造函数情势(圣杯方式)

地点借用构造函数方式最终改革的版本依旧存在难点,它把父类的原型直接赋值给子类的原型,那就会招致一个标题,正是一旦对子类的原型做了改动,那么那一个修改同时也会潜移默化到父类的原型,进而影响父类对象,那么些一定不是豪门所企望见到的。为了缓解那几个难点就有了权且构造函数方式。

复制代码 代码如下:

var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
var F = new Function(){} ;
F.prototype = Parent.prototype ;
Child.prototype = new F() ;

var parent = new Parent(‘myParent’) ;
再谈javascript原型继承,浓密之创立对象的种种措施以及优缺点。var child = new Child(‘myChild’) ;

console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

该方法的原型继承图如下:

必发88 4

很简单能够看看,通过在父类原型和子类原型之间投入1个临时的构造函数F,切断了子类原型和父类原型之间的联系,那样当子类原型做修改时就不会潜移默化到父类原型。

自笔者的法门

《Javascript情势》中到圣杯形式就结束了,但是不管上面哪种情势都有二个不易于被察觉的题材。我们能够看来本人在’Parent’的prototype属性中参预了2个obj对象字面量属性,然则平素都未曾用。大家在圣杯形式的底蕴上来看看上边那种情况:

复制代码 代码如下:

var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
var F = new Function(){} ;
F.prototype = Parent.prototype ;
Child.prototype = new F() ;

var parent = new Parent(‘myParent’) ;
var child = new Child(‘myChild’) ;

console.log(child.obj.a) ; //1
console.log(parent.obj.a) ; //1
child.obj.a = 2 ;
console.log(child.obj.a) ; //2
console.log(parent.obj.a) ; //2

在地方那种情形中,当本身修改child对象obj.a的时候,同时父类的原型中的obj.a也会被改动,那就生出了和共享原型同样的题材。出现那一个状态是因为当访问child.obj.a的时候,大家会顺着原型链一贯找到父类的prototype中,然后找到了obj属性,然后对obj.a实行修改。再看看上面那种气象:

复制代码 代码如下:

var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
var F = new Function(){} ;
F.prototype = Parent.prototype ;
Child.prototype = new F() ;

var parent = new Parent(‘myParent’) ;
var child = new Child(‘myChild’) ;

console.log(child.obj.a) ; //1
必发88,console.log(parent.obj.a) ; //1
child.obj.a = 2 ;
console.log(child.obj.a) ; //2
console.log(parent.obj.a) ; //2

此地有3个注重的难题,当目的访问原型中的属性时,原型中的属性对于目标的话是只读的,也等于说child对象足以读取obj对象,可是相当小概修改原型中obj对象引用,所以当child修改obj的时候并不会对原型中的obj发生潜移默化,它只是在本身对象添加了2个obj属性,覆盖了父类原型中的obj属性。而当child对象修改obj.a时,它先读取了原型中obj的引用,那时候child.obj和Parent.prototype.obj是指向同贰个对象的,所以child对obj.a的改动会潜移默化到Parent.prototype.obj.a的值,进而影响父类的目的。AngularJS中关于$scope嵌套的持续方式正是模范Javasript中的原型继承来兑现的。
依照地点的叙说,只要子类对象中做客到的原型跟父类原型是同贰个指标,那么就晤面世上边这种景况,所以我们能够对父类原型进行拷贝然后再赋值给子类原型,这样当子类修改原型中的属性时就只是修改父类原型的3个正片,并不会潜移默化到父类原型。具体落实如下:

复制代码 代码如下:

var deepClone = function(source,target){
    source = source || {} ;
    var toStr = Object.prototype.toString ,
        arrStr = ‘[object array]’ ;
    for(var i in source){
        if(source.hasOwnProperty(i)){
            var item = source[i] ;
            if(typeof item === ‘object’){
                target[i] = (toStr.apply(item).toLowerCase() ===
arrStr) : [] ? {} ;
                deepClone(item,target[i]) ;   
            }else{
                deepClone(item,target[i]) ;
            }
        }
    }
    return target ;
} ;
var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : ‘1’} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
Child.prototype = deepClone(Parent.prototype) ;

var child = new Child(‘child’) ;
var parent = new Parent(‘parent’) ;

console.log(child.obj.a) ; //1
console.log(parent.obj.a) ; //1
child.obj.a = ‘2’ ;
console.log(child.obj.a) ; //2
console.log(parent.obj.a) ; //1

总结上边装有的设想,Javascript继承的实际达成如下,那里只考虑了Child和Parent都以函数的事态下:

复制代码 代码如下:

var deepClone = function(source,target){
    source = source || {} ;
    var toStr = Object.prototype.toString ,
        arrStr = ‘[object array]’ ;
    for(var i in source){
        if(source.hasOwnProperty(i)){
            var item = source[i] ;
            if(typeof item === ‘object’){
                target[i] = (toStr.apply(item).toLowerCase() ===
arrStr) : [] ? {} ;
                deepClone(item,target[i]) ;   
            }else{
                deepClone(item,target[i]) ;
            }
        }
    }
    return target ;
} ;

var extend = function(Parent,Child){
    Child = Child || function(){} ;
    if(Parent === undefined)
        return Child ;
    //借用父类构造函数
    Child = function(){
        Parent.apply(this,argument) ;
    } ;
    //通过深拷贝继承父类原型   
    Child.prototype = deepClone(Parent.prototype) ;
    //重置constructor属性
    Child.prototype.constructor = Child ;
} ;

总结

说了那般多,其实Javascript中完成持续是十分灵活四种的,并不曾一种最好的方法,须求基于差其他要求达成差别方法的后续,最重庆大学的是要知道Javascript中贯彻持续的原理,也正是原型和原型链的难题,只要精通了那一个,本人实现持续就能够游刃有余。

真正意义上来说Javascript并不是一门面向对象的语言,没有提供守旧的接轨格局,可是它提供了一种…

写在前方

正文讲解JavaScript各样继承格局和优缺点。

可是注意:

这篇小说更像是笔记,哎,再让小编惊讶一句:《JavaScript高级程序设计》写得真是太好了!

写在前面

那篇作品讲解创造对象的各个措施,以及优缺点。

唯独注意:

这篇文章更像是笔记,因为《JavaScript高级程序设计》写得真是太好了!

原型与原型链

1.原型链继承

function Parent () { this.name = ‘kevin’; } Parent.prototype.getName =
function () { console.log(this.name); } function Child () { }
Child.prototype = new Parent(); var child1 = new Child();
console.log(child1.getName()) // kevin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Parent () {
    this.name = ‘kevin’;
}
 
Parent.prototype.getName = function () {
    console.log(this.name);
}
 
function Child () {
 
}
 
Child.prototype = new Parent();
 
var child1 = new Child();
 
console.log(child1.getName()) // kevin

问题:

1.引用类型的性质被抱有实例共享,举个例证:

function Parent () { this.names = [‘kevin’, ‘daisy’]; } function Child
() { } Child.prototype = new Parent(); var child1 = new Child();
child1.names.push(‘yayu’); console.log(child1.names); // [“kevin”,
“daisy”, “yayu”] var child2 = new Child(); console.log(child2.names);
// [“kevin”, “daisy”, “yayu”]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Parent () {
    this.names = [‘kevin’, ‘daisy’];
}
 
function Child () {
 
}
 
Child.prototype = new Parent();
 
var child1 = new Child();
 
child1.names.push(‘yayu’);
 
console.log(child1.names); // ["kevin", "daisy", "yayu"]
 
var child2 = new Child();
 
console.log(child2.names); // ["kevin", "daisy", "yayu"]

2.在成立 Child 的实例时,无法向Parent传参

1. 厂子方式

function createPerson(name) { var o = new Object(); o.name = name;
o.getName = function () { console.log(this.name); }; return o; } var
person1 = createPerson(‘kevin’);

1
2
3
4
5
6
7
8
9
10
11
function createPerson(name) {
    var o = new Object();
    o.name = name;
    o.getName = function () {
        console.log(this.name);
    };
 
    return o;
}
 
var person1 = createPerson(‘kevin’);

缺点:对象不可能甄别,因为有着的实例都对准3个原型

说原型继承从前依旧要先说说原型和原型链,究竟那是贯彻原型继承的底蕴。
在Javascript中,每一个函数都有3个原型属性prototype指向自己的原型,而由那一个函数成立的靶子也有多个__proto__性能指向这些原型,而函数的原型是1个对象,所以那么些指标也会有三个__proto__针对自个儿的原型,那样逐层深入直到Object对象的原型,那样就形成了原型链。上面那张图很好的说明了Javascript中的原型和原型链的关联。

2.借出构造函数(经典连续)

function Parent () { this.names = [‘kevin’, ‘daisy’]; } function Child
() { Parent.call(this); } var child1 = new Child();
child1.names.push(‘yayu’); console.log(child1.names); // [“kevin”,
“daisy”, “yayu”] var child2 = new Child(); console.log(child2.names);
// [“kevin”, “daisy”]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Parent () {
    this.names = [‘kevin’, ‘daisy’];
}
 
function Child () {
    Parent.call(this);
}
 
var child1 = new Child();
 
child1.names.push(‘yayu’);
 
console.log(child1.names); // ["kevin", "daisy", "yayu"]
 
var child2 = new Child();
 
console.log(child2.names); // ["kevin", "daisy"]

优点:

1.制止了引用类型的质量被有着实例共享

2.可以在 Child 中向 Parent 传参

举个例子:

function Parent (name) { this.name = name; } function Child (name) {
Parent.call(this, name); } var child1 = new Child(‘kevin’);
console.log(child1.name); // kevin var child2 = new Child(‘daisy’);
console.log(child2.name); // daisy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Parent (name) {
    this.name = name;
}
 
function Child (name) {
    Parent.call(this, name);
}
 
var child1 = new Child(‘kevin’);
 
console.log(child1.name); // kevin
 
var child2 = new Child(‘daisy’);
 
console.log(child2.name); // daisy

缺点:

艺术都在构造函数中定义,每一遍创设实例都会创设一回方法。

2. 构造函数形式

function Person(name) { this.name = name; this.getName = function () {
console.log(this.name); }; } var person1 = new Person(‘kevin’);

1
2
3
4
5
6
7
8
function Person(name) {
    this.name = name;
    this.getName = function () {
        console.log(this.name);
    };
}
 
var person1 = new Person(‘kevin’);

优点:实例能够辨别为3个特定的档次

缺陷:每回成立实例时,每种方法都要被创建一次

必发88 5

3.组合继承

原型链继承和经文三番4回双剑合璧。

function Parent (name) { this.name = name; this.colors = [‘red’,
‘blue’, ‘green’]; } Parent.prototype.getName = function () {
console.log(this.name) } function Child (name, age) { Parent.call(this,
name); this.age = age; } Child.prototype = new Parent(); var child1 =
new Child(‘kevin’, ’18’); child1.colors.push(‘black’);
console.log(child1.name); // kevin console.log(child1.age); // 18
console.log(child1.colors); // [“red”, “blue”, “green”, “black”] var
child2 = new Child(‘daisy’, ’20’); console.log(child2.name); // daisy
console.log(child2.age); // 20 console.log(child2.colors); // [“red”,
“blue”, “green”]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function Parent (name) {
    this.name = name;
    this.colors = [‘red’, ‘blue’, ‘green’];
}
 
Parent.prototype.getName = function () {
    console.log(this.name)
}
 
function Child (name, age) {
 
    Parent.call(this, name);
    
    this.age = age;
 
}
 
Child.prototype = new Parent();
 
var child1 = new Child(‘kevin’, ’18’);
 
child1.colors.push(‘black’);
 
console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
 
var child2 = new Child(‘daisy’, ’20’);
 
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]

亮点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的接二连三形式。

2.1 构造函数形式优化

function Person(name) { this.name = name; this.getName = getName; }
function getName() { console.log(this.name); } var person1 = new
Person(‘kevin’);

1
2
3
4
5
6
7
8
9
10
function Person(name) {
    this.name = name;
    this.getName = getName;
}
 
function getName() {
    console.log(this.name);
}
 
var person1 = new Person(‘kevin’);

可取:化解了各种方法都要被再次创立的题材

缺陷:这叫什么封装……

各类函数都以Function函数创建的指标,所以每一个函数也有四个__proto__质量指向Function函数的原型。那里必要提议的是,真正形成原型链的是各种对象的__proto__品质,而不是函数的prototype属性,那是很重点的。

4.原型式继承

function createObj(o) { function F(){} F.prototype = o; return new F();
}

1
2
3
4
5
function createObj(o) {
    function F(){}
    F.prototype = o;
    return new F();
}

纵使 ES5 Object.create 的效仿实现,将盛传的靶子作为创立的靶子的原型。

缺点:

带有引用类型的属性值始终都会共享相应的值,那一点跟原型链继承一样。

var person = { name: ‘kevin’, friends: [‘daisy’, ‘kelly’] } var
person1 = createObj(person); var person2 = createObj(person);
person1.name = ‘person1’; console.log(person2.name); // kevin
person1.firends.push(‘taylor’); console.log(person2.friends); //
[“daisy”, “kelly”, “taylor”]

1
2
3
4
5
6
7
8
9
10
11
12
13
var person = {
    name: ‘kevin’,
    friends: [‘daisy’, ‘kelly’]
}
 
var person1 = createObj(person);
var person2 = createObj(person);
 
person1.name = ‘person1’;
console.log(person2.name); // kevin
 
person1.firends.push(‘taylor’);
console.log(person2.friends); // ["daisy", "kelly", "taylor"]

注意:修改person1.name的值,person2.name的值并未发生变更,并不是因为person1person2有单独的
name 值,而是因为person1.name = 'person1',给person1添加了 name
值,并非修改了原型上的 name 值。

3. 原型方式

function Person(name) { } Person.prototype.name = ‘keivn’;
Person.prototype.getName = function () { console.log(this.name); }; var
person1 = new Person();

1
2
3
4
5
6
7
8
9
10
function Person(name) {
 
}
 
Person.prototype.name = ‘keivn’;
Person.prototype.getName = function () {
    console.log(this.name);
};
 
var person1 = new Person();

优点:方法不会再度成立

缺点:1. 全数的属性和艺术都共享 2. 不能够初步化参数

原型继承

5. 寄生式继承

始建2个仅用于封装继承进度的函数,该函数在中间以某种情势来做拉长对象,最后回到对象。

function createObj (o) { var clone = object.create(o); clone.sayName =
function () { console.log(‘hi’); } return clone; }

1
2
3
4
5
6
7
function createObj (o) {
    var clone = object.create(o);
    clone.sayName = function () {
        console.log(‘hi’);
    }
    return clone;
}

缺点:跟借用构造函数情势一样,每一趟创造对象都会创立3次方法。

3.1 原型格局优化

function Person(name) { } Person.prototype = { name: ‘kevin’, getName:
function () { console.log(this.name); } }; var person1 = new Person();

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name) {
 
}
 
Person.prototype = {
    name: ‘kevin’,
    getName: function () {
        console.log(this.name);
    }
};
 
var person1 = new Person();

优点:封装性好了好几

症结:重写了原型,丢失了constructor属性

基本情势

6. 寄生组合式继承

为了有利于大家阅读,在此间再度一下整合继承的代码:

function Parent (name) { this.name = name; this.colors = [‘red’,
‘blue’, ‘green’]; } Parent.prototype.getName = function () {
console.log(this.name) } function Child (name, age) { Parent.call(this,
name); this.age = age; } Child.prototype = new Parent(); var child1 =
new Child(‘kevin’, ’18’); console.log(child1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Parent (name) {
    this.name = name;
    this.colors = [‘red’, ‘blue’, ‘green’];
}
 
Parent.prototype.getName = function () {
    console.log(this.name)
}
 
function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}
 
Child.prototype = new Parent();
 
var child1 = new Child(‘kevin’, ’18’);
 
console.log(child1)

结合继承最大的后天不足是会调用五遍父构造函数。

一遍是设置子类型实例的原型的时候:

Child.prototype = new Parent();

1
Child.prototype = new Parent();

一遍在成立子类型实例的时候:

var child1 = new Child(‘kevin’, ’18’);

1
var child1 = new Child(‘kevin’, ’18’);

追思下 new 的一成不变达成,其实在那句中,大家会进行:

Parent.call(this, name);

1
Parent.call(this, name);

在此间,大家又会调用了3次 Parent 构造函数。

故而,在那么些事例中,假设我们打字与印刷 child1 目的,大家会发现 Child.prototype
和 child1 都有壹天性质为colors,属性值为['red', 'blue', 'green']

那正是说大家该怎么革新,幸免那1遍重复调用呢?

要是大家不行使 Child.prototype = new Parent() ,而是间接的让
Child.prototype 访问到 Parent.prototype 呢?

看看哪些达成:

function Parent (name) { this.name = name; this.colors = [‘red’,
‘blue’, ‘green’]; } Parent.prototype.getName = function () {
console.log(this.name) } function Child (name, age) { Parent.call(this,
name); this.age = age; } // 关键的三步 var F = function () {};
F.prototype = Parent.prototype; Child.prototype = new F(); var child1 =
new Child(‘kevin’, ’18’); console.log(child1);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function Parent (name) {
    this.name = name;
    this.colors = [‘red’, ‘blue’, ‘green’];
}
 
Parent.prototype.getName = function () {
    console.log(this.name)
}
 
function Child (name, age) {
    Parent.call(this, name);
    this.age = age;
}
 
// 关键的三步
var F = function () {};
 
F.prototype = Parent.prototype;
 
Child.prototype = new F();
 
 
var child1 = new Child(‘kevin’, ’18’);
 
console.log(child1);

末尾大家封装一下那个接二连三方法:

function object(o) { function F() {} F.prototype = o; return new F(); }
function prototype(child, parent) { var prototype =
object(parent.prototype); prototype.constructor = child; child.prototype
= prototype; } // 当我们运用的时候: prototype(Child, Parent);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}
 
function prototype(child, parent) {
    var prototype = object(parent.prototype);
    prototype.constructor = child;
    child.prototype = prototype;
}
 
// 当我们使用的时候:
prototype(Child, Parent);

引用《JavaScript高级程序设计》中对寄生组合式继承的赞颂正是:

那种方法的高功能展示它只调用了3遍 Parent 构造函数,并且为此制止了在
Parent.prototype
上面创立不要求的、多余的属性。与此同时,原型链还是能保全不变;因而,还是能够健康使用
instanceof 和
isPrototypeOf。开发人士普遍认为寄生组合式继承是引用类型最出彩的连续范式。

3.2 原型格局优化

function Person(name) { } Person.prototype = { constructor: Person,
name: ‘kevin’, getName: function () { console.log(this.name); } }; var
person1 = new Person();

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name) {
 
}
 
Person.prototype = {
    constructor: Person,
    name: ‘kevin’,
    getName: function () {
        console.log(this.name);
    }
};
 
var person1 = new Person();

优点:实例能够经过constructor属性找到所属构造函数

缺点:原型格局该有的毛病依然有

复制代码 代码如下:

深深连串

JavaScript深切类别目录地址:。

JavaScript深入体系推断写十五篇左右,目的在于帮我们捋顺JavaScript底层知识,重点教学如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

假设有不当大概不胆战心惊的地点,请务必给予指正,10分多谢。假设喜欢大概具有启发,欢迎star,对作者也是一种鞭策。

  1. JavaScirpt 深切之从原型到原型链
  2. JavaScript
    深切之词法成效域和动态功用域
  3. JavaScript 深远之推行上下文栈
  4. JavaScript 深切之变量对象
  5. JavaScript 深入之功用域链
  6. JavaScript 深远之从 ECMAScript 规范解读
    this
  7. JavaScript 深切之推行上下文
  8. JavaScript 深入之闭包
  9. JavaScript 深刻之参数按值传递
  10. JavaScript
    深切之call和apply的效仿完成
  11. JavaScript 长远之bind的依样画葫芦完结
  12. JavaScript 深切之new的效仿完毕
  13. JavaScript 深刻之类数组对象与
    arguments
  14. JavaScript
    长远之创建对象的有余艺术以及优缺点

    1 赞 3 收藏
    评论

必发88 6

4. 组成情势

构造函数形式与原型情势双剑合璧。

function Person(name) { this.name = name; } Person.prototype = {
constructor: Person, getName: function () { console.log(this.name); } };
var person1 = new Person();

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name) {
    this.name = name;
}
 
Person.prototype = {
    constructor: Person,
    getName: function () {
        console.log(this.name);
    }
};
 
var person1 = new Person();

优点:该共享的共享,该民用的个体,使用最普遍的法子

症结:有的人就是指望全体都写在一块儿,即更好的封装性

var Parent = function(){
    this.name = ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

4.1 动态原型格局

function Person(name) { this.name = name; if (typeof this.getName !=
“function”) { Person.prototype.getName = function () {
console.log(this.name); } } } var person1 = new Person();

1
2
3
4
5
6
7
8
9
10
function Person(name) {
    this.name = name;
    if (typeof this.getName != "function") {
        Person.prototype.getName = function () {
            console.log(this.name);
        }
    }
}
 
var person1 = new Person();

留意:使用动态原型格局时,不可能用对象字面量重写原型

释疑下为何:

function Person(name) { this.name = name; if (typeof this.getName !=
“function”) { Person.prototype = { constructor: Person, getName:
function () { console.log(this.name); } } } } var person1 = new
Person(‘kevin’); var person2 = new Person(‘daisy’); // 报错 并没有该方法
person1.getName(); // 注释掉上边的代码,那句是能够执行的。
person2.getName();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Person(name) {
    this.name = name;
    if (typeof this.getName != "function") {
        Person.prototype = {
            constructor: Person,
            getName: function () {
                console.log(this.name);
            }
        }
    }
}
 
var person1 = new Person(‘kevin’);
var person2 = new Person(‘daisy’);
 
// 报错 并没有该方法
person1.getName();
 
// 注释掉上面的代码,这句是可以执行的。
person2.getName();

为了表明那一个标题,假使开端履行var person1 = new Person('kevin')

万一对 new 和 apply
的平底执行进程不是很熟知,能够阅读尾部相关链接中的作品。

咱俩想起下 new 的落到实处步骤:

  1. 率先新建叁个指标
  2. 然后将对象的原型指向 Person.prototype
  3. 然后 Person.apply(obj)
  4. 回去这么些目的

留神这几个时候,回看下 apply 的兑现步骤,会履行 obj.Person
方法,这么些时候就会实施 if 语句里的始末,注意构造函数的 prototype
属性指向了实例的原型,使用字面量形式一向覆盖
Person.prototype,并不会转移实例的原型的值,person1
照旧是指向了原先的原型,而不是 Person.prototype。而此前的原型是尚未
getName 方法的,所以就报错了!

假诺您不怕想用字面量方式写代码,能够品尝下这种:

function Person(name) { this.name = name; if (typeof this.getName !=
“function”) { Person.prototype = { constructor: Person, getName:
function () { console.log(this.name); } } return new Person(name); } }
var person1 = new Person(‘kevin’); var person2 = new Person(‘daisy’);
person1.getName(); // kevin person2.getName(); // daisy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person(name) {
    this.name = name;
    if (typeof this.getName != "function") {
        Person.prototype = {
            constructor: Person,
            getName: function () {
                console.log(this.name);
            }
        }
 
        return new Person(name);
    }
}
 
var person1 = new Person(‘kevin’);
var person2 = new Person(‘daisy’);
 
person1.getName(); // kevin
person2.getName();  // daisy

var Child = function(){
    this.name = ‘child’ ;
} ;
Child.prototype = new Parent() ;

5.1 寄生构造函数方式

function Person(name) { var o = new Object(); o.name = name; o.getName =
function () { console.log(this.name); }; return o; } var person1 = new
Person(‘kevin’); console.log(person1 instanceof Person) // false
console.log(person1 instanceof Object) // true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(name) {
 
    var o = new Object();
    o.name = name;
    o.getName = function () {
        console.log(this.name);
    };
 
    return o;
 
}
 
var person1 = new Person(‘kevin’);
console.log(person1 instanceof Person) // false
console.log(person1 instanceof Object)  // true

寄生构造函数情势,笔者个人认为应该那样读:

寄生-构造函数-情势,也正是说寄生在构造函数的一种艺术。

也便是说打着构造函数的金字招牌挂羊头卖狗肉,你看创建的实例使用 instanceof
都无法儿指向构造函数!

这么方法能够在非正规情状下利用。比如大家想创建3个有着额外措施的特种数组,可是又不想直接修改Array构造函数,大家得以那样写:

function SpecialArray() { var values = new Array(); for (var i = 0, len
= arguments.length; i len; i++) { values.push(arguments[i]); }
values.toPipedString = function () { return this.join(“|”); }; return
values; } var colors = new SpecialArray(‘red’, ‘blue’, ‘green’); var
colors2 = SpecialArray(‘red2’, ‘blue2’, ‘green2’); console.log(colors);
console.log(colors.toPipedString()); // red|blue|green
console.log(colors2); console.log(colors2.toPipedString()); //
red2|blue2|green2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function SpecialArray() {
    var values = new Array();
 
    for (var i = 0, len = arguments.length; i  len; i++) {
        values.push(arguments[i]);
    }
 
    values.toPipedString = function () {
        return this.join("|");
    };
    return values;
}
 
var colors = new SpecialArray(‘red’, ‘blue’, ‘green’);
var colors2 = SpecialArray(‘red2’, ‘blue2’, ‘green2’);
 
 
console.log(colors);
console.log(colors.toPipedString()); // red|blue|green
 
console.log(colors2);
console.log(colors2.toPipedString()); // red2|blue2|green2

你会意识,其实所谓的寄生构造函数方式正是比工厂格局在创设对象的时候,多使用了2个new,实际上两者的结果是相同的。

然则小编恐怕是愿意能像使用普通 Array 一样使用 SpecialArray,就算把
SpecialArray 当成函数也一如既往能用,然则那并不是小编的原意,也变得不优雅。

在能够行使别的情势的景观下,不要采取那种形式。

只是值得一提的是,上边例子中的循环:

for (var i = 0, len = arguments.length; i len; i++) {
values.push(arguments[i]); }

1
2
3
for (var i = 0, len = arguments.length; i  len; i++) {
    values.push(arguments[i]);
}

能够替换来:

values.push.apply(values, arguments);

1
values.push.apply(values, arguments);

var parent = new Parent() ;
var child = new Child() ;

5.2 稳当构造函数情势

function person(name){ var o = new Object(); o.sayName = function(){
console.log(name); }; return o; } var person1 = person(‘kevin’);
person1.sayName(); // kevin person1.name = “daisy”; person1.sayName();
// kevin console.log(person1.name); // daisy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function person(name){
    var o = new Object();
    o.sayName = function(){
        console.log(name);
    };
    return o;
}
 
var person1 = person(‘kevin’);
 
person1.sayName(); // kevin
 
person1.name = "daisy";
 
person1.sayName(); // kevin
 
console.log(person1.name); // daisy

所谓妥帖对象,指的是未曾国有属性,而且其形式也不引用 this 的对象。

与寄生构造函数方式有两点不一样:

  1. 新创造的实例方法不引用 this
  2. 不选拔 new 操作符调用构造函数

安妥对象最契合在有的康宁的条件中。

安妥构造函数形式也跟工厂情势一样,不能分辨对象所属类型。

console.log(parent.getName()) ; //parent
console.log(child.getName()) ; //child

深入类别

JavaScript深刻类别目录地址:。

JavaScript深远种类揣摸写十五篇左右,意在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

假定有不当或然不严格的地点,请务必给予指正,十二分谢谢。假设喜欢可能持有启发,欢迎star,对作者也是一种鞭策。

  1. JavaScirpt 深切之从原型到原型链
  2. JavaScript
    深刻之词法效能域和动态效能域
  3. JavaScript 长远之实施上下文栈
  4. JavaScript 深刻之变量对象
  5. JavaScript 深刻之效果域链
  6. JavaScript 深切之从 ECMAScript 规范解读
    this
  7. JavaScript 深刻之实践上下文
  8. JavaScript 深远之闭包
  9. JavaScript 深刻之参数按值传递
  10. JavaScript
    深远之call和apply的模拟达成
  11. JavaScript 深远之bind的模仿达成
  12. JavaScript 深刻之new的萧规曹随达成
  13. JavaScript 深切之类数组对象与
    arguments

    1 赞 收藏
    评论

必发88 7

那种是最简便达成原型继承的方法,直接把父类的对象赋值给子类构造函数的原型,那样子类的靶子就能够访问到父类以及父类构造函数的prototype中的属性。
那种办法的原型继承图如下:

必发88 8

那种办法的长处很醒目,实现丰裕大概,不需求其余特殊的操作;同时缺点也很扎眼,借使子类必要做跟父类构造函数中相同的初阶化动作,那么就得在子类构造函数中再重复贰回父类中的操作:

复制代码 代码如下:

var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    this.name = name || ‘child’ ;
} ;
Child.prototype = new Parent() ;

var parent = new Parent(‘myParent’) ;
var child = new Child(‘myChild’) ;

console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

上边这种景况还只是内需早先化name属性,假诺初叶化学工业作不断增多,这种方法是很不便宜的。因此就有了下边一种创新的办法。

借用构造函数

复制代码 代码如下:

var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
Child.prototype = new Parent() ;

var parent = new Parent(‘myParent’) ;
var child = new Child(‘myChild’) ;

console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

地点那种办法在子类构造函数中通过apply调用父类的构造函数来开始展览同样的开首化学工业作,那样不管父类中做了稍稍起始化学工业作,子类也得以实施同一的开端化学工业作。不过下面那种完结还设有一个题材,父类构造函数被执行了三回,叁次是在子类构造函数中,三次在赋值子类原型时,那是很多余的,所以我们还须求做贰个改革:

复制代码 代码如下:

var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
Child.prototype = Parent.prototype ;

var parent = new Parent(‘myParent’) ;
var child = new Child(‘myChild’) ;

console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

如此我们就只需求在子类构造函数中实施二回父类的构造函数,同时又有什么不可接二连三父类原型中的属性,那也比较符合原型的初衷,正是把需求复用的情节放在原型中,大家也只是再而三了原型中可复用的内容。上边那种办法的原型图如下:

必发88 9

一时构造函数形式(圣杯形式)

地方借用构造函数情势最后改革的版本依然存在难点,它把父类的原型间接赋值给子类的原型,那就会导致三个难题,正是尽管对子类的原型做了修改,那么这些修改同时也会潜移默化到父类的原型,进而影响父类对象,那一个一定不是豪门所希望见到的。为了解决这一个标题就有了一时半刻构造函数格局。

复制代码 代码如下:

var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
var F = new Function(){} ;
F.prototype = Parent.prototype ;
Child.prototype = new F() ;

var parent = new Parent(‘myParent’) ;
var child = new Child(‘myChild’) ;

console.log(parent.getName()) ; //myParent
console.log(child.getName()) ; //myChild

该方法的原型继承图如下:

必发88 10

很简单能够看来,通过在父类原型和子类原型之间投入三个一时的构造函数F,切断了子类原型和父类原型之间的联系,那样当子类原型做修改时就不会潜移默化到父类原型。

本身的章程

《Javascript情势》中到圣杯情势就谢世了,可是不管下面哪个种类格局都有1个不易于被察觉的标题。大家能够观望自家在’Parent’的prototype属性中进入了3个obj对象字面量属性,然而从来都并未用。大家在圣杯情势的底子上来看望上边那种意况:

复制代码 代码如下:

var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
var F = new Function(){} ;
F.prototype = Parent.prototype ;
Child.prototype = new F() ;

var parent = new Parent(‘myParent’) ;
var child = new Child(‘myChild’) ;

console.log(child.obj.a) ; //1
console.log(parent.obj.a) ; //1
child.obj.a = 2 ;
console.log(child.obj.a) ; //2
console.log(parent.obj.a) ; //2

在上边那种状态中,当本身修改child对象obj.a的时候,同时父类的原型中的obj.a也会被涂改,那就生出了和共享原型同样的标题。出现这些境况是因为当访问child.obj.a的时候,大家会沿着原型链一向找到父类的prototype中,然后找到了obj属性,然后对obj.a实行改动。再看看上边那种地方:

复制代码 代码如下:

var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
var F = new Function(){} ;
F.prototype = Parent.prototype ;
Child.prototype = new F() ;

var parent = new Parent(‘myParent’) ;
var child = new Child(‘myChild’) ;

console.log(child.obj.a) ; //1
console.log(parent.obj.a) ; //1
child.obj.a = 2 ;
console.log(child.obj.a) ; //2
console.log(parent.obj.a) ; //2

那边有贰个首要的标题,当对象访问原型中的属性时,原型中的属性对于目的的话是只读的,也正是说child对象足以读取obj对象,不过力不从心修改原型中obj对象引用,所以当child修改obj的时候并不会对原型中的obj爆发震慑,它只是在自小编对象添加了贰个obj属性,覆盖了父类原型中的obj属性。而当child对象修改obj.a时,它先读取了原型中obj的引用,那时候child.obj和Parent.prototype.obj是指向同2个目的的,所以child对obj.a的修改会影响到Parent.prototype.obj.a的值,进而影响父类的指标。AngularJS中关于$scope嵌套的延续方式就是模范Javasript中的原型继承来促成的。
基于上边的描述,只要子类对象中访问到的原型跟父类原型是同2个指标,那么就会现出上边那种气象,所以大家得以对父类原型实行拷贝然后再赋值给子类原型,那样当子类修改原型中的属性时就只是修改父类原型的一个拷贝,并不会影响到父类原型。具体落到实处如下:

复制代码 代码如下:

var deepClone = function(source,target){
    source = source || {} ;
    var toStr = Object.prototype.toString ,
        arrStr = ‘[object array]’ ;
    for(var i in source){
        if(source.hasOwnProperty(i)){
            var item = source[i] ;
            if(typeof item === ‘object’){
                target[i] = (toStr.apply(item).toLowerCase() ===
arrStr) : [] ? {} ;
                deepClone(item,target[i]) ;   
            }else{
                deepClone(item,target[i]) ;
            }
        }
    }
    return target ;
} ;
var Parent = function(name){
    this.name = name || ‘parent’ ;
} ;
Parent.prototype.getName = function(){
    return this.name ;
} ;
Parent.prototype.obj = {a : ‘1’} ;

var Child = function(name){
    Parent.apply(this,arguments) ;
} ;
Child.prototype = deepClone(Parent.prototype) ;

var child = new Child(‘child’) ;
var parent = new Parent(‘parent’) ;

console.log(child.obj.a) ; //1
console.log(parent.obj.a) ; //1
child.obj.a = ‘2’ ;
console.log(child.obj.a) ; //2
console.log(parent.obj.a) ; //1

汇总下边装有的设想,Javascript继承的实际贯彻如下,那里只考虑了Child和Parent都以函数的意况下:

复制代码 代码如下:

var deepClone = function(source,target){
    source = source || {} ;
    var toStr = Object.prototype.toString ,
        arrStr = ‘[object array]’ ;
    for(var i in source){
        if(source.hasOwnProperty(i)){
            var item = source[i] ;
            if(typeof item === ‘object’){
                target[i] = (toStr.apply(item).toLowerCase() ===
arrStr) : [] ? {} ;
                deepClone(item,target[i]) ;   
            }else{
                deepClone(item,target[i]) ;
            }
        }
    }
    return target ;
} ;

var extend = function(Parent,Child){
    Child = Child || function(){} ;
    if(Parent === undefined)
        return Child ;
    //借用父类构造函数
    Child = function(){
        Parent.apply(this,argument) ;
    } ;
    //通过深拷贝继承父类原型   
    Child.prototype = deepClone(Parent.prototype) ;
    //重置constructor属性
    Child.prototype.constructor = Child ;
} ;

总结

说了那样多,其实Javascript中实现持续是拾叁分灵活多样的,并不曾一种最好的情势,需求基于差异的要求落成区别方法的再三再四,最根本的是要清楚Javascript中落实接二连三的原理,也正是原型和原型链的题材,只要理解了那个,本身达成三番五次就足以游刃有余。

你只怕感兴趣的小说:

  • 用JavaScript完毕单继承和多继承的归纳方法
  • ExtJS4中应用mixins达成多一而再示例
  • JavaScript
    mixin实现多三番五次的法门详解
  • js中继续的两种用法计算(apply,call,prototype)
  • 贯彻JavaScript中继承的二种艺术
  • JS继承–原型链继承和类式继承
  • Javascript基于对象三大特征(封装性、继承性、多态性)
  • Javascript 继承机制的贯彻
  • JavaScript继承与多接二连三实例分析

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图