- 浏览: 23265 次
- 性别:
- 来自: 北京
文章分类
最新评论
一. 基础篇
Javascript学习笔记1——数据类型
在Javascript中只有五种简单类型,分别为null,undefined,boolean,String和Number.一种复杂类型:object。
代码类型只有一种形式就是function。
undefined:未定义,我们也可称之为不存在,typeof(undefined)=undefined.
null:为空。undefined是不存在,而null是存在,但却无。typeof(null)=object,但null又不是object,这就是null的神奇而独特之处。
boolean:true or false。
Number:NaN和Infinity是两个特殊之数,NaN代表一个无法用数值来表示的数字,而Infinity代表一个无穷大的数字,相对的,-Infinify则代表负无穷大。在此有两点特殊的地方:NaN!=NaN,Infinity/Infinity=NaN。在Number类型中,有个非常有用的方法:ToString(),他可以接受一个从2到36的数字,然后把我们的Number转换为相应的进制数。
String:字符串,Javascript中没有字符的概念,字符串是表示文本的最小单位。在字符串中,有这样的两个函数,分别是charAt(index)和charCodeAt(index)分别返回对应索引的字符和字符Unicode编码。在我们平时,可能经常会使用下标的方式访问,如s[10],可是这并不是ECMAScript的标准,应该尽量避免。
Javascript的一切类型都是基于这五个简单类型向上搭建。这五个类型之间又有着万千复杂的关系,undefined,null,0,“”转换为boolean时就是false,而除去这四个外,所有的都为true。但是在这五个当中,除了undefined==null,其他又都不相等。
我们在此又有着这样和强类型语言不通之处,例如123==“123”。那么我们如何能够区分类型呢?这个时候:全等于:===就发挥了用场。
Javascript学习笔记2——函数
在Javascript中,function才是Javascript的第一型。当我们写下一段函数时,其实不过是建立了一个function类型的实体。
就像我们可以写成这样的形式一样:
functionHello() {
alert("Hello");
}
Hello();
varHello = function() {
alert("Hello");
}
Hello();
其实都是一样的。
但是当我们对其中的函数进行修改时,会发现很奇怪的问题。
<scripttype="text/javascript">
functionHello() {
alert("Hello");
}
Hello();
functionHello() {
alert("Hello World");
}
Hello();
</script>
我们会看到这样的结果:连续输出了两次Hello World。而非我们想象中的Hello和Hello World。
这是因为Javascript并非完全的按顺序解释执行,而是在解释之前会对Javascript进行一次“预编译”,在预编译的过程中,会把定义式的函数优先执行,也会把所有var变量创建,默认值为undefined,以提高程序的执行效率。也就是说上面的一段代码其实被JS引擎预编译为这样的形式:
<scripttype="text/javascript">
varHello = function() {
alert("Hello");
}
Hello = function() {
alert("Hello World");
}
Hello();
Hello();
</script>
我们可以通过上面的代码很清晰地看到,其实函数也是数据,也是变量,我们也可以对“函数“进行赋值(重赋值)。当然,我们为了防止这样的情况,也可以这样:
<scripttype="text/javascript">
functionHello() {
alert("Hello");
}
Hello();
</script>
<scripttype="text/javascript">
functionHello() {
alert("Hello World");
}
Hello();
</script>
这样,程序被分成了两段,JS引擎也就不会把他们放到一起了。
Javascript学习笔记3——作用域
每个写过程序的人都不会对作用域这个概念陌生,那在这篇文章中就来谈下Javascript的作用域。
在Javascript,全局环境本身就一个对象。在浏览器宿主中这个对象是window,而当Javascript用于其它非浏览器的宿主,如嵌入式的环境中,可能会是其它的对象。
在这里也纠正一个观念,有很多人都认为Javascript只在浏览器中使用,其实Javascript也能在很多非Web情况下使用,据介绍Javascript在一些基于嵌入式的应用领域表现得也很出色,当然这些我也只是听过传说而已。
言归正传,当我们写下:var i=1时,其实就是声明了一个window作用域的一个变量。
而当我们写下i=1时,是声明了一个window的属性。
看这样一段代码:
<scripttype="text/javascript">
vara = "hello";
b = "world";
Test();
functionTest() {
alert(a + " "+ b);
vara = "welcome";
b = "china";
alert(a + " "+ b);
}
alert(a + " "+ b);
</script>
这段代码分别输出的结果是:undefined world,welcome china, hello china.
我们来分别解释:
在上文中,我们说过,在Javascript预编译时,会把所有var变量创建,默认值为undefined,我们在这里可以举一个例子:
我们可以写这样一段代码:
<scripttype="text/javascript">
alert(a);
alert(b);
vara = "111";
b = "111";
</script>
当我们执行运行这段脚本时,可以发现,首先弹出undefined,然后回提示脚本错误,提示b不存在。由此就可以表明,a在预编译的过程中就已经被创建并且初始化为undefined,而b却只能在实际运行时按顺序去解释。其实在预编译后的Javascript代码可以近乎理解如下:
<scripttype="text/javascript">
vara = undefined;
alert(a);
alert(b);
a = "111";
b = "111";
</script>
接下来我们可以谈一下函数的作用域问题,每当代码运行进入一个函数时,Javascript引擎就会自动创建一个新的作用域,然后把这个新作用域作为当前作用域的子作用域,然后把当前的代码作用域切换到这个新作用域。当代码退出函数时,这个作用域销毁,把代码作用域交还给他的父作用域。
好,准备工作差不多了,接下来我们就来解释第一个问题:问什么会输出undefined world。
首先代码进行在预编译,当进入 Test方法时,开启一个新作用域,然后把全局作用域作为他的父作用域。然后对Test内的方法进行预编译,和上面的代码一样,Test方法被预编译后方法体大致如下:
functionTest() {
vara = undefined;
alert(a + " "+ b);
vara = "welcome";
b = "china";
alert(a + " "+ b);
}
当然,在当前作用域下无法找到b,于是他就会到他的父作用域下,也就是全局作用域找到了b=“world”。于是也就产生了这样的结果。
第二次弹出welcome china,没什么好说的。
第三次,弹出hello china。我们可以这样理解,var a 只是 方法Test的一个局部变量,而b由于事先未声明,因此他会去父作用域中去找到对应的定义。
好,接下来,我们再看一下这个方法的若干个变体。
<scripttype="text/javascript">
vara = "hello";
b = "world";
Test();
functionTest() {
alert(a + " "+ b);
a = "welcome";
b = "china";
alert(a + " "+ b);
}
alert(a + " "+ b);
</script>
首先,我们将方法体内的var a改成a,我们先不看答案,直接来分析,首先,在预编译阶段,方法体内几乎没有任何改变,因此此时a和b一样,都要去他们的父作用域中去寻找,因此第一次出的结果应该是hello world,第二次没什么说的:welcome china,第三次由于a和b在本作用域内都没有事先定义,因此都是再改变父作用域内的值,因此应该输出welcome china.
我们继续:
<scripttype="text/javascript">
vara = "hello";
b = "world";
Test();
functionTest() {
alert(a + " "+ b);
vara = "welcome";
varb = "china";
alert(a + " "+ b);
}
alert(a + " "+ b);
</script>
和上面的分析一样,应该输出undefined undefined,welcome china,hello world.
继续:
<scripttype="text/javascript">
a = "hello";
b = "world";
Test();
functionTest() {
alert(a + " "+ b);
vara = "welcome";
b = "china";
alert(a + " "+ b);
}
alert(a + " "+ b);
</script>
应该是undefined world,welcome china,hello china.
经试验,都没问题, 不知道你明白了么?
因此我们可以得出,每个变量在找不到自己的定义时,都会沿着作用链向上寻找,这样就很可能会出现未预知的错误,给排错添加了很多困难。更麻烦的是,还可能会对父作用域上的变量值进行修改,因此我们在声明变量时应该尽量加上var,尽管Javascript并不强迫我们这样做。
Javascript学习笔记4——Eval函数
在初学JS的时候就知道这个函数,却一直没有了解过他的用途,也一直都是睁一只眼闭一只眼,这次来深入地了解一下这个函数的作用。
eval的作用其实很简单,就是把一段字符串传递给JS解释器,由Javascript解释器将这段字符串解释成Javascript代码,并且执行他。
举个最简单的例子:
<scripttype="text/javascript">
eval("alert(1+1)");
</script>
很简单,把字符串解释成JS代码并执行,弹出2。
当然,上面的例子只是个玩具,在实际中没有人会傻到这么用。我想大家最基本的使用eval函数都是应该在DOM中,例如我们有div1,div2,div3,那么在document.getElementByID时我们的ID没有办法去得到,那么最简单的办法就是在for循环中,使用eval来拼接这么一段程序。例如这样:
<scripttype="text/javascript">
for(varloop = 1; loop < 10; loop++) {
eval('document.getElementById("div"+loop).innerHTML="123"');
}
</script>
最基本的用法说完,相信大家还是对这个函数意犹未尽,如果这个函数只有这么点用法,那就太无聊了。那我们就一点点来剖下一下eval()函数。
我们就先从eval的作用域说起,先看这样一段函数:
<scripttype="text/javascript">
eval("var i=3");
alert(i);
</script>
代码很简单,结果可以弹出3。接下来再对比这段代码:
<scripttype="text/javascript">
vartest = function() {
eval("var i=3");
alert(i);
}
test();
alert(i);
</script>
结果是首先弹出3,然后是undefined。
那么说明:eval()函数动态执行的代码并不会创建新的作用域,其代码就是在当前的作用域执行的。因此也就是说,eval()函数也完全可以使用当前作用域的this,argument等对象。
在IE中,支持这样一种和eval()非常类似的函数叫做:execScript()。我们可以来写段简单的代码。
<scripttype="text/javascript">
vartest = function() {
execScript("var i=3");
alert(i);
}
test();
alert(i);
</script>
结果弹出了2个3,这也就看出了execScript函数的特点,首先他和eval相类似,都能将字符串解释成JS代码并且执行,但是他的作用域不是当前作用域,而是全局作用域。当我们把上面的代码放到Firefox和谷歌浏览器上去试试:发现在Firefox上execScript上代码是无效的,那么也说明一个问题,execScript代码的浏览器兼容性是有问题的。
那么就引申出这样一个问题,我们如何能把这两个函数的“优点”给汇总到一起呢,也就是说,全局+浏览器兼容性。上网搜了下,自己给汇总了一下,大概是这样:
<scripttype="text/javascript">
varStrongEval = function(code) {
if(window.navigator.userAgent.indexOf("MSIE") >= 1) {
execScript(code);
}
if(window.navigator.userAgent.indexOf("Firefox") >= 1) {
window.eval(code);
}
else{
execScript(code);
}
};
varTest = function() {
StrongEval("var i=3");
}
Test();
alert(i);
</script>
这样就可以完美地兼容FF和IE了,其本质代码就在于在FF中eval和window.eval并不等效,这是个很奇妙的事。
另外,我们还可以用eval+with实现一些奇淫技巧。
我们在一般意义上可以写出这样的代码:
varobj = function() {
this.a = 1;
this.b = 2;
this.c = 5;
this.fun = function() {
this.c = this.a + this.b;
}
};
varo = newobj();
o.fun();
alert(o.c);
或者是这样:
varobj = {
a: 1,
b: 2,
c: 5,
fun: function() {
this.c = this.a + this.b;
}
}
再或者是这样:
varobj = function() {
this.a = 1;
this.b = 2;
this.c = 5;
};
obj.prototype.fun = function() {
this.c = this.a + this.b;
}
varo = newobj();
o.fun();
alert(o.c);
无论怎么样,你是不是对这样的this感觉厌烦了呢?那就让我们采取个很另类的办法吧,让至少在感官上可能会舒服一点。
<scripttype="text/javascript">
varfuntemp = function() {
c = a + b;
}
varobj = {
a: 1,
b: 2,
c: 5
};
varfun;
with(obj) {
eval("fun = "+ funtemp);
}
fun();
alert(obj.c);
</script>
这个很勉强,那么好,我们不讨论什么看着舒服不舒服。我们来讨论这样一种情况。
<script>
varDBCommon = function() {
alert("1."); CreateConnection();
alert("2."); OpenConnection();
alert("3."); CreateCommand();
alert("4."); ExcuteCommand();
alert("5."); CloseConnection();
}
varSQLServerCommon = {
CreateConnection: function() { alert("建立SQL Server连接"); },
OpenConnection: function() { alert("打开SQL Server连接"); },
CreateCommand: function() { alert("创建¨SQL Server命令"); },
ExcuteCommand: function() { alert("执行DSQL Server命令"); },
CloseConnection: function() { alert("关闭SQL Server连接"); }
};
varOracleCommon = {
CreateConnection: function() { alert("建立¢Oracle连接"); },
OpenConnection: function() { alert("打开aOracle连接"); },
CreateCommand: function() { alert("创建¨Oracle命令"); },
ExcuteCommand: function() { alert("执行DOracle命令"); },
CloseConnection: function() { alert("关闭?Oracle连接"); }
};
with(SQLServerCommon) {
eval("forSQLServer="+ DBCommon);
}
with(OracleCommon) {
eval("forOracle="+ DBCommon);
}
forSQLServer();
forOracle();
</script>
我们又是否可以把这个看成是一个简陋的模板方法模式呢?呵呵。我们也可以把这个称为利用eval和with配合改变函数的上下文。
不过话又说回来,Eval在一般的情况中是很少被用到的,我们是完全可以避免来使用它的。
Javascript学习笔记5——类和对象
首先,不得不说,我无法达到抛开类和对象的概念来看Javascript的境界,对于Javascript是否是面向对象的说法有很多,不过我最认同的还是Javascript是一种“基于prototype的面向对象语言”。
面向对象语言三大特点:继承,多态,封装,这三点虽然Javascript没有提供天然的语法实现,但是我们都可以通过prototype等技巧来实现,因此这种说法似乎不过分。
在Javascript中,构造对象有三种方式:
1. 首先,我们要明确一个概念,Javascript是一种弱类型的语言,一方面体现在Javascript的变量,返回类型都是没有强类型约束的,另一方面,Javascript可以为对象任意添加属性和方法。根据这个,我们可以写出这样的代码:
<scripttype="text/javascript">
varperson = {};
person.name = "飞林沙";
person.age = 21;
person.Introduce = function() {
alert("My name is "+ this.name + ".I'm "+ this.age);
};
person.Introduce();
</script>
这里的person就是我们构造出的一个对象。
2. 我们也可以利用JSON的形式来构造一个对象。
<scripttype="text/javascript">
varperson = {
name: "飞林沙",
age: 21,
Introduce: function() { alert("My name is "+ this.name + ".I'm "+ this.age); }
};
person.Introduce();
</script>
这个是不是很像我们在C#3.0里提出的匿名对象呢?
protected voidPage_Load(objectsender, EventArgse)
{
varperson = new
{
name = "飞林沙",
age = 21
};
Response.Write("My name is "+ person.name + ".I'm "+ person.age);
}
不同的是在Javascript中,函数是一种类型,所以可以赋给某个变量,但是C#不可以。
但是上面两种方法我们看到,我们都是单独定义了一个对象。接下来让我们把他们抽象出来成为一个类。
<scripttype="text/javascript">
varPerson = function() {
this.name = "飞林沙";
this.age = 21;
this.Introduce = function() {
alert("My name is "+ this.name + ".I'm "+ this.age);
};
};
varperson = newPerson();
person.Introduce();
</script>
可是在这里,我们看到,属性都已经被写死了,我们根本没办法为每个对象单独订制,解决办法很简单:
<scripttype="text/javascript">
varPerson = function(name, age) {
this.name = name;
this.age = age;
this.Introduce = function() {
alert("My name is "+ this.name + ".I'm "+ this.age);
};
};
varperson = newPerson("飞林沙", 21);
person.Introduce();
</script>
好,我们来对比一下第二种和第三种写法,两者是等效的。在第二种写法中,实际上是构建了一个JSON对象,而我们又知道JSON本质上其实就是一个键值对,那么我们是否也可以用同样的方式来理解一个对象呢?
我们来写出这样的测试代码试试:
<scripttype="text/javascript">
varPerson = function(name, age) {
this.name = name;
this.age = age;
this.Introduce = function() {
alert("My name is "+ name + ".I'm "+ age);
};
};
varperson = newPerson("飞林沙", 21);
for(varp inperson) {
alert(p);
}
alert(person["name"]);
</script>
这样的代码没偶任何问题,首先用遍历的方式来找到person所有的key(属性和方法名)。然后我们用索引的方式来访问person对象的name属性。
这些都没有问题,可是我们是不是看到了一个引申的问题,从传统面向对象的语言来看,name和age应该属于私有变量,那么这样用person简简单单的访问,是不是破坏了封装性呢?
还记得我们在前文中说过的么?var的叫变量,没有var的叫属性。那么我们如果讲代码改成这个样子。
<scripttype="text/javascript">
varPerson = function(name, age) {
varname = name;
varage = age;
this.GetName = function() {
returnname;
}
this.GetAge = function() {
returnage;
}
this.Introduce = function() {
alert("My name is "+ name + ".I'm "+ age);
};
};
varperson = newPerson("飞é林?沙3", 21);
alert(person["name"]);
alert(person.GetName());
</script>
这样就可以封装得很好了,这也是在Javascript中的封装方式。
好,关于Javascript的类和对象就说到这,但是这里面仍然有一些问题。我们会在下文中提及。
Javascript学习笔记6——prototype的提出
首先我们继续上文的代码,我们来把这段代码延伸一下:
<scripttype="text/javascript">
varPerson = function(name, age) {
this.name = name;
this.age = age;
this.Introduce = function() {
alert("My name is "+ this.name + ".I'm "+ this.age);
};
};
varperson1 = newPerson("飞林沙", 21);
varperson2 = newPerson("kym", 26);
alert(person1.Introduce == person2.Introduce);
</script>
结果弹出false。也就是说,这两个对象的方法是不同的方法。那么我们知道,在C#中,每个对象会维护着一个方法表,可是方法表应该指向同一块地址。如果是这样的话,那当我们声明了100个对象,是不是要建立100个对象拷贝,对空间是不是一个很大的浪费呢?
于是我们就想了这样的解决办法,用prototype:
<scripttype="text/javascript">
varPerson = function(name, age) {
this.name = name;
this.age = age;
};
Person.prototype.Introduce = function() {
alert("My name is "+ this.name + ".I'm "+ this.age);
}
varperson1 = newPerson("飞林沙", 21);
varperson2 = newPerson("kym", 26);
alert(person1.Introduce == person2.Introduce);
</script>
这样就可以了。所以你还会再说是否用prototype都是一样的么?其实我以前也是这么理解的,在这次偶然的试验中看到了这个问题。
Javascript学习笔记7——原型链的原理
说到prototype,就不得不先说下new的过程。
我们先看看这样一段代码:
<scripttype="text/javascript">
varPerson = function() { };
varp = newPerson();
</script>
很简单的一段代码,我们来看看这个new究竟做了什么?我们可以把new的过程拆分成以下三步:
<1> var p={}; 也就是说,初始化一个对象p。
<2> p.__proto__=Person.prototype;
<3> Person.call(p);也就是说构造p,也可以称之为初始化p。
关键在于第二步,我们来证明一下:
<scripttype="text/javascript">
varPerson = function() { };
varp = newPerson();
alert(p.__proto__ === Person.prototype);
</script>
这段代码会返回true。说明我们步骤2的正确。
那么__proto__是什么?我们在这里简单地说下。每个对象都会在其内部初始化一个属性,就是__proto__,当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去__proto__里找这个属性,这个__proto__又会有自己的__proto__,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
按照标准,__proto__是不对外公开的,也就是说是个私有属性,但是Firefox的引擎将他暴露了出来成为了一个共有的属性,我们可以对外访问和设置。
好,概念说清了,让我们看一下下面这些代码:
<scripttype="text/javascript">
varPerson = function() { };
Person.prototype.Say = function() {
alert("Person say");
}
varp = newPerson();
p.Say();
</script>
这段代码很简单,相信每个人都这样写过,那就让我们看下为什么p可以访问Person的Say。
首先var p=new Person();可以得出p.__proto__=Person.prototype。那么当我们调用p.Say()时,首先p中没有Say这个属性,于是,他就需要到他的__proto__中去找,也就是Person.prototype,而我们在上面定义了Person.prototype.Say=function(){}; 于是,就找到了这个方法。
好,接下来,让我们看个更复杂的。
<scripttype="text/javascript">
varPerson = function() { };
Person.prototype.Say = function() {
alert("Person say");
}
Person.prototype.Salary = 50000;
varProgrammer = function() { };
Programmer.prototype = newPerson();
Programmer.prototype.WriteCode = function() {
alert("programmer writes code");
};
Programmer.prototype.Salary = 500;
varp = newProgrammer();
p.Say();
p.WriteCode();
alert(p.Salary);
</script>
我们来做这样的推导:
var p=new Programmer()可以得出p.__proto__=Programmer.prototype;
而在上面我们指定了Programmer.prototype=new Person();我们来这样拆分,var p1=new Person();Programmer.prototype=p1;那么:
p1.__proto__=Person.prototype;
Programmer.prototype.__proto__=Person.prototype;
由根据上面得到p.__proto__=Programmer.prototype。可以得到p.__proto__.__proto__=Person.prototype。
好,算清楚了之后我们来看上面的结果,p.Say()。由于p没有Say这个属性,于是去p.__proto__,也就是Programmer.prototype,也就是p1中去找,由于p1中也没有Say,那就去p.__proto__.__proto__,也就是Person.prototype中去找,于是就找到了alert(“Person say”)的方法。
其余的也都是同样的道理。
这也就是原型链的实现原理。
最后,其实prototype只是一个假象,他在实现原型链中只是起到了一个辅助作用,换句话说,他只是在new的时候有着一定的价值,而原型链的本质,其实在于__proto__!
二. 实战篇
Javascript学习笔记8——用JSON做原型
在Javascript学习笔记5——类和对象中,我简单地提到了利用JSON去构造一个对象。代码如下:
<scripttype="text/javascript">
varPeople = {
name: "kym",
age: 21,
SayHello: function() {
alert("Hello,My name is "+ this.name + ".I am "+ this.age);
}
}
alert(People.name);
People.SayHello();
</script>
但是我们是不能重用这个对象的,我们如何把这个对象作为原型的呢?
首先,在一个JSON对象有一个构造方法是不可能的了,那么我们就做一个简单的“工厂”吧,写一个方法来专门负责创建。
<scripttype="text/javascript">
varPeople = {
Create: function(name, age) {
this.name = name;
this.age = age;
},
SayHello: function() {
alert("Hello,My name is "+ this.name + ".I am "+ this.age);
}
}
People.Create("kym", 21);
People.SayHello();
</script>
但是通过这个方法我们却发现,我们没有办法用People作为原型,让我们回顾一下:Javascript学习笔记7——原型链的原理 这篇文章,我们想一下这个过程:
var p=new People();==>p.__proto__=People.prototype。于是当我们p.SayHello()的时候就会去People.prototype中去找,结果什么都找不到。
如果可以People.prototype.SayHello=function(){}就可以解决这个问题。但是我们知道,只有function才可以有prototype。
那么我们想想之前的推导公式,怎么样能让p.SayHello()呢?如果可以p.__proto__=People就好了。那么我们想个办法:
既然在new的时候,某个对象的__proto__只能等于某个函数的prototype,我们设置一个函数X,令p.__proto__=X.prototype,我们再令X.prototype=People。这样的关系是这样:
<scripttype="text/javascript">
varPeople = {
Create: function(name, age) {
this.name = name;
this.age = age;
},
SayHello: function() {
alert("Hello,My name is "+ this.name + ".I am "+ this.age);
}
};
varX = function() { };
X.prototype = People;
varp = newX();
p.Create("kym", 21);
p.SayHello();
</script>
这样就相当于用X做了一个中间变量,使得我们可以访问JSON对象的内部属性。但是这样是不是不太优雅呢?我们每次创建一个对象时,都需要来写这样一个辅助的函数。那好,我们就把这个过程封装起来:
varFactory = {
CreatePeople : function(className,name,age) {
vartemp = function() {
className.Create(name, age);
};
temp.prototype = className;
varresult = newtemp();
returnresult;
}
};
varpeople = Factory.CreatePeople(People,"kym",21);
people.SayHello();
但是这样也有一个缺点,就是每次我增加一个类,就需要向Factory里注册一个新方法,这样是很麻烦的,我在很久以前的 玩转方法:call和apply 中说过关于call和apply的区别,因为这里的参数不固定,我们不可能一一列举,因此我们在这里可以用apply来改善这个方法:
<scripttype="text/javascript">
varPeople = {
Create: function(name, age) {
this.name = name;
this.age = age;
},
SayHello: function() {
alert("Hello,My name is "+ this.name + ".I am "+ this.age);
}
};
varFactory = {
Create: function(className, params) {
vartemp = function() {
className.Create.apply(this, params);
};
temp.prototype = className;
varresult = newtemp();
returnresult;
}
};
varpeople = Factory.Create(People,["kym",21]);
people.SayHello();
</script>
这样,一个完整的创建类就诞生了!那么我们每次创建“类”时就都可以用JSON来做了,然后用户每次都统一来调用Factory.Create()就可以了!
Javascript学习笔记9——prototype封装继承
在上文中,我利用prototype的原理做了一个封装的New,然后我就想到,我是否可以用prototype的原理进一步封装面向对象的一些基本特征呢?比如继承。
好,那就让我们一步步打造,首先让我们来看下继承原本的写法:
<script>
varPerson = function(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.SayHello = function() {
alert(this.name + ","+ this.age);
};
varProgrammer = function(name, age, salary) {
Person.call(this, name, age);
this.salary = salary;
};
Programmer.prototype = newPerson();
varpro = newProgrammer("kym", 21, 500);
pro.SayHello();
</script>
我们看到,在实际上,继承的根本就在于这一步Programmer.prototype=new Person()。也就是说把Person加到原型链上。这一点在Javascript学习笔记7——原型链的原理 已经有过比较详尽的解释。
那也就是说,我们实现的关键就在于原型链的打造。
在上文中,我们用JSON来打造了一个原型,其原型链是p.__proto__=Person。那么我们希望在这个上封装继承,那么原型链应该是p.__proto__.__proto__=SuperClass,也就是说Person.__proto__=SuperClass。但是按照我们上面代码的继承方法,原型链关系是Person.__proto__=SuperClass.prototype。
这个和我们在上文中一样,我们的办法就是借助一个辅助函数,将原来的函数内的属性赋给X,然后令X.prototype=SuperClass即可,也就是说我们将子原型进行一个封装。
好,就按照这个思路,我们来实现利用原型链的继承关系的封装。
<script>
varFactory = {
Create: function(className, params) {
vartemp = function() {
className.Create.apply(this, params);
};
temp.prototype = className;
varresult = newtemp();
returnresult;
},
CreateBaseClass: function(baseClass, subClass) {
vartemp = function() {
for(varmember insubClass) {
this[member] = subClass[member];
}
};
temp.prototype = baseClass;
return newtemp();
}
};
varPeople = {
Create: function(name, age) {
this.name = name;
this.age = age;
},
SayHello: function() {
alert("Hello,My name is "+ this.name + ".I am "+ this.age);
}
};
varTemp = {
Create: function(name, age, salary) {
People.Create.call(this, name, age);
this.salary = salary;
},
Introduce: function() {
alert(this.name + "$"+ this.age + "$"+ this.salary);
}
};
varProgrammer = Factory.CreateBaseClass(People, Temp);
varpro = Factory.Create(Programmer, ["kym", 21, 500]);
pro.SayHello();
</script>
这样就完成了我们对继承关系的封装。当然,我们也可以不单独写一个变量:
varProgrammer = Factory.CreateBaseClass(People,
{
Create: function(name, age, salary) {
People.Create.call(this, name, age);
this.salary = salary;
},
Introduce: function() {
alert(this.name + "$"+ this.age + "$"+ this.salary);
}
});
当然,这全凭个人爱好了,个人认为第一种办法相对更清晰一些,但是第二种办法则更优雅。
三. DOM相关
Javascript学习笔记10——网页运行原理
当我们打开一个网页的时候,浏览器会首先创建一个窗口,这个窗口就是我所知道的window对象,也就是整个Javascript运行所依附的全局变量。
为了加载网页文档,当前窗口又需要创建一个Document对象,然后把打开的网页加载到Document下。网页就是在这个加载的过程中,一边加载一边呈现,所以我们当网速非常慢的时候可以看到,网页从上到下一点点地打开。
当我们用<script src=’’>引入其他的JS时,浏览器可能会派遣其他线程去下载,但是浏览器也会等待需要的JS文件下载完成,然后再有主线程按顺序加载JS其他的代码。在Web标准下,限制对同一个域名最多只允许使用两个线程可以同时加载内容,当然可以通过修改注册表来强迫Windows模块突破这一限制。
同时,许多网站会把js放到不同的子域名下,这样就可以使浏览器开启更多的线程并行加载这些资源,从而更加充分地利用网络带宽。
当整个页面都加载结束后,浏览器开始触发window对象或者body对象的onload事件,其实window对象和body对象的load事件是想通的,这也就意味着两个事件只能有一个起作用。当然,在常规意义上,也没有同时设置两者的需求。
到此结束,然后JS引擎就暂停工作,等待着下一次的触发。因此我们可以说:“JS总是被动触发的”。
Javascript学习笔记11——包装DOM对象
我们在日常的应用中,使用Javascript大多数时间都是在用DOM ,以致于很多人都有一种看法就是DOM==JS,虽然这种看法是错误的,但是也可以说明DOM的重要性。
这就导致了我们在写JS的时候,首先会考虑的是这个方法在页面上会产生什么样的变化之类的问题,用架构的思想来说:我们可以说这样把用户界面和业务逻辑掺杂到了一起。我们也知道,这样对于一个稍大的项目来说,满脑袋都是DIV,都是CSS是做不好东西的。
那么怎么办?我们还是用对象的角度,从逻辑上来考虑这个问题,让我们忘记那些DOM对象。
我们来举一个例子吧:
对于某个回复,可能是回复本贴,也可能是举报。那么暂时让我们忘记那些DOM对象,让我们想清楚逻辑:
点击“回复本贴”时,隐藏举报窗口,打开回复窗口。
点击“举报本贴”时,隐藏回复窗口,打开举报窗口。
OK,也就是说整个逻辑包含两个对象,一个是举报窗口对象,一个是回复窗口对象,每个对象有两个方法,一个是打开,一个是隐藏。由于某个页面可能会有很多这样的对象,每个对象都应该是被创建的一个原型,于是就应该有这样的代码:
<scripttype="text/javascript">
varComment = function(x, y) {
varx = x;
vary = y;
};
Comment.prototype.Create = function() {
};
Comment.prototype.Hide = function() {
};
varReport = function(x, y) {
varx = x;
vary = y;
};
Report.prototype.Create = function() {
};
Report.prototype.Hide = function() {
};
</script>
至于逻辑就是:
buttonCommert.onclick = function() {
GetReport(“id”).Hide();
HideOthers(); // 关闭本页面其它的回复窗口
varc = newComment("1","1");
c.Create();
}
举报按钮也近似如此。
好了大致逻辑如此,我们需要的只是实现原型中的创建和隐藏方法。
varComment = function(x, y) {
varx = x;
vary = y;
varConfirmComment = function() {
//Ajax提á交?评à论?
};
};
Comment.prototype.Create = function() {
varcom = document.createElement("div");
document.getElementById("XXXX").appendChild(com);
com.x = x;
com.y = y;
com.style.left = "XXpx";
com.style.top = "YYpx";
vartxt = document.createElement("input");
txt.nodeType = "text";
com.appendChild(txt);
varbtn = document.createElement("input");
btn.nodeType = "button";
btn.onclick = ConfirmComment();
com.appendChild(btn);
};
以上皆为伪代码,只是提供一种封装DOM的思路。
在工程中,将DOM对象包装成符合我们自己业务逻辑的Javascript对象是一种非常好的做法,这也是企业JS库形成的一个过程。
说句极端话,如果足够成熟后,也许页面中写JS看不到DOM,而皆为包装好的JS对象,笑谈尔….
Javascript学习笔记12——Ajax入门
Ajax:Asynchronous Javascript And XML。写个简单的例子:
<body>
<formid="form1"runat="server">
<div>
<asp:LabelID="LabelTime"runat="server"></asp:Label>
</div>
</form>
<scripttype="text/javascript">
if(!window.XMLHttpRequest) {
window.XMLHttpRequest = function() {
return newActiveXObject("Microsoft.XMLHTTP");
};
}
functionUpdateClock() {
varrequest = newXMLHttpRequest();
request.open("post", "TimeTest.aspx", false);
request.send("");
document.getElementById("LabelTime").innerText = request.responseText;
}
setInterval(UpdateClock, 1000);
</script>
</body>
而在另一个页面写下当前时间,这样就形成了一个钟表。
代码很简单,就是操纵一个XMLHttpRequest对象来获取服务器时间,然后更新时间。上面的代码在与服务器交互时,并没有页面整体刷新,而是局部刷新。
但是上面的代码在request.open时,最后一个参数为false,表示发出的XMLHttpRequest是同步的,由于Javascript是单线程的,所以在等待请求的过程中,线程会被阻塞,如果请求时间过长,浏览器会停止响应。
虽然Javascript是单线程的,但是XMLHttpRequest具备异步处理请求的能力。代码如下:
<body>
<formid="form1"runat="server">
<div>
<asp:LabelID="LabelTime"runat="server"></asp:Label>
</div>
</form>
<scripttype="text/javascript">
if(!window.XMLHttpRequest) {
window.XMLHttpRequest = function() {
return newActiveXObject("Microsoft.XMLHTTP");
};
}
functionAsynRequest() {
varrequest = newXMLHttpRequest();
request.open("post", "TimeTest.aspx", true);
request.onreadystatechange = function() {
if(request.readyState === 4) {
UpdateClock(request.responseText);
}
};
request.send("");
}
functionUpdateClock(time) {
document.getElementById("LabelTime").innerText = time;
}
setInterval(AsynRequest, 1000);
</script>
</body>
哎,没有智能提示的日子真不好过。
Javascript学习笔记13——关于响应事件
具体的事情记不清了,某一天下班后听到两个同事在说关于关于事件的问题。
这里简单说下:
<inputtype="button"runat="server" value="Click Me"id="ButtonTest" />
很普通的一个按钮,我们要为其添加点击事件,有两种方法:
A.
<inputtype="button"runat="server"onclick="Alert()"value="Click Me"id="ButtonTest" />
<scripttype="text/javascript">
functionAlert() {
alert("Click Me");
}
</script>
B.
<inputtype="button"runat="server"value="Click Me"id="ButtonTest" />
<scripttype="text/javascript">
document.getElementById("ButtonTest").onclick = function() {
alert("Click Me");
}
</script>
我们称A方法为静态绑定,B方法为动态绑定。我们来看A方法,他近乎可等于这样的效果:
<inputtype="button"runat="server"onclick="Alert()"value="Click Me"id="ButtonTest" />
<scripttype="text/javascript">
document.getElementById("ButtonTest").onclick = function() {
Alert();
}
</script>
也就是说,当我们静态绑定一个事件的时候,实际上是系统默认为我们做了一个匿名函数,然后把我们的方法体包于其中。由于这样,就涉及到了this的问题。
我们来看这样一段代码,这也是我在公司初学JS的时候,公司的JS小牛抛给我的问题。
<inputtype="button"runat="server"onclick="Alert1()"value="Test1"id="ButtonTest1" />
<inputtype="button"runat="server"onclick="Alert2(this)"value="Test2"id="ButtonTest2" />
<scripttype="text/javascript">
functionAlert1() {
alert(this.value);
}
functionAlert2(obj) {
alert(obj.value);
}
</script>
当试验这样一段代码时,便会发现第一个按钮会弹出”undefined”,第二个按钮弹出”Test2”。原因就是如我上面所说,在按钮一中,方法等于声明了一个匿名函数,然后讲Alert1()闭包于其中,也就是说,Alert1()是无法找到其调用者的,所以这时,他会去找window对象的value属性,结果发现未定义,我们可以这样证明:
<inputtype="button"runat="server"onclick="Alert1()"value="Test1"id="ButtonTest1" />
<inputtype="button"runat="server"onclick="Alert2(this)"value="Test2"id="ButtonTest2" />
<scripttype="text/javascript">
value = "window";
functionAlert1() {
alert(this.value);
}
functionAlert2(obj) {
alert(obj.value);
}
</script>
Javascript学习笔记(14)
1. Javascript的数值类型
《Javascript语言精粹》中有这样一句话:Javascript只有单一的数字类型。
在我看来,这句话说的并不准确,应该说,Javascript在声明时,只有单一的数据类型。或者说,Javascript的所有数值类型都被存储成同一种格式,就是64位的浮点数类型。
这样说比较准确。
<scripttype="text/javascript">
vari = 1.2;
alert(typeof(i));
</script>
这样无论把i换成整数还是浮点数,都会得到Number的结果。
但是我说这样不准确的意思是说,在Javascript还提供了一些方法来作为数值之间的类型转换。比如:
<scripttype="text/javascript">
vari = 1.2;
alert(Math.floor(i));
alert(parseInt(i));
</script>
2. Javascript没有连接器
在我之前的C语言文章中,有一节着重介绍了连接器的作用,但是在Javascript中,是没有连接器的,那么多个编译单元之间如何组合到一起呢?答案就是Javascript只是“粗鲁“地将他们抛入到同一个全局的命名空间当中。
3. Javascript的默认值技巧
其实这只是一个小技巧,相信大部分人都用过,看个简单的小例子:
<scripttype="text/javascript">
varperson = { name: "kym", age: 21 };
alert(person.love || "reading");
</script>
这就是对person.love赋默认值的方法。
4. Javascript判断自身属性
我们知道对象其实本质上来说就是一个键值对,那么我们就可以用 for来遍历 Javascript中的属性。写出这样的代码:
<scripttype="text/javascript">
varperson = function(name, age) {
this.name = name;
this.age = age;
};
person.prototype.Say = function() {
alert("Hi");
}
varstudent = function(name, age, teacher) {
person(name, age);
this.teacher = teacher;
};
student.prototype = newperson(this.name,this.age);
student.prototype.SayHello = function() {
alert("Hello");
}
vars = newstudent("kym", 21, "sss");
for(varpro ins) {
alert(pro);
}
</script>
但是我们可以发现,其实他得到的不只只是他自身的属性,还有原型链上的属性,那么我们就要用hasownproperty来得到:
<scripttype="text/javascript">
varperson = function(name, age) {
this.name = name;
this.age = age;
};
person.prototype.Say = function() {
alert("Hi");
}
varstudent = function(name, age, teacher) {
person(name, age);
this.teacher = teacher;
};
student.prototype = newperson(this.name,this.age);
student.prototype.SayHello = function() {
alert("Hello");
}
vars = newstudent("kym", 21, "sss");
for(varpro ins) {
if(s.hasOwnProperty(pro)) {
alert(pro);
}
}
</script>
Javascript玩转继承(一)
最近一直在学Javascript,打算写一些文章,算做自己的学习心得吧,也可以算是学习笔记。没有系统的知识点,太基础的不想写,主要是写一些自己觉得有价值的地方。
今天写第一篇。
Javascript究竟是一门面向对象的语言,还是一门支持对象的语言,我想每个人都有着自己的看法。那些Javascript忠实的Fans一定讲Javascript是一门面向对象的语言,像《Javascript王者归来》一书中对Javascript的说法是基于原型的面向对象。我谈谈我个人的看法。面向对象的三个特征,继承,多态,封装,Javascript虽然实现起来不像Java,C#等面向对象的语言来得快,但是毕竟也有着一定的支持。因此说Javascript是面向对象的语言是有着一定道理的,但是从继承这个部分来谈,一系列的继承法,但是每个继承法都无法实现真正面向对象语言的威力,因此,说他面向对象有着一定的牵强。综上,我对Javascript的理解,更愿意把它叫做一种简化的面向对象,或者说"伪"面向对象(这个伪字绝无贬义)。
今天就从面向对象这个第一个特征:继承来谈。
什么是继承?这个我不想废话,有一只动物,有一个人,有一个女孩,这个就是一个最简单,也是典型的继承链。
在C#等面向对象中,很容易。
class Animal
{ }
class People:Animal
{ }
class Girl:People
{ }
那么在Javascript中,没有类,没有继承的提供实现,我们该怎么做呢?
对象伪装(构造继承法)
什么是对象伪装呢?我们可能叫做构造继承更容易理解一些。顾名思义,就是用构造函数来玩继承。其实就是说把父类的构造函数当成是一个普通的方法,放到子类的构造函数中去执行,这样的话,当构造对象的时候,子类的对象当然就可以构造父类的方法啦!
还是用上面的那个例子,代码如下:
function Animal()
{
this.Run=function(){alert("I can run");};
}
function People(name)
{
//在这里就是传入了父类的构造方法,然后执行父类的构造方法,这个时候就//可以使用父类中的方法了。
this.father=Animal;
this.father();
//记得要删除,否则在子类添加于父类相同名称的方法时,会修改到父类。
delete this.Father;
this.name=name;
this.Say=function(){alert("My name is "+this.name);}
}
function Girl(name,age)
{
this.father=People;
this.father(name);
delete this.father;
this.age=age;
this.Introduce=function(){alert("My name is "+this.name+".I am "+this.age);};
}
这样的话就实现了一个继承链,测试下:
var a=new Animal();
a.Run();
var p=new People("Windking");
p.Run();
p.Say();
var g=new Girl("Xuan",22);
g.Run();
g.Say();
g.Introduce();
结果如下:
a.
b.
c.
d.
e.
f.
测试成功!
我们来总结一下这段代码的关键,指定父类,声明父类对象,然后删除临时变量,您是否觉得有些麻烦呢?至少我是这么觉得的,一旦忘记了delete,还要承担父类被修改的风险,针对这个,我们对这个用call和apply来改进!
接着看代码,还是上面的例子(为了更加容易大家理解,需求改变一下,Animal有了名字):
function Animal(name)
{
this.Run=function(){alert("I can Run");};
}
function People(name)
{
//使用call方法实现继承
this.father=Animal;
this.father.call(this,name);
this.name=name;
this.SayName=function(){alert("My name is "+this.name;);};
}
function Girl(name,age)
{
//使用apply方法来实现继承
this.father=People;
this.father.apply(this,new Array(name));
this.age=age;
this.Introduce=function(){alert("My name is "+this.name+".I am "+this.age);};
}
用一样的测试代码,发现测试一样成功。
如果是新手,可能看后面的这两段代码有些晕晕乎乎,什么是call,什么是apply呢?好,在玩转继承这个专题中,我加入一个增刊系列,如果对这个有不了解,可以看我的这个文章:《玩转方法:call和apply 》。
对象伪装,这只是一种实现继承的方式,在接下来的文章,我会继续写出其他的继承方式以及几种继承方式的优劣,欢迎继续关注。
Javascript玩转继承(二)
在《Javascript玩转继承(一)》中,我主要介绍了对象伪装来实现继承。我们在这里先来说一下这种方式的优缺点。
毋庸置疑,这种方式是比较容易理解的,在子类中调用父类的构造函数。另外,这种方法最大的一个优点就是说构造继承可以实现多继承,复习下这个代码:
function A()
{ }
function B()
{ }
function C()
{
this.father=A;
this.father();
delete this.father;
this.father=B;
this.father();
delete this.father;
}
但是这种方式也有着这样和那样的缺点:
熟悉面向对象的我们来看这样一段C#代码:
class Program
{
static void Main(string[] args)
{
B b=new B();
bool temp = (typeof(A)).IsInstanceOfType(b);
Console.WriteLine(temp);
}
}
class A
{
public A()
{
}
}
class B : A
{
public B()
{
}
}
结果呢?b当然是A的一个实例:
然而我们对上面的那段使用构造继承的Javascript代码做这样的测试:
function A()
{ }
function B()
{ }
function C()
{
this.father=A;
this.father();
delete this.father;
this.father=B;
this.father();
delete this.father;
}
var c=new C();
alert(c instanceof A);
可是得到的结果却不是我们想象的那样:
其实很容易解释:构造继承只是通过了调用父类的构造方法复制的父类的属性,其他的什么搜没有做,所以很多资料中并不把这种继承方式称做继承。
看到缺点的同时也记住优点:支持多继承。
我们看C# 的继承,发现与这个继承有两个最典型的不同:C#不支持多继承,以及我上面提到的构造继承的缺点。于是就产生了一种新的继承方式,我们成为原型继承。
看到名字,可以大致理解,原型继承就是使用原型(prototype)的特性来实现继承的。这是在Javascript最流行的一种继承方式。如果对原型有不理解,请关注我的另一篇文章:《玩转原型——prototype》。
我们先来看代码,在这里,我借鉴《Javascript王者归来》中的一段代码:
function Point(dimension)
{
this.dimension=dimension;
this.Test=function(){
alert("Success");
}
}
function Point2D(x,y)
{
this.x=x;
this.y=y;
}
Point2D.prototype=new Point(2);
var p=new Point2D(3,4);
p.Test();
测试通过。说明Point2D已经继承了父类的方法,再看看instance。
alert(p instanceof Point);
成功!好,我们来分析下原型继承:
原型继承的优点我不再多说,结构简单,容易理解,而且可以instance。但是他的缺点同样显著,还记得我上一篇关于Animal,People,Girl的例子么?我们用原型继承来实现下:
function Animal()
{
this.Run=function(){alert("I can run");};
}
function People(name)
{
this.Say=function(){alert("My name is "+this.name);}
}
People.prototype=new Animal();
function Girl(name,age)
{
this.age=age;
this.Introduce=function(){alert("My name is "+this.name+".I am "+this.age);};
}
Girl.prototype=new People(???);
大家注意看我红色加粗部分的那行代码,People是Girl的原型,那么我们在初始化People的时候就应该传入name参数,但是每个Girl的名字是不一样的,所以原型继承不使用的场合一:在原型继承阶段你不能确定用什么参数来初始化父类对象。场合二:很简单,每一个类只能有一个原型,那么也就是说,原型继承不能用于多继承。这是件好事,也是件坏事。因为:
在Java和C#等面向对象语言中,本来就不支持多继承,并且认为多继承是不符合面向对象的
无法实现多个接口了!
好了,今天就写到这,总结下,Prototype继承解决了构造继承的一些问题,又引入了一些新的问题。总体来说,原型继承是应用最广泛的继承方式,也是Javascript文法中真正意思上实现继承的方式!
Javascript玩转继承(三)
在前两篇文章中,介绍了构造继承和原型继承。今天把剩下的两种写完,这两种的应用相对于前两种来说应用很少,因此我把他们称为是非主流继承方式。
首先,我们来看非主流继承一:实例继承法。
我也不说那么多废话了,既然是非主流继承,就一定不常用,既然不常用还存在,那就只有一个因素,他用于特定的场合。实例继承法,就主要用于核心对象的继承,也是目前为止唯一能够解决核心对象继承的方式。
核心对象的继承拥有一定的价值,比如说Error对象,我们公司可能要自己实现一个Error 类来简化今后的开发,那么这个时候我就要使用实例继承法来继承Error。
代码如下:
function ExtendingError(mes)
{
var instance=new Error(mes);
instance.NewError=function(){
alert("Your Error is "+mes);
}
return instance;
}
好,测试下:
var e=new ExtendingError("Your number is less than one");
e.NewError();
alert(e.toString());
结果让我们满意:
好,废话不多说,这个是非主流继承方式,基本只用于核心对象的继承,记住就好!
接下来看看非主流继承二:拷贝继承法.
顾名思义,拷贝继承,就是通过拷贝实现对象的继承,拷贝什么呢?很明显,就是对象的属性和方法,还记得Javascript中,类其实就一个Hashtable么?如果想不起来的话,就回去复习一下基础,我可能会在过一段时间写一篇关于Javascript对象的知识。
了解了这个就好办了,直接看代码:
首先写一个Extend方法:
Function.prototype.Extend=function(){
for(var pro in obj)
{
//这样其实就是把父类的属性和方法完全复制过去了
this.prototype[pro]=obj[pro];
}
}
好了,再写段代码看下如何使用:
function Animal()
{ }
function People()
{ }
People.Extend(new Animal())
{ }
明眼人一眼看出来,这个方法的缺点太明显了:
将对象的属性方法一一复制的时候,其实用的是反射,反射对效率的损伤我就不多说了。
和原型继承一样,必须初始化父类对象,当确定继承关系时,但是参数还不确定时,玩不转!
总之,这个方法一般情况下不用。
好了,下面说个常用的东西。混合继承!
这个是基于两个主流继承方式的。对比两个继承方式,我们可以发现两个继承方式的优缺点是互补的,那就好办了,混合到一起吧!
function People(name)
{
this.name=name;
this.SayName=function(){
alert("My name is "+name);
}
}
function Girl(name,age)
{
//构造继承
this.father=People;
this.father(name);
delete this.father;
this.Introduce=function(){
alert("My name is "+name+".I am"+age);
}
}
//原型继承
Girl.prototype=new People();
好了,两种方式的混合,现在看看,是不是问题解决了呢?
var g=new Girl("Xuan",22);
alert(g instanceof People);
g.SayName();
g.Introduce();
测试通过!
这是一个相对完美的解决方案,但是却增加了代码的复杂度,所以具体的方案还要靠大家在实践中去选择。
Javascript玩转继承的方式就这些,欢迎大家继续关注我的其他文章。
玩转方法:call和apply
在《Javascript玩转继承(一)》中,在实现继承的时候,用到了两个很特殊的方法,call和apply,下面,我就来说一下这个两个方法。
在ECMAScript v3中,给Function原型定义了这两个方法,这两个方法的作用都是一样的:使用这两个方法可以像调用其他对象方法一样调用函数,这句话是从书上抄的,至少我是没读明白这是什么意思。
下面说简单易懂的,先看段代码:
function Introduce(name,age)
{
document.write("My name is "+name+".I am "+age);
}
var p=new People();
Introduce.call(p,"Windking",20);
就说上面的这段代码,用了call之后,Introduce就成了p的方法,不知道这样说你明白了么?使用了call方法,上述的代码就等同于了这个代码:
function People(name,age)
{
this.name=name;
this.age=age;
this.Introduce=function(){
document.write("My name is "+name+".I am "+age);
};
}
明白意思了么?apply也是一样的作用。
好,我们不管这个方法到底能在实际中用到什么,先讲语法。
call接受至少一个参数,call的第一个参数是指你所需要的对象,比如说上面的那个例子,Introduce方法希望他能够被对象p所调用,那么就把p作为call的第一个参数。剩余的参数个数是任意的,作用是作为Introduce方法的参数。顺序按照Introduce参数声明的顺序。比如Introduce.call(p,"Windking",20),假如Introduce是p的一个实例方法,那么也就是这样的:p.Introduce("Windking",20)。明白了么?记住,传入参数的顺序要与函数声明参数的顺序保持一致。
了解了call,apply方法就容易理解了,apply和call唯一的区别是call接受至少一个参数,而apply只接受两个参数,第一个参数与call一样,第二个参数是一个带下标的集合,比如说Introduce.call(p,"Windking",20)就可以改写成Introduce.apply(p,["Windking",20])了。这次明白了么?
那究竟这两个方法有什么用呢?如果我们只是为了实现上面的那个功能,把Introduce实现为People的方法不是更好么?
我把应用总结为两条:
共享方法。先看代码:
function Introduce(name,age)
{
document.write("My name is "+name+".I am "+age);
}
这是一个自我介绍的方法,现在假设我们有一个男孩的类,和一个女孩的类(在这里我只是为了演示,在实际中,会用一个People的父类),因为他们的Introduce都是一样的,于是我们就可以共享这个方法。
function Boy()
{
this.BoyIntroduce=function(){
Introduce.call(this,name,age);
};
}
同理,Girl中也是一样,这样的话,我们就可以避免写代码了。其实这个有些牵强,因为我们完全也可以写成:
function Boy()
{
this.BoyIntroduce=function(){
Introduce(name,age);
}
}
但是这个时候,我们如果用Apply的话,就看上去简单多了:
function Boy()
{
this.BoyIntroduce=function(){
Introduce.apply(this,arguments);
};
}
是不是简单了很多呢?如果参数很多的话,那么是不是不用再写那么一场串密密麻麻的参数了呢!
跨域调用
看一个简单的例子(仅为演示,无任何价值):
function Boy(name,age)
{
this.BoyIntroduce=function(){
document.write("My name is "+name+".I am "+age);
}
}
function Girl(name,age)
{
}
这是一个Boy和一个Girl类,然后我们写如下的代码:
var b=new Boy("Windking",20);
b.BoyIntroduce();
这没有任何异议。假设有一天有一个女孩也希望做一下自我介绍,只是偶然用一下,那么我就没有必要修改Girl类,因为其他的女孩比较害羞,不喜欢自我介绍。那么这个时候我就可以这样。
var g=new Girl("Xuan",22);
Introduce.call(g,"Xuan",22);
真正用处——继承
Javascript学习笔记1——数据类型
在Javascript中只有五种简单类型,分别为null,undefined,boolean,String和Number.一种复杂类型:object。
代码类型只有一种形式就是function。
undefined:未定义,我们也可称之为不存在,typeof(undefined)=undefined.
null:为空。undefined是不存在,而null是存在,但却无。typeof(null)=object,但null又不是object,这就是null的神奇而独特之处。
boolean:true or false。
Number:NaN和Infinity是两个特殊之数,NaN代表一个无法用数值来表示的数字,而Infinity代表一个无穷大的数字,相对的,-Infinify则代表负无穷大。在此有两点特殊的地方:NaN!=NaN,Infinity/Infinity=NaN。在Number类型中,有个非常有用的方法:ToString(),他可以接受一个从2到36的数字,然后把我们的Number转换为相应的进制数。
String:字符串,Javascript中没有字符的概念,字符串是表示文本的最小单位。在字符串中,有这样的两个函数,分别是charAt(index)和charCodeAt(index)分别返回对应索引的字符和字符Unicode编码。在我们平时,可能经常会使用下标的方式访问,如s[10],可是这并不是ECMAScript的标准,应该尽量避免。
Javascript的一切类型都是基于这五个简单类型向上搭建。这五个类型之间又有着万千复杂的关系,undefined,null,0,“”转换为boolean时就是false,而除去这四个外,所有的都为true。但是在这五个当中,除了undefined==null,其他又都不相等。
我们在此又有着这样和强类型语言不通之处,例如123==“123”。那么我们如何能够区分类型呢?这个时候:全等于:===就发挥了用场。
Javascript学习笔记2——函数
在Javascript中,function才是Javascript的第一型。当我们写下一段函数时,其实不过是建立了一个function类型的实体。
就像我们可以写成这样的形式一样:
functionHello() {
alert("Hello");
}
Hello();
varHello = function() {
alert("Hello");
}
Hello();
其实都是一样的。
但是当我们对其中的函数进行修改时,会发现很奇怪的问题。
<scripttype="text/javascript">
functionHello() {
alert("Hello");
}
Hello();
functionHello() {
alert("Hello World");
}
Hello();
</script>
我们会看到这样的结果:连续输出了两次Hello World。而非我们想象中的Hello和Hello World。
这是因为Javascript并非完全的按顺序解释执行,而是在解释之前会对Javascript进行一次“预编译”,在预编译的过程中,会把定义式的函数优先执行,也会把所有var变量创建,默认值为undefined,以提高程序的执行效率。也就是说上面的一段代码其实被JS引擎预编译为这样的形式:
<scripttype="text/javascript">
varHello = function() {
alert("Hello");
}
Hello = function() {
alert("Hello World");
}
Hello();
Hello();
</script>
我们可以通过上面的代码很清晰地看到,其实函数也是数据,也是变量,我们也可以对“函数“进行赋值(重赋值)。当然,我们为了防止这样的情况,也可以这样:
<scripttype="text/javascript">
functionHello() {
alert("Hello");
}
Hello();
</script>
<scripttype="text/javascript">
functionHello() {
alert("Hello World");
}
Hello();
</script>
这样,程序被分成了两段,JS引擎也就不会把他们放到一起了。
Javascript学习笔记3——作用域
每个写过程序的人都不会对作用域这个概念陌生,那在这篇文章中就来谈下Javascript的作用域。
在Javascript,全局环境本身就一个对象。在浏览器宿主中这个对象是window,而当Javascript用于其它非浏览器的宿主,如嵌入式的环境中,可能会是其它的对象。
在这里也纠正一个观念,有很多人都认为Javascript只在浏览器中使用,其实Javascript也能在很多非Web情况下使用,据介绍Javascript在一些基于嵌入式的应用领域表现得也很出色,当然这些我也只是听过传说而已。
言归正传,当我们写下:var i=1时,其实就是声明了一个window作用域的一个变量。
而当我们写下i=1时,是声明了一个window的属性。
看这样一段代码:
<scripttype="text/javascript">
vara = "hello";
b = "world";
Test();
functionTest() {
alert(a + " "+ b);
vara = "welcome";
b = "china";
alert(a + " "+ b);
}
alert(a + " "+ b);
</script>
这段代码分别输出的结果是:undefined world,welcome china, hello china.
我们来分别解释:
在上文中,我们说过,在Javascript预编译时,会把所有var变量创建,默认值为undefined,我们在这里可以举一个例子:
我们可以写这样一段代码:
<scripttype="text/javascript">
alert(a);
alert(b);
vara = "111";
b = "111";
</script>
当我们执行运行这段脚本时,可以发现,首先弹出undefined,然后回提示脚本错误,提示b不存在。由此就可以表明,a在预编译的过程中就已经被创建并且初始化为undefined,而b却只能在实际运行时按顺序去解释。其实在预编译后的Javascript代码可以近乎理解如下:
<scripttype="text/javascript">
vara = undefined;
alert(a);
alert(b);
a = "111";
b = "111";
</script>
接下来我们可以谈一下函数的作用域问题,每当代码运行进入一个函数时,Javascript引擎就会自动创建一个新的作用域,然后把这个新作用域作为当前作用域的子作用域,然后把当前的代码作用域切换到这个新作用域。当代码退出函数时,这个作用域销毁,把代码作用域交还给他的父作用域。
好,准备工作差不多了,接下来我们就来解释第一个问题:问什么会输出undefined world。
首先代码进行在预编译,当进入 Test方法时,开启一个新作用域,然后把全局作用域作为他的父作用域。然后对Test内的方法进行预编译,和上面的代码一样,Test方法被预编译后方法体大致如下:
functionTest() {
vara = undefined;
alert(a + " "+ b);
vara = "welcome";
b = "china";
alert(a + " "+ b);
}
当然,在当前作用域下无法找到b,于是他就会到他的父作用域下,也就是全局作用域找到了b=“world”。于是也就产生了这样的结果。
第二次弹出welcome china,没什么好说的。
第三次,弹出hello china。我们可以这样理解,var a 只是 方法Test的一个局部变量,而b由于事先未声明,因此他会去父作用域中去找到对应的定义。
好,接下来,我们再看一下这个方法的若干个变体。
<scripttype="text/javascript">
vara = "hello";
b = "world";
Test();
functionTest() {
alert(a + " "+ b);
a = "welcome";
b = "china";
alert(a + " "+ b);
}
alert(a + " "+ b);
</script>
首先,我们将方法体内的var a改成a,我们先不看答案,直接来分析,首先,在预编译阶段,方法体内几乎没有任何改变,因此此时a和b一样,都要去他们的父作用域中去寻找,因此第一次出的结果应该是hello world,第二次没什么说的:welcome china,第三次由于a和b在本作用域内都没有事先定义,因此都是再改变父作用域内的值,因此应该输出welcome china.
我们继续:
<scripttype="text/javascript">
vara = "hello";
b = "world";
Test();
functionTest() {
alert(a + " "+ b);
vara = "welcome";
varb = "china";
alert(a + " "+ b);
}
alert(a + " "+ b);
</script>
和上面的分析一样,应该输出undefined undefined,welcome china,hello world.
继续:
<scripttype="text/javascript">
a = "hello";
b = "world";
Test();
functionTest() {
alert(a + " "+ b);
vara = "welcome";
b = "china";
alert(a + " "+ b);
}
alert(a + " "+ b);
</script>
应该是undefined world,welcome china,hello china.
经试验,都没问题, 不知道你明白了么?
因此我们可以得出,每个变量在找不到自己的定义时,都会沿着作用链向上寻找,这样就很可能会出现未预知的错误,给排错添加了很多困难。更麻烦的是,还可能会对父作用域上的变量值进行修改,因此我们在声明变量时应该尽量加上var,尽管Javascript并不强迫我们这样做。
Javascript学习笔记4——Eval函数
在初学JS的时候就知道这个函数,却一直没有了解过他的用途,也一直都是睁一只眼闭一只眼,这次来深入地了解一下这个函数的作用。
eval的作用其实很简单,就是把一段字符串传递给JS解释器,由Javascript解释器将这段字符串解释成Javascript代码,并且执行他。
举个最简单的例子:
<scripttype="text/javascript">
eval("alert(1+1)");
</script>
很简单,把字符串解释成JS代码并执行,弹出2。
当然,上面的例子只是个玩具,在实际中没有人会傻到这么用。我想大家最基本的使用eval函数都是应该在DOM中,例如我们有div1,div2,div3,那么在document.getElementByID时我们的ID没有办法去得到,那么最简单的办法就是在for循环中,使用eval来拼接这么一段程序。例如这样:
<scripttype="text/javascript">
for(varloop = 1; loop < 10; loop++) {
eval('document.getElementById("div"+loop).innerHTML="123"');
}
</script>
最基本的用法说完,相信大家还是对这个函数意犹未尽,如果这个函数只有这么点用法,那就太无聊了。那我们就一点点来剖下一下eval()函数。
我们就先从eval的作用域说起,先看这样一段函数:
<scripttype="text/javascript">
eval("var i=3");
alert(i);
</script>
代码很简单,结果可以弹出3。接下来再对比这段代码:
<scripttype="text/javascript">
vartest = function() {
eval("var i=3");
alert(i);
}
test();
alert(i);
</script>
结果是首先弹出3,然后是undefined。
那么说明:eval()函数动态执行的代码并不会创建新的作用域,其代码就是在当前的作用域执行的。因此也就是说,eval()函数也完全可以使用当前作用域的this,argument等对象。
在IE中,支持这样一种和eval()非常类似的函数叫做:execScript()。我们可以来写段简单的代码。
<scripttype="text/javascript">
vartest = function() {
execScript("var i=3");
alert(i);
}
test();
alert(i);
</script>
结果弹出了2个3,这也就看出了execScript函数的特点,首先他和eval相类似,都能将字符串解释成JS代码并且执行,但是他的作用域不是当前作用域,而是全局作用域。当我们把上面的代码放到Firefox和谷歌浏览器上去试试:发现在Firefox上execScript上代码是无效的,那么也说明一个问题,execScript代码的浏览器兼容性是有问题的。
那么就引申出这样一个问题,我们如何能把这两个函数的“优点”给汇总到一起呢,也就是说,全局+浏览器兼容性。上网搜了下,自己给汇总了一下,大概是这样:
<scripttype="text/javascript">
varStrongEval = function(code) {
if(window.navigator.userAgent.indexOf("MSIE") >= 1) {
execScript(code);
}
if(window.navigator.userAgent.indexOf("Firefox") >= 1) {
window.eval(code);
}
else{
execScript(code);
}
};
varTest = function() {
StrongEval("var i=3");
}
Test();
alert(i);
</script>
这样就可以完美地兼容FF和IE了,其本质代码就在于在FF中eval和window.eval并不等效,这是个很奇妙的事。
另外,我们还可以用eval+with实现一些奇淫技巧。
我们在一般意义上可以写出这样的代码:
varobj = function() {
this.a = 1;
this.b = 2;
this.c = 5;
this.fun = function() {
this.c = this.a + this.b;
}
};
varo = newobj();
o.fun();
alert(o.c);
或者是这样:
varobj = {
a: 1,
b: 2,
c: 5,
fun: function() {
this.c = this.a + this.b;
}
}
再或者是这样:
varobj = function() {
this.a = 1;
this.b = 2;
this.c = 5;
};
obj.prototype.fun = function() {
this.c = this.a + this.b;
}
varo = newobj();
o.fun();
alert(o.c);
无论怎么样,你是不是对这样的this感觉厌烦了呢?那就让我们采取个很另类的办法吧,让至少在感官上可能会舒服一点。
<scripttype="text/javascript">
varfuntemp = function() {
c = a + b;
}
varobj = {
a: 1,
b: 2,
c: 5
};
varfun;
with(obj) {
eval("fun = "+ funtemp);
}
fun();
alert(obj.c);
</script>
这个很勉强,那么好,我们不讨论什么看着舒服不舒服。我们来讨论这样一种情况。
<script>
varDBCommon = function() {
alert("1."); CreateConnection();
alert("2."); OpenConnection();
alert("3."); CreateCommand();
alert("4."); ExcuteCommand();
alert("5."); CloseConnection();
}
varSQLServerCommon = {
CreateConnection: function() { alert("建立SQL Server连接"); },
OpenConnection: function() { alert("打开SQL Server连接"); },
CreateCommand: function() { alert("创建¨SQL Server命令"); },
ExcuteCommand: function() { alert("执行DSQL Server命令"); },
CloseConnection: function() { alert("关闭SQL Server连接"); }
};
varOracleCommon = {
CreateConnection: function() { alert("建立¢Oracle连接"); },
OpenConnection: function() { alert("打开aOracle连接"); },
CreateCommand: function() { alert("创建¨Oracle命令"); },
ExcuteCommand: function() { alert("执行DOracle命令"); },
CloseConnection: function() { alert("关闭?Oracle连接"); }
};
with(SQLServerCommon) {
eval("forSQLServer="+ DBCommon);
}
with(OracleCommon) {
eval("forOracle="+ DBCommon);
}
forSQLServer();
forOracle();
</script>
我们又是否可以把这个看成是一个简陋的模板方法模式呢?呵呵。我们也可以把这个称为利用eval和with配合改变函数的上下文。
不过话又说回来,Eval在一般的情况中是很少被用到的,我们是完全可以避免来使用它的。
Javascript学习笔记5——类和对象
首先,不得不说,我无法达到抛开类和对象的概念来看Javascript的境界,对于Javascript是否是面向对象的说法有很多,不过我最认同的还是Javascript是一种“基于prototype的面向对象语言”。
面向对象语言三大特点:继承,多态,封装,这三点虽然Javascript没有提供天然的语法实现,但是我们都可以通过prototype等技巧来实现,因此这种说法似乎不过分。
在Javascript中,构造对象有三种方式:
1. 首先,我们要明确一个概念,Javascript是一种弱类型的语言,一方面体现在Javascript的变量,返回类型都是没有强类型约束的,另一方面,Javascript可以为对象任意添加属性和方法。根据这个,我们可以写出这样的代码:
<scripttype="text/javascript">
varperson = {};
person.name = "飞林沙";
person.age = 21;
person.Introduce = function() {
alert("My name is "+ this.name + ".I'm "+ this.age);
};
person.Introduce();
</script>
这里的person就是我们构造出的一个对象。
2. 我们也可以利用JSON的形式来构造一个对象。
<scripttype="text/javascript">
varperson = {
name: "飞林沙",
age: 21,
Introduce: function() { alert("My name is "+ this.name + ".I'm "+ this.age); }
};
person.Introduce();
</script>
这个是不是很像我们在C#3.0里提出的匿名对象呢?
protected voidPage_Load(objectsender, EventArgse)
{
varperson = new
{
name = "飞林沙",
age = 21
};
Response.Write("My name is "+ person.name + ".I'm "+ person.age);
}
不同的是在Javascript中,函数是一种类型,所以可以赋给某个变量,但是C#不可以。
但是上面两种方法我们看到,我们都是单独定义了一个对象。接下来让我们把他们抽象出来成为一个类。
<scripttype="text/javascript">
varPerson = function() {
this.name = "飞林沙";
this.age = 21;
this.Introduce = function() {
alert("My name is "+ this.name + ".I'm "+ this.age);
};
};
varperson = newPerson();
person.Introduce();
</script>
可是在这里,我们看到,属性都已经被写死了,我们根本没办法为每个对象单独订制,解决办法很简单:
<scripttype="text/javascript">
varPerson = function(name, age) {
this.name = name;
this.age = age;
this.Introduce = function() {
alert("My name is "+ this.name + ".I'm "+ this.age);
};
};
varperson = newPerson("飞林沙", 21);
person.Introduce();
</script>
好,我们来对比一下第二种和第三种写法,两者是等效的。在第二种写法中,实际上是构建了一个JSON对象,而我们又知道JSON本质上其实就是一个键值对,那么我们是否也可以用同样的方式来理解一个对象呢?
我们来写出这样的测试代码试试:
<scripttype="text/javascript">
varPerson = function(name, age) {
this.name = name;
this.age = age;
this.Introduce = function() {
alert("My name is "+ name + ".I'm "+ age);
};
};
varperson = newPerson("飞林沙", 21);
for(varp inperson) {
alert(p);
}
alert(person["name"]);
</script>
这样的代码没偶任何问题,首先用遍历的方式来找到person所有的key(属性和方法名)。然后我们用索引的方式来访问person对象的name属性。
这些都没有问题,可是我们是不是看到了一个引申的问题,从传统面向对象的语言来看,name和age应该属于私有变量,那么这样用person简简单单的访问,是不是破坏了封装性呢?
还记得我们在前文中说过的么?var的叫变量,没有var的叫属性。那么我们如果讲代码改成这个样子。
<scripttype="text/javascript">
varPerson = function(name, age) {
varname = name;
varage = age;
this.GetName = function() {
returnname;
}
this.GetAge = function() {
returnage;
}
this.Introduce = function() {
alert("My name is "+ name + ".I'm "+ age);
};
};
varperson = newPerson("飞é林?沙3", 21);
alert(person["name"]);
alert(person.GetName());
</script>
这样就可以封装得很好了,这也是在Javascript中的封装方式。
好,关于Javascript的类和对象就说到这,但是这里面仍然有一些问题。我们会在下文中提及。
Javascript学习笔记6——prototype的提出
首先我们继续上文的代码,我们来把这段代码延伸一下:
<scripttype="text/javascript">
varPerson = function(name, age) {
this.name = name;
this.age = age;
this.Introduce = function() {
alert("My name is "+ this.name + ".I'm "+ this.age);
};
};
varperson1 = newPerson("飞林沙", 21);
varperson2 = newPerson("kym", 26);
alert(person1.Introduce == person2.Introduce);
</script>
结果弹出false。也就是说,这两个对象的方法是不同的方法。那么我们知道,在C#中,每个对象会维护着一个方法表,可是方法表应该指向同一块地址。如果是这样的话,那当我们声明了100个对象,是不是要建立100个对象拷贝,对空间是不是一个很大的浪费呢?
于是我们就想了这样的解决办法,用prototype:
<scripttype="text/javascript">
varPerson = function(name, age) {
this.name = name;
this.age = age;
};
Person.prototype.Introduce = function() {
alert("My name is "+ this.name + ".I'm "+ this.age);
}
varperson1 = newPerson("飞林沙", 21);
varperson2 = newPerson("kym", 26);
alert(person1.Introduce == person2.Introduce);
</script>
这样就可以了。所以你还会再说是否用prototype都是一样的么?其实我以前也是这么理解的,在这次偶然的试验中看到了这个问题。
Javascript学习笔记7——原型链的原理
说到prototype,就不得不先说下new的过程。
我们先看看这样一段代码:
<scripttype="text/javascript">
varPerson = function() { };
varp = newPerson();
</script>
很简单的一段代码,我们来看看这个new究竟做了什么?我们可以把new的过程拆分成以下三步:
<1> var p={}; 也就是说,初始化一个对象p。
<2> p.__proto__=Person.prototype;
<3> Person.call(p);也就是说构造p,也可以称之为初始化p。
关键在于第二步,我们来证明一下:
<scripttype="text/javascript">
varPerson = function() { };
varp = newPerson();
alert(p.__proto__ === Person.prototype);
</script>
这段代码会返回true。说明我们步骤2的正确。
那么__proto__是什么?我们在这里简单地说下。每个对象都会在其内部初始化一个属性,就是__proto__,当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去__proto__里找这个属性,这个__proto__又会有自己的__proto__,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
按照标准,__proto__是不对外公开的,也就是说是个私有属性,但是Firefox的引擎将他暴露了出来成为了一个共有的属性,我们可以对外访问和设置。
好,概念说清了,让我们看一下下面这些代码:
<scripttype="text/javascript">
varPerson = function() { };
Person.prototype.Say = function() {
alert("Person say");
}
varp = newPerson();
p.Say();
</script>
这段代码很简单,相信每个人都这样写过,那就让我们看下为什么p可以访问Person的Say。
首先var p=new Person();可以得出p.__proto__=Person.prototype。那么当我们调用p.Say()时,首先p中没有Say这个属性,于是,他就需要到他的__proto__中去找,也就是Person.prototype,而我们在上面定义了Person.prototype.Say=function(){}; 于是,就找到了这个方法。
好,接下来,让我们看个更复杂的。
<scripttype="text/javascript">
varPerson = function() { };
Person.prototype.Say = function() {
alert("Person say");
}
Person.prototype.Salary = 50000;
varProgrammer = function() { };
Programmer.prototype = newPerson();
Programmer.prototype.WriteCode = function() {
alert("programmer writes code");
};
Programmer.prototype.Salary = 500;
varp = newProgrammer();
p.Say();
p.WriteCode();
alert(p.Salary);
</script>
我们来做这样的推导:
var p=new Programmer()可以得出p.__proto__=Programmer.prototype;
而在上面我们指定了Programmer.prototype=new Person();我们来这样拆分,var p1=new Person();Programmer.prototype=p1;那么:
p1.__proto__=Person.prototype;
Programmer.prototype.__proto__=Person.prototype;
由根据上面得到p.__proto__=Programmer.prototype。可以得到p.__proto__.__proto__=Person.prototype。
好,算清楚了之后我们来看上面的结果,p.Say()。由于p没有Say这个属性,于是去p.__proto__,也就是Programmer.prototype,也就是p1中去找,由于p1中也没有Say,那就去p.__proto__.__proto__,也就是Person.prototype中去找,于是就找到了alert(“Person say”)的方法。
其余的也都是同样的道理。
这也就是原型链的实现原理。
最后,其实prototype只是一个假象,他在实现原型链中只是起到了一个辅助作用,换句话说,他只是在new的时候有着一定的价值,而原型链的本质,其实在于__proto__!
二. 实战篇
Javascript学习笔记8——用JSON做原型
在Javascript学习笔记5——类和对象中,我简单地提到了利用JSON去构造一个对象。代码如下:
<scripttype="text/javascript">
varPeople = {
name: "kym",
age: 21,
SayHello: function() {
alert("Hello,My name is "+ this.name + ".I am "+ this.age);
}
}
alert(People.name);
People.SayHello();
</script>
但是我们是不能重用这个对象的,我们如何把这个对象作为原型的呢?
首先,在一个JSON对象有一个构造方法是不可能的了,那么我们就做一个简单的“工厂”吧,写一个方法来专门负责创建。
<scripttype="text/javascript">
varPeople = {
Create: function(name, age) {
this.name = name;
this.age = age;
},
SayHello: function() {
alert("Hello,My name is "+ this.name + ".I am "+ this.age);
}
}
People.Create("kym", 21);
People.SayHello();
</script>
但是通过这个方法我们却发现,我们没有办法用People作为原型,让我们回顾一下:Javascript学习笔记7——原型链的原理 这篇文章,我们想一下这个过程:
var p=new People();==>p.__proto__=People.prototype。于是当我们p.SayHello()的时候就会去People.prototype中去找,结果什么都找不到。
如果可以People.prototype.SayHello=function(){}就可以解决这个问题。但是我们知道,只有function才可以有prototype。
那么我们想想之前的推导公式,怎么样能让p.SayHello()呢?如果可以p.__proto__=People就好了。那么我们想个办法:
既然在new的时候,某个对象的__proto__只能等于某个函数的prototype,我们设置一个函数X,令p.__proto__=X.prototype,我们再令X.prototype=People。这样的关系是这样:
<scripttype="text/javascript">
varPeople = {
Create: function(name, age) {
this.name = name;
this.age = age;
},
SayHello: function() {
alert("Hello,My name is "+ this.name + ".I am "+ this.age);
}
};
varX = function() { };
X.prototype = People;
varp = newX();
p.Create("kym", 21);
p.SayHello();
</script>
这样就相当于用X做了一个中间变量,使得我们可以访问JSON对象的内部属性。但是这样是不是不太优雅呢?我们每次创建一个对象时,都需要来写这样一个辅助的函数。那好,我们就把这个过程封装起来:
varFactory = {
CreatePeople : function(className,name,age) {
vartemp = function() {
className.Create(name, age);
};
temp.prototype = className;
varresult = newtemp();
returnresult;
}
};
varpeople = Factory.CreatePeople(People,"kym",21);
people.SayHello();
但是这样也有一个缺点,就是每次我增加一个类,就需要向Factory里注册一个新方法,这样是很麻烦的,我在很久以前的 玩转方法:call和apply 中说过关于call和apply的区别,因为这里的参数不固定,我们不可能一一列举,因此我们在这里可以用apply来改善这个方法:
<scripttype="text/javascript">
varPeople = {
Create: function(name, age) {
this.name = name;
this.age = age;
},
SayHello: function() {
alert("Hello,My name is "+ this.name + ".I am "+ this.age);
}
};
varFactory = {
Create: function(className, params) {
vartemp = function() {
className.Create.apply(this, params);
};
temp.prototype = className;
varresult = newtemp();
returnresult;
}
};
varpeople = Factory.Create(People,["kym",21]);
people.SayHello();
</script>
这样,一个完整的创建类就诞生了!那么我们每次创建“类”时就都可以用JSON来做了,然后用户每次都统一来调用Factory.Create()就可以了!
Javascript学习笔记9——prototype封装继承
在上文中,我利用prototype的原理做了一个封装的New,然后我就想到,我是否可以用prototype的原理进一步封装面向对象的一些基本特征呢?比如继承。
好,那就让我们一步步打造,首先让我们来看下继承原本的写法:
<script>
varPerson = function(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.SayHello = function() {
alert(this.name + ","+ this.age);
};
varProgrammer = function(name, age, salary) {
Person.call(this, name, age);
this.salary = salary;
};
Programmer.prototype = newPerson();
varpro = newProgrammer("kym", 21, 500);
pro.SayHello();
</script>
我们看到,在实际上,继承的根本就在于这一步Programmer.prototype=new Person()。也就是说把Person加到原型链上。这一点在Javascript学习笔记7——原型链的原理 已经有过比较详尽的解释。
那也就是说,我们实现的关键就在于原型链的打造。
在上文中,我们用JSON来打造了一个原型,其原型链是p.__proto__=Person。那么我们希望在这个上封装继承,那么原型链应该是p.__proto__.__proto__=SuperClass,也就是说Person.__proto__=SuperClass。但是按照我们上面代码的继承方法,原型链关系是Person.__proto__=SuperClass.prototype。
这个和我们在上文中一样,我们的办法就是借助一个辅助函数,将原来的函数内的属性赋给X,然后令X.prototype=SuperClass即可,也就是说我们将子原型进行一个封装。
好,就按照这个思路,我们来实现利用原型链的继承关系的封装。
<script>
varFactory = {
Create: function(className, params) {
vartemp = function() {
className.Create.apply(this, params);
};
temp.prototype = className;
varresult = newtemp();
returnresult;
},
CreateBaseClass: function(baseClass, subClass) {
vartemp = function() {
for(varmember insubClass) {
this[member] = subClass[member];
}
};
temp.prototype = baseClass;
return newtemp();
}
};
varPeople = {
Create: function(name, age) {
this.name = name;
this.age = age;
},
SayHello: function() {
alert("Hello,My name is "+ this.name + ".I am "+ this.age);
}
};
varTemp = {
Create: function(name, age, salary) {
People.Create.call(this, name, age);
this.salary = salary;
},
Introduce: function() {
alert(this.name + "$"+ this.age + "$"+ this.salary);
}
};
varProgrammer = Factory.CreateBaseClass(People, Temp);
varpro = Factory.Create(Programmer, ["kym", 21, 500]);
pro.SayHello();
</script>
这样就完成了我们对继承关系的封装。当然,我们也可以不单独写一个变量:
varProgrammer = Factory.CreateBaseClass(People,
{
Create: function(name, age, salary) {
People.Create.call(this, name, age);
this.salary = salary;
},
Introduce: function() {
alert(this.name + "$"+ this.age + "$"+ this.salary);
}
});
当然,这全凭个人爱好了,个人认为第一种办法相对更清晰一些,但是第二种办法则更优雅。
三. DOM相关
Javascript学习笔记10——网页运行原理
当我们打开一个网页的时候,浏览器会首先创建一个窗口,这个窗口就是我所知道的window对象,也就是整个Javascript运行所依附的全局变量。
为了加载网页文档,当前窗口又需要创建一个Document对象,然后把打开的网页加载到Document下。网页就是在这个加载的过程中,一边加载一边呈现,所以我们当网速非常慢的时候可以看到,网页从上到下一点点地打开。
当我们用<script src=’’>引入其他的JS时,浏览器可能会派遣其他线程去下载,但是浏览器也会等待需要的JS文件下载完成,然后再有主线程按顺序加载JS其他的代码。在Web标准下,限制对同一个域名最多只允许使用两个线程可以同时加载内容,当然可以通过修改注册表来强迫Windows模块突破这一限制。
同时,许多网站会把js放到不同的子域名下,这样就可以使浏览器开启更多的线程并行加载这些资源,从而更加充分地利用网络带宽。
当整个页面都加载结束后,浏览器开始触发window对象或者body对象的onload事件,其实window对象和body对象的load事件是想通的,这也就意味着两个事件只能有一个起作用。当然,在常规意义上,也没有同时设置两者的需求。
到此结束,然后JS引擎就暂停工作,等待着下一次的触发。因此我们可以说:“JS总是被动触发的”。
Javascript学习笔记11——包装DOM对象
我们在日常的应用中,使用Javascript大多数时间都是在用DOM ,以致于很多人都有一种看法就是DOM==JS,虽然这种看法是错误的,但是也可以说明DOM的重要性。
这就导致了我们在写JS的时候,首先会考虑的是这个方法在页面上会产生什么样的变化之类的问题,用架构的思想来说:我们可以说这样把用户界面和业务逻辑掺杂到了一起。我们也知道,这样对于一个稍大的项目来说,满脑袋都是DIV,都是CSS是做不好东西的。
那么怎么办?我们还是用对象的角度,从逻辑上来考虑这个问题,让我们忘记那些DOM对象。
我们来举一个例子吧:
对于某个回复,可能是回复本贴,也可能是举报。那么暂时让我们忘记那些DOM对象,让我们想清楚逻辑:
点击“回复本贴”时,隐藏举报窗口,打开回复窗口。
点击“举报本贴”时,隐藏回复窗口,打开举报窗口。
OK,也就是说整个逻辑包含两个对象,一个是举报窗口对象,一个是回复窗口对象,每个对象有两个方法,一个是打开,一个是隐藏。由于某个页面可能会有很多这样的对象,每个对象都应该是被创建的一个原型,于是就应该有这样的代码:
<scripttype="text/javascript">
varComment = function(x, y) {
varx = x;
vary = y;
};
Comment.prototype.Create = function() {
};
Comment.prototype.Hide = function() {
};
varReport = function(x, y) {
varx = x;
vary = y;
};
Report.prototype.Create = function() {
};
Report.prototype.Hide = function() {
};
</script>
至于逻辑就是:
buttonCommert.onclick = function() {
GetReport(“id”).Hide();
HideOthers(); // 关闭本页面其它的回复窗口
varc = newComment("1","1");
c.Create();
}
举报按钮也近似如此。
好了大致逻辑如此,我们需要的只是实现原型中的创建和隐藏方法。
varComment = function(x, y) {
varx = x;
vary = y;
varConfirmComment = function() {
//Ajax提á交?评à论?
};
};
Comment.prototype.Create = function() {
varcom = document.createElement("div");
document.getElementById("XXXX").appendChild(com);
com.x = x;
com.y = y;
com.style.left = "XXpx";
com.style.top = "YYpx";
vartxt = document.createElement("input");
txt.nodeType = "text";
com.appendChild(txt);
varbtn = document.createElement("input");
btn.nodeType = "button";
btn.onclick = ConfirmComment();
com.appendChild(btn);
};
以上皆为伪代码,只是提供一种封装DOM的思路。
在工程中,将DOM对象包装成符合我们自己业务逻辑的Javascript对象是一种非常好的做法,这也是企业JS库形成的一个过程。
说句极端话,如果足够成熟后,也许页面中写JS看不到DOM,而皆为包装好的JS对象,笑谈尔….
Javascript学习笔记12——Ajax入门
Ajax:Asynchronous Javascript And XML。写个简单的例子:
<body>
<formid="form1"runat="server">
<div>
<asp:LabelID="LabelTime"runat="server"></asp:Label>
</div>
</form>
<scripttype="text/javascript">
if(!window.XMLHttpRequest) {
window.XMLHttpRequest = function() {
return newActiveXObject("Microsoft.XMLHTTP");
};
}
functionUpdateClock() {
varrequest = newXMLHttpRequest();
request.open("post", "TimeTest.aspx", false);
request.send("");
document.getElementById("LabelTime").innerText = request.responseText;
}
setInterval(UpdateClock, 1000);
</script>
</body>
而在另一个页面写下当前时间,这样就形成了一个钟表。
代码很简单,就是操纵一个XMLHttpRequest对象来获取服务器时间,然后更新时间。上面的代码在与服务器交互时,并没有页面整体刷新,而是局部刷新。
但是上面的代码在request.open时,最后一个参数为false,表示发出的XMLHttpRequest是同步的,由于Javascript是单线程的,所以在等待请求的过程中,线程会被阻塞,如果请求时间过长,浏览器会停止响应。
虽然Javascript是单线程的,但是XMLHttpRequest具备异步处理请求的能力。代码如下:
<body>
<formid="form1"runat="server">
<div>
<asp:LabelID="LabelTime"runat="server"></asp:Label>
</div>
</form>
<scripttype="text/javascript">
if(!window.XMLHttpRequest) {
window.XMLHttpRequest = function() {
return newActiveXObject("Microsoft.XMLHTTP");
};
}
functionAsynRequest() {
varrequest = newXMLHttpRequest();
request.open("post", "TimeTest.aspx", true);
request.onreadystatechange = function() {
if(request.readyState === 4) {
UpdateClock(request.responseText);
}
};
request.send("");
}
functionUpdateClock(time) {
document.getElementById("LabelTime").innerText = time;
}
setInterval(AsynRequest, 1000);
</script>
</body>
哎,没有智能提示的日子真不好过。
Javascript学习笔记13——关于响应事件
具体的事情记不清了,某一天下班后听到两个同事在说关于关于事件的问题。
这里简单说下:
<inputtype="button"runat="server" value="Click Me"id="ButtonTest" />
很普通的一个按钮,我们要为其添加点击事件,有两种方法:
A.
<inputtype="button"runat="server"onclick="Alert()"value="Click Me"id="ButtonTest" />
<scripttype="text/javascript">
functionAlert() {
alert("Click Me");
}
</script>
B.
<inputtype="button"runat="server"value="Click Me"id="ButtonTest" />
<scripttype="text/javascript">
document.getElementById("ButtonTest").onclick = function() {
alert("Click Me");
}
</script>
我们称A方法为静态绑定,B方法为动态绑定。我们来看A方法,他近乎可等于这样的效果:
<inputtype="button"runat="server"onclick="Alert()"value="Click Me"id="ButtonTest" />
<scripttype="text/javascript">
document.getElementById("ButtonTest").onclick = function() {
Alert();
}
</script>
也就是说,当我们静态绑定一个事件的时候,实际上是系统默认为我们做了一个匿名函数,然后把我们的方法体包于其中。由于这样,就涉及到了this的问题。
我们来看这样一段代码,这也是我在公司初学JS的时候,公司的JS小牛抛给我的问题。
<inputtype="button"runat="server"onclick="Alert1()"value="Test1"id="ButtonTest1" />
<inputtype="button"runat="server"onclick="Alert2(this)"value="Test2"id="ButtonTest2" />
<scripttype="text/javascript">
functionAlert1() {
alert(this.value);
}
functionAlert2(obj) {
alert(obj.value);
}
</script>
当试验这样一段代码时,便会发现第一个按钮会弹出”undefined”,第二个按钮弹出”Test2”。原因就是如我上面所说,在按钮一中,方法等于声明了一个匿名函数,然后讲Alert1()闭包于其中,也就是说,Alert1()是无法找到其调用者的,所以这时,他会去找window对象的value属性,结果发现未定义,我们可以这样证明:
<inputtype="button"runat="server"onclick="Alert1()"value="Test1"id="ButtonTest1" />
<inputtype="button"runat="server"onclick="Alert2(this)"value="Test2"id="ButtonTest2" />
<scripttype="text/javascript">
value = "window";
functionAlert1() {
alert(this.value);
}
functionAlert2(obj) {
alert(obj.value);
}
</script>
Javascript学习笔记(14)
1. Javascript的数值类型
《Javascript语言精粹》中有这样一句话:Javascript只有单一的数字类型。
在我看来,这句话说的并不准确,应该说,Javascript在声明时,只有单一的数据类型。或者说,Javascript的所有数值类型都被存储成同一种格式,就是64位的浮点数类型。
这样说比较准确。
<scripttype="text/javascript">
vari = 1.2;
alert(typeof(i));
</script>
这样无论把i换成整数还是浮点数,都会得到Number的结果。
但是我说这样不准确的意思是说,在Javascript还提供了一些方法来作为数值之间的类型转换。比如:
<scripttype="text/javascript">
vari = 1.2;
alert(Math.floor(i));
alert(parseInt(i));
</script>
2. Javascript没有连接器
在我之前的C语言文章中,有一节着重介绍了连接器的作用,但是在Javascript中,是没有连接器的,那么多个编译单元之间如何组合到一起呢?答案就是Javascript只是“粗鲁“地将他们抛入到同一个全局的命名空间当中。
3. Javascript的默认值技巧
其实这只是一个小技巧,相信大部分人都用过,看个简单的小例子:
<scripttype="text/javascript">
varperson = { name: "kym", age: 21 };
alert(person.love || "reading");
</script>
这就是对person.love赋默认值的方法。
4. Javascript判断自身属性
我们知道对象其实本质上来说就是一个键值对,那么我们就可以用 for来遍历 Javascript中的属性。写出这样的代码:
<scripttype="text/javascript">
varperson = function(name, age) {
this.name = name;
this.age = age;
};
person.prototype.Say = function() {
alert("Hi");
}
varstudent = function(name, age, teacher) {
person(name, age);
this.teacher = teacher;
};
student.prototype = newperson(this.name,this.age);
student.prototype.SayHello = function() {
alert("Hello");
}
vars = newstudent("kym", 21, "sss");
for(varpro ins) {
alert(pro);
}
</script>
但是我们可以发现,其实他得到的不只只是他自身的属性,还有原型链上的属性,那么我们就要用hasownproperty来得到:
<scripttype="text/javascript">
varperson = function(name, age) {
this.name = name;
this.age = age;
};
person.prototype.Say = function() {
alert("Hi");
}
varstudent = function(name, age, teacher) {
person(name, age);
this.teacher = teacher;
};
student.prototype = newperson(this.name,this.age);
student.prototype.SayHello = function() {
alert("Hello");
}
vars = newstudent("kym", 21, "sss");
for(varpro ins) {
if(s.hasOwnProperty(pro)) {
alert(pro);
}
}
</script>
Javascript玩转继承(一)
最近一直在学Javascript,打算写一些文章,算做自己的学习心得吧,也可以算是学习笔记。没有系统的知识点,太基础的不想写,主要是写一些自己觉得有价值的地方。
今天写第一篇。
Javascript究竟是一门面向对象的语言,还是一门支持对象的语言,我想每个人都有着自己的看法。那些Javascript忠实的Fans一定讲Javascript是一门面向对象的语言,像《Javascript王者归来》一书中对Javascript的说法是基于原型的面向对象。我谈谈我个人的看法。面向对象的三个特征,继承,多态,封装,Javascript虽然实现起来不像Java,C#等面向对象的语言来得快,但是毕竟也有着一定的支持。因此说Javascript是面向对象的语言是有着一定道理的,但是从继承这个部分来谈,一系列的继承法,但是每个继承法都无法实现真正面向对象语言的威力,因此,说他面向对象有着一定的牵强。综上,我对Javascript的理解,更愿意把它叫做一种简化的面向对象,或者说"伪"面向对象(这个伪字绝无贬义)。
今天就从面向对象这个第一个特征:继承来谈。
什么是继承?这个我不想废话,有一只动物,有一个人,有一个女孩,这个就是一个最简单,也是典型的继承链。
在C#等面向对象中,很容易。
class Animal
{ }
class People:Animal
{ }
class Girl:People
{ }
那么在Javascript中,没有类,没有继承的提供实现,我们该怎么做呢?
对象伪装(构造继承法)
什么是对象伪装呢?我们可能叫做构造继承更容易理解一些。顾名思义,就是用构造函数来玩继承。其实就是说把父类的构造函数当成是一个普通的方法,放到子类的构造函数中去执行,这样的话,当构造对象的时候,子类的对象当然就可以构造父类的方法啦!
还是用上面的那个例子,代码如下:
function Animal()
{
this.Run=function(){alert("I can run");};
}
function People(name)
{
//在这里就是传入了父类的构造方法,然后执行父类的构造方法,这个时候就//可以使用父类中的方法了。
this.father=Animal;
this.father();
//记得要删除,否则在子类添加于父类相同名称的方法时,会修改到父类。
delete this.Father;
this.name=name;
this.Say=function(){alert("My name is "+this.name);}
}
function Girl(name,age)
{
this.father=People;
this.father(name);
delete this.father;
this.age=age;
this.Introduce=function(){alert("My name is "+this.name+".I am "+this.age);};
}
这样的话就实现了一个继承链,测试下:
var a=new Animal();
a.Run();
var p=new People("Windking");
p.Run();
p.Say();
var g=new Girl("Xuan",22);
g.Run();
g.Say();
g.Introduce();
结果如下:
a.
b.
c.
d.
e.
f.
测试成功!
我们来总结一下这段代码的关键,指定父类,声明父类对象,然后删除临时变量,您是否觉得有些麻烦呢?至少我是这么觉得的,一旦忘记了delete,还要承担父类被修改的风险,针对这个,我们对这个用call和apply来改进!
接着看代码,还是上面的例子(为了更加容易大家理解,需求改变一下,Animal有了名字):
function Animal(name)
{
this.Run=function(){alert("I can Run");};
}
function People(name)
{
//使用call方法实现继承
this.father=Animal;
this.father.call(this,name);
this.name=name;
this.SayName=function(){alert("My name is "+this.name;);};
}
function Girl(name,age)
{
//使用apply方法来实现继承
this.father=People;
this.father.apply(this,new Array(name));
this.age=age;
this.Introduce=function(){alert("My name is "+this.name+".I am "+this.age);};
}
用一样的测试代码,发现测试一样成功。
如果是新手,可能看后面的这两段代码有些晕晕乎乎,什么是call,什么是apply呢?好,在玩转继承这个专题中,我加入一个增刊系列,如果对这个有不了解,可以看我的这个文章:《玩转方法:call和apply 》。
对象伪装,这只是一种实现继承的方式,在接下来的文章,我会继续写出其他的继承方式以及几种继承方式的优劣,欢迎继续关注。
Javascript玩转继承(二)
在《Javascript玩转继承(一)》中,我主要介绍了对象伪装来实现继承。我们在这里先来说一下这种方式的优缺点。
毋庸置疑,这种方式是比较容易理解的,在子类中调用父类的构造函数。另外,这种方法最大的一个优点就是说构造继承可以实现多继承,复习下这个代码:
function A()
{ }
function B()
{ }
function C()
{
this.father=A;
this.father();
delete this.father;
this.father=B;
this.father();
delete this.father;
}
但是这种方式也有着这样和那样的缺点:
熟悉面向对象的我们来看这样一段C#代码:
class Program
{
static void Main(string[] args)
{
B b=new B();
bool temp = (typeof(A)).IsInstanceOfType(b);
Console.WriteLine(temp);
}
}
class A
{
public A()
{
}
}
class B : A
{
public B()
{
}
}
结果呢?b当然是A的一个实例:
然而我们对上面的那段使用构造继承的Javascript代码做这样的测试:
function A()
{ }
function B()
{ }
function C()
{
this.father=A;
this.father();
delete this.father;
this.father=B;
this.father();
delete this.father;
}
var c=new C();
alert(c instanceof A);
可是得到的结果却不是我们想象的那样:
其实很容易解释:构造继承只是通过了调用父类的构造方法复制的父类的属性,其他的什么搜没有做,所以很多资料中并不把这种继承方式称做继承。
看到缺点的同时也记住优点:支持多继承。
我们看C# 的继承,发现与这个继承有两个最典型的不同:C#不支持多继承,以及我上面提到的构造继承的缺点。于是就产生了一种新的继承方式,我们成为原型继承。
看到名字,可以大致理解,原型继承就是使用原型(prototype)的特性来实现继承的。这是在Javascript最流行的一种继承方式。如果对原型有不理解,请关注我的另一篇文章:《玩转原型——prototype》。
我们先来看代码,在这里,我借鉴《Javascript王者归来》中的一段代码:
function Point(dimension)
{
this.dimension=dimension;
this.Test=function(){
alert("Success");
}
}
function Point2D(x,y)
{
this.x=x;
this.y=y;
}
Point2D.prototype=new Point(2);
var p=new Point2D(3,4);
p.Test();
测试通过。说明Point2D已经继承了父类的方法,再看看instance。
alert(p instanceof Point);
成功!好,我们来分析下原型继承:
原型继承的优点我不再多说,结构简单,容易理解,而且可以instance。但是他的缺点同样显著,还记得我上一篇关于Animal,People,Girl的例子么?我们用原型继承来实现下:
function Animal()
{
this.Run=function(){alert("I can run");};
}
function People(name)
{
this.Say=function(){alert("My name is "+this.name);}
}
People.prototype=new Animal();
function Girl(name,age)
{
this.age=age;
this.Introduce=function(){alert("My name is "+this.name+".I am "+this.age);};
}
Girl.prototype=new People(???);
大家注意看我红色加粗部分的那行代码,People是Girl的原型,那么我们在初始化People的时候就应该传入name参数,但是每个Girl的名字是不一样的,所以原型继承不使用的场合一:在原型继承阶段你不能确定用什么参数来初始化父类对象。场合二:很简单,每一个类只能有一个原型,那么也就是说,原型继承不能用于多继承。这是件好事,也是件坏事。因为:
在Java和C#等面向对象语言中,本来就不支持多继承,并且认为多继承是不符合面向对象的
无法实现多个接口了!
好了,今天就写到这,总结下,Prototype继承解决了构造继承的一些问题,又引入了一些新的问题。总体来说,原型继承是应用最广泛的继承方式,也是Javascript文法中真正意思上实现继承的方式!
Javascript玩转继承(三)
在前两篇文章中,介绍了构造继承和原型继承。今天把剩下的两种写完,这两种的应用相对于前两种来说应用很少,因此我把他们称为是非主流继承方式。
首先,我们来看非主流继承一:实例继承法。
我也不说那么多废话了,既然是非主流继承,就一定不常用,既然不常用还存在,那就只有一个因素,他用于特定的场合。实例继承法,就主要用于核心对象的继承,也是目前为止唯一能够解决核心对象继承的方式。
核心对象的继承拥有一定的价值,比如说Error对象,我们公司可能要自己实现一个Error 类来简化今后的开发,那么这个时候我就要使用实例继承法来继承Error。
代码如下:
function ExtendingError(mes)
{
var instance=new Error(mes);
instance.NewError=function(){
alert("Your Error is "+mes);
}
return instance;
}
好,测试下:
var e=new ExtendingError("Your number is less than one");
e.NewError();
alert(e.toString());
结果让我们满意:
好,废话不多说,这个是非主流继承方式,基本只用于核心对象的继承,记住就好!
接下来看看非主流继承二:拷贝继承法.
顾名思义,拷贝继承,就是通过拷贝实现对象的继承,拷贝什么呢?很明显,就是对象的属性和方法,还记得Javascript中,类其实就一个Hashtable么?如果想不起来的话,就回去复习一下基础,我可能会在过一段时间写一篇关于Javascript对象的知识。
了解了这个就好办了,直接看代码:
首先写一个Extend方法:
Function.prototype.Extend=function(){
for(var pro in obj)
{
//这样其实就是把父类的属性和方法完全复制过去了
this.prototype[pro]=obj[pro];
}
}
好了,再写段代码看下如何使用:
function Animal()
{ }
function People()
{ }
People.Extend(new Animal())
{ }
明眼人一眼看出来,这个方法的缺点太明显了:
将对象的属性方法一一复制的时候,其实用的是反射,反射对效率的损伤我就不多说了。
和原型继承一样,必须初始化父类对象,当确定继承关系时,但是参数还不确定时,玩不转!
总之,这个方法一般情况下不用。
好了,下面说个常用的东西。混合继承!
这个是基于两个主流继承方式的。对比两个继承方式,我们可以发现两个继承方式的优缺点是互补的,那就好办了,混合到一起吧!
function People(name)
{
this.name=name;
this.SayName=function(){
alert("My name is "+name);
}
}
function Girl(name,age)
{
//构造继承
this.father=People;
this.father(name);
delete this.father;
this.Introduce=function(){
alert("My name is "+name+".I am"+age);
}
}
//原型继承
Girl.prototype=new People();
好了,两种方式的混合,现在看看,是不是问题解决了呢?
var g=new Girl("Xuan",22);
alert(g instanceof People);
g.SayName();
g.Introduce();
测试通过!
这是一个相对完美的解决方案,但是却增加了代码的复杂度,所以具体的方案还要靠大家在实践中去选择。
Javascript玩转继承的方式就这些,欢迎大家继续关注我的其他文章。
玩转方法:call和apply
在《Javascript玩转继承(一)》中,在实现继承的时候,用到了两个很特殊的方法,call和apply,下面,我就来说一下这个两个方法。
在ECMAScript v3中,给Function原型定义了这两个方法,这两个方法的作用都是一样的:使用这两个方法可以像调用其他对象方法一样调用函数,这句话是从书上抄的,至少我是没读明白这是什么意思。
下面说简单易懂的,先看段代码:
function Introduce(name,age)
{
document.write("My name is "+name+".I am "+age);
}
var p=new People();
Introduce.call(p,"Windking",20);
就说上面的这段代码,用了call之后,Introduce就成了p的方法,不知道这样说你明白了么?使用了call方法,上述的代码就等同于了这个代码:
function People(name,age)
{
this.name=name;
this.age=age;
this.Introduce=function(){
document.write("My name is "+name+".I am "+age);
};
}
明白意思了么?apply也是一样的作用。
好,我们不管这个方法到底能在实际中用到什么,先讲语法。
call接受至少一个参数,call的第一个参数是指你所需要的对象,比如说上面的那个例子,Introduce方法希望他能够被对象p所调用,那么就把p作为call的第一个参数。剩余的参数个数是任意的,作用是作为Introduce方法的参数。顺序按照Introduce参数声明的顺序。比如Introduce.call(p,"Windking",20),假如Introduce是p的一个实例方法,那么也就是这样的:p.Introduce("Windking",20)。明白了么?记住,传入参数的顺序要与函数声明参数的顺序保持一致。
了解了call,apply方法就容易理解了,apply和call唯一的区别是call接受至少一个参数,而apply只接受两个参数,第一个参数与call一样,第二个参数是一个带下标的集合,比如说Introduce.call(p,"Windking",20)就可以改写成Introduce.apply(p,["Windking",20])了。这次明白了么?
那究竟这两个方法有什么用呢?如果我们只是为了实现上面的那个功能,把Introduce实现为People的方法不是更好么?
我把应用总结为两条:
共享方法。先看代码:
function Introduce(name,age)
{
document.write("My name is "+name+".I am "+age);
}
这是一个自我介绍的方法,现在假设我们有一个男孩的类,和一个女孩的类(在这里我只是为了演示,在实际中,会用一个People的父类),因为他们的Introduce都是一样的,于是我们就可以共享这个方法。
function Boy()
{
this.BoyIntroduce=function(){
Introduce.call(this,name,age);
};
}
同理,Girl中也是一样,这样的话,我们就可以避免写代码了。其实这个有些牵强,因为我们完全也可以写成:
function Boy()
{
this.BoyIntroduce=function(){
Introduce(name,age);
}
}
但是这个时候,我们如果用Apply的话,就看上去简单多了:
function Boy()
{
this.BoyIntroduce=function(){
Introduce.apply(this,arguments);
};
}
是不是简单了很多呢?如果参数很多的话,那么是不是不用再写那么一场串密密麻麻的参数了呢!
跨域调用
看一个简单的例子(仅为演示,无任何价值):
function Boy(name,age)
{
this.BoyIntroduce=function(){
document.write("My name is "+name+".I am "+age);
}
}
function Girl(name,age)
{
}
这是一个Boy和一个Girl类,然后我们写如下的代码:
var b=new Boy("Windking",20);
b.BoyIntroduce();
这没有任何异议。假设有一天有一个女孩也希望做一下自我介绍,只是偶然用一下,那么我就没有必要修改Girl类,因为其他的女孩比较害羞,不喜欢自我介绍。那么这个时候我就可以这样。
var g=new Girl("Xuan",22);
Introduce.call(g,"Xuan",22);
真正用处——继承
相关推荐
JavaScript学习笔记JavaScript学习笔记
个人Javascript学习笔记 精华版 个人Javascript学习笔记 精华版 个人Javascript学习笔记 精华版
JavaScript学习笔记,javascript基础知识,基础语法整理.pdf
详细的javascript学习笔记,DOM,BOM,AJAX等详细笔记!
javascript 学习笔记
挺好的javascript 学习笔记javascript 学习笔记javascript 学习笔记javascript 学习笔记javascript 学习笔记
javascript学习笔记
JavaScript学习笔记.pdf
javascript学习笔记讲解版参考.pdf
javaScript学习笔记总结.docx
JavaScript 学习笔记集和代码库JavaScript 学习笔记集和代码库JavaScript 学习笔记集和代码库JavaScript 学习笔记集和代码库JavaScript 学习笔记集和代码库JavaScript 学习笔记集和代码库JavaScript 学习笔记集和...
JavaScript学习笔记归纳.pdf
javascript学习笔记发放1,以后陆续发布。