-
注:
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语言类似,它指向函数据以执行的对象.所有的函数都有这么一个引用.
什么叫据以执行的对象?!
不太好形容这个东西,大概可以理解为,这个函数运行在哪个对象之下(还是不大好理解),直接举例吧.
12345678910111213141516171819//输出自己的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
}
如何定义对象的函数呢?!
你可以这样:
1234567function Dog(name, color) {
this
.name = name;
this
.color = color;
this
.sayName = function(){
console.log(
this
.name);
};
}
前文说过,function也是对象,这就相当于在执行构造函数的过程中,给对象增加了一个类型为function的sayName属性,但这样做有一个问题,就是每次执行构造函数的过程中,都会创建一个新的函数对象,显然,所有的Dog对象共享一个sayName函数就可以了.按照目前所讲的知识,你自然可以这样:
12345678function Dog(name, color) {
this
.name = name;
this
.color = color;
this
.sayName = sayName;
}
function sayName() {
console.log(
this
.name);
}
在这里,定义了一个sayName的全局函数,这样,每次执行构造函数时,就不会重新创建一个新的sayName函数了.但这样做,想给对象定义多个函数,就要定义多个的全局函数.而当你想要定义多种对象时,全局函数的数量就会爆炸,变的让你无法掌控.
3.prototype!!!!!
幸好,可以使用函数的prototype这个对象,看下面的代码:
123456789function Dog(name, color) {
this
.name = name;
this
.color = color;
}
Dog.prototype.sayName = function() {
console.log(
this
.name);
};
var myDog =
new
Dog(
"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,自然也可以共享属性.
12345678910111213141516171819202122function Dog(name, color) {
this
.name = name;
this
.color = color;
}
Dog.prototype.sayName = function() {
console.log(
this
.name);
};
Dog.prototype.kind =
"Dog"
;
var myDog =
new
Dog(
"Odie"
,
"Yellow"
);
var youDog =
new
Dog(
"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"正常"的引用呢——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函数继承父构造函数的属性:
12345678910111213141516//父构造函数
function SuperType(name) {
this
.name = name;
}
//子构造函数
function SubType(name, age) {
SuperType.call(
this
, name);
//调用父构造函数
this
.age = age;
}
var sub =
new
SubType(
"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对象么!!
123456789101112131415161718192021222324252627//父构造函数
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 =
new
SubType(
"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属性就可以了.
继承就这样完成了~
javascript 创建对象——类,继承
最新推荐文章于 2022-08-09 15:00:06 发布