Javascript学习笔记

一. 基础篇
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();

其实都是一样的。
但是当我们对其中的函数进行修改时,会发现很奇怪的问题。

functionHello() {
alert("Hello");
}
Hello();
functionHello() {
alert("Hello World");
}
Hello();



我们会看到这样的结果:连续输出了两次Hello World。而非我们想象中的Hello和Hello World。
这是因为Javascript并非完全的按顺序解释执行,而是在解释之前会对Javascript进行一次“预编译”,在预编译的过程中,会把定义式的函数优先执行,也会把所有var变量创建,默认值为undefined,以提高程序的执行效率。也就是说上面的一段代码其实被JS引擎预编译为这样的形式:

varHello = function() {
alert("Hello");
}
Hello = function() {
alert("Hello World");
}
Hello();
Hello();



我们可以通过上面的代码很清晰地看到,其实函数也是数据,也是变量,我们也可以对“函数“进行赋值(重赋值)。当然,我们为了防止这样的情况,也可以这样:


functionHello() {
alert("Hello");
}
Hello();


functionHello() {
alert("Hello World");
}
Hello();



这样,程序被分成了两段,JS引擎也就不会把他们放到一起了。

Javascript学习笔记3——作用域
每个写过程序的人都不会对作用域这个概念陌生,那在这篇文章中就来谈下Javascript的作用域。
在Javascript,全局环境本身就一个对象。在浏览器宿主中这个对象是window,而当Javascript用于其它非浏览器的宿主,如嵌入式的环境中,可能会是其它的对象。
在这里也纠正一个观念,有很多人都认为Javascript只在浏览器中使用,其实Javascript也能在很多非Web情况下使用,据介绍Javascript在一些基于嵌入式的应用领域表现得也很出色,当然这些我也只是听过传说而已。
言归正传,当我们写下:var i=1时,其实就是声明了一个window作用域的一个变量。
而当我们写下i=1时,是声明了一个window的属性。
看这样一段代码:

vara = "hello";
b = "world";
Test();
functionTest() {
alert(a + " "+ b);
vara = "welcome";
b = "china";
alert(a + " "+ b);
}
alert(a + " "+ b);



这段代码分别输出的结果是:undefined world,welcome china, hello china.
我们来分别解释:
在上文中,我们说过,在Javascript预编译时,会把所有var变量创建,默认值为undefined,我们在这里可以举一个例子:
我们可以写这样一段代码:

alert(a);
alert(b);
vara = "111";
b = "111";



当我们执行运行这段脚本时,可以发现,首先弹出undefined,然后回提示脚本错误,提示b不存在。由此就可以表明,a在预编译的过程中就已经被创建并且初始化为undefined,而b却只能在实际运行时按顺序去解释。其实在预编译后的Javascript代码可以近乎理解如下:

vara = undefined;
alert(a);
alert(b);
a = "111";
b = "111";



接下来我们可以谈一下函数的作用域问题,每当代码运行进入一个函数时,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由于事先未声明,因此他会去父作用域中去找到对应的定义。
好,接下来,我们再看一下这个方法的若干个变体。

vara = "hello";
b = "world";
Test();
functionTest() {
alert(a + " "+ b);
a = "welcome";
b = "china";
alert(a + " "+ b);
}
alert(a + " "+ b);



首先,我们将方法体内的var a改成a,我们先不看答案,直接来分析,首先,在预编译阶段,方法体内几乎没有任何改变,因此此时a和b一样,都要去他们的父作用域中去寻找,因此第一次出的结果应该是hello world,第二次没什么说的:welcome china,第三次由于a和b在本作用域内都没有事先定义,因此都是再改变父作用域内的值,因此应该输出welcome china.
我们继续:

vara = "hello";
b = "world";
Test();
functionTest() {
alert(a + " "+ b);
vara = "welcome";
varb = "china";
alert(a + " "+ b);
}
alert(a + " "+ b);



和上面的分析一样,应该输出undefined undefined,welcome china,hello world.
继续:

a = "hello";
b = "world";
Test();
functionTest() {
alert(a + " "+ b);
vara = "welcome";
b = "china";
alert(a + " "+ b);
}
alert(a + " "+ b);



应该是undefined world,welcome china,hello china.
经试验,都没问题, 不知道你明白了么?
因此我们可以得出,每个变量在找不到自己的定义时,都会沿着作用链向上寻找,这样就很可能会出现未预知的错误,给排错添加了很多困难。更麻烦的是,还可能会对父作用域上的变量值进行修改,因此我们在声明变量时应该尽量加上var,尽管Javascript并不强迫我们这样做。

Javascript学习笔记4——Eval函数
在初学JS的时候就知道这个函数,却一直没有了解过他的用途,也一直都是睁一只眼闭一只眼,这次来深入地了解一下这个函数的作用。
eval的作用其实很简单,就是把一段字符串传递给JS解释器,由Javascript解释器将这段字符串解释成Javascript代码,并且执行他。
举个最简单的例子:

eval("alert(1+1)");



很简单,把字符串解释成JS代码并执行,弹出2。
当然,上面的例子只是个玩具,在实际中没有人会傻到这么用。我想大家最基本的使用eval函数都是应该在DOM中,例如我们有div1,div2,div3,那么在document.getElementByID时我们的ID没有办法去得到,那么最简单的办法就是在for循环中,使用eval来拼接这么一段程序。例如这样:

for(varloop = 1; loop < 10; loop++) {
eval('document.getElementById("div"+loop).innerHTML="123"');
}



最基本的用法说完,相信大家还是对这个函数意犹未尽,如果这个函数只有这么点用法,那就太无聊了。那我们就一点点来剖下一下eval()函数。
我们就先从eval的作用域说起,先看这样一段函数:

eval("var i=3");
alert(i);



代码很简单,结果可以弹出3。接下来再对比这段代码:

vartest = function() {
eval("var i=3");
alert(i);
}
test();
alert(i);



结果是首先弹出3,然后是undefined。
那么说明:eval()函数动态执行的代码并不会创建新的作用域,其代码就是在当前的作用域执行的。因此也就是说,eval()函数也完全可以使用当前作用域的this,argument等对象。
在IE中,支持这样一种和eval()非常类似的函数叫做:execScript()。我们可以来写段简单的代码。

vartest = function() {
execScript("var i=3");
alert(i);
}
test();
alert(i);



结果弹出了2个3,这也就看出了execScript函数的特点,首先他和eval相类似,都能将字符串解释成JS代码并且执行,但是他的作用域不是当前作用域,而是全局作用域。当我们把上面的代码放到Firefox和谷歌浏览器上去试试:发现在Firefox上execScript上代码是无效的,那么也说明一个问题,execScript代码的浏览器兼容性是有问题的。
那么就引申出这样一个问题,我们如何能把这两个函数的“优点”给汇总到一起呢,也就是说,全局+浏览器兼容性。上网搜了下,自己给汇总了一下,大概是这样:

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);



这样就可以完美地兼容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感觉厌烦了呢?那就让我们采取个很另类的办法吧,让至少在感官上可能会舒服一点。

varfuntemp = function() {
c = a + b;
}
varobj = {
a: 1,
b: 2,
c: 5
};
varfun;
with(obj) {
eval("fun = "+ funtemp);
}
fun();
alert(obj.c);



这个很勉强,那么好,我们不讨论什么看着舒服不舒服。我们来讨论这样一种情况。



我们又是否可以把这个看成是一个简陋的模板方法模式呢?呵呵。我们也可以把这个称为利用eval和with配合改变函数的上下文。
不过话又说回来,Eval在一般的情况中是很少被用到的,我们是完全可以避免来使用它的。


Javascript学习笔记5——类和对象
首先,不得不说,我无法达到抛开类和对象的概念来看Javascript的境界,对于Javascript是否是面向对象的说法有很多,不过我最认同的还是Javascript是一种“基于prototype的面向对象语言”。
面向对象语言三大特点:继承,多态,封装,这三点虽然Javascript没有提供天然的语法实现,但是我们都可以通过prototype等技巧来实现,因此这种说法似乎不过分。
在Javascript中,构造对象有三种方式:
1. 首先,我们要明确一个概念,Javascript是一种弱类型的语言,一方面体现在Javascript的变量,返回类型都是没有强类型约束的,另一方面,Javascript可以为对象任意添加属性和方法。根据这个,我们可以写出这样的代码:

varperson = {};
person.name = "飞林沙";
person.age = 21;
person.Introduce = function() {
alert("My name is "+ this.name + ".I'm "+ this.age);
};

person.Introduce();



这里的person就是我们构造出的一个对象。
2. 我们也可以利用JSON的形式来构造一个对象。

varperson = {
name: "飞林沙",
age: 21,
Introduce: function() { alert("My name is "+ this.name + ".I'm "+ this.age); }
};
person.Introduce();



这个是不是很像我们在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#不可以。
但是上面两种方法我们看到,我们都是单独定义了一个对象。接下来让我们把他们抽象出来成为一个类。

varPerson = function() {
this.name = "飞林沙";
this.age = 21;
this.Introduce = function() {
alert("My name is "+ this.name + ".I'm "+ this.age);
};
};
varperson = newPerson();
person.Introduce();



可是在这里,我们看到,属性都已经被写死了,我们根本没办法为每个对象单独订制,解决办法很简单:

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();



好,我们来对比一下第二种和第三种写法,两者是等效的。在第二种写法中,实际上是构建了一个JSON对象,而我们又知道JSON本质上其实就是一个键值对,那么我们是否也可以用同样的方式来理解一个对象呢?
我们来写出这样的测试代码试试:

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"]);



这样的代码没偶任何问题,首先用遍历的方式来找到person所有的key(属性和方法名)。然后我们用索引的方式来访问person对象的name属性。
这些都没有问题,可是我们是不是看到了一个引申的问题,从传统面向对象的语言来看,name和age应该属于私有变量,那么这样用person简简单单的访问,是不是破坏了封装性呢?
还记得我们在前文中说过的么?var的叫变量,没有var的叫属性。那么我们如果讲代码改成这个样子。

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());



这样就可以封装得很好了,这也是在Javascript中的封装方式。
好,关于Javascript的类和对象就说到这,但是这里面仍然有一些问题。我们会在下文中提及。

Javascript学习笔记6——prototype的提出
首先我们继续上文的代码,我们来把这段代码延伸一下:

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);



结果弹出false。也就是说,这两个对象的方法是不同的方法。那么我们知道,在C#中,每个对象会维护着一个方法表,可是方法表应该指向同一块地址。如果是这样的话,那当我们声明了100个对象,是不是要建立100个对象拷贝,对空间是不是一个很大的浪费呢?
于是我们就想了这样的解决办法,用prototype:

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);



这样就可以了。所以你还会再说是否用prototype都是一样的么?其实我以前也是这么理解的,在这次偶然的试验中看到了这个问题。

Javascript学习笔记7——原型链的原理
说到prototype,就不得不先说下new的过程。
我们先看看这样一段代码:

varPerson = function() { };
varp = newPerson();



很简单的一段代码,我们来看看这个new究竟做了什么?我们可以把new的过程拆分成以下三步:
<1> var p={}; 也就是说,初始化一个对象p。
<2> p.__proto__=Person.prototype;
<3> Person.call(p);也就是说构造p,也可以称之为初始化p。
关键在于第二步,我们来证明一下:

varPerson = function() { };
varp = newPerson();
alert(p.__proto__ === Person.prototype);



这段代码会返回true。说明我们步骤2的正确。
那么__proto__是什么?我们在这里简单地说下。每个对象都会在其内部初始化一个属性,就是__proto__,当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去__proto__里找这个属性,这个__proto__又会有自己的__proto__,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
按照标准,__proto__是不对外公开的,也就是说是个私有属性,但是Firefox的引擎将他暴露了出来成为了一个共有的属性,我们可以对外访问和设置。
好,概念说清了,让我们看一下下面这些代码:

varPerson = function() { };
Person.prototype.Say = function() {
alert("Person say");
}
varp = newPerson();
p.Say();



这段代码很简单,相信每个人都这样写过,那就让我们看下为什么p可以访问Person的Say。
首先var p=new Person();可以得出p.__proto__=Person.prototype。那么当我们调用p.Say()时,首先p中没有Say这个属性,于是,他就需要到他的__proto__中去找,也就是Person.prototype,而我们在上面定义了Person.prototype.Say=function(){}; 于是,就找到了这个方法。
好,接下来,让我们看个更复杂的。

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);



我们来做这样的推导:
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去构造一个对象。代码如下:

varPeople = {
name: "kym",
age: 21,
SayHello: function() {
alert("Hello,My name is "+ this.name + ".I am "+ this.age);
}
}
alert(People.name);
People.SayHello();



但是我们是不能重用这个对象的,我们如何把这个对象作为原型的呢?
首先,在一个JSON对象有一个构造方法是不可能的了,那么我们就做一个简单的“工厂”吧,写一个方法来专门负责创建。

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();



但是通过这个方法我们却发现,我们没有办法用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。这样的关系是这样:

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();



这样就相当于用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来改善这个方法:

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();



这样,一个完整的创建类就诞生了!那么我们每次创建“类”时就都可以用JSON来做了,然后用户每次都统一来调用Factory.Create()就可以了!
Javascript学习笔记9——prototype封装继承
在上文中,我利用prototype的原理做了一个封装的New,然后我就想到,我是否可以用prototype的原理进一步封装面向对象的一些基本特征呢?比如继承。
好,那就让我们一步步打造,首先让我们来看下继承原本的写法:



我们看到,在实际上,继承的根本就在于这一步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即可,也就是说我们将子原型进行一个封装。
好,就按照这个思路,我们来实现利用原型链的继承关系的封装。



这样就完成了我们对继承关系的封装。当然,我们也可以不单独写一个变量:
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下。网页就是在这个加载的过程中,一边加载一边呈现,所以我们当网速非常慢的时候可以看到,网页从上到下一点点地打开。
当我们用


至于逻辑就是:
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。写个简单的例子:







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);





而在另一个页面写下当前时间,这样就形成了一个钟表。
代码很简单,就是操纵一个XMLHttpRequest对象来获取服务器时间,然后更新时间。上面的代码在与服务器交互时,并没有页面整体刷新,而是局部刷新。
但是上面的代码在request.open时,最后一个参数为false,表示发出的XMLHttpRequest是同步的,由于Javascript是单线程的,所以在等待请求的过程中,线程会被阻塞,如果请求时间过长,浏览器会停止响应。
虽然Javascript是单线程的,但是XMLHttpRequest具备异步处理请求的能力。代码如下:







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);





哎,没有智能提示的日子真不好过。
Javascript学习笔记13——关于响应事件
具体的事情记不清了,某一天下班后听到两个同事在说关于关于事件的问题。
这里简单说下:


很普通的一个按钮,我们要为其添加点击事件,有两种方法:
A.


functionAlert() {
alert("Click Me");
}



B.


document.getElementById("ButtonTest").onclick = function() {
alert("Click Me");
}



我们称A方法为静态绑定,B方法为动态绑定。我们来看A方法,他近乎可等于这样的效果:


document.getElementById("ButtonTest").onclick = function() {
Alert();
}



也就是说,当我们静态绑定一个事件的时候,实际上是系统默认为我们做了一个匿名函数,然后把我们的方法体包于其中。由于这样,就涉及到了this的问题。
我们来看这样一段代码,这也是我在公司初学JS的时候,公司的JS小牛抛给我的问题。



functionAlert1() {
alert(this.value);
}
functionAlert2(obj) {
alert(obj.value);
}



当试验这样一段代码时,便会发现第一个按钮会弹出”undefined”,第二个按钮弹出”Test2”。原因就是如我上面所说,在按钮一中,方法等于声明了一个匿名函数,然后讲Alert1()闭包于其中,也就是说,Alert1()是无法找到其调用者的,所以这时,他会去找window对象的value属性,结果发现未定义,我们可以这样证明:



value = "window";
functionAlert1() {
alert(this.value);
}
functionAlert2(obj) {
alert(obj.value);
}



Javascript学习笔记(14)
1. Javascript的数值类型
《Javascript语言精粹》中有这样一句话:Javascript只有单一的数字类型。
在我看来,这句话说的并不准确,应该说,Javascript在声明时,只有单一的数据类型。或者说,Javascript的所有数值类型都被存储成同一种格式,就是64位的浮点数类型。
这样说比较准确。

vari = 1.2;
alert(typeof(i));



这样无论把i换成整数还是浮点数,都会得到Number的结果。
但是我说这样不准确的意思是说,在Javascript还提供了一些方法来作为数值之间的类型转换。比如:

vari = 1.2;
alert(Math.floor(i));
alert(parseInt(i));



2. Javascript没有连接器
在我之前的C语言文章中,有一节着重介绍了连接器的作用,但是在Javascript中,是没有连接器的,那么多个编译单元之间如何组合到一起呢?答案就是Javascript只是“粗鲁“地将他们抛入到同一个全局的命名空间当中。
3. Javascript的默认值技巧
其实这只是一个小技巧,相信大部分人都用过,看个简单的小例子:

varperson = { name: "kym", age: 21 };
alert(person.love || "reading");



这就是对person.love赋默认值的方法。
4. Javascript判断自身属性
我们知道对象其实本质上来说就是一个键值对,那么我们就可以用 for来遍历 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);
}



但是我们可以发现,其实他得到的不只只是他自身的属性,还有原型链上的属性,那么我们就要用hasownproperty来得到:

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);
}
}




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)