javascript 创建对象——类,继承

来源:互联网 发布:mac 把自己改为管理员 编辑:程序博客网 时间:2024/05/28 11:30

注:

a.本文所有代码在chrome浏览器下测试通过,建议读者也安装一个chrome浏览器;

b.本文所述遵循ECMA-262第五版规范

c.本文输出使用console.log函数,请按F12进入调试模式,观看控制台输出;

d.源码链接地址

e.转载请注明出处.


1.什么是对象?

javascript本身,是没有类的概念的,只有对象的概念,除了基本类型(string,number,boolean,null,undefined)外,其余均是对象,就连function也是对象.
那么,什么是对象?!javascript中的对象,类似于一组键值对的集合.你甚至可以以键值对的方式来操作javascript中的对象,就像这样:

var myDog= new Object();
myDog["name"] = "Odie";
myDog["color"] = "Yellow";
console.log(myDog ["name"] );
console.log(myDog ["color"] );


跟这种方式创建对象的效果是一样的:

var myDog = new Object();
myDog .name = "Odie";
myDog .color = "Yellow";
console.log(myDog.name );
console.log(myDog.color );


显然,第二种访问方式更加方便.通常只有在并不确定我们访问的对象的属性名字的时候(比如json数据),才会使用这种方式访问.当然,如果你想起一个类似"hello world"(有空格)这样奇葩的属性名字,那你就必须使用键值对的方式创建对象啦.


2.如何创建对象?


exampleA:


//字面量方式创建对象,简单方便,可以认为,这是exampleB方式的简写
var myDog= {name: "Odie", color: "Yellow"};

exampleB:
//使用new操作符创建对象,再追加属性
var myDog= new Object();
myDog.name = "Odie";
myDog.color = "Yellow";

在js中,如果你赋值操作的属性没有,就会创建一个,1话题中就已经用过这种方式了.exampleA中自然也可以这样继续追加属性,A和B最大的区别在于创建对象的方式(主要区别在第一行),而不在于追加属性上.值得注意是只有在赋值操作时,才会这样,你做一个访问的操作,它自然是不会创建的(记住这一点,在后面的话题中很重要).
简单验证一下:
var myDog= {name: "Odie", color: "Yellow"};
console.log(myDog.age);//undefined
for(var pro in myDog){console.log(pro);}//name,color,没有age

exampleC:

//一个很少使用的方式
var myDog= Object.create(new Object());
myDog.name = "Odie";
myDog.color = "Yellow";

这种方式着实很少用,它有什么用途在这里不好讲,留到后面吧.
其实A、B、C三种方式创建对象还是有差别的,在这里也不好说,也留在后面吧.

exampleD:


//如果是简单的对象,似乎exampleA是最方便的,但当你要创建复杂的对象,亦或是大量类似的对象时,就应当考虑下面这个方法了.

function Dog(name, color) {

this.name = name;
this.color = color;
}
var myDog= new Dog("Odie", "Yellow");

等等,为什么new了一个function呢?!学过其他OO语言的人都会觉的怪怪的.之前说过,javascript根本就没有类这个概念,我们输出一下浏览器内置的Object和Date:

console.log(Object); 
console.log(Date);

在chrome中的结果:
function Object() { [native code] }
function Date() { [native code] }
在firefox中的结果:
[object Function]
[object Function]

真相了,Object和Date也不是所谓的类,而是function!在javascript中,就是使用"new function(参数)"这种方式创建对象的.我们称这种function为构造函数,exampleB和exampleD是同一种方式,只是在exampleD中,使用了自己定义的构造函数.

那么在 "new 构造函数(参数)" 的过程中,都发生了什么呢?!

1.创建一个新对象//对象的隐性引用"__proto__"指向构造函数的"prototype"(先忽略后面这半句);
2.将构造函数的 "this" 指向新对象;
3.执行构造函数(初始化,向新对象添加属性);
4.返回这个对象.

经过以上步骤,一个新的对象就创建出来了.


构造函数与其他函数有什么区别么?!
唯一区别是你在调用构造函数上需要使用new关键字,但在语法上并没有区别,所有的函数都有prototype和this引用,构造函数只是定义的时候通常首字母大写,调用的时候记得使用new关键字,但这都靠自觉偷笑,javascript本身没有语法强制规定什么样的函数才是构造函数.你不用new关键字调用构造函数(非strict mode)亦或你new一个普通的函数也不会报错,当然,结果自然不是你想要的(后面会讲会发生什么).

this是什么东西?!

this是function内部的一个引用,跟其他OO语言类似,它指向函数据以执行的对象.所有的函数都有这么一个引用.

什么叫据以执行的对象?!

不太好形容这个东西,大概可以理解为,这个函数运行在哪个对象之下(还是不大好理解),直接举例吧.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//输出自己的this
function log_this() {
    console.log(this);
}
 
//初始化
window.onload = function() {
     
    //example_one
    log_division("example_one");
    log_this();//window对象
    //example_two假设你有一个btn按钮   
    document.getElementById("btn").onclick = log_this;//点击-->dom对象
    //example_three
    log_division("example_three");
    var temp = {};//exampleA中创建对象的方法哦
    temp.logThis = log_this;//function也是对象,可以这么给对象添加function属性,上面那个onclick监听回调函数的原理,跟这个类似
    temp.logThis();//Object   
}

这回好理解了吧,this指向哪里,是根据运行环境有关的,this指向它的运行环境对象,one例子当中,它就指向了window,two中就指向了dom对象,three中就指向了自定义对象.因此,如果你不用new调用构造函数,就会将属性添加到window上.//题外话,在strict mode下,这么做会报错的.

如何定义对象的函数呢?!

你可以这样:

?
1
2
3
4
5
6
7
function Dog(name, color) {
    this.name = name;
    this.color = color;
    this.sayName = function(){
        console.log(this.name);
    };
}

前文说过,function也是对象,这就相当于在执行构造函数的过程中,给对象增加了一个类型为function的sayName属性,但这样做有一个问题,就是每次执行构造函数的过程中,都会创建一个新的函数对象,显然,所有的Dog对象共享一个sayName函数就可以了.按照目前所讲的知识,你自然可以这样:


?
1
2
3
4
5
6
7
8
function Dog(name, color) {
    this.name = name;
    this.color = color;
    this.sayName = sayName;
}
function sayName() {
    console.log(this.name);
}

在这里,定义了一个sayName的全局函数,这样,每次执行构造函数时,就不会重新创建一个新的sayName函数了.但这样做,想给对象定义多个函数,就要定义多个的全局函数.而当你想要定义多种对象时,全局函数的数量就会爆炸,变的让你无法掌控.


3.prototype!!!!!


幸好,可以使用函数的prototype这个对象,看下面的代码:

?
1
2
3
4
5
6
7
8
9
function Dog(name, color) {
    this.name = name;
    this.color = color;
}
Dog.prototype.sayName = function() {
    console.log(this.name);
};
var myDog = newDog("Odie","Yellow");
myDog.sayName();//Odie

再次强调,function也是对象(除了基本类型都是对象).在javascript中,每个function都有一个prototype对象.那么,"Dog.prototype.sayName = function(){...};"这种写法就是给Dog的ptototype增加一个名字为sayName的function对象.

咦?!,在上述的代码中,新对象myDog是如何指向了Dog的prototype的sayName函数(有点绕)呢?!

还记得"new 构造函数(参数)" 的过程中,都发生了什么么?!
1.创建一个新对象,对象的隐性引用"__proto__"指向构造函数的"prototype";
2.将构造函数的 "this" 指向新对象;
3.执行构造函数(初始化,向新对象添加属性);
4.返回这个对象.


下面对前面忽略的后半句进行解释.

每一个对象都有一个指向它的构造函数的prototype的隐性的引用(在构造的时候被赋值).

什么是隐性引用,就是你无法看到的,你无法使用myDog.prototype这样的方式获取prototype(在firefox和chrome中可以通过myDog.__proto__来访问).但是你却可以使用它的属性!所以你可以使用"myDog.sayName();"这种方式调用构造函数Dog的prototype的sayName函数.


如果对象的属性和对象的构造函数的prototype的属性重名了呢?!

自然是先访问对象本身的属性了.每当访问一个对象的属性时,先从对象自身查找,如果它自己没有,再查找它的__proto__指向的prototype对象有没有这个属性.

如果还没有呢?!

不会停止! 别忘了,prototype也是对象,它也有一个__proto__隐性引用,它会继续根据这个引用查找下去...查找下去...查找下去...查找下去,直到这个__proto__引用指向null为止.
在上面的例子中,很快就指向了null了.

var myDog = new Dog("Odie", "Yellow");
console.log(myDog.__proto__);
console.log(myDog.__proto__.__proto__);
console.log(myDog.__proto__.__proto__.__proto__);

在chrome中的结果:
Dog {say: function}//还有更详细的内容
Object {}
null
在firefox中的结果:
[object Object]
[object Object]
null

正是javascript这种链式查找的机制,使"继承"成为了可能(此处Dog继承Object).这个在后面讲.

javascript中的prototype实现了对象的共享机制,由同一个构造函数创造出来的对象,都有一个指向构造函数的prototype的隐性指针,再通过javascript访问属性的查找机制,就实现了共享.这类似于其他OO语言中的类的静态变量和静态函数.既然通过prototype共享function,自然也可以共享属性.


?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Dog(name, color) {
    this.name = name;
    this.color = color;
}
 
Dog.prototype.sayName = function() {
    console.log(this.name);
};
 
Dog.prototype.kind = "Dog";
 
var myDog = newDog("Odie","Yellow");
var youDog = newDog("Oalive","Black");
 
//通过对象读取
console.log(myDog.kind);//Dog
console.log(youDog.kind);//Dog
 
//但不能通过对象修改prototype属性
myDog.kind = "cat";
console.log(myDog.kind);//cat
console.log(youDog.kind);//dog


上面这段代码,定义了一个prototype的kind属性,可以看到,可以直接通过对象访问该属性,却不可以通过对象进行修改.

为什么呢?!

前面讲过,每个对象都有一个指向prototype的隐性引用,这个引用是你摸不到的,"__proto__"这种写法只是chrome和firefox的浏览器支持,它并不是javascript标准(退一步说,即使是标准,你修改的方式也该是myDog1.__proto__.kind = "dog").你之所以能共通过对象访问到全局属性,是由于javascript查找机制决定的,在你访问一个属性时,在自身找不到,会继续向对象隐性引用指向的prototype继续找.

在最初讲对象的时候,特意强调了一下,访问一个对象的属性时,如果没有,并不会给对象追加一个属性,当时没有说后半句,后半句就是它会继续向它所指向prototype对象查找.这是javascript对访问属性时的处理方式.

而赋值操作呢,不会有这个查找的过程,如果没有,直接追加一个属性!所以myDog1.kind = "dog";这是给myDog1追加了一个kind属性,而对myDog1访问kind属性时,在自身就找到了这个属性,自然就不会继续查找了.


那怎么修改prototype的属性呢?

谁有prototype"正常"的引用呢——function——也就是构造函数嘛.

Dog.prototype.kind = "dog";

也可以是这样,但__proto__不是标准,即使是,也不建议这么写:

myDog1.__proto__.kind = "dog";

A.B.C三种创建对象方式的不同之处 

exampleA:

//字面量方式创建对象,简单方便,可以认为,这是exampleB方式的简写
var myDog = {name: "Odie", color: "Yellow"};

exampleB:

//使用new操作符创建对象,再追加属性
var myDog = new Object();
myDog.name = "Odie";
myDog.color = "Yellow";

exampleC:

//一个很少使用的方式
var myDog = Object.create(new Object());
myDog.name = "Odie";
myDog.color = "Yellow";

A相当于B的简写方式,唯一的区别就是,通常,在浏览器中,A这种方式创建对象是不调用Object的构造函数的.

C呢,之前没有解释,这个方式跟A.B两种区别很大.

首先要说明Object.create(prototype,descriptors)这个函数,它可以创建一个指定原型的对象,参数prototype就是想要给创建出来的对象添加的指定原型//忽略后面的参数(它是可选的).
"var myDog = Object.create(new Object());"就是创建了一个以Object的实例为原型的对象,就是创建出来的对象的隐性指针指向了Object的实例.

过程类似于:
function(proto){
fucntion F(){};
F.prototype = proto;
return new F();
}

输出一下myDog的__proto__;

console.log(myDog.__proto__);//Object {}
console.log(myDog.__proto__.__proto__);//Object {}
console.log(myDog.__proto__.__proto__.__proto__);//null

为什么前两个都是"Object{}"呢,因为这是以Object的实例做为prototype的,myDog的__proto__指向Object的实例,而实例才真正指向Object的prototype.

如果这么写:
var myDog = Object.create(Object.prototype);
就和
var myDog = new Object();
是一样的了.

原型链图
\
加载中...

CF --> constructZ喎�"/kf/ware/vc/" target="_blank" class="keylink">vciBmdW5jdGlvbiAtLT65udTsuq/K/Txicj4KQ0ZwIC0tPmNvbnN0cnVjdG9yIGZ1bmN0aW9uIHByb3RvdHlwZSAtLT65udTsuq/K/bXE1K3QzTxicj4KY2YxLS1jZjUgLS0+IENGubnU7LXEyrXA/Txicj4K0OnP3yAtLT4g0v7Q1NL908M8YnI+Csq1z98gLS0+INX9s6PS/dPDPGJyPgo8YnI+CmNmMS0tY2Y11eLQqbbUz/O5ss/tQ0a1xHByb3RvdHlwZTs8YnI+CkNGcLG+ye3SssrHttTP89Ky09DSu7j20OnP37XE0v7Q1NL908M7PGJyPgpDRtKyyse21M/zLNKy09DSu7j20OnP37XE0v7Q1NL908Ms1sHT2srHyrLDtCzV4tKqv7Tkr8DAxvfKx9T1w7TKtc/WtcTByy48YnI+Cjxicj4KPHN0cm9uZz5wcm90b3R5cGXSstPQ0ru49ta4z/LL/LXEubnU7Lqvyv21xNL908M8L3N0cm9uZz48YnI+Cjxicj4Kyc/D5rXEzbzDu9PQserD9yzG5Mq1cHJvdG90eXBl0rLT0NK7uPbWuM/yubnU7Lqvyv21xNL908MsvdDX9mNvbnN0cnVjdG9yLtXiw7TLtbK7zKvXvMi3LNOmuMPLtcO/uPZwcm90b3R5cGXSstPQ0ru49ta4z/LP4NOmtcRmdW5jdGlvbrXE0v3Tw6OocHJvdG90eXBlsqKyu8rHubnU7Lqvyv22wNPQtcSjqS48YnI+Cjxicj4K0vK0yyzKx7/J0tTNqLn9ttTP86Oovq25/XByb3RvdHlwZaOpt8POymNvbnN0cnVjdG9ytcQuPGJyPgo8YnI+CnZhciBteURvZyA9IG5ldyBEb2co"Odie", "Yellow");
console.log(myDog.constructor);

怎么证明constructor是prototype的属性而不是对象myDog1的呢?!

Object有一个hasOwnProperty(name)函数来判断:

console.log(myDog.hasOwnProperty("constructor"));//false
console.log(myDog.hasOwnProperty("hasOwnProperty"));//false,hasOwnProperty是Object的prototype的函数哦.
console.log(myDog.hasOwnProperty("sayName"));//false,这是Dog的prototype的函数
console.log(myDog.hasOwnProperty("name"));//这个才是自己的属性

4继承

javascript的对象可以分为两部分,自己的属性和从prototype共享的属性.
那么,想要在javascript中实现继承,就需要:

1.子构造函数在执行过程中,也构造父构造函数构造出的属性;
2.继承父构造函数的共享属性——将子构造函数的prototype对象的隐性引用__proto__指向父构造函数的prototype,这样根据javascript的查找机制,就可以共享父类的prototype了;

javascript没有类,只有对象,非要向其他OO语言靠拢的话,可以认为把上述的父构造函数替换为父类.

构造父构造函数构造的属性

如果能在子构造函数构造的过程中,把它的this直接传递给父构造函数,在跑一遍父构造函数就好了.

javascript确实提供了这样一个方式.就是function对象call函数.

call函数的用法

还记得function的this引用吧——就是函数据以执行的对象的引用(可以叫做函数的运行环境引用,亦或者叫函数的上下文引用),使用call函数,就可以指定函数运行时this引用指向的对象.

call(thisObj,arg0,arg1....);

thisObj这个参数就是你要给函数运行时指定的对象.
arg0,arg1....是可选的,是函数运行时真正的参数.

用call函数继承父构造函数的属性:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//父构造函数
function SuperType(name) {
    this.name = name;
}
//子构造函数
function SubType(name, age) {
    SuperType.call(this, name);//调用父构造函数
    this.age = age;
}
 
 
var sub = newSubType("js",20);
console.log(sub.name);//js
console.log(sub.age);//20
console.log(sub.hasOwnProperty("name"));//true
console.log(sub.hasOwnProperty("age"));//true

前半部分就这么实现了,so easy是吧.

继承父构造函数的共享属性

还记得那个create函数么——创建一个指定原型的对象——可以指定被创建出来的对象的隐性引用(__proto__)指向那个对象.
那创建一个隐性引用指向父构造函数的prototype的对象:

var obj = Object.create(SuperType.prototype);

这是什么?!
这不就是我们想要的子构造函数的prototype对象么!!

?
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
//父构造函数
function SuperType(name) {
    this.name = name;
}
 
SuperType.prototype.sayName = function() {
    console.log(this.name);
}
//子构造函数
function SubType(name, age) {
    SuperType.call(this, name);//调用父构造函数
    this.age = age;
}
 
SubType.prototype = Object.create(SuperType.prototype);
 
SubType.prototype.sayAge = function() {
    console.log(this.age);
}
 
var sub = newSubType("js",20);
console.log(sub.name);//js
console.log(sub.age);//20
sub.sayName();//js
sub.sayAge();//20
 
console.log(sub.constructor);//function SuperType{...}

啊呀,sub的prototypetype怎么指向SuperType了?!

从头找,sub本身没有constructor这个属性,向他的原型找;
它的原型被修改了——"Object.create(SuperType.prototype)",这个对象也没有constructor属相啊,继续向它的原型找;
那就是SuperType.prototype了,它的constructor指向了SuperType.


没关系,只要小小的改动一下就好了.

SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.constructor = SubType;
console.log(sub.constructor);//function subType{...}

只要给SuperType.prototype加一个constructor属性就可以了.

继承就这样完成了~

0 0
原创粉丝点击