面向对象编程,深入理解Javascript面向对象编程

深远明白Javascript面向对象编程

2015/12/23 · JavaScript · 1 评论 · 面向对象

原来的文章出处: 涂根华   

一:通晓构造函数原型(prototype)机制

prototype是javascript完结与管理持续的一种机制,也是面向对象的规划观念.构造函数的原型存款和储蓄着援引对象的四个指针,该指针指向与叁个原型对象,对象内部存款和储蓄着函数的原始属性和艺术;大家能够借助prototype属性,能够访问原型内部的属性和方法。

当构造函数被实列化后,所有的实例对象都足以访谈构造函数的原型成员,要是在原型中声称八个成员,全数的实列方法都得以共享它,比如如下代码:

JavaScript

// 构造函数A 它的原型有多少个getName方法 function A(name){ this.name = name; } A.prototype.getName = function(){ return this.name; } // 实列化2次后 该2个实列都有原型getName方法;如下代码 var instance1 = new A("longen1"); var instance2 = new A("longen2"); console.log(instance1.getName()); //longen1 console.log(instance2.getName()); // longen2

1
2
3
4
5
6
7
8
9
10
11
12
// 构造函数A 它的原型有一个getName方法
function A(name){
    this.name = name;
}
A.prototype.getName = function(){
    return this.name;
}
// 实列化2次后 该2个实列都有原型getName方法;如下代码
var instance1 = new A("longen1");
var instance2 = new A("longen2");
console.log(instance1.getName()); //longen1
console.log(instance2.getName()); // longen2

原型具备普通对象组织,能够将其余平日对象设置为原型对象; 日常意况下,对象都三番五次与Object,也足以掌握Object是持有指标的超类,Object是绝非原型的,而构造函数具备原型,由此实列化的指标也是Object的实列,如下代码:

JavaScript

// 实列化对象是构造函数的实列 console.log(instance1 instanceof A); //true console.log(instance2 instanceof A); // true // 实列化对象也是Object的实列 console.log(instance1 instanceof Object); //true console.log(instance2 instanceof Object); //true //Object 对象是富有指标的超类,由此构造函数也是Object的实列 console.log(A instanceof Object); // true // 然则实列化对象 不是Function对象的实列 如下代码 console.log(instance1 instanceof Function); // false console.log(instance2 instanceof Function); // false // 不过Object与Function有涉嫌 如下代码说明 console.log(Function instanceof Object); // true console.log(Object instanceof Function); // true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 实列化对象是构造函数的实列
console.log(instance1 instanceof A); //true
console.log(instance2 instanceof A); // true
 
// 实列化对象也是Object的实列
console.log(instance1 instanceof Object); //true
console.log(instance2 instanceof Object); //true
 
//Object 对象是所有对象的超类,因此构造函数也是Object的实列
console.log(A instanceof Object); // true
 
// 但是实列化对象 不是Function对象的实列 如下代码
console.log(instance1 instanceof Function); // false
console.log(instance2 instanceof Function); // false
 
// 但是Object与Function有关系 如下代码说明
console.log(Function instanceof Object);  // true
console.log(Object instanceof Function);  // true

如上代码,Function是Object的实列,也足以是Object也是Function的实列;他们是2个分裂的构造器,大家继续看如下代码:

JavaScript

var f = new Function(); var o = new Object(); console.log("------------"); console.log(f instanceof Function); //true console.log(o instanceof Function); // false console.log(f instanceof Object); // true console.log(o instanceof Object); // true

1
2
3
4
5
6
7
var f = new Function();
var o = new Object();
console.log("------------");
console.log(f instanceof Function);  //true
console.log(o instanceof Function);  // false
console.log(f instanceof Object);    // true
console.log(o instanceof Object);   // true

我们精晓,在原型上加码成员属性可能措施的话,它被抱有的实列化对象所分享属性和情势,可是只要实列化对象有和原型同样的分子成员名字的话,那么它取到的积极分子是本实列化对象,假诺本实列对象中未有的话,那么它会到原型中去寻找该成员,假如原型找到就赶回,否则的会再次来到undefined,如下代码测量检验

JavaScript

function B(){ this.name = "longen2"; } B.prototype.name = "AA"; B.prototype.getName = function(){ return this.name; }; var b1 = new B(); // 在本实列查找,找到就再次回到,不然到原型查找 console.log(b1.name); // longen2 // 在本实列未有找到该措施,就到原型去搜索console.log(b1.getName());//longen2 // 假诺在本实列未有找到的话,到原型上搜寻也不曾找到的话,就重回undefined console.log(b1.a); // undefined // 未来自己利用delete运算符删除本地实列属性,那么取到的是正是原型属性了,如下代码: delete b1.name; console.log(b1.name); // AA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function B(){
    this.name = "longen2";
}
B.prototype.name = "AA";
B.prototype.getName = function(){
    return this.name;
};
 
var b1 = new B();
// 在本实列查找,找到就返回,否则到原型查找
console.log(b1.name); // longen2
 
// 在本实列没有找到该方法,就到原型去查找
console.log(b1.getName());//longen2
 
// 如果在本实列没有找到的话,到原型上查找也没有找到的话,就返回undefined
console.log(b1.a); // undefined
 
// 现在我使用delete运算符删除本地实列属性,那么取到的是就是原型属性了,如下代码:
delete b1.name;
console.log(b1.name); // AA

二:精通原型域链的定义

原型的优点是力所能致以指标协会为载体,成立大气的实列,这个实列能分享原型中的成员(属性和措施);同期也得以利用原型实现面向对象中的承机场接人制~ 如下代码:下边我们来看那个组织函数AA和协会函数BB,当BB.prototype = new AA(11);推行那些的时候,那么B就持续与A,B中的原型就有x的属性值为11

JavaScript

function AA(x){ this.x = x; } function BB(x) { this.x = x; } BB.prototype = new AA(11); console.log(BB.prototype.x); //11 // 大家再来明白原型承接和原型链的概念,代码如下,都有注释 function A(x) { this.x = x; } // 在A的原型上定义四个属性x = 0 A.prototype.x = 0; function B(x) { this.x = x; } B.prototype = new A(1);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function AA(x){
    this.x = x;
}
function BB(x) {
    this.x = x;
}
BB.prototype = new AA(11);
console.log(BB.prototype.x); //11
 
// 我们再来理解原型继承和原型链的概念,代码如下,都有注释
function A(x) {
    this.x = x;
}
// 在A的原型上定义一个属性x = 0
A.prototype.x = 0;
function B(x) {
    this.x = x;
}
B.prototype = new A(1);

实列化A new A(1)的时候 在A函数内this.x =1, B.prototype = new A(1);B.prototype 是A的实列 也便是B承继于A, 即B.prototype.x = 1;  如下代码:

JavaScript

console.log(B.prototype.x); // 1 // 定义C的构造函数 function C(x) { this.x = x; } C.prototype = new B(2);

1
2
3
4
5
6
console.log(B.prototype.x); // 1
// 定义C的构造函数
function C(x) {
    this.x = x;
}
C.prototype = new B(2);

C.prototype = new B(2); 相当于C.prototype 是B的实列,C承继于B;那么new B(2)的时候 在B的构造函数内 this.x = 2;那么 C的原型上会有一个属性x =2 即C.prototype.x = 2; 如下代码:

JavaScript

console.log(C.prototype.x); // 2

1
console.log(C.prototype.x); // 2

上边是实列化 var d = new C(3); 实列化C的构造函数时候,那么在C的构造函数内this.x = 3; 因而如下打字与印刷实列化后的d.x = 3;如下代码:

JavaScript

var d = new C(3); console.log(d.x); // 3

1
2
var d = new C(3);
console.log(d.x); // 3

删去d.x 再拜候d.x的时候 本实列对象被删掉,只可以从原型上去寻找;由于C.prototype = new B(2); 约等于C承继于B,由此C的原型也是有x = 2;即C.prototype.x = 2; 如下代码:

JavaScript

delete d.x; console.log(d.x); //2

1
2
delete d.x;
console.log(d.x);  //2

剔除C.prototype.x后,我们从地方代码知道,C是继承于B的,自己的原型被删掉后,会去搜索父成分的原型链,由此在B的原型上找到x =1; 如下代码:

JavaScript

delete C.prototype.x; console.log(d.x); // 1

1
2
delete C.prototype.x;
console.log(d.x);  // 1

当删除B的原型属性x后,由于B是后续于A的,由此会从父成分的原型链上查找A原型上是不是有x的本性,纵然有的话,就回去,否则看A是还是不是有承继,未有持续的话,继续往Object上去寻觅,若无找到就重回undefined 由此当删除B的原型x后,delete B.prototype.x; 打字与印刷出A上的原型x=0; 如下代码:

JavaScript

delete B.prototype.x; console.log(d.x); // 0 // 继续删除A的原型x后 结果尚未找到,就重临undefined了; delete A.prototype.x; console.log(d.x); // undefined

1
2
3
4
5
6
delete B.prototype.x;
console.log(d.x);  // 0
 
// 继续删除A的原型x后 结果没有找到,就返回undefined了;
delete A.prototype.x;
console.log(d.x);  // undefined

在javascript中,一切都以对象,Function和Object都是函数的实列;构造函数的父原型指向于Function原型,Function.prototype的父原型指向与Object的原型,Object的父原型也本着与Function原型,Object.prototype是所有原型的顶层;

日常来讲代码:

JavaScript

Function.prototype.a = function(){ console.log("笔者是父原型Function"); } Object.prototype.a = function(){ console.log("小编是 父原型Object"); } function A(){ this.a = "a"; } A.prototype = { B: function(){ console.log("b"); } } // Function 和 Object都以函数的实列 如下: console.log(A instanceof Function); // true console.log(A instanceof Object); // true // A.prototype是四个目的,它是Object的实列,但不是Function的实列 console.log(A.prototype instanceof Function); // false console.log(A.prototype instanceof Object); // true // Function是Object的实列 同是Object也是Function的实列 console.log(Function instanceof Object); // true console.log(Object instanceof Function); // true /* * Function.prototype是Object的实列 但是Object.prototype不是Function的实列 * 表达Object.prototype是独具父原型的顶层 */ console.log(Function.prototype instanceof Object); //true console.log(Object.prototype instanceof Function); // false

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.prototype.a = function(){
    console.log("我是父原型Function");
}
Object.prototype.a = function(){
    console.log("我是 父原型Object");
}
function A(){
    this.a = "a";
}
A.prototype = {
    B: function(){
        console.log("b");
    }
}
// Function 和 Object都是函数的实列 如下:
console.log(A instanceof Function);  // true
console.log(A instanceof Object); // true
 
// A.prototype是一个对象,它是Object的实列,但不是Function的实列
console.log(A.prototype instanceof Function); // false
console.log(A.prototype instanceof Object); // true
 
// Function是Object的实列 同是Object也是Function的实列
console.log(Function instanceof Object);   // true
console.log(Object instanceof Function); // true
 
/*
* Function.prototype是Object的实列 但是Object.prototype不是Function的实列
* 说明Object.prototype是所有父原型的顶层
*/
console.log(Function.prototype instanceof Object);  //true
console.log(Object.prototype instanceof Function);  // false

三:驾驭原型承袭机制

构造函数皆有一个指南针指向原型,Object.prototype是颇有原型对象的顶层,举例如下代码:

JavaScript

var obj = {}; Object.prototype.name = "tugenhua"; console.log(obj.name); // tugenhua

1
2
3
var obj = {};
Object.prototype.name = "tugenhua";
console.log(obj.name); // tugenhua

给Object.prototype 定义三个属性,通过字面量构建的目的的话,都会从父类那边获得Object.prototype的质量;

从上面代码大家通晓,原型承继的点子是:假若A要求接二连三于B,那么A.prototype(A的原型) = new B()(作为B的实列) 就能够完毕A承接于B; 由此大家上边能够开首化三个空的构造函数;然后把目的赋值给构造函数的原型,然后回来该构造函数的实列; 就可以落成三番五次; 如下代码:

JavaScript

if(typeof Object.create !== 'function') { Object.create = function(o) { var F = new Function(); F.prototype = o; return new F(); } } var a = { name: 'longen', getName: function(){ return this.name; } }; var b = {}; b = Object.create(a); console.log(typeof b); //object console.log(b.name); // longen console.log(b.getName()); // longen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if(typeof Object.create !== 'function') {
    Object.create = function(o) {
        var F = new Function();
        F.prototype = o;
        return new F();
    }
}
var a = {
    name: 'longen',
    getName: function(){
        return this.name;
    }
};
var b = {};
b = Object.create(a);
console.log(typeof b); //object
console.log(b.name);   // longen
console.log(b.getName()); // longen

如上代码:大家先检验Object是不是业已有Object.create该措施;若无的话就创建二个; 该方法内创立四个空的构造器,把参数对象传递给构造函数的原型,最后回来该构造函数的实列,就贯彻了持续方式;如上测量试验代码:先定义三个a目的,有成员属性name=’longen’,还会有叁个getName()方法;最终回到该name属性; 然后定义一个b空对象,使用Object.create(a);把a对象承继给b对象,因而b对象也会有总体性name和分子方法getName();

 驾驭原型查找原理:目的查找先在该构造函数内搜索对应的品质,假若该目的未有该属性的话,

这便是说javascript会试着从该原型上去寻觅,假设原型对象中也远非该属性的话,那么它们会从原型中的原型去寻找,直到查找的Object.prototype也未曾该属性的话,那么就能够重临undefined;因而我们想要仅在该指标内搜索的话,为了进步质量,大家得以选取hasOwnProperty()来判断该对象内有未有该属性,借使有的话,就实施代码(使用for-in循环查找):如下:

JavaScript

var obj = { "name":'tugenhua', "age":'28' }; // 使用for-in循环 for(var i in obj) { if(obj.hasOwnProperty(i)) { console.log(obj[i]); //tugenhua 28 } }

1
2
3
4
5
6
7
8
9
10
var obj = {
    "name":'tugenhua',
    "age":'28'
};
// 使用for-in循环
for(var i in obj) {
    if(obj.hasOwnProperty(i)) {
        console.log(obj[i]); //tugenhua 28
    }
}

如上采纳for-in循环查找对象里面包车型客车脾性,可是大家须求通晓的是:for-in循环查找对象的质量,它是不保障顺序的,for-in循环和for循环;最实质的区分是:for循环是有种种的,for-in循环遍历对象是严节的,因而大家假设要求对象保险顺序的话,可以把目的转变为数组来,然后再利用for循环遍历就能够;

下边大家来商讨原型承接的可取和劣势

JavaScript

// 先看上面包车型客车代码: // 定义构造函数A,定义特权属性和特权方法 function A(x) { this.x1 = x; this.getX1 = function(){ return this.x1; } } // 定义构造函数B,定义特权属性和特权方法 function B(x) { this.x2 = x; this.getX2 = function(){ return this.x1 this.x2; } } B.prototype = new A(1);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 先看下面的代码:
// 定义构造函数A,定义特权属性和特权方法
function A(x) {
    this.x1 = x;
    this.getX1 = function(){
        return this.x1;
    }
}
// 定义构造函数B,定义特权属性和特权方法
function B(x) {
    this.x2 = x;
    this.getX2 = function(){
        return this.x1 this.x2;
    }
}
B.prototype = new A(1);

B.prototype = new A(1);那句代码推行的时候,B的原型承接于A,因而B.prototype也会有A的性子和措施,即:B.prototype.x1 = 1; B.prototype.getX1 方法;可是B也许有和好的特权属性x2和特权方法getX2; 如下代码:

JavaScript

function C(x) { this.x3 = x; this.getX3 = function(){ return this.x3 this.x2; } } C.prototype = new B(2); C.prototype = new B(2);那句代码实践的时候,C的原型承袭于B,因而C.prototype.x2 = 2; C.prototype.getX2方法且C也可能有投机的特权属性x3和特权方法getX3, var b = new B(2); var c = new C(3); console.log(b.x1); // 1 console.log(c.x1); // 1 console.log(c.getX3()); // 5 console.log(c.getX2()); // 3 var b = new B(2);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function C(x) {
    this.x3 = x;
    this.getX3 = function(){
        return this.x3 this.x2;
    }
}
C.prototype = new B(2);
C.prototype = new B(2);这句代码执行的时候,C的原型继承于B,因此C.prototype.x2 = 2; C.prototype.getX2方法且C也有自己的特权属性x3和特权方法getX3,
var b = new B(2);
var c = new C(3);
console.log(b.x1);  // 1
console.log(c.x1);  // 1
console.log(c.getX3()); // 5
console.log(c.getX2()); // 3
var b = new B(2);

实列化B的时候 b.x1 首先会在构造函数内查找x1属性,未有找到,由于B的原型承接于A,由此A有x1属性,由此B.prototype.x1 = 1找到了;var c = new C(3); 实列化C的时候,从地点的代码能够看来C承接于B,B承接于A,由此在C函数中平昔不找到x1属性,会往原型继续查找,直到找到父成分A有x1属性,因而c.x1 = 1;c.getX3()方法; 重返this.x3 this.x2 this.x3 = 3;this.x2 是B的习性,因而this.x2 = 2;c.getX2(); 查找的方法也一致,不再解释

prototype的久治不愈的疾病与亮点如下:

可取是:能够允许三个目标实列分享原型对象的积极分子及办法,

症结是:1. 各种构造函数唯有多个原型,由此不直接援救多种承接;

2. 无法很好地支撑多参数或动态参数的父类。在原型继承阶段,客户还无法调整以

什么参数来实列化构造函数。

四:精晓使用类承袭(承接的越来越好的方案)

类承袭也叫做构造函数承接,在子类中进行父类的构造函数;达成原理是:可以将一个构造函数A的不二等秘书籍赋值给另三个构造函数B,然后调用该格局,使组织函数A在布局函数B内部被推行,那时候构造函数B就具备了组织函数A中的属性和办法,那便是利用类承接完毕B承继与A的基本原理;

正如代码达成demo:

JavaScript

function A(x) { this.x = x; this.say = function(){ return this.x; } } function B(x,y) { this.m = A; // 把结构函数A作为二个经常函数引用给有的时候措施m this.m(x); // 实行组织函数A; delete this.m; // 清除有的时候措施this.m this.y = y; this.method = function(){ return this.y; } } var a = new A(1); var b = new B(2,3); console.log(a.say()); //输出1, 施行组织函数A中的say方法 console.log(b.say()); //输出2, 能推行该办法求证被接二连三了A中的方法 console.log(b.method()); // 输出3, 构造函数也不无本身的法子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function A(x) {
    this.x = x;
    this.say = function(){
        return this.x;
    }
}
function B(x,y) {
    this.m = A; // 把构造函数A作为一个普通函数引用给临时方法m
    this.m(x);  // 执行构造函数A;
    delete this.m; // 清除临时方法this.m
    this.y = y;
    this.method = function(){
        return this.y;
    }
}
var a = new A(1);
var b = new B(2,3);
console.log(a.say()); //输出1, 执行构造函数A中的say方法
console.log(b.say()); //输出2, 能执行该方法说明被继承了A中的方法
console.log(b.method()); // 输出3, 构造函数也拥有自己的方法

上面包车型大巴代码完结了简短的类传承的根底,可是在错综相连的编制程序中是不会利用方面包车型客车不二秘籍的,因为地点的代码远远不足严苛;代码的耦合性高;我们能够应用越来越好的方法如下:

JavaScript

function A(x) { this.x = x; } A.prototype.getX = function(){ return this.x; } // 实例化A var a = new A(1); console.log(a.x); // 1 console.log(a.getX()); // 输出1 // 现行反革命大家来创制构造函数B,让其B承袭与A,如下代码: function B(x,y) { this.y = y; A.call(this,x); } B.prototype = new A(); // 原型承接console.log(B.prototype.constructor); // 输出构造函数A,指针指向与布局函数A B.prototype.constructor = B; // 重新安装构造函数,使之指向B console.log(B.prototype.constructor); // 指向构造函数B B.prototype.getY = function(){ return this.y; } var b = new B(1,2); console.log(b.x); // 1 console.log(b.getX()); // 1 console.log(b.getY()); // 2 // 上边是言传身教对构造函数getX实行重写的点子如下: B.prototype.getX = function(){ return this.x; } var b2 = new B(10,20); console.log(b2.getX()); // 输出10

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
33
function A(x) {
    this.x = x;
}
A.prototype.getX = function(){
    return this.x;
}
// 实例化A
var a = new A(1);
console.log(a.x); // 1
console.log(a.getX()); // 输出1
// 现在我们来创建构造函数B,让其B继承与A,如下代码:
function B(x,y) {
    this.y = y;
    A.call(this,x);
}
B.prototype = new A();  // 原型继承
console.log(B.prototype.constructor); // 输出构造函数A,指针指向与构造函数A
B.prototype.constructor = B;          // 重新设置构造函数,使之指向B
console.log(B.prototype.constructor); // 指向构造函数B
B.prototype.getY = function(){
    return this.y;
}
var b = new B(1,2);
console.log(b.x); // 1
console.log(b.getX()); // 1
console.log(b.getY()); // 2
 
// 下面是演示对构造函数getX进行重写的方法如下:
B.prototype.getX = function(){
    return this.x;
}
var b2 = new B(10,20);
console.log(b2.getX());  // 输出10

下边大家来分析上面的代码:

在构造函数B内,使用A.call(this,x);那句代码的意思是:大家都知情使用call恐怕apply方法能够转移this指针指向,进而能够完成类的接二连三,由此在B构造函数内,把x的参数字传送递给A构造函数,而且一而再于结构函数A中的属性和措施;

运用那句代码:B.prototype = new A();  能够兑现原型承袭,也等于B能够承接A中的原型全体的不二法门;console.log(B.prototype.constructor); 打字与印刷出输出构造函数A,指针指向与结构函数A;大家明白的是,当定义构造函数时候,其原型对象暗中同意是一个Object类型的叁个实例,其结构器私下认可会被装置为构造函数自个儿,借使退换构造函数prototype属性值,使其针对性于另一个对象的话,那么新指标就不会怀有原本的constructor的值,举个例子第一遍打字与印刷console.log(B.prototype.constructor); 指向于被实例化后的构造函数A,重写设置B的constructor的属性值的时候,第贰遍打字与印刷就针对于自身B;因而B承继与构造A及其原型的保有属性和章程,当然我们也足以对构造函数B重写构造函数A中的方法,如上边最后几句代码是对结构函数A中的getX方法举行重写,来贯彻协和的事情~;

五:提议选拔封装类实现持续

封装类完结一连的基本原理:先定义三个封装函数extend;该函数有2个参数,Sub代表子类,Sup代表超类;在函数内,先定义三个空函数F, 用来兑现效果与利益中间转播,先设置F的原型为超类的原型,然后把空函数的实例传递给子类的原型,使用一个空函数的功利是:制止间接实例化超类也许会带动系统性子难点,举例超类的实例相当大的话,实例化会占用相当多内部存款和储蓄器;

如下代码:

JavaScript

function extend(Sub,Sup) { //Sub代表子类,Sup代表超类 // 首先定义三个空函数 var F = function(){}; // 设置空函数的原型为超类的原型 F.prototype = Sup.prototype; // 实例化空函数,并把超类原型引用传递给子类 Sub.prototype = new F(); // 重新载入参数子类原型的构造器为子类本身Sub.prototype.constructor = Sub; // 在子类中保存超类的原型,幸免子类与超类耦合 Sub.sup = Sup.prototype; if(Sup.prototype.constructor === Object.prototype.constructor) { // 检查评定超类原型的构造器是或不是为原型自个儿 Sup.prototype.constructor = Sup; } } 测量检验代码如下: // 上面大家定义2个类A和类B,大家目标是兑现B承继于A function A(x) { this.x = x; this.getX = function(){ return this.x; } } A.prototype.add = function(){ return this.x this.x; } A.prototype.mul = function(){ return this.x * this.x; } // 构造函数B function B(x){ A.call(this,x); // 继承构造函数A中的全体属性及措施 } extend(B,A); // B承袭于A var b = new B(11); console.log(b.getX()); // 11 console.log(b.add()); // 22 console.log(b.mul()); // 121

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function extend(Sub,Sup) {
    //Sub表示子类,Sup表示超类
    // 首先定义一个空函数
    var F = function(){};
 
    // 设置空函数的原型为超类的原型
    F.prototype = Sup.prototype;
 
// 实例化空函数,并把超类原型引用传递给子类
    Sub.prototype = new F();
 
    // 重置子类原型的构造器为子类自身
    Sub.prototype.constructor = Sub;
 
    // 在子类中保存超类的原型,避免子类与超类耦合
    Sub.sup = Sup.prototype;
 
    if(Sup.prototype.constructor === Object.prototype.constructor) {
        // 检测超类原型的构造器是否为原型自身
        Sup.prototype.constructor = Sup;
    }
 
}
测试代码如下:
// 下面我们定义2个类A和类B,我们目的是实现B继承于A
function A(x) {
    this.x = x;
    this.getX = function(){
        return this.x;
    }
}
A.prototype.add = function(){
    return this.x this.x;
}
A.prototype.mul = function(){
    return this.x * this.x;
}
// 构造函数B
function B(x){
    A.call(this,x); // 继承构造函数A中的所有属性及方法
}
extend(B,A);  // B继承于A
var b = new B(11);
console.log(b.getX()); // 11
console.log(b.add());  // 22
console.log(b.mul());  // 121

注意:在封装函数中,有那般一句代码:Sub.sup = Sup.prototype; 大家前几天得以来掌握下它的意义:

比如在B承继与A后,作者给B函数的原型再定义一个与A一样的原型一样的方式add();

平日来讲代码

JavaScript

extend(B,A); // B继承于A var b = new B(11); B.prototype.add = function(){ return this.x "" this.x; } console.log(b.add()); // 1111

1
2
3
4
5
6
extend(B,A);  // B继承于A
var b = new B(11);
B.prototype.add = function(){
    return this.x "" this.x;
}
console.log(b.add()); // 1111

那便是说B函数中的add方法会覆盖A函数中的add方法;因而为了不掩瞒A类中的add()方法,且调用A函数中的add方法;能够如下编写代码:

JavaScript

B.prototype.add = function(){ //return this.x "" this.x; return B.sup.add.call(this); } console.log(b.add()); // 22

1
2
3
4
5
B.prototype.add = function(){
    //return this.x "" this.x;
    return B.sup.add.call(this);
}
console.log(b.add()); // 22

B.sup.add.call(this); 中的B.sup就隐含了协会函数A函数的指针,因而包罗A函数的装有属性和形式;由此得以调用A函数中的add方法;

如上是促成持续的二种方法,类承袭和原型承继,但是这几个后续不大概持续DOM对象,也不援救承继系统静态对象,静态方法等;譬如Date对象如下:

JavaScript

// 使用类承袭Date对象 function D(){ Date.apply(this,arguments); // 调用Date对象,对其引述,完毕承继 } var d = new D(); console.log(d.toLocaleString()); // [object object]

1
2
3
4
5
6
// 使用类继承Date对象
function D(){
    Date.apply(this,arguments); // 调用Date对象,对其引用,实现继承
}
var d = new D();
console.log(d.toLocaleString()); // [object object]

如上代码运维打字与印刷出object,大家能够看见采纳类承接不或者兑现系统静态方法date对象的三番四次,因为她不是粗略的函数结构,对申明,赋值和发轫化都进展了包装,因而不恐怕继续;

上边大家再来看看使用原型继承date对象;

JavaScript

function D(){} D.prototype = new D(); var d = new D(); console.log(d.toLocaleString());//[object object]

1
2
3
4
function D(){}
D.prototype = new D();
var d = new D();
console.log(d.toLocaleString());//[object object]

大家从代码中观望,使用原型传承也心有余而力不足持续Date静态方法;不过我们得以如下封装代码承继:

JavaScript

function D(){ var d = new Date(); // 实例化Date对象 d.get = function(){ // 定义本地点法,直接调用Date对象的情势 console.log(d.toLocaleString()); } return d; } var d = new D(); d.get(); // 二零一六/12/21 中午12:08:38

1
2
3
4
5
6
7
8
9
function D(){
    var d = new Date();  // 实例化Date对象
    d.get = function(){ // 定义本地方法,间接调用Date对象的方法
        console.log(d.toLocaleString());
    }
    return d;
}
var d = new D();
d.get(); // 2015/12/21 上午12:08:38

六:理解使用复制承继

复制承袭的基本原理是:先规划一个空对象,然后使用for-in循环来遍历对象的分子,将该目的的分子三个叁个复制给新的空对象里面;那样就落到实处了复制承接了;如下代码:

JavaScript

function A(x,y) { this.x = x; this.y = y; this.add = function(){ return this.x this.y; } } A.prototype.mul = function(){ return this.x * this.y; } var a = new A(2,3); var obj = {}; for(var i in a) { obj[i] = a[i]; } console.log(obj); // object console.log(obj.x); // 2 console.log(obj.y); // 3 console.log(obj.add()); // 5 console.log(obj.mul()); // 6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function A(x,y) {
    this.x = x;
    this.y = y;
    this.add = function(){
        return this.x this.y;
    }
}
A.prototype.mul = function(){
    return this.x * this.y;
}
var a = new A(2,3);
var obj = {};
for(var i in a) {
    obj[i] = a[i];
}
console.log(obj); // object
console.log(obj.x); // 2
console.log(obj.y); // 3
console.log(obj.add()); // 5
console.log(obj.mul()); // 6

如上代码:先定义二个构造函数A,函数里面有2个属性x,y,还会有一个add方法,该构造函数原型有三个mul方法,首先实列化下A后,再次创下制一个空对象obj,遍历对象四个个复制给空对象obj,从地点的打字与印刷效果来看,大家能够看看已经达成了复制承接了;对于复制承袭,大家得以封装成如下方法来调用:

JavaScript

// 为Function扩张复制承接方法 Function.prototype.extend = function(o) { for(var i in o) { //把参数对象的分子复制给当下指标的构造函数原型对象 this.constructor.prototype[i] = o[i]; } } // 测量检验代码如下: var o = function(){}; o.extend(new A(1,2)); console.log(o.x); // 1 console.log(o.y); // 2 console.log(o.add()); // 3 console.log(o.mul()); // 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 为Function扩展复制继承方法
Function.prototype.extend = function(o) {
    for(var i in o) {
        //把参数对象的成员复制给当前对象的构造函数原型对象
        this.constructor.prototype[i] = o[i];
    }
}
// 测试代码如下:
var o = function(){};
o.extend(new A(1,2));
console.log(o.x);  // 1
console.log(o.y);  // 2
console.log(o.add()); // 3
console.log(o.mul()); // 2

地点封装的恢宏承接方法中的this对象指向于当下实列化后的指标,实际不是指向于构造函数本人,因而要选择原型扩张成员来讲,就必要运用constructor属性来指向它的构造器,然后通过prototype属性指向构造函数的原型;

复制承接有如下优点:

1. 它不可能继续系统宗旨对象的只读方法和性质

2. 万一目的数据比很多以来,那样三个个复制的话,品质是十分低的;

3. 唯有对象被实列化后,技巧给遍历对象的成员和性能,相对来讲缺乏利索;

4. 复制承接只是简短的赋值,所以只要赋值的对象是援用类型的对象的话,只怕会设有有的副功用;如上大家看出有如上有的破绽,上边我们得以采纳clone(克隆的议程)来优化下:

基本思路是:为Function扩大贰个措施,该办法能够把参数对象赋值赋值八个空构造函数的原型对象,然后实列化构造函数并回到实列对象,那样该目的就具备了该对象的具备成员;代码如下:

JavaScript

Function.prototype.clone = function(o){ function Temp(){}; Temp.prototype = o; return Temp(); } // 测量试验代码如下: Function.clone(new A(1,2)); console.log(o.x); // 1 console.log(o.y); // 2 console.log(o.add()); // 3 console.log(o.mul()); // 2

1
2
3
4
5
6
7
8
9
10
11
Function.prototype.clone = function(o){
    function Temp(){};
    Temp.prototype = o;
    return Temp();
}
// 测试代码如下:
Function.clone(new A(1,2));
console.log(o.x);  // 1
console.log(o.y);  // 2
console.log(o.add()); // 3
console.log(o.mul()); // 2

2 赞 19 收藏 1 评论

图片 1

一:掌握构造函数原型(prototype)机制

浓郁精晓JavaScript体系(18):面向对象编制程序之ECMAScript实现,

介绍

本章是有关ECMAScript面向对象达成的第2篇,第1篇我们谈谈的是概论和CEMAScript的可比,假使您还未曾读第1篇,在打开本章在此以前,我刚毅提出你先读一下第1篇,因为本篇实在太长了(35页)。

克罗地亚共和国(Republika Hrvatska)语原稿:
注:由于篇幅太长了,难免出现谬误,时刻保持考订中。

在概论里,大家延伸到了ECMAScript,现在,当大家精晓它OOP达成时,大家再来正确定义一下:
复制代码 代码如下:
ECMAScript is an object-oriented programming language supporting delegating inheritance based on prototypes.

ECMAScript是一种面向对象语言,协理基于原型的委托式承接。
大家将从最中央的数据类型来分析,首先要询问的是ECMAScript用原始值(primitive values)和对象(objects)来分别实体,由此有个别小说里说的“在JavaScript里,一切都以对象”是荒谬的(不完全对),原始值正是大家这里要研商的一对数据类型。

数据类型

固然如此ECMAScript是能够动态转化项指标动态弱类型语言,它依然有数据类型的。也正是说,二个指标要属于二个活生生的花色。
标准标准里定义了9种数据类型,但只有6种是在ECMAScript程序里能够间接访谈的,它们是:Undefined、Null、Boolean、String、Number、Object。

其他3种档案的次序只可以在落实品级访谈(ECMAScript对象是无法使用这个品种的)并用以标准来解释一些操作行为、保存中间值。那3种档案的次序是:Reference、List和Completion。

故而,Reference是用来注解delete、typeof、this那样的操作符,並且带有三个基对象和叁天质量名称;List描述的是参数列表的行事(在new表明式和函数调用的时候);Completion是用来表明行为break、continue、return和throw语句的。

原始值类型 回头来看6中用于ECMAScript程序的数据类型,前5种是原始值类型,包蕴Undefined、Null、Boolean、String、Number、Object。
原始值类型例子:
复制代码 代码如下:
var a = undefined;
var b = null;
var c = true;
var d = 'test';
var e = 10;

那些值是在底部上一向促成的,他们不是object,所以未有原型,未有构造函数。

大爷注:那些原生值和我们向来用的(Boolean、String、Number、Object)纵然名字上日常,但不是同三个东西。所以typeof(true)和typeof(Boolean)结果是不平等的,因为typeof(Boolean)的结果是function,所以函数Boolean、String、Number是有原型的(上边包车型地铁读写属性章节也会波及)。

想精晓数码是哪种类型用typeof是最为可是了,有个例证必要注意一下,就算用typeof来剖断null的类型,结果是object,为啥吗?因为null的门类是概念为Null的。
复制代码 代码如下:
alert(typeof null); // "object"

呈现"object"原因是因为专门的工作便是那样规定的:对于Null值的typeof字符串值再次回到"object“。

专门的学业没有想像解释那个,然而Brendan Eich (JavaScript发明人)注意到null相对于undefined大多数都以用于对象出现的地点,举个例子设置二个对象为空引用。但是某些文书档案里有一点点气人将之总结为bug,何况将该bug放在Brendan Eich也参预探究的bug列表里,结果就是原始,照旧把typeof null的结果设置为object(纵然262-3的正规化是定义null的品种是Null,262-5曾经将行业内部修改为null的花色是object了)。

Object类型

任何时候,Object类型(不要和Object构造函数混淆了,以往只谈谈抽象类型)是陈说ECMAScript对象的独一贰个数据类型。

Object is an unordered collection of key-value pairs.
指标是三个包含key-value对的冬天集聚

对象的key值被称作属性,属性是原始值和任何对象的器皿。如若属性的值是函数大家称它为方式。

例如:
复制代码 代码如下:
var x = { // 对象"x"有3个属性: a, b, c
  a: 10, // 原始值
  b: {z: 100}, // 对象"b"有三个属性z
  c: function () { // 函数(方法)
    alert('method x.c');
  }
};
 
alert(x.a); // 10
alert(x.b); // [object Object]
alert(x.b.z); // 100
x.c(); // 'method x.c'

动态性

正如大家在第17章中提出的,ES中的对象是全然动态的。那表示,在程序施行的时候我们能够从心所欲地加上,修改或删除对象的属性。

例如:
复制代码 代码如下:
var foo = {x: 10};
 
// 加多新属性
foo.y = 20;
console.log(foo); // {x: 10, y: 20}
 
// 将属性值修改为函数
foo.x = function () {
  console.log('foo.x');
};
 
foo.x(); // 'foo.x'
 
// 删除属性
delete foo.x;
console.log(foo); // {y: 20}

多少属性不能够被退换——(只读属性、已去除属性或不足配置的性质)。 大家将稍后在性质天性里上课。

其他,ES5规范规定,静态对象无法扩充新的特性,何况它的性格页不可能去除或然涂改。他们是所谓的冷冻对象,可以通过使用Object.freeze(o)方法获得。
复制代码 代码如下:
var foo = {x: 10};
 
// 冻结对象
Object.freeze(foo);
console.log(Object.isFrozen(foo)); // true
 
// 不可能改改
foo.x = 100;
 
// 不可能扩展
foo.y = 200;
 
// 无法去除
delete foo.x;
 
console.log(foo); // {x: 10}

在ES5行业内部里,也运用Object.preventExtensions(o)方法制止扩展,或许选拔Object.defineProperty(o)方法来定义属性:
复制代码 代码如下:
var foo = {x : 10};
 
Object.defineProperty(foo, "y", {
  value: 20,
  writable: false, // 只读
  configurable: false // 不可配置
});
 
// 无法修改
foo.y = 200;
 
// 不可能去除
delete foo.y; // false
 
// 防治扩大
Object.preventExtensions(foo);
console.log(Object.isExtensible(foo)); // false
 
// 不能够增添新属性
foo.z = 30;
 
console.log(foo); {x: 10, y: 20}

停放对象、原生对象及宿主对象

有至关重要要求小心的是正规还区分了那内置对象、成分对象和宿主对象。

内置对象和要素对象是被ECMAScript标准定义和兑现的,两个之间的反差卑不足道。全体ECMAScript完成的对象都以原生对象(个中一部分是放到对象、一些在程序施行的时候创立,比如客商自定义对象)。内置对象是原生对象的多少个子集、是在前后相继开端以前放置到ECMAScript里的(比如,parseInt, Match等)。全部的宿主对象是由宿主意况提供的,常常是浏览器,并恐怕包罗如window、alert等。

只顾,宿主对象只怕是ES自个儿实现的,完全相符标准的语义。从那点以来,他们能称为“原生宿主”对象(尽快很理论),可是行业内部未有概念“原生宿主”对象的定义。

Boolean,String和Number对象

除此以外,标准也定义了有的原生的分裂平日包装类,这一个目的是:

1.布尔指标
2.字符串对象
3.数字对象

那几个指标的创始,是经过相应的放到构造器成立,并且带有原生值作为其里面属性,那一个目的足以调换省原始值,反之亦然。

复制代码 代码如下:
var c = new Boolean(true);
var d = new String('test');
var e = new Number(10);
 
// 调换到原始值
// 使用不带new关键字的函数
с = Boolean(c);
d = String(d);
e = Number(e);
 
// 重新调换来对象
с = Object(c);
d = Object(d);
e = Object(e);

别的,也可能有对象是由新鲜的内置构造函数创建: Function(函数对象构造器)、Array(数构筑造器) RegExp(正则表明式构造器)、Math(数学模块)、 Date(日期的构造器)等等,那些目标也是Object对象类型的值,他们互相的差距是由个中属性处理的,我们在上边研讨那几个剧情。

字面量Literal

对此多少个指标的值:对象(object),数组(array)和正则表达式(regular expression),他们各自有简写的标志符称为:对象伊始化器、数组伊始化器、和正则表达式开端化器:
复制代码 代码如下:
// 等价于new Array(1, 2, 3);
// 或者array = new Array();
// array[0] = 1;
// array[1] = 2;
// array[2] = 3;
var array = [1, 2, 3];
 
// 等价于
// var object = new Object();
// object.a = 1;
// object.b = 2;
// object.c = 3;
var object = {a: 1, b: 2, c: 3};
 
// 等价于new RegExp("^\d $", "g")
var re = /^d $/g;

瞩目,假诺上述四个对象实行重复赋值名称到新的项目上的话,那随着的落到实处语义正是依据新赋值的品种来使用,比如在近些日子的Rhino和老版本SpiderMonkey 1.7的兑现上,会中标以new关键字的构造器来创造对象,但多少完成(当前Spider/TraceMonkey)字面量的语义在档案的次序改变今后却不自然改动。
复制代码 代码如下:
var getClass = Object.prototype.toString;
 
Object = Number;
 
var foo = new Object;
alert([foo, getClass.call(foo)]); // 0, "[object Number]"
 
var bar = {};
 
// Rhino, SpiderMonkey 1.7中 - 0, "[object Number]"
// 其它: still "[object Object]", "[object Object]"
alert([bar, getClass.call(bar)]);
 
// Array也是完全一样的成效
Array = Number;
 
foo = new Array;
alert([foo, getClass.call(foo)]); // 0, "[object Number]"
 
bar = [];
 
// Rhino, SpiderMonkey 1.7中 - 0, "[object Number]"
// 其它: still "", "[object Object]"
alert([bar, getClass.call(bar)]);
 
// 但对RegExp,字面量的语义是不被改动的。 semantics of the literal
// isn't being changed in all tested implementations
 
RegExp = Number;
 
foo = new RegExp;
alert([foo, getClass.call(foo)]); // 0, "[object Number]"
 
bar = /(?!)/g;
alert([bar, getClass.call(bar)]); // /(?!)/g, "[object RegExp]"

正则表达式字面量和RegExp对象

小心,上面2个例子在第三版的标准里,正则表达式的语义都以等价的,regexp字面量只在一句里存在,并且再深入分析阶段创造,但RegExp构造器创立的却是新对象,所以那只怕会导致出有些题目,如lastIndex的值在测验的时候结果是不对的:
复制代码 代码如下:
for (var k = 0; k < 4; k ) {
  var re = /ecma/g;
  alert(re.lastIndex); // 0, 4, 0, 4
  alert(re.test("ecmascript")); // true, false, true, false
}
 
// 对比
 
for (var k = 0; k < 4; k ) {
  var re = new RegExp("ecma", "g");
  alert(re.lastIndex); // 0, 0, 0, 0
  alert(re.test("ecmascript")); // true, true, true, true
}

注:可是这几个难点在第5版的ES规范都曾经纠正了,不管是依照字面量的依旧构造器的,正则都以创制新对象。

提到数组

各样文字静态研讨,JavaScript对象(平时是用对象初步化器{}来创建)被称得上哈希表哈希表或另外轻便的名目:哈希(Ruby或Perl里的定义), 处理数组(PHP里的概念),词典 (Python里的定义)等。

唯有如此的术语,主假若因为她俩的布局都以相似的,就是利用“键-值”对来囤积对象,完全契合“关联数组 ”或“哈希表 ”理论定义的数据结构。 别的,哈希表抽象数据类型常常是在落实层面使用。

然而,纵然术语上来陈说那个定义,但实质上那么些是一无所能,从ECMAScript来看:ECMAScript独有贰个对象以至项目以至它的子类型,这和“键-值”对存款和储蓄未有怎么分别,由此在此方面未有特别的概念。 因为任何对象的中间属性都能够积攒为键-值”对:
复制代码 代码如下:
var a = {x: 10};
a['y'] = 20;
a.z = 30;
 
var b = new Number(1);
b.x = 10;
b.y = 20;
b['z'] = 30;
 
var c = new Function('');
c.x = 10;
c.y = 20;
c['z'] = 30;
 
// 等等,狂妄对象的子类型"subtype"

其他,由于在ECMAScript中目的足以是空的,所以"hash"的定义在这地也是不科学的:
复制代码 代码如下:
Object.prototype.x = 10;
 
var a = {}; // 创建空"hash"
 
alert(a["x"]); // 10, 但不为空
alert(a.toString); // function
 
a["y"] = 20; // 增添新的键值对到 "hash"
alert(a["y"]); // 20
 
Object.prototype.y = 20; // 增多原型属性
 
delete a["y"]; // 删除
alert(a["y"]); // 但这里key和value依旧有值 – 20

请稳重, ES5正规能够让我们成立没原型的对象(使用Object.create(null)方法完毕)对,从那么些角度来讲,那样的靶子足以称作哈希表:
复制代码 代码如下:
var aHashTable = Object.create(null);
console.log(aHashTable.toString); // 未定义

别的,一些本性有一定的getter / setter方法​​,所以也说不定导致混淆那么些定义:
复制代码 代码如下:
var a = new String("foo");
a['length'] = 10;
alert(a['length']); // 3

不过,就算以为“哈希”也可能有多个“原型”(举例,在Ruby或Python里弄委员会托哈希对象的类),在ECMAScript里,那个术语也是不对的,因为2个表示法之间从未语义上的区分(即用点表示法a.b和a["b"]表示法)。

在ECMAScript中的“property属性”的概念语义上和"key"、数组索引、方法未有分其他,这里全体指标的性质读写都要依照统一的法则:检查原型链。

在底下Ruby的事例中,大家得以看见语义上的区分:
复制代码 代码如下:
a = {}
a.class # Hash
 
a.length # 0
 
# new "key-value" pair
a['length'] = 10;
 
# 语义上,用点访谈的是性质或措施,并不是key
 
a.length # 1
 
# 而索引器访谈访谈的是hash里的key
 
a['length'] # 10
 
# 就类似于在存活对象上动态评释Hash类
# 然后声称新属性或措施
 
class Hash
  def z
    100
  end
end
 
# 新天性能够访谈
 
a.z # 100
 
# 但不是"key"
 
a['z'] # nil

ECMA-262-3正式并不曾定义“哈希”(以至近似)的概念。然而,有那般的结构理论的话,那也许那些命名的指标。

目的调换

将对象转化成原始值能够用valueOf方法,正如大家所说的,当函数的构造函数调用做为function(对于一些类别的),但万一不用new关键字就是将对象转化成原始值,就相当于隐式的valueOf方法调用:
复制代码 代码如下:
var a = new Number(1);
var primitiveA = Number(a); // 隐式"valueOf"调用
var alsoPrimitiveA = a.valueOf(); // 显式调用
 
alert([
  typeof a, // "object"
  typeof primitiveA, // "number"
  typeof alsoPrimitiveA // "number"
]);

这种艺术允许对象参与各类操作,举个例子:
复制代码 代码如下:
var a = new Number(1);
var b = new Number(2);
 
alert(a b); // 3
 
// 甚至
 
var c = {
  x: 10,
  y: 20,
  valueOf: function () {
    return this.x this.y;
  }
};
 
var d = {
  x: 30,
  y: 40,
  // 和c的valueOf效率雷同
  valueOf: c.valueOf
};
 
alert(c d); // 100

valueOf的暗中认可值会依据依照指标的品种改造(即使不被隐蔽的话),对一些对象,他回到的是this——举例:Object.prototype.valueOf(),还应该有计算型的值:Date.prototype.valueOf()重临的是日期时间:
复制代码 代码如下:
var a = {};
alert(a.valueOf() === a); // true, "valueOf"返回this
 
var d = new Date();
alert(d.valueOf()); // time
alert(d.valueOf() === d.getTime()); // true

其余,对象还应该有一个更原始的代表性——字符串体现。 那个toString方法是保障的,它在好几操作上是全自动使用的:
复制代码 代码如下:
var a = {
  valueOf: function () {
    return 100;
  },
  toString: function () {
    return '__test';
  }
};
 
// 那些操作里,toString方法自动调用
alert(a); // "__test"
 
// 可是此地,调用的却是valueOf()方法
alert(a 10); // 110
 
// 但,一旦valueOf删除今后
// toString又能够自动调用了
delete a.valueOf;
alert(a 10); // "_test10"

Object.prototype上定义的toString方法具备非同小可含义,它回到的我们下边就要切磋的在那之中[[Class]]属性值。

和转化成原始值(ToPrimitive)相比较,将值转化成对象类型也可以有三个转会标准(ToObject)。

三个显式方法是选择内置的Object构造函数作为function来调用ToObject(有些周边通过new关键字也足以):
复制代码 代码如下:
var n = Object(1); // [object Number]
var s = Object('test'); // [object String]
 
// 一些类似,使用new操作符也得以
var b = new Object(true); // [object Boolean]
 
// 应用参数new Object的话创造的是简约对象
var o = new Object(); // [object Object]
 
// 如若参数是三个存世的对象
// 那创立的结果正是轻松再次来到该指标
var a = [];
alert(a === new Object(a)); // true
alert(a === Object(a)); // true

有关调用内置构造函数,使用恐怕不适用new操作符未有通用法规,决定于构造函数。 譬喻Array或Function当使用new操作符的构造函数恐怕不使用new操作符的粗略函数使用发生同样的结果的:
复制代码 代码如下:
var a = Array(1, 2, 3); // [object Array]
var b = new Array(1, 2, 3); // [object Array]
var c = [1, 2, 3]; // [object Array]
 
var d = Function(''); // [object Function]
var e = new Function(''); // [object Function]

稍稍操作符使用的时候,也是有局地显得和隐式转化:
复制代码 代码如下:
var a = 1;
var b = 2;
 
// 隐式
var c = a b; // 3, number
var d = a b '5' // "35", string
 
// 显式
var e = '10'; // "10", string
var f = e; // 10, number
var g = parseInt(e, 10); // 10, number
 
// 等等

品质的特征

负有的属性(property) 都足以有非常多特色(attributes)。

1.{ReadOnly}——忽视向属性赋值的写操作尝,但只读属性能够由宿主情况作为退换——也便是说不是“恒定值” ;
2.{DontEnum}——属性无法被for..in循环枚举
3.{DontDelete}——糊了delete操作符的行事被忽略(即删不掉);
4.{Internal}——内部属性,没著名字(仅在落实规模使用),ECMAScript里不能够访问那样的性质。

静心,在ES5里{ReadOnly},{DontEnum}和{DontDelete}被重新命名称为[[Writable]],[[Enumerable]]和[[Configurable]],能够手工业通过Object.defineProperty或近乎的艺术来治本那个属性。

复制代码 代码如下:
var foo = {};
 
Object.defineProperty(foo, "x", {
  value: 10,
  writable: true, // 即{ReadOnly} = false
  enumerable: false, // 即{DontEnum} = true
  configurable: true // 即{DontDelete} = false
});
 
console.log(foo.x); // 10
 
// 通过descriptor获取天性集attributes
var desc = Object.getOwnPropertyDescriptor(foo, "x");
 
console.log(desc.enumerable); // false
console.log(desc.writable); // true
// 等等

个中属性和措施

指标也得以有中间属性(完成规模的一片段),並且ECMAScript程序不可能直接访谈(不过上边大家将见到,一些实现允许访谈一些如此的本性)。 那个属性通过嵌套的中括号[[ ]]进展探望。大家来看中间的一对,这么些属性的描述能够到专门的学业里查见到。

各种对象都应有完成如下内部属性和措施:

1.[[Prototype]]——对象的原型(就要上面详细介绍)
2.[[Class]]——字符串对象的一种象征(比方,Object Array ,Function Object,Function等);用来不同对象
3.[[Get]]——得到属性值的格局
4.[[Put]]——设置属性值的法子
5.[[CanPut]]——检查属性是或不是可写
6.[[HasProperty]]——检查对象是还是不是早就有所该属性
7.[[Delete]]——从目的删除该属性
8.[[DefaultValue]]回去对象对于的原始值(调用valueOf方法,某个对象可能会抛出TypeError非凡)。
通过Object.prototype.toString()方法能够直接获得内部属性[[Class]]的值,该情势应该回到下列字符串: "[object " [[Class]] "]" 。例如:
复制代码 代码如下:
var getClass = Object.prototype.toString;
 
getClass.call({}); // [object Object]
getClass.call([]); // [object Array]
getClass.call(new Number(1)); // [object Number]
// 等等

这些作用经常是用来检核对象用的,但规范上说宿主对象的[[Class]]可以为率性值,蕴含内置对象的[[Class]]品质的值,所以理论上来看是不能够百分百来保障准确的。举个例子,document.childNodes.item(...)方法的[[Class]]脾气,在IE里重回"String",但其他完结里重回的确实"Function"。
复制代码 代码如下:
// in IE - "String", in other - "Function"
alert(getClass.call(document.childNodes.item));

构造函数

为此,正如大家地点提到的,在ECMAScript中的对象是经过所谓的构造函数来成立的。

Constructor is a function that creates and initializes the newly created object.
构造函数是一个函数,用来创制并开端化新创造的目的。
对象创设(内存分配)是由构造函数的中间方法[[Construct]]担当的。该内部方法的行事是概念好的,全部的构造函数都是选取该办法来为新对象分配内部存款和储蓄器的。

而开始化是经过新建对象上下上调用该函数来治本的,那是由构造函数的里边方法[[Call]]来负总责的。

在乎,客户代码只好在起初化阶段访谈,即使在开始化阶段大家得以回到差异的对象(忽视第一品级创制的tihs对象):
复制代码 代码如下:
function A() {
  // 更新新成立的靶子
  this.x = 10;
  // 但回来的是见仁见智的目的
  return [1, 2, 3];
}
 
var a = new A();
console.log(a.x, a); undefined, [1, 2, 3]

援引15章函数——创造函数的算法小节,大家得以看来该函数是贰个原生对象,包括[[Construct]] ]和[[Call]] ]天性以至显示的prototype原型属性——今后指标的原型(注:NativeObject是对于native object原生对象的预定,在底下的伪代码中央银行使)。
复制代码 代码如下:
F = new NativeObject();
 
F.[[Class]] = "Function"
 
.... // 此外性质
 
F.[[Call]] = <reference to function> // function自身
 
F.[[Construct]] = internalConstructor // 普通的此中构造函数
 
.... // 其余性质
 
// F构造函数成立的指标原型
__objectPrototype = {};
__objectPrototype.constructor = F // {DontEnum}
F.prototype = __objectPrototype

[[Call]] ]是除[[Class]]本性(这里等同于"Function" )之外区分对象的机要方式,因而,对象的此中[[Call]]天性作为函数调用。 那样的目的用typeof运算操作符的话再次来到的是"function"。然则它最首若是和原生对象有关,有个别意况的落到实处在用typeof获取值的是不等同的,比如:window.alert (...)在IE中的效果:
复制代码 代码如下:
// IE浏览器中 - "Object", "object", 此外浏览器 - "Function", "function"
alert(Object.prototype.toString.call(window.alert));
alert(typeof window.alert); // "Object"

中间方法[[Construct]]是经过应用带new运算符的构造函数来激活的,正如大家所说的这一个办法是承担内部存款和储蓄器分配和指标创立的。若无参数,调用构造函数的括号也足以简简单单:
复制代码 代码如下:
function A(x) { // constructor А
  this.x = x || 10;
}
 
// 不传参数的话,括号也足以轻便
var a = new A; // or new A();
alert(a.x); // 10
 
// 显式传入参数x
var b = new A(20);
alert(b.x); // 20

作者们也晓得,构造函数(初叶化阶段)里的shis棉被服装置为新创设的目的 。

让我们钻探一下对象成立的算法。

对象成立的算法

里面方法[[Construct]] 的一举一动可以描述成如下:
复制代码 代码如下:
F.[[Construct]](initialParameters):
 
O = new NativeObject();
 
// 属性[[Class]]被设置为"Object"
O.[[Class]] = "Object"
 
// 援用F.prototype的时候获得该对象g
var __objectPrototype = F.prototype;
 
// 如果__objectPrototype是对象,就:
O.[[Prototype]] = __objectPrototype
// 否则:
O.[[Prototype]] = Object.prototype;
// 这里O.[[Prototype]]是Object对象的原型
 
// 新成立对象初阶化的时候利用了F.[[Call]]
// 将this设置为新创造的对象O
// 参数和F里的initialParameters是千篇一律的
R = F.[[Call]](initialParameters); this === O;
// 这里R是[[Call]]的再次来到值
// 在JS里看,像这样:
// R = F.apply(O, initialParameters);
 
// 如果R是对象
return R
// 否则
return O

请在意四个根本特点:

1.首先,新创制对象的原型是从当前时时函数的prototype属性获取的(那意味着同三个构造函数创造的七个创立对象的原型能够分歧是因为函数的prototype属性也得以差异)。
2.其次,正如大家地点提到的,借使在指标初叶化的时候,[[Call]]回去的是目的,那恰恰是用以全数new操作符的结果:
复制代码 代码如下:
function A() {}
A.prototype.x = 10;
 
var a = new A();
alert(a.x); // 10 – 从原型上赢得
 
// 设置.prototype属性为新目的
// 为何显式表明.constructor属性将要底下表达
A.prototype = {
  constructor: A,
  y: 100
};
 
var b = new A();
// 对象"b"有了新属性
alert(b.x); // undefined
alert(b.y); // 100 – 从原型上得到
 
// 但a对象的原型依旧得以获取原本的结果
alert(a.x); // 10 - 从原型上得到
 
function B() {
  this.x = 10;
  return new Array();
}
 
// 倘诺"B"构造函数未有回去(或回到this)
// 那么this对象就能够利用,然而上面包车型大巴情事重返的是array
var b = new B();
alert(b.x); // undefined
alert(Object.prototype.toString.call(b)); // [object Array]

让我们来详细询问一下原型

原型

各类对象都有三个原型(一些系统对象除了)。原型通讯是经过内部的、隐式的、不可直接待上访谈[[Prototype]]原型属性来进行的,原型能够是二个对象,也足以是null值。

属性构造函数(Property constructor)

地方的例证有有2个根本的知识点,第贰个是关于函数的constructor属性的prototype属性,在函数创立的算法里,我们知晓constructor属性在函数创立阶段被安装为函数的prototype属性,constructor属性的值是函数自个儿的关键援用:

复制代码 代码如下:
function A() {}
var a = new A();
alert(a.constructor); // function A() {}, by delegation
alert(a.constructor === A); // true

日常在这里种景观下,存在着三个误区:constructor构造属性作为新成立对象自己的质量是指鹿为马的,不过,正如大家所看见的的,这些本性属于原型何况经过持续来拜访对象。

因而持续constructor属性的实例,能够直接获得的原型对象的引用:
复制代码 代码如下:
function A() {}
A.prototype.x = new Number(10);
 
var a = new A();
alert(a.constructor.prototype); // [object Object]
 
alert(a.x); // 10, 通过原型
// 和a.[[Prototype]].x效果同样
alert(a.constructor.prototype.x); // 10
 
alert(a.constructor.prototype.x === a.x); // true

但请当心,函数的constructor和prototype属性在对象成立现在都足以再次定义的。在此种场地下,对象失去上边所说的编写制定。假使经过函数的prototype属性去编辑元素的prototype原型的话(增多新对象或涂改现存对象),实例上将看见新扩大加的性子。

唯独,如若大家深透改动函数的prototype属性(通过分配叁个新的对象),那本来构造函数的引用正是错失,那是因为大家创建的靶子不富含constructor属性:
复制代码 代码如下:
function A() {}
A.prototype = {
  x: 10
};
 
var a = new A();
alert(a.x); // 10
alert(a.constructor === A); // false!

所以,对函数的原型援引必要手工业复苏:
复制代码 代码如下:
function A() {}
A.prototype = {
  constructor: A,
  x: 10
};
 
var a = new A();
alert(a.x); // 10
alert(a.constructor === A); // true

小心就算手动复苏了constructor属性,和原先错失的原型比较,{DontEnum}天性未有了,也等于说A.prototype里的for..in循环语句不协助了,可是第5版正式里,通过[[Enumerable]] 特性提供了调整可枚举状态enumerable的力量。
复制代码 代码如下:
var foo = {x: 10};
 
Object.defineProperty(foo, "y", {
  value: 20,
  enumerable: false // aka {DontEnum} = true
});
 
console.log(foo.x, foo.y); // 10, 20
 
for (var k in foo) {
  console.log(k); // only "x"
}
 
var xDesc = Object.getOwnPropertyDescriptor(foo, "x");
var yDesc = Object.getOwnPropertyDescriptor(foo, "y");
 
console.log(
  xDesc.enumerable, // true
  yDesc.enumerable  // false
);

显式prototype和隐式[[Prototype]]属性

经常来说,贰个目的的原型通过函数的prototype属性显式援引是不科学的,他援用的是同一个指标,对象的[[Prototype]]属性:

a.[[Prototype]] ----> Prototype <---- A.prototype

此外, 实例的[[Prototype]]值确实是在构造函数的prototype属性上猎取的。

而是,提交prototype属性不会影响已经创立对象的原型(唯有在构造函数的prototype属性退换的时候才会影响到),就是说新创立的对象才有有新的原型,而已创造对象照旧援引到原本的旧原型(那一个原型已经不可能被再被修改了)。
复制代码 代码如下:
// 在修改A.prototype原型在此以前的情况
a.[[Prototype]] ----> Prototype <---- A.prototype
 
// 修改以往
A.prototype ----> New prototype // 新对象会有所那个原型
a.[[Prototype]] ----> Prototype // 教导的原本的原型上

例如:
复制代码 代码如下:
function A() {}
A.prototype.x = 10;
 
var a = new A();
alert(a.x); // 10
 
A.prototype = {
  constructor: A,
  x: 20
  y: 30
};
 
// 对象a是经过隐式的[[Prototype]]援用从原油的prototype上获取的值
alert(a.x); // 10
alert(a.y) // undefined
 
var b = new A();
 
// 但新对象是从新原型上获得的值
alert(b.x); // 20
alert(b.y) // 30

故此,有的作品说“动态修改原型将震慑全数的对象都会具备新的原型”是荒谬的,新原型仅仅在原型修改以往的新创设对象上生效。

这里的显要法规是:对象的原型是目的的创建的时候成立的,况兼在那之后无法修改为新的对象,纵然依然援用到同多少个对象,能够透过构造函数的显式prototype援引,对象创立现在,只好对原型的习性进行加多或修改。

非标准的__proto__属性

而是,有个别实现(比如SpiderMonkey),提供了不标准的__proto__显式属性来引用对象的原型:
复制代码 代码如下:
function A() {}
A.prototype.x = 10;
 
var a = new A();
alert(a.x); // 10
 
var __newPrototype = {
  constructor: A,
  x: 20,
  y: 30
};
 
// 援引到新指标
A.prototype = __newPrototype;
 
var b = new A();
alert(b.x); // 20
alert(b.y); // 30
 
// "a"对象使用的依然是旧的原型
alert(a.x); // 10
alert(a.y); // undefined
 
// 显式修改原型
a.__proto__ = __newPrototype;
 
// 现在"а"对象引用的是新对象
alert(a.x); // 20
alert(a.y); // 30

在意,ES5提供了Object.getPrototypeOf(O)方法,该情势间接回到对象的[[Prototype]]品质——实例的发端原型。 不过,和__proto__相对来讲,它只是getter,它分裂意set值。
复制代码 代码如下:
var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // true

指标独立于构造函数 因为实例的原型独立于构造函数和构造函数的prototype属性,构造函数完结了友好的首要性职业(创制对象)今后能够去除。原型对象通过援引[[Prototype]]品质持续存在:
复制代码 代码如下:
function A() {}
A.prototype.x = 10;
 
var a = new A();
alert(a.x); // 10
 
// 设置A为null - 展现援用构造函数
A = null;
 
// 但要是.constructor属性未有改变的话,
// 依旧得以因此它制造对象
var b = new a.constructor();
alert(b.x); // 10
 
// 隐式的援引也删除掉
delete a.constructor.prototype.constructor;
delete b.constructor.prototype.constructor;
 
// 通过A的构造函数再也无法创立对象了
// 但那2个对象依旧有本身的原型
alert(a.x); // 10
alert(b.x); // 10

instanceof操作符的性状 大家是通过构造函数的prototype属性来显示援用原型的,这和instanceof操作符有关。该操作符是和原型链一同专门的学业的,并不是构造函数,思量到那或多或少,当检查评定对象的时候一再会有误解:
复制代码 代码如下:
if (foo instanceof Foo) {
  ...
}

那不是用来检查实验对象foo是还是不是是用Foo构造函数创立的,全体instanceof运算符只供给一个目的属性——foo.[[Prototype]],在原型链中从Foo.prototype初始反省其是还是不是留存。instanceof运算符是通过构造函数里的当中方法[[HasInstance]]来激活的。

让我们来拜见这几个事例:
复制代码 代码如下:
function A() {}
A.prototype.x = 10;
 
var a = new A();
alert(a.x); // 10
 
alert(a instanceof A); // true
 
// 固然设置原型为null
A.prototype = null;
 
// ..."a"依旧得以通过a.[[Prototype]]拜见原型
alert(a.x); // 10
 
// 可是,instanceof操作符无法再常常使用了
// 因为它是从构造函数的prototype属性来兑现的
alert(a instanceof A); // 错误,A.prototype不是目的

三只,可以由构造函数来创立对象,但假使目的的[[Prototype]]质量和构造函数的prototype属性的值设置的是平等的话,instanceof检查的时候会再次来到true:
复制代码 代码如下:
function B() {}
var b = new B();
 
alert(b instanceof B); // true
 
function C() {}
 
var __proto = {
  constructor: C
};
 
C.prototype = __proto;
b.__proto__ = __proto;
 
alert(b instanceof C); // true
alert(b instanceof B); // false

原型能够寄存方法并分享属性 半数以上顺序里选拔原型是用来累积对象的方法、暗许状态和分享对象的属性。

事实上,对象足以具备和煦的情形 ,但方法日常是一样的。 由此,为了内部存款和储蓄器优化,方法日常是在原型里定义的。 那表示,这么些构造函数创造的具有实例都得以分享找个法子。
复制代码 代码如下:
function A(x) {
  this.x = x || 100;
}
 
A.prototype = (function () {
 
  // 开头化上下文
  // 使用额外的靶子
 
  var _someSharedVar = 500;
 
  function _someHelper() {
    alert('internal helper: ' _someSharedVar);
  }
 
  function method1() {
    alert('method1: ' this.x);
  }
 
  function method2() {
    alert('method2: ' this.x);
    _someHelper();
  }
 
  // 原型自个儿
  return {
    constructor: A,
    method1: method1,
    method2: method2
  };
 
})();
 
var a = new A(10);
var b = new A(20);
 
a.method1(); // method1: 10
a.method2(); // method2: 10, internal helper: 500
 
b.method1(); // method1: 20
b.method2(); // method2: 20, internal helper: 500
 
// 2个目的使用的是原型里同样的格局
alert(a.method1 === b.method1); // true
alert(a.method2 === b.method2); // true

读写属性

正如小编辈关系,读取和写入属性值是通过内部的[[Get]]和[[Put]]方式。那些内部方法是通过质量访问器激活的:点标识法只怕索引标识法:
复制代码 代码如下:
// 写入
foo.bar = 10; // 调用了[[Put]]
 
console.log(foo.bar); // 10, 调用了[[Get]]
console.log(foo['bar']); // 效果等同

让我们用伪代码来看一下这一个措施是怎么样行事的:

[[Get]]方法

[[Get]]也会从原型链中查询属性,所以经过对象也足以访谈原型中的属性。

O.[[Get]](P):
复制代码 代码如下:
// 假设是谐和的属性,就回到
if (O.hasOwnProperty(P)) {
  return O.P;
}
 
// 否则,继续解析原型
var __proto = O.[[Prototype]];
 
// 倘若原型是null,重返undefined
// 那是唯恐的:最顶层Object.prototype.[[Prototype]]是null
if (__proto === null) {
  return undefined;
}
 
// 不然,对原型链递归调用[[Get]],在各层的原型中查找属性
// 直到原型为null
return __proto.[[Get]](P)

请注意,因为[[Get]]在如下情形也会重返undefined:
复制代码 代码如下:
if (window.someObject) {
  ...
}

此处,在window里没有找到someObject属性,然后会在原型里找,原型的原型里找,由此及彼,如果都找不到,根据定义就再次来到undefined。

在乎:in操作符也得以承受找寻属性(也会搜索原型链):
复制代码 代码如下:
if ('someObject' in window) {
  ...
}

那有辅助幸免有个别非凡难题:比方就算someObject存在,在someObject等于false的时候,第一批检测就通然而。

[[Put]]方法

[[Put]]办法可以创制、更新指标自己的属性,并且掩盖原型里的同名属性。

O.[[Put]](P, V):
复制代码 代码如下:
// 若是不能够给属性写值,就退出
if (!O.[[CanPut]](P)) {
  return;
}
 
// 如若指标未有自个儿的习性,就创建它
// 全数的attributes本性都以false
if (!O.hasOwnProperty(P)) {
  createNewProperty(O, P, attributes: {
    ReadOnly: false,
    DontEnum: false,
    DontDelete: false,
    Internal: false
  });
}
 
// 如若属性存在就安装值,但不转移attributes脾气
O.P = V
 
return;

例如:
复制代码 代码如下:
Object.prototype.x = 100;
 
var foo = {};
console.log(foo.x); // 100, 承接属性
 
foo.x = 10; // [[Put]]
console.log(foo.x); // 10, 自个儿性质
 
delete foo.x;
console.log(foo.x); // 重新是100,承继属性
请小心,不能够遮住原型里的只读属性,赋值结果将忽视,那是由在那之中方法[[CanPut]]控制的。

// 比如,属性length是只读的,大家来覆盖一下length试试
 
function SuperString() {
  /* nothing */
}
 
SuperString.prototype = new String("abc");
 
var foo = new SuperString();
 
console.log(foo.length); // 3, "abc"的长度
 
// 尝试蒙蔽
foo.length = 5;
console.log(foo.length); // 依然是3

但在ES5的从严方式下,要是掩盖只读属性的话,会保存TypeError错误。

质量访问器

中间方法[[Get]]和[[Put]]在ECMAScript里是通过点符号恐怕索引法来激活的,假若属性标示符是官方的名字的话,能够透过“.”来做客,而索引方运维动态定义名称。
复制代码 代码如下:
var a = {testProperty: 10};
 
alert(a.testProperty); // 10, 点
alert(a['testProperty']); // 10, 索引
 
var propertyName = 'Property';
alert(a['test' propertyName]); // 10, 动态属性通过索引的章程

这里有一个拾分首要的风味——属性访问器总是利用ToObject规范来相比“.”左侧的值。这种隐式转化和那句“在JavaScript中一切都是对象”有涉嫌,(然则,当大家曾经清楚了,JavaScript里不是装有的值都以指标)。

一经对原始值举行品质访问器取值,访问以前会先对原始值实行对象包装(饱含原始值),然后通过包装的靶子开展拜会属性,属性访谈之后,包装对象就能够被去除。

例如:
复制代码 代码如下:
var a = 10; // 原始值
 
// 可是足以访问方法(就好像对象一样)
alert(a.toString()); // "10"
 
// 别的,我们能够在a上创立三个心属性
a.test = 100; // 好疑似没难题的
 
// 但,[[Get]]办法未有再次回到该属性的值,重临的却是undefined
alert(a.test); // undefined

那就是说,为何整个例子里的原始值能够访谈toString办法,而不可能访谈新成立的test属性呢?

答案很轻松:

先是,正如我们所说,使用性质访谈器以往,它早就不是原始值了,而是一个打包过的中游对象(整个例子是采取new Number(a)),而toString方法那时候是透过原型链查找到的:
复制代码 代码如下:
// 执行a.toString()的原理:  

  1. wrapper = new Number(a);
  2. wrapper.toString(); // "10"
  3. delete wrapper;

接下来,[[Put]]艺术成立新性子时候,也是经过包装装的对象举行的:
复制代码 代码如下:
// 执行a.test = 100的原理:  

  1. wrapper = new Number(a);
  2. wrapper.test = 100;
  3. delete wrapper;

咱俩看看,在第3步的时候,包装的对象以至去除了,随着新创建的属性页被剔除了——删除包装对象自己。

接下来利用[[Get]]取得test值的时候,再三回制造了打包对象,但此时包装的靶子已经未有test属性了,所以回来的是undefined:
复制代码 代码如下:
// 执行a.test的原理:  

  1. wrapper = new Number(a);
  2. wrapper.test; // undefined

这种方法批注了原始值的读取格局,其他,任何原始值要是平日用在探望属性的话,时间功用惦念,都以直接用二个对象代替他;与此相反,假若不日常访谈,也许只是用于计算的话,到能够保存这种格局。

继承

大家精通,ECMAScript是使用基于原型的委托式承继。链和原型在原型链里已经提到过了。其实,全数寄托的落到实处和原型链的探究剖判都缩水到[[Get]]方法了。

如若您一点一滴知晓[[Get]]措施,那JavaScript中的承接那么些标题将不解自答了。

时常在论坛上探究JavaScript中的承接时,作者都以用一行代码来呈现,事实上,大家无需制造任何对象或函数,因为该语言已然是基于承袭的了,代码如下:
复制代码 代码如下:
alert(1..toString()); // "1"

作者们早已领会了[[Get]]方法和总体性访谈器的法则了,我们来拜望都发出了怎样:

1.首先,从原始值1,通过new Number(1)创立包装对象
2.然后toString方法是从这几个包裹对象上继续取得的

缘何是继续的? 因为在ECMAScript中的对象能够有友好的习性,包装对象在这里种气象下未有toString方法。 因而它是从原理里持续的,即Number.prototype。

在意有个神秘的地点,在上头的例子中的三个点不是三个荒唐。第一点是意味着小数部分,第二个才是贰个属性访问器:
复制代码 代码如下:
1.toString(); // 语法错误!
 
(1).toString(); // OK
 
1..toString(); // OK
 
1['toString'](); // OK

原型链

让大家显示怎么样为顾客定义对象创设原型链,很简单:
复制代码 代码如下:
function A() {
  alert('A.[[Call]] activated');
  this.x = 10;
}
A.prototype.y = 20;
 
var a = new A();
alert([a.x, a.y]); // 10 (自身), 20 (继承)
 
function B() {}
 
// 目前的原型链格局正是设置对象的原型为别的二个新对象
B.prototype = new A();
 
// 修复原型的constructor属性,不然的话是A了
B.prototype.constructor = B;
 
var b = new B();
alert([b.x, b.y]); // 10, 20, 2个都是继续的
 
// [[Get]] b.x:
// b.x (no) -->
// b.[[Prototype]].x (yes) - 10
 
// [[Get]] b.y
// b.y (no) -->
// b.[[Prototype]].y (no) -->
// b.[[Prototype]].[[Prototype]].y (yes) - 20
 
// where b.[[Prototype]] === B.prototype,
// and b.[[Prototype]].[[Prototype]] === A.prototype

这种措施有八个特色:

率先,B.prototype将含有x属性。乍一看这可能不对,你恐怕会想x属性是在A里定义的还要B构造函数也是那般期待的。固然原型承袭符合规律景况是没难点的,但B构造函数临时候大概不要求x属性,与基于class的接续比较,全体的属性都复制到后代子类里了。

即使如此,假若有须要(模拟基于类的继续)将x属性赋给B构造函数创设的目的上,有部分主意,大家后来来呈现此中一种格局。

附带,那不是叁个特点而是劣势——子类原型成立的时候,构造函数的代码也进行了,大家可以观察消息"A.[[Call]] activated"呈现了一回——当用A构造函数成立对象赋给B.prototype属性的时候,另外一场是a对象创造本人的时候!

上面的事例比较主要,在父类的构造函数抛出的不行:只怕实际指标创制的时候须求检讨吧,但很醒目,同样的case,也便是正是选用那一个父对象作为原型的时候就能出错。
复制代码 代码如下:
function A(param) {
  if (!param) {
    throw 'Param required';
  }
  this.param = param;
}
A.prototype.x = 10;
 
var a = new A(20);
alert([a.x, a.param]); // 10, 20
 
function B() {}
B.prototype = new A(); // Error

其他,在父类的构造函数有太多代码的话也是一种瑕疵。

消除这几个“功用”和难题,程序猿使用原型链的正规化方式(上面展示),首要目标就是在中间包装构造函数的创导,那么些包裹构造函数的链里包罗需求的原型。
复制代码 代码如下:
function A() {
  alert('A.[[Call]] activated');
  this.x = 10;
}
A.prototype.y = 20;
 
var a = new A();
alert([a.x, a.y]); // 10 (自身), 20 (集成)
 
function B() {
  // 只怕采取A.apply(this, arguments)
  B.superproto.constructor.apply(this, arguments);
}
 
// 承袭:通过空的中档构造函数将原型连在一同
var F = function () {};
F.prototype = A.prototype; // 引用
B.prototype = new F();
B.superproto = A.prototype; // 突显引用到别的三个原型上, "sugar"
 
// 修复原型的constructor属性,不然的正是A了
B.prototype.constructor = B;
 
var b = new B();
alert([b.x, b.y]); // 10 (自身), 20 (集成)

留意,大家在b实例上创设了和谐的x属性,通过B.superproto.constructor调用父构造函数来援用新创造对象的上下文。

笔者们也修复了父构造函数在创制子原型的时候不须求的调用,此时,音信"A.[[Call]] activated"在要求的时候才博览会示。

为了在原型链里重复雷同的作为(中间构造函数成立,设置superproto,苏醒原本构造函数),上面包车型地铁模板能够封装成二个要命方面包车型客车工具函数,其目标是连续原型的时候不是基于构造函数的莫过于名称。
复制代码 代码如下:
function inherit(child, parent) {
  var F = function () {};
  F.prototype = parent.prototype
  child.prototype = new F();
  child.prototype.constructor = child;
  child.superproto = parent.prototype;
  return child;
}

因此,继承:
复制代码 代码如下:
function A() {}
A.prototype.x = 10;
 
function B() {}
inherit(B, A); // 连接原型
 
var b = new B();
alert(b.x); // 10, 在A.prototype查找到

也会有大多语法情势(包装而成),但持有的语法行皆认为着削减上述代码里的一举一动。

举例,若是大家把高级中学级的构造函数放到外面,就足以优化前边的代码(由此,唯有贰个函数被创设),然后重用它:
复制代码 代码如下:
var inherit = (function(){
  function F() {}
  return function (child, parent) {
    F.prototype = parent.prototype;
    child.prototype = new F;
    child.prototype.constructor = child;
    child.superproto = parent.prototype;
    return child;
  };
})();

鉴于目的的实际原型是[[Prototype]]天性,那意味着F.prototype可以很轻巧修改和任用,因为经过new F创设的child.prototype能够从child.prototype的当前值里获取[[Prototype]]:
复制代码 代码如下:
function A() {}
A.prototype.x = 10;
 
function B() {}
inherit(B, A);
 
B.prototype.y = 20;
 
B.prototype.foo = function () {
  alert("B#foo");
};
 
var b = new B();
alert(b.x); // 10, 在A.prototype里查到
 
function C() {}
inherit(C, B);
 
// 使用"superproto"语法糖
// 调用父原型的同名方法
 
C.ptototype.foo = function () {
  C.superproto.foo.call(this);
  alert("C#foo");
};
 
var c = new C();
alert([c.x, c.y]); // 10, 20
 
c.foo(); // B#foo, C#foo

只顾,ES5为原型链规范化了这么些工具函数,那就是Object.create方法。ES3得以应用以下办法贯彻:
复制代码 代码如下:
Object.create ||
Object.create = function (parent, properties) {
  function F() {}
  F.prototype = parent;
  var child = new F;
  for (var k in properties) {
    child[k] = properties[k].value;
  }
  return child;
}

// 用法
var foo = {x: 10};
var bar = Object.create(foo, {y: {value: 20}});
console.log(bar.x, bar.y); // 10, 20

其余,全数模仿以后依附类的经文一连方式都以基于那个标准完毕的,以后得以看来,它实质上不是依据类的接二连三,而是连接原型的一个很有益于的代码重用。

结论

本章内容早就很丰富和详细了,希望这个材质对您有用,何况消除你对ECMAScript的疑云,假让你有别的难点,请留言,大家一齐谈谈。

介绍 本章是有关ECMAScript面向对象达成的第2篇,第1篇我们谈谈的是概论和CEM...

 

prototype是javascript达成与管理持续的一种机制,也是面向对象的统一准备观念.构造函数的原型存款和储蓄着引用对象的二个指南针,该指针指向与三个原型对象,对象内部存款和储蓄着函数的原始属性和议程;大家得以依赖prototype属性,能够访谈原型内部的性质和艺术。

 

当构造函数被实列化后,全体的实例对象都能够访谈构造函数的原型成员,借使在原型中宣称二个成员,全体的实列方法都足以分享它,比如如下代码:

 

// 构造函数A 它的原型有二个getName方法

function A(name){

this.name = name;

}

A.prototype.getName = function(){

return this.name;

}

// 实列化2次后 该2个实列皆有原型getName方法;如下代码

var instance1 = new A("longen1");

var instance2 = new A("longen2");

console.log(instance1.getName()); //longen1

console.log(instance2.getName()); // longen2

 

原型具备普通对象组织,能够将其他日常对象设置为原型对象; 平时情况下,对象都卫冕与Object,也足以知道Object是全数指标的超类,Object是不曾原型的,而构造函数具备原型,因而实列化的靶子也是Object的实列,如下代码:

 

// 实列化对象是构造函数的实列

console.log(instance1 instanceof A); //true

console.log(instance2 instanceof A); // true

 

// 实列化对象也是Object的实列

console.log(instance1 instanceof Object); //true

console.log(instance2 instanceof Object); //true

 

//Object 对象是有所目的的超类,因而构造函数也是Object的实列

console.log(A instanceof Object); // true

 

// 但是实列化对象 不是Function对象的实列 如下代码

console.log(instance1 instanceof Function); // false

console.log(instance2 instanceof Function); // false

 

// 可是Object与Function有涉及 如下代码表明

console.log(Function instanceof Object); // true

console.log(Object instanceof Function); // true

 

如上代码,Function是Object的实列,也能够是Object也是Function的实列;他们是2个不等的构造器,大家继续看如下代码:

 

var f = new Function();

var o = new Object();

console.log("------------");

console.log(f instanceof Function); //true

console.log(o instanceof Function); // false

console.log(f instanceof Object); // true

console.log(o instanceof Object); // true

 

我们了解,在原型上平添成员属性大概措施的话,它被所有的实列化对象所分享属性和艺术,可是只要实列化对象有和原型一样的积极分子成员名字的话,那么它取到的成员是本实列化对象,固然本实列对象中从未的话,那么它会到原型中去研究该成员,假设原型找到就赶回,不然的会再次来到undefined,如下代码测验

 

function B(){

this.name = "longen2";

}

B.prototype.name = "AA";

B.prototype.getName = function(){

return this.name;

};

 

var b1 = new B();

// 在本实列查找,找到就赶回,不然到原型查找

console.log(b1.name); // longen2

 

// 在本实列未有找到该办法,就到原型去追寻

console.log(b1.getName());//longen2

 

// 即使在本实列未有找到的话,到原型上寻找也不曾找到的话,就再次来到undefined

console.log(b1.a); // undefined

 

// 现在自个儿使用delete运算符删除本地实列属性,那么取到的是正是原型属性了,如下代码:

delete b1.name;

console.log(b1.name); // AA

 

二:精晓原型域链的概念

 

原型的独到之处是能够以指标组织为载体,创造大气的实列,这一个实列能分享原型中的成员(属性和艺术);相同的时候也足以选用原型完结面向对象中的承接机制~ 如下代码:上面我们来看这几个组织函数AA和布局函数BB,当BB.prototype = new AA(11);执行这么些的时候,那么B就继续与A,B中的原型就有x的属性值为11

 

function AA(x){

this.x = x;

}

function BB(x) {

this.x = x;

}

BB.prototype = new AA(11);

console.log(BB.prototype.x); //11

 

// 大家再来掌握原型承继和原型链的概念,代码如下,都有注释

function A(x) {

this.x = x;

}

// 在A的原型上定义二个属性x = 0

A.prototype.x = 0;

function B(x) {

this.x = x;

}

B.prototype = new A(1);

 

实列化A new A(1)的时候 在A函数内this.x =1, B.prototype = new A(1);B.prototype 是A的实列 也正是B承接于A, 即B.prototype.x = 1; 如下代码:

 

console.log(B.prototype.x); // 1

// 定义C的构造函数

function C(x) {

this.x = x;

}

C.prototype = new B(2);

 

C.prototype = new B(2); 也正是C.prototype 是B的实列,C承接于B;那么new B(2)的时候 在B的构造函数内 this.x = 2;那么 C的原型上会有三个属性x =2 即C.prototype.x = 2; 如下代码:

 

console.log(C.prototype.x); // 2

 

上面是实列化 var d = new C(3); 实列化C的构造函数时候,那么在C的构造函数内this.x = 3; 因而如下打字与印刷实列化后的d.x = 3;如下代码:

 

var d = new C(3);

console.log(d.x); // 3

 

去除d.x 再走访d.x的时候 本实列对象被删掉,只好从原型上去寻觅;由于C.prototype = new B(2); 也便是C承袭于B,因而C的原型也可能有x = 2;即C.prototype.x = 2; 如下代码:

 

delete d.x;

console.log(d.x); //2

 

删去C.prototype.x后,大家从地方代码知道,C是三番四回于B的,本身的原型被删掉后,会去追寻父元素的原型链,因而在B的原型上找到x =1; 如下代码:

 

delete C.prototype.x;

console.log(d.x); // 1

 

当删除B的原型属性x后,由于B是持续于A的,由此会从父成分的原型链上查找A原型上是不是有x的性质,要是有的话,就回来,不然看A是还是不是有一而再,没有继续的话,继续往Object上去搜索,若无找到就返回undefined 因而当删除B的原型x后,delete B.prototype.x; 打字与印刷出A上的原型x=0; 如下代码:

 

delete B.prototype.x;

console.log(d.x); // 0

 

// 继续删除A的原型x后 结果尚未找到,就重临undefined了;

delete A.prototype.x;

console.log(d.x); // undefined

 

在javascript中,一切都是对象,Function和Object都以函数的实列;构造函数的父原型指向于Function原型,Function.prototype的父原型指向与Object的原型,Object的父原型也针对与Function原型,Object.prototype是兼备原型的顶层;

 

正如代码:

 

Function.prototype.a = function(){

console.log("笔者是父原型Function");

}

Object.prototype.a = function(){

console.log("我是 父原型Object");

}

function A(){

this.a = "a";

}

A.prototype = {

B: function(){

console.log("b");

}

}

// Function 和 Object都以函数的实列 如下:

console.log(A instanceof Function); // true

console.log(A instanceof Object); // true

 

// A.prototype是三个目标,它是Object的实列,但不是Function的实列

console.log(A.prototype instanceof Function); // false

console.log(A.prototype instanceof Object); // true

 

// Function是Object的实列 同是Object也是Function的实列

console.log(Function instanceof Object); // true

console.log(Object instanceof Function); // true

 

/*

* Function.prototype是Object的实列 但是Object.prototype不是Function的实列

* 表达Object.prototype是独具父原型的顶层

*/

console.log(Function.prototype instanceof Object); //true

console.log(Object.prototype instanceof Function); // false

 

三:理解原型承继机制

 

构造函数都有贰个指南针指向原型,Object.prototype是装有原型对象的顶层,比方如下代码:

 

var obj = {};

Object.prototype.name = "tugenhua";

console.log(obj.name); // tugenhua

 

给Object.prototype 定义壹天性格,通过字面量营造的靶子的话,都会从父类那边获得Object.prototype的性质;

 

从上面代码我们知晓,原型承继的法子是:假设A必要一而再于B,那么A.prototype(A的原型) = new B()(作为B的实列) 就可以完毕A承接于B; 因而大家上面能够初阶化贰个空的构造函数;然后把指标赋值给构造函数的原型,然后回来该构造函数的实列; 就可以完结接二连三; 如下代码:

 

if(typeof Object.create !== 'function') {

Object.create = function(o) {

var F = new Function();

F.prototype = o;

return new F();

}

}

var a = {

name: 'longen',

getName: function(){

return this.name;

}

};

var b = {};

b = Object.create(a);

console.log(typeof b); //object

console.log(b.name); // longen

console.log(b.getName()); // longen

 

如上代码:我们先检查测量试验Object是或不是早就有Object.create该方法;若无的话就制造二个; 该措施内创制三个空的构造器,把参数对象传递给构造函数的原型,最终回来该构造函数的实列,就兑现了后续格局;如上测量试验代码:先定义二个a对象,有成员属性name=’longen’,还可能有二个getName()方法;最终回到该name属性; 然后定义叁个b空对象,使用Object.create(a);把a对象承继给b对象,因而b对象也可能有总体性name和成员方法getName();

 

驾驭原型查找原理:对象查找先在该构造函数内搜索对应的天性,假如该对象未有该属性的话,

 

那便是说javascript会试着从该原型上去搜索,假诺原型对象中也尚未该属性的话,那么它们会从原型中的原型去寻找,直到查找的Object.prototype也尚无该属性的话,那么就能够再次来到undefined;由此我们想要仅在该对象内寻觅的话,为了抓实质量,大家得以行使hasOwnProperty()来判别该对象内有未有该属性,倘若有的话,就施行代码(使用for-in循环查找):如下:

 

var obj = {

"name":'tugenhua',

"age":'28'

};

// 使用for-in循环

for(var i in obj) {

if(obj.hasOwnProperty(i)) {

console.log(obj[i]); //tugenhua 28

}

}

 

如上使用for-in循环查找对象里面包车型大巴性质,可是大家须要精通的是:for-in循环查找对象的性格,它是不保障顺序的,for-in循环和for循环;最实质的分别是:for循环是有种种的,for-in循环遍历对象是冬辰的,由此大家如若要求对象保障顺序的话,能够把指标转变为数组来,然后再利用for循环遍历就可以;

 

上边大家来谈谈原型承袭的长处和缺欠

 

// 先看上面包车型客车代码:

// 定义构造函数A,定义特权属性和特权方法

function A(x) {

this.x1 = x;

this.getX1 = function(){

return this.x1;

}

}

// 定义构造函数B,定义特权属性和特权方法

function B(x) {

this.x2 = x;

this.getX2 = function(){

return this.x1 this.x2;

}

}

B.prototype = new A(1);

 

B.prototype = new A(1);那句代码实行的时候,B的原型承接于A,因而B.prototype也可以有A的性质和艺术,即:B.prototype.x1 = 1; B.prototype.getX1 方法;不过B也是有和谐的特权属性x2和特权方法getX2; 如下代码:

 

function C(x) {

this.x3 = x;

this.getX3 = function(){

return this.x3 this.x2;

}

}

C.prototype = new B(2);

C.prototype = new B(2);那句代码施行的时候,C的原型承袭于B,由此C.prototype.x2 = 2; C.prototype.getX2方法且C也会有协调的特权属性x3和特权方法getX3,

var b = new B(2);

var c = new C(3);

console.log(b.x1); // 1

console.log(c.x1); // 1

console.log(c.getX3()); // 5

console.log(c.getX2()); // 3

var b = new B(2);

 

实列化B的时候 b.x1 首先会在构造函数内查找x1属性,未有找到,由于B的原型承接于A,因而A有x1属性,因而B.prototype.x1 = 1找到了;var c = new C(3); 实列化C的时候,从上边的代码能够看看C承继于B,B承袭于A,因而在C函数中绝非找到x1属性,会往原型继续寻觅,直到找到父成分A有x1属性,因而c.x1 = 1;c.getX3()方法; 重临this.x3 this.x2 this.x3 = 3;this.x2 是B的性质,由此this.x2 = 2;c.getX2(); 查找的措施也一致,不再解释

 

prototype的败笔与亮点如下:

 

可取是:能够允许八个指标实列分享原型对象的积极分子及情势,

 

症结是:1. 每种构造函数独有贰个原型,由此不直接协理多种承继;

 

2. 不能够很好地扶持多参数或动态参数的父类。在原型承接阶段,顾客还不可能决定以

 

哪些参数来实列化构造函数。

 

四:精通使用类承袭(继承的越来越好的方案)

 

类承袭也称得上构造函数承接,在子类中实行父类的构造函数;完结原理是:能够将贰个结构函数A的主意赋值给另二个构造函数B,然后调用该办法,使协会函数A在协会函数B内部被实施,那时候构造函数B就具备了协会函数A中的属性和措施,那正是利用类承继落成B承袭与A的基本原理;

 

平常来讲代码完成demo:

 

function A(x) {

this.x = x;

this.say = function(){

return this.x;

}

}

function B(x,y) {

this.m = A; // 把结构函数A作为三个平日函数引用给一时方法m

this.m(x); // 推行组织函数A;

delete this.m; // 清除有时措施this.m

this.y = y;

this.method = function(){

return this.y;

}

}

var a = new A(1);

var b = new B(2,3);

console.log(a.say()); //输出1, 实践协会函数A中的say方法

console.log(b.say()); //输出2, 能试行该格局求证被两次三番了A中的方法

console.log(b.method()); // 输出3, 构造函数也享有自个儿的格局

 

地方的代码完结了简要的类承接的根底,不过在纷纷的编制程序中是不会采纳方面包车型客车秘籍的,因为下面的代码非常不够严俊;代码的耦合性高;我们得以选取越来越好的措施如下:

 

function A(x) {

this.x = x;

}

A.prototype.getX = function(){

return this.x;

}

// 实例化A

var a = new A(1);

console.log(a.x); // 1

console.log(a.getX()); // 输出1

// 以往我们来制造构造函数B,让其B承袭与A,如下代码:

function B(x,y) {

this.y = y;

A.call(this,x);

}

B.prototype = new A(); // 原型承袭

console.log(B.prototype.constructor); // 输出构造函数A,指针指向与组织函数A

B.prototype.constructor = B; // 重新恢复设置构造函数,使之指向B

console.log(B.prototype.constructor); // 指向结构函数B

B.prototype.getY = function(){

return this.y;

}

var b = new B(1,2);

console.log(b.x); // 1

console.log(b.getX()); // 1

console.log(b.getY()); // 2

 

// 上面是身体力行对构造函数getX实行重写的主意如下:

B.prototype.getX = function(){

return this.x;

}

var b2 = new B(10,20);

console.log(b2.getX()); // 输出10

 

上边大家来深入分析上面的代码:

 

在构造函数B内,使用A.call(this,x);那句代码的意义是:大家都知晓使用call或许apply方法能够改动this指针指向,进而得以兑现类的连续,由此在B构造函数内,把x的参数字传送递给A构造函数,並且再三再四于协会函数A中的属性和办法;

 

行使那句代码:B.prototype = new A(); 能够完毕原型承继,也正是B能够承袭A中的原型全数的章程;console.log(B.prototype.constructor); 打字与印刷出输出构造函数A,指针指向与结构函数A;咱们了解的是,当定义构造函数时候,其原型对象暗中同意是三个Object类型的叁个实例,其布局器私下认可会被设置为构造函数自个儿,假如改换构造函数prototype属性值,使其针对性于另二个指标的话,那么新目的就不会怀有原本的constructor的值,比方第一遍打字与印刷console.log(B.prototype.constructor); 指向于被实例化后的构造函数A,重写设置B的constructor的属性值的时候,第叁次打字与印刷就对准于小编B;由此B承接与构造A及其原型的具有属性和方法,当然大家也得以对构造函数B重写构造函数A中的方法,如上面最终几句代码是对结构函数A中的getX方法开展重写,来促成和睦的职业~;

 

五:提议采纳封装类落成持续

 

封装类达成一而再的基本原理:先定义叁个封装函数extend;该函数有2个参数,Sub代表子类,Sup代表超类;在函数内,先定义贰个空函数F, 用来落实际效果果与利益中间转播,先设置F的原型为超类的原型,然后把空函数的实例传递给子类的原型,使用一个空函数的裨益是:幸免直接实例化超类大概会拉动系统质量难题,举个例子超类的实例一点都不小的话,实例化会占用相当多内存;

 

平日来讲代码:

 

function extend(Sub,Sup) {

//Sub代表子类,Sup表示超类

// 首先定义贰个空函数

var F = function(){};

 

// 设置空函数的原型为超类的原型

F.prototype = Sup.prototype;

 

// 实例化空函数,并把超类原型引用传递给子类

Sub.prototype = new F();

 

// 复位子类原型的构造器为子类自个儿

Sub.prototype.constructor = Sub;

 

// 在子类中保存超类的原型,防止子类与超类耦合

Sub.sup = Sup.prototype;

 

if(Sup.prototype.constructor === Object.prototype.constructor) {

// 检查评定超类原型的构造器是不是为原型本身

Sup.prototype.constructor = Sup;

}

 

}

测验代码如下:

// 下面大家定义2个类A和类B,大家指标是促成B承接于A

function A(x) {

this.x = x;

this.getX = function(){

return this.x;

}

}

A.prototype.add = function(){

return this.x this.x;

}

A.prototype.mul = function(){

return this.x * this.x;

}

// 构造函数B

function B(x){

A.call(this,x); // 承袭构造函数A中的全数属性及方法

}

extend(B,A); // B继承于A

var b = new B(11);

console.log(b.getX()); // 11

console.log(b.add()); // 22

console.log(b.mul()); // 121

 

在乎:在封装函数中,有与此相类似一句代码:Sub.sup = Sup.prototype; 大家以往能够来驾驭下它的含义:

 

例如在B承接与A后,笔者给B函数的原型再定义二个与A一样的原型一样的主意add();

 

如下代码

 

extend(B,A); // B继承于A

var b = new B(11);

B.prototype.add = function(){

return this.x "" this.x;

}

console.log(b.add()); // 1111

 

那正是说B函数中的add方法会覆盖A函数中的add方法;因而为了不掩瞒A类中的add()方法,且调用A函数中的add方法;能够如下编写代码:

 

B.prototype.add = function(){

//return this.x "" this.x;

return B.sup.add.call(this);

}

console.log(b.add()); // 22

 

B.sup.add.call(this); 中的B.sup就含有了结构函数A函数的指针,因而满含A函数的兼具属性和格局;因而得以调用A函数中的add方法;

 

如上是落到实处一而再的两种方法,类继承和原型承继,不过那一个后续非常的小概继续DOM对象,也不支持承袭系统静态对象,静态方法等;比如Date对象如下:

 

// 使用类承袭Date对象

function D(){

Date.apply(this,arguments); // 调用Date对象,对其引述,完结一而再

}

var d = new D();

console.log(d.toLocaleString()); // [object object]

 

如上代码运营打字与印刷出object,咱们能够看出使用类承继不可能兑现系统静态方法date对象的接轨,因为她不是粗略的函数结构,对注脚,赋值和早先化都进行了打包,由此不能继续;

 

下边大家再来看看使用原型承继date对象;

 

function D(){}

D.prototype = new D();

var d = new D();

console.log(d.toLocaleString());//[object object]

 

大家从代码中看见,使用原型承袭也无从持续Date静态方法;不过大家能够如下封装代码承袭:

 

function D(){

var d = new Date(); // 实例化Date对象

d.get = function(){ // 定义当地方法,直接调用Date对象的艺术

console.log(d.toLocaleString());

}

return d;

}

var d = new D();

d.get(); // 2015/12/21 上午12:08:38

 

六:精通使用复制继承

 

复制继承的基本原理是:先规划贰个空对象,然后选拔for-in循环来遍历对象的成员,将该对象的分子一个二个复制给新的空对象里面;这样就实现了复制承继了;如下代码:

 

function A(x,y) {

this.x = x;

this.y = y;

this.add = function(){

return this.x this.y;

}

}

A.prototype.mul = function(){

return this.x * this.y;

}

var a = new A(2,3);

var obj = {};

for(var i in a) {

obj[i] = a[i];

}

console.log(obj); // object

console.log(obj.x); // 2

console.log(obj.y); // 3

console.log(obj.add()); // 5

console.log(obj.mul()); // 6

 

如上代码:先定义多少个构造函数A,函数里面有2个属性x,y,还会有三个add方法,该构造函数原型有贰个mul方法,首先实列化下A后,更创设四个空对象obj,遍历对象一个个复制给空对象obj,从上边的打字与印刷效果来看,大家得以看来曾经落实了复制承袭了;对于复制承接,我们能够封装成如下方法来调用:

 

// 为Function增加复制承继方法

Function.prototype.extend = function(o) {

for(var i in o) {

//把参数对象的成员复制给当下目的的构造函数原型对象

this.constructor.prototype[i] = o[i];

}

}

// 测验代码如下:

var o = function(){};

o.extend(new A(1,2));

console.log(o.x); // 1

console.log(o.y); // 2

console.log(o.add()); // 3

console.log(o.mul()); // 2

 

地方封装的恢弘承接方法中的this对象指向于这段日子实列化后的靶子,实际不是指向于构造函数自个儿,因而要使用原型扩张成员来讲,就须求利用constructor属性来指向它的构造器,然后通过prototype属性指向构造函数的原型;

 

复制承继有如下优点:

 

  1. 它无法承继系统宗旨对象的只读方法和总体性

 

  1. 假如目的数据充足多以来,那样一个个复制的话,品质是很低的;

 

  1. 除非对象被实列化后,才干给遍历对象的分子和属性,绝对来讲远远不够灵活;

 

4. 复制承袭只是简短的赋值,所以假使赋值的对象是引用类型的指标的话,可能会设有一点点副效率;如上我们看看有如上部分劣点,上面大家得以应用clone(克隆的诀窍)来优化下:

 

基本思路是:为Function扩张一个方法,该情势能够把参数对象赋值赋值三个空构造函数的原型对象,然后实列化构造函数并回到实列对象,那样该目标就全部了该目的的装有成员;代码如下:

 

Function.prototype.clone = function(o){

function Temp(){};

Temp.prototype = o;

return Temp();

}

// 测量检验代码如下:

Function.clone(new A(1,2));

console.log(o.x); // 1

console.log(o.y); // 2

console.log(o.add()); // 3

console.log(o.mul()); // 2

本文由星彩网app下载发布于前端技术,转载请注明出处:面向对象编程,深入理解Javascript面向对象编程

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。