注意:typeof(undefined) 返回也是 undefined。
可以将undefined赋值给任何变量或属性,但并不意味了清除了该变量,反而会因此多了一个属性。
注意:typeof(null)返回object,但null并非object,具有null值的变量也并非object。
注意:typeof(NaN)和typeof(Infinity)都返回number 。
NaN参与任何数值计算的结构都是NaN,而且 NaN != NaN 。
Infinity / Infinity = NaN 。
没有类
object就是对象的类型。在JavaScript中不管多么复杂的数据和代码,都可以组织成object形式的对象。
for (life.age = 1 ; life.age <= 3 ; life.age ++ )
{
switch (life.age)
{
case 1 : life.body = " 卵细胞 " ;
life.say = function (){alert( this .age + this .body)};
break ;
case 2 : life.tail = " 尾巴 " ;
life.gill = " 腮 " ;
life.body = " 蝌蚪 " ;
life.say = function (){alert( this .age + this .body + " - " + this .tail + " , " + this .gill)};
break ;
case 3 : delete life.tail;
delete life.gill;
life.legs = " 四条腿 " ;
life.lung = " 肺 " ;
life.body = " 青蛙 " ;
life.say = function (){alert( this .age + this .body + " - " + this .legs + " , " + this .lung)};
break ;
};
life.say();
};
{
alert( " hello " );
};
alert( typeof (myfunc));
{
alert( " hello " );
};
alert( typeof (myfunc));
{
alert( " hello " );
};
myfunc(); // 第一次调用myfunc,输出hello
myfunc = function ()
{
alert( " yeah " );
};
myfunc(); // 第二次调用myfunc,将输出yeah
{
alert( " hello " );
};
myfunc(); // 这里调用myfunc,输出yeah而不是hello
function myfunc ()
{
alert( " yeah " );
};
myfunc(); // 这里调用myfunc,当然输出yeah
function myfunc ()
{
alert( " hello " );
};
myfunc(); // 这里调用myfunc,输出hello
</ script >
< script >
function myfunc ()
{
alert( " yeah " );
};
myfunc(); // 这里调用myfunc,输出yeah
</ script >
奇妙的对象
先来说说函数的对象化能力。
{
with (arguments.callee)
alert(author + " : " + poem);
};
Sing.author = " 李白 " ;
Sing.poem = " 汉家秦地月,流影照明妃。一上玉关道,天涯去不归 " ;
Sing();
Sing.author = " 李战 " ;
Sing.poem = " 日出汉家天,月落阴山前。女儿琵琶怨,已唱三千年 " ;
Sing();
anObject.aProperty = " Property of object " ; // 对象的一个属性
anObject.aMethod = function (){alert( " Method of object " )}; // 对象的一个方法
// 主要看下面:
alert(anObject[ " aProperty " ]); // 可以将对象当数组以属性名作为下标来访问属性
anObject[ " aMethod " ](); // 可以将对象当数组以方法名作为下标来调用方法
for ( var s in anObject) // 遍历对象的所有属性和方法进行迭代化处理
alert(s + " is a " + typeof (anObject[s]));
aFunction.aProperty = " Property of function " ; // 函数的一个属性
aFunction.aMethod = function (){alert( " Method of function " )}; // 函数的一个方法
// 主要看下面:
alert(aFunction[ " aProperty " ]); // 可以将函数当数组以属性名作为下标来访问属性
aFunction[ " aMethod " ](); // 可以将函数当数组以方法名作为下标来调用方法
for ( var s in aFunction) // 遍历函数的所有属性和方法进行迭代化处理
alert(s + " is a " + typeof (aFunction[s]));
放下对象
我们再来看看function与object的超然结合吧。
{
alert( " I'm " + this .name + " of " + typeof ( this ));
};
WhoAmI(); // 此时是this当前这段代码的全局对象,在浏览器中就是window对象,其name属性为空字符串。输出:I'm of object
var BillGates = {name: " Bill Gates " };
BillGates.WhoAmI = WhoAmI; // 将函数WhoAmI作为BillGates的方法。
BillGates.WhoAmI(); // 此时的this是BillGates。输出:I'm Bill Gates of object
var SteveJobs = {name: " Steve Jobs " };
SteveJobs.WhoAmI = WhoAmI; // 将函数WhoAmI作为SteveJobs的方法。
SteveJobs.WhoAmI(); // 此时的this是SteveJobs。输出:I'm Steve Jobs of object
WhoAmI.call(BillGates); // 直接将BillGates作为this,调用WhoAmI。输出:I'm Bill Gates of object
WhoAmI.call(SteveJobs); // 直接将SteveJobs作为this,调用WhoAmI。输出:I'm Steve Jobs of object
BillGates.WhoAmI.call(SteveJobs); // 将SteveJobs作为this,却调用BillGates的WhoAmI方法。输出:I'm Steve Jobs of object
SteveJobs.WhoAmI.call(BillGates); // 将BillGates作为this,却调用SteveJobs的WhoAmI方法。输出:I'm Bill Gates of object
WhoAmI.WhoAmI = WhoAmI; // 将WhoAmI函数设置为自身的方法。
WhoAmI.name = " WhoAmI " ;
WhoAmI.WhoAmI(); // 此时的this是WhoAmI函数自己。输出:I'm WhoAmI of function
({name: " nobody " , WhoAmI: WhoAmI}).WhoAmI(); // 临时创建一个匿名对象并设置属性后调用WhoAmI方法。输出:I'm nobody of object
对象素描
怎样建立对象?
{
name: " Microsoft " ,
product: " softwares " ,
chairman: {name: " Bill Gates " , age: 53 , Married: true },
employees: [{name: " Angel " , age: 26 , Married: false }, {name: " Hanson " , age: 32 , Marred: true }],
readme: function () {document.write( this .name + " product " + this .product);}
};
var anObj = new MyFunc(); // 使用new操作符,借助MyFun函数,就创建了一个对象
var anObj = {}; // 创建一个对象
MyFunc.call(anObj); // 将anObj对象作为this指针调用MyFunc函数
我们就可以这样理解,JavaScript先用new操作符创建了一个对象,紧接着就将这个对象作为this参数调用了后面的函数。其实, JavaScript内部就是这么做的,而且任何函数都可以被这样调用!但从 “anObj = new MyFunc()” 这种形式,我们又看到一个熟悉的身影,C++和C#不就是这样创建对象的吗?原来,条条大路通灵山,殊途同归啊!
君看到此处也许会想,我们为什么不可以把这个MyFunc当作构造函数呢?恭喜你,答对了!JavaScript也是这么想的!请看下面的代码:
2 {
3 this .name = name; // 将参数值赋给给this对象的属性
4 this .SayHello = function () {alert( " Hello, I'm " + this .name);}; // 给this对象定义一个SayHello方法。
5 };
6
7 function Employee(name, salary) // 子构造函数
8 {
9 Person.call( this , name); // 将this传给父构造函数
10 this .salary = salary; // 设置一个this的salary属性
11 this .ShowMeTheMoney = function () {alert( this .name + " $ " + this .salary);}; // 添加ShowMeTheMoney方法。
12 };
13
14 var BillGates = new Person( " Bill Gates " ); // 用Person构造函数创建BillGates对象
15 var SteveJobs = new Employee( " Steve Jobs " , 1234 ); // 用Empolyee构造函数创建SteveJobs对象
16
17 BillGates.SayHello(); // 显示:I'm Bill Gates
18 SteveJobs.SayHello(); // 显示:I'm Steve Jobs
19 SteveJobs.ShowMeTheMoney(); // 显示:Steve Jobs $1234
20
21 alert(BillGates.constructor == Person); // 显示:true
22 alert(SteveJobs.constructor == Employee); // 显示:true
23
24 alert(BillGates.SayHello == SteveJobs.SayHello); // 显示:false
其实,如果你愿意把函数当作“类”的话,她就是“类”,因为她本来就有“类”的那些特征。难道不是吗?她生出的儿子各个都有相同的特征,而且构造函数也与类同名嘛!
{
alert( " Hello, I'm " + this .name);
};
function Person(name) // 带参数的构造函数
{
this .name = name; // 将参数值赋给给this对象的属性
this .SayHello = SayHello; // 给this对象SayHello方法赋值为前面那份SayHello代码。
};
var BillGates = new Person( " Bill Gates " ); // 创建BillGates对象
var SteveJobs = new Person( " Steve Jobs " ); // 创建SteveJobs对象
alert(BillGates.SayHello == SteveJobs.SayHello); // 显示:true
prototype源自法语,软件界的标准翻译为“原型”,代表事物的初始形态,也含有模型和样板的意义。JavaScript中的prototype概念恰如其分地反映了这个词的内含,我们不能将其理解为C++的prototype那种预先声明的概念。
{
this .name = name; // 设置对象属性,每个对象各自一份属性数据
};
Person.prototype.SayHello = function () // 给Person函数的prototype添加SayHello方法。
{
alert( " Hello, I'm " + this .name);
}
var BillGates = new Person( " Bill Gates " ); // 创建BillGates对象
var SteveJobs = new Person( " Steve Jobs " ); // 创建SteveJobs对象
BillGates.SayHello(); // 通过BillGates对象直接调用到SayHello方法
SteveJobs.SayHello(); // 通过SteveJobs对象直接调用到SayHello方法
alert(BillGates.SayHello == SteveJobs.SayHello); // 因为两个对象是共享prototype的SayHello,所以显示:true
那么,对于多层次类型的构造函数情况又如何呢?
我们再来看下面的代码:
2 {
3 this .name = name;
4 };
5
6 Person.prototype.SayHello = function () // 给基类构造函数的prototype添加方法
7 {
8 alert( " Hello, I'm " + this .name);
9 };
10
11 function Employee(name, salary) // 子类构造函数
12 {
13 Person.call( this , name); // 调用基类构造函数
14 this .salary = salary;
15 };
16
17 Employee.prototype = new Person(); // 建一个基类的对象作为子类原型的原型,这里很有意思
18
19 Employee.prototype.ShowMeTheMoney = function () // 给子类添构造函数的prototype添加方法
20 {
21 alert( this .name + " $ " + this .salary);
22 };
23
24 var BillGates = new Person( " Bill Gates " ); // 创建基类Person的BillGates对象
25 var SteveJobs = new Employee( " Steve Jobs " , 1234 ); // 创建子类Employee的SteveJobs对象
26
27 BillGates.SayHello(); // 通过对象直接调用到prototype的方法
28 SteveJobs.SayHello(); // 通过子类对象直接调用基类prototype的方法,关注!
29 SteveJobs.ShowMeTheMoney(); // 通过子类对象直接调用子类prototype的方法
30
31 alert(BillGates.SayHello == SteveJobs.SayHello); // 显示:true,表明prototype的方法是共享的
原来,在JavaScript中, prototype不但能让对象共享自己财富,而且prototype还有寻根问祖的天性 ,从而使得先辈们的遗产可以代 代相传。当从一个对象那里读取属性或调用方法时,如果该对象自身不存在这样的属性或方法,就会去自己关联的prototype对象那里寻找;如果 prototype没有,又会去prototype自己关联的前辈prototype那里寻找,直到找到或追溯过程结束为止。
{
this .name = name;
};
Person.prototype.company = " Microsoft " ; // 原型的属性
Person.prototype.SayHello = function () // 原型的方法
{
alert( " Hello, I'm " + this .name + " of " + this .company);
};
var BillGates = new Person( " Bill Gates " );
BillGates.SayHello(); // 由于继承了原型的东西,规规矩矩输出:Hello, I'm Bill Gates
var SteveJobs = new Person( " Steve Jobs " );
SteveJobs.company = " Apple " ; // 设置自己的company属性,掩盖了原型的company属性
SteveJobs.SayHello = function () // 实现了自己的SayHello方法,掩盖了原型的SayHello方法
{
alert( " Hi, " + this .name + " like " + this .company + " , ha ha ha " );
};
SteveJobs.SayHello(); // 都是自己覆盖的属性和方法,输出:Hi, Steve Jobs like Apple, ha ha ha
BillGates.SayHello(); // SteveJobs的覆盖没有影响原型对象,BillGates还是按老样子输出
{
this .name = name;
};
Person.prototype.SayHello = function () // 建立对象前定义的方法
{
alert( " Hello, I'm " + this .name);
};
var BillGates = new Person( " Bill Gates " ); // 建立对象
BillGates.SayHello();
Person.prototype.Retire = function () // 建立对象后再动态扩展原型的方法
{
alert( " Poor " + this .name + " , bye bye! " );
};
BillGates.Retire(); // 动态扩展的方法即可被先前建立的对象立即调用
如果在JavaScript内置的那些如Object和Function等函数的prototype上添加些新的方法和属性,是不是就能扩展JavaScript的功能呢?
if (arguments.length !== 0 ) throw Error.parameterCount();
return this .replace( / ^\s+|\s+$ / g, '' );
}
当然,几乎 很少有人去给Object的prototype添加方法 ,因为那会影响到所有的对象,除非在你的架构中这种方法的确是所有对象都需要的。
{
// 私有变量:
var _firstName = firstName;
var _lastName = lastName;
// 公共变量:
this .age = age;
// 方法:
this .getName = function ()
{
return (firstName + " " + lastName);
};
this .SayHello = function ()
{
alert( " Hello, I'm " + firstName + " " + lastName);
};
};
var BillGates = new Person( " Bill " , " Gates " , 53 );
var SteveJobs = new Person( " Steve " , " Jobs " , 53 );
BillGates.SayHello();
SteveJobs.SayHello();
alert(BillGates.getName() + " " + BillGates.age);
alert(BillGates.firstName); // 这里不能访问到私有变量
原型模型需要一个构造函数来定义对象的成员,而方法却依附在该构造函数的原型上。大致写法如下:
function Person(name)
{
this .name = name; // 在构造函数中定义成员
};
// 方法定义到构造函数的prototype上
Person.prototype.SayHello = function ()
{
alert( " Hello, I'm " + this .name);
};
// 子类构造函数
function Employee(name, salary)
{
Person.call( this , name); // 调用上层构造函数
this .salary = salary; // 扩展的成员
};
// 子类构造函数首先需要用上层构造函数来建立prototype对象,实现继承的概念
Employee.prototype = new Person() // 只需要其prototype的方法,此对象的成员没有任何意义!
// 子类方法也定义到构造函数之上
Employee.prototype.ShowMeTheMoney = function ()
{
alert( this .name + " $ " + this .salary);
};
var BillGates = new Person( " Bill Gates " );
BillGates.SayHello();
var SteveJobs = new Employee( " Steve Jobs " , 1234 );
SteveJobs.SayHello();
SteveJobs.ShowMeTheMoney();
原型类模型虽然不能模拟真正的私有变量,而且也要分两部分来定义类,显得不怎么“优雅”。不过,对象间的方法是共享的,不会遇到垃圾回收问题,而且性能优于“闭包”模型。正所谓“有失必有得”嘛。
在原型模型中,为了实现类继承,必须首先将子类构造函数的prototype设置为一个父类的对象实例。创建这个父类对象实例的目的就是为 了构成原型链,以起到共享上层原型方法作用。但创建这个实例对象时,上层构造函数也会给它设置对象成员,这些对象成员对于继承来说是没有意义的。虽然,我 们也没有给构造函数传递参数,但确实创建了若干没有用的成员,尽管其值是undefined,这也是一种浪费啊。
唉!世界上没有完美的事情啊!
原型真谛
我们已经知道,用 var anObject = new aFunction() 形式创建对象的过程实际上可以分为三步:第一步是建立一个新对象;第二步将该对象内置的原型对象设置为构造函数prototype引用的那个原型对象;第 三步就是将该对象作为this参数调用构造函数,完成成员设置等初始化工作。对象建立之后,对象上的任何访问和操作都只与对象自身及其原型链上的那串对象有关,与构造函数再扯不上关系了。换句话说,构造函数只是在创建对象时起到介绍原型对象和初始化对象两个作用。
那么,我们能否自己定义一个对象来当作原型,并在这个原型上描述类,然后将这个原型设置给新创建的对象,将其当作对象的类呢?我们又能否将这个原型中的一个方法当作构造函数,去初始化新建的对象呢?例如,我们定义这样一个原型对象:
{
Create: function (name, age) // 这个当构造函数
{
this .name = name;
this .age = age;
},
SayHello: function () // 定义方法
{
alert( " Hello, I'm " + this .name);
},
HowOld: function () // 定义方法
{
alert( this .name + " is " + this .age + " years old. " );
}
};
这个JSON形式的写法多么象一个C#的类啊!既有构造函数,又有各种方法。如果可以用某种形式来创建对象,并将对象的内置的原型设置为上面这个“类”对象,不就相当于创建该类的对象了吗?
但遗憾的是,我们几乎不能访问到对象内置的原型属性!尽管有些浏览器可以访问到对象的内置原型,但这样做的话就只能限定了用户必须使用那种浏览器。这也几乎不可行。
那么,我们可不可以通过一个函数对象来做媒介,利用该函数对象的prototype属性来中转这个原型,并用new操作符传递给新建的对象呢?
其实,象这样的代码就可以实现这一目标:
anyfunc.prototype = Person; // 将原型对象放到中转站prototype
var BillGates = new anyfunc(); // 新建对象的内置原型将是我们期望的原型对象
不过,这个anyfunc函数只是一个躯壳,在使用过这个躯壳之后它就成了多余的东西了,而且这和直接使用构造函数来创建对象也没啥不同,有点不爽。
可是,如果我们将这些代码写成一个通用函数,而那个函数躯壳也就成了函数内的函数,这个内部函数不就可以在外层函数退出作用域后自动消亡吗?而且,我们可以将原型对象作为通用函数的参数,让通用函数返回创建的对象。我们需要的就是下面这个形式:
{
function new_() // 定义临时的中转函数壳
{
aClass.Create.apply( this , aParams); // 调用原型中定义的的构造函数,中转构造逻辑及构造参数
};
new_.prototype = aClass; // 准备中转原型对象
return new new_(); // 返回建立最终建立的对象
};
var Person = // 定义的类
{
Create: function (name, age)
{
this .name = name;
this .age = age;
},
SayHello: function ()
{
alert( " Hello, I'm " + this .name);
},
HowOld: function ()
{
alert( this .name + " is " + this .age + " years old. " );
}
};
var BillGates = New(Person, [ " Bill Gates " , 53 ]); // 调用通用函数创建对象,并以数组形式传递构造参数
BillGates.SayHello();
BillGates.HowOld();
alert(BillGates.constructor == Object); // 输出:true
这里的通用函数New()就是一个“语法甘露”!这个语法甘露不但中转了原型对象,还中转了构造函数逻辑及构造参数。
有趣的是,每次创建完对象退出New函数作用域时,临时的new_函数对象会被自动释放。由于new_的prototype属性被设置为新的原型 对象,其原来的原型对象和new_之间就已解开了引用链,临时函数及其原来的原型对象都会被正确回收了。上面代码的最后一句证明,新创建的对象的 constructor属性返回的是Object函数。其实新建的对象自己及其原型里没有constructor属性,那返回的只是最顶层原型对象的构造 函数,即Object。
有了New这个语法甘露,类的定义就很像C#那些静态对象语言的形式了,这样的代码显得多么文静而优雅啊!
当然,这个代码仅仅展示了“语法甘露”的概念。我们还需要多一些的语法甘露,才能实现用简洁而优雅的代码书写类层次及其继承关系。好了,我们再来看一个更丰富的示例吧:
var object = // 定义小写的object基本类,用于实现最基础的方法等
{
isA: function (aType) // 一个判断类与类之间以及对象与类之间关系的基础方法
{
var self = this ;
while (self)
{
if (self == aType)
return true ;
self = self.Type;
};
return false ;
}
};
function Class(aBaseClass, aClassDefine) // 创建类的函数,用于声明类及继承关系
{
function class_() // 创建类的临时函数壳
{
this .Type = aBaseClass; // 我们给每一个类约定一个Type属性,引用其继承的类
for ( var member in aClassDefine)
this [member] = aClassDefine[member]; // 复制类的全部定义到当前创建的类
};
class_.prototype = aBaseClass;
return new class_();
};
function New(aClass, aParams) // 创建对象的函数,用于任意类的对象创建
{
function new_() // 创建对象的临时函数壳
{
this .Type = aClass; // 我们也给每一个对象约定一个Type属性,据此可以访问到对象所属的类
if (aClass.Create)
aClass.Create.apply( this , aParams); // 我们约定所有类的构造函数都叫Create,这和DELPHI比较相似
};
new_.prototype = aClass;
return new new_();
};
// 语法甘露的应用效果:
var Person = Class(object, // 派生至object基本类
{
Create: function (name, age)
{
this .name = name;
this .age = age;
},
SayHello: function ()
{
alert( " Hello, I'm " + this .name + " , " + this .age + " years old. " );
}
});
var Employee = Class(Person, // 派生至Person类,是不是和一般对象语言很相似?
{
Create: function (name, age, salary)
{
Person.Create.call( this , name, age); // 调用基类的构造函数
this .salary = salary;
},
ShowMeTheMoney: function ()
{
alert( this .name + " $ " + this .salary);
}
});
var BillGates = New(Person, [ " Bill Gates " , 53 ]);
var SteveJobs = New(Employee, [ " Steve Jobs " , 53 , 1234 ]);
BillGates.SayHello();
SteveJobs.SayHello();
SteveJobs.ShowMeTheMoney();
var LittleBill = New(BillGates.Type, [ " Little Bill " , 6 ]); // 根据BillGate的类型创建LittleBill
LittleBill.SayHello();
alert(BillGates.isA(Person)); // true
alert(BillGates.isA(Employee)); // false
alert(SteveJobs.isA(Person)); // true
alert(Person.isA(Employee)); // false
alert(Employee.isA(Person)); // true
“语法甘露”不用太多,只要那么一点点,就能改观整个代码的易读性和流畅性,从而让代码显得更优雅。有了这些语法甘露,JavaScript就很像一般对象语言了,写起代码了感觉也就爽多了!
令人高兴的是,受这些甘露滋养的JavaScript程序效率会更高。因为其原型对象里既没有了毫无用处的那些对象级的成员,而且还不存在 constructor属性体,少了与构造函数间的牵连,但依旧保持了方法的共享性。这让JavaScript在追溯原型链和搜索属性及方法时,少费许多工夫啊。
我们就把这种形式称为“甘露模型”吧!其实,这种“甘露模型”的原型用法才是符合prototype概念的本意,才是的JavaScript原型的真谛!
原文地址:http://7685941014.blog.163.com/blog/static/124615480201061782347164/