重拾web开发-JavaScript复习
以下内容都是本人之前学习的一些笔记很多是摘自网上并非完全原创,特此申明。
最简单的一句js:
<html> <body> <script type="text/javascript"> document.write("Hello World!"); </script> </body> </html>
防止不支持js的浏览器将js脚本作为页面内容显示出来可以这样:
<html> <body> <script type="text/javascript"> <!-- document.write("Hello World!"); //--> </script> </body> </html>
在哪里放置js
页面中的脚本会在页面载入浏览器后立即执行。我们并不总希望这样。有时,我们希望当页面载入时执行脚本,而另外的时候,我们则希望当用户触发事件时才执行脚本。
- head部分:包含函数的脚本位于文档的 head 部分。这样我们就可以确保在调用函数前,脚本已经载入了。
<html> <head> <script type="text/javascript"> .... </script> </head> ....
- body部分:在页面载入时脚本就会被执行。当你把脚本放置于 body 部分后,它就会生成页面的内容。
<html> <head> </head> <body> <script type="text/javascript"> .... </script> </body> </html>
- 在body和head部分的脚本:你可以在文档中放置任何数量的脚本,因此你既可以把脚本放置到 body,又可以放置到 head 部分。
<html> <head> <script type="text/javascript"> .... </script> </head> <body> <script type="text/javascript"> .... </script> </body> </html>
- 使用外部javascript:
<html> <head> <script src="xxx.js">....</script> </head> <body> </body> </html>
注意:外部文件不能包含 <script> 标签。
数据类型
在ECMAScript中和C#类似变了可以存放两种类型的值,原始值和引用值。原始值指的就是代表原始数据类型(基本数据类型)的值,即Undefined,Null,Number,String,Boolean类型所表示的值。引用值指的就是复合数据类型的值,即Object,Function,Array,以及自定义对象,等等。因为JavaScript其实并不是纯粹的面向对象的语言,关于数据类型的问题参看这:http://blog.csdn.net/aimingoo/article/details/6634977/。在javascript中所有的基本数据类型都有一个与其对应的引用类型,number Number,string String,boolean Boolean...,他们具有完全相同的行为,并且相互之间会产生自动拆箱与装箱的操作(这个概念和java以及c#中的概念是一样的)。之所以有栈和堆是因为考虑效率和内存的结果。
两套类型系统
- 基础类型系统
typeof识别的只有6种类型:undefined、number、boolean、string、object与function。我一般也称之为基础类型系统。之所以称为“基础”,是因为第二套类型系统是以它为基础。(为什么没有null?)
- 对象类型系统
instanceof来识别。
对象类型系统与基础类型系统存在映射关系,例如基础类型的string影射到对象系统中的String。但这只是影射,所以本质上来说string类型不是String类型。在JavaScript中这两种类型系统可以混用的原因是因为装箱和拆箱的存在:'abc'.length实际上等效于Object('abc').length。
<script type="text/javascript"> var str = new String("test"); alert("test" instanceof String);//false alert(str instanceof String);//true </script>
感觉与基本数据类型对应的对象类型(包装对象)非常鸡肋啊,非常容易混淆(后面会推荐使用litera syntax申明变量)。关于JavaScript中的更多的设计缺陷看这里。
typeof
下面的结果都是什么?
typeof(1); typeof(NaN); typeof(Number.MIN_VALUE); typeof(Infinity); typeof("123"); typeof(true); typeof(window); typeof(document); typeof(null); typeof(eval); typeof(Date); typeof(sss); typeof(undefined);
- 对于数字类型的操作数而言, typeof 返回的值是 number。比如说:typeof(1),返回的值就是number。
- 对于字符串类型, typeof 返回的值是 string。比如typeof("123")返回的值是string。
- 对于布尔类型, typeof 返回的值是 boolean 。比如typeof(true)返回的值是boolean。
- 对于对象、数组、null 返回的值是 object 。比如typeof(window),typeof(document),typeof(null)返回的值都是object。
- 对于函数类型,返回的值是 function。比如:typeof(eval),typeof(Date)返回的值都是function。
- 如果运算数是没有定义的(比如说不存在的变量、函数或者undefined),将返回undefined。比如:typeof(sss)、typeof(undefined)都返回undefined。
注意:关于typeof(null)为什么返回"object"而不是"null",看这里(一个历史遗留问题)。
类型检查
当变量不是object和array类型的时候typeof很有用,但对于自定义对象就不能用这个方法进行检查了,因为它只返回object,很难跟其他的object区分开来,第二种检查对象类型的方法需要引用所有JavaScript对象都带有的一个属性,称为构造函数(constructor)。这个属性引用的是原本用来构造该对象的那个函数:
<html> <head> </head> <body> <script type="text/javascript"> function User(name, age) { this.name = name; this.age = age; } var myUser = new User("zjd", 16); alert(typeof (myUser)); //object alert(myUser.constructor == User); //true </script> </body> </html>
通过typeof和构造函数检查对象类型参看下表:
变量 | typeof变量 | 变量.构造函数 |
{an:"object"} | object | Object |
["an","arrary"] | object | Arrary |
function(){} | function | Function |
"a string" | string | String |
55 | number | Number |
true | boolean | Boolean |
new User() | object | User |
几种特殊的数据类型和值
- undefined和null
其实在 ECMAScript 的原始类型中,是有Undefined 和 Null 类型的。 这两种类型都分别对应了属于自己的唯一专用值,即undefined 和 null。 值 undefined 实际上是从值 null 派生来的,因此 ECMAScript 把它们定义为相等的(值相等):
alert(undefined == null); //true
从内存上很容易理解,”==“判断的是栈中变量存放的值是否相等;
但不是绝对相等的(值和类型都相等):
alert(undefined === null); //false
因为他们的类型分别为Udefined和Null,Udefined代表没有赋值的基本数据类型,Null代表没有赋值的引用数据类型。
null 参与数值运算时其值会自动转换为 0。
typeof null 返回object,因为null代表是无值的引用。
- NaN
Number类型中有一个特殊的数值NaN(非数值 Not a Number)。这个数值用于表示一个本来要返回数值的操作数未返回数值的情况(这样就不会抛出错误了)。例如,在其他编程语言中,任何数值除以0都会导致错误,从而停止代码执行。但在JavaScript中,任何数值除以0会返回NaN,因此不会影响其他代码的执行。NaN本身有两个非同寻常的特点。首先,任何涉及NaN的操作(例如NaN/10)都会返回NaN,其次,NaN与任何值都不相等,包括NaN本身。例如,下面的代码会返回false。
alert(NaN == NaN);//false
栈和堆
与基本数据类型和引用类型相对应的数据结构是栈和堆,这个栈和堆从抽象层面来看和C#中的栈和堆是类似的。基本数据类型的值存放在栈中,引用类型的变量存放在栈中,引用对象本身存放在堆中,栈中存放的是指向堆中对象的指针。
function Person(id,name,age){ this.id = id; this.name = name; this.age = age; } var num = 10; var bol = true; var str = "abc"; var obj = new Object(); var arr = ['a','b','c']; var person = new Person(100,"笨蛋的座右铭",25);
变量的定义
js是弱类型的语言使用var关键字申明变量:
var x; var carname;
以上申明变量后变量并没有赋值,这时候输出为undefined
变量必须先声明后引用。
alert(myStr); // 弹出"undefined"; var myStr = "Hello World!"; alert(myStr); // 弹出"Hello World";
Literal Syntax
使用var num1=123;这样的代码,直接返回基本数据类型,就是说返回的对象不是派生自Number和Object类型,用num1 instanceof Object测试为false;使用new关键字创建则返回Number类型,例如var num2=new Number(123); num2 instanceof Number为true。将Number当作函数调用,返回结果会转换成简单数值类型:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4 var num1 = new Number(123); //num1 derived from Number & Object num1 instanceof Number //result: true num1 instanceof Object //result: true //convert the num1 from Number type to primitive type, so it's no longer an instance of Number or Object num1 = Number(num1); num1 instanceof Number //result: false num1 instanceof Object //result: false var num2 = 123; //num2 is a primitive type num2 instanceof Number //result: false num2 instanceof Object //result: false
Number: var i = 100; //替代var i = new Number(100); Boolean: var b = true; //替代var b = new Boolean(true); String: var str = 'this is a string.'; //替代var str = new String('this is a string');
这种类似于var i = 100;var b=true;var str='this is a string'这种定义方式就叫做Literal Syntax,这种方式更加简单和高效(基本类型和其对应的包装类型之间的转换类似C#中的装箱拆箱操作)。除了简单的数据类型复合数据类型也一样可以Literal Syntax申明:
//对象定义的字面量表示法 var obj = {name:'test',age:25} /* //对象的非字面量表示法 var obj = new Object(); obj.name = '笨蛋的座右铭'; obj.age = 25; */ //数组定义的字面量表示法 var arr = ['test',25]; /* //数组的非字面量表示法 var arr = new Array(); arr[0]='笨蛋的座右铭']; arr[1]=25; */ //正则表达式字面量表式法 var reg = /\d+/; /* //正则表达式非字面量表式法 var reg = new RegExp("\d+"); */
一个例子
var five = 5; five.three = 3; alert(five + five.three);
结果为NaN,为什么?
第一行代码实际创建的是原始值类型number,而第二行实际上产生了一个临时的Object对象(类似C#中装箱操作)并且依赖于Number构造函数,该构造函数会产生一个对象,而不是一个原始值.第二行代码实际上等同于下面的代码:
(new Number(five)).three=3;
这里并没有将新生成的Number对象的引用保存到一个变量中.在该表达式运行过后,被添加three属性的这个对象会被丢弃.而five变量的值没有任何变化。第三行代码中的表达式five.three会再一次创建一个Number对象.这个新的对象并没有three属性,所以返回了特殊值undefined.结果等同于:
alert(5+undefined);
加法运算符会把两边的操作数全部转换为数字.在本例中,undefined会被转换为NaN,也就成了:
alert(5+NaN);
所以最后结果为NaN。这里还涉及到JavaScript中的隐士转换看这里。
js的执行顺序
函数的定义
上面数据类型中提到typeof识别的6种基础类型中有一个是function,但严格的来说函数在ECMAScript中是对象,每个函数都是Function这个类的一个实例,既然函数是一个对象,那么它也就是一个引用类型了,所以一个函数只是一个变量名而已,因此,在很多场合常常可以看到,将函数名作为参数,传进函数中,然后进行调用,这就类似于C#中的委托。我们平时理解函数名的时候将它理解为'指向函数的指针"。函数的定义已经是上面提到的Literal Syntax的表示形式了。主要有两种方式:
//“定义式”函数定义 function Fn1(){ alert("Hello World!"); } //“赋值式”函数定义 var Fn2 = function(){ alert("Hello wild!"); }
页面加载过程中,浏览器会对页面上或载入的每个js代码块(或文件)进行扫描,如果遇到定义式函数,则进行预处理(类似于C等的编译),处理完成之后再开始由上至下执行;遇到赋值式函数,则只是将函数赋给一个变量,不进行预处理,待调用到的时候才进行处理。下面举个简单的例子:
//“定义式”函数定义 Fn1(); function Fn1(){ alert("Hello World!"); }
正常执行,弹出“Hello World!”,浏览器对Fn1进行了预处理,再从Fn1();开始执行。
//“赋值式”函数定义 Fn2(); var Fn2 = function(){ alert("Hello wild!"); }
Firebug报错:Fn2 is not a function,浏览器未对Fn2进行预处理,依序执行,所以报错Fn2未定义。
浏览器对每个块或文件进行独立的扫描,然后对全局的代码进行顺序执行。所以,在一个块(文件)中,函数可以在调用之后进行“定义式”定义;但在两个块中,定义函数所在的块必须在函数被调用的块之前。
<script type="text/javascript"> Fn(); </script> <script type="text/javascript"> function Fn(){ alert("Hello World!"); } </script> // 报错:Fn is notdefined,两个块换过来就对了
重复定义函数会覆盖前面的定义。
body的onload函数与body内部函数的执行:
//html head... <script type="text/javascript"> function fnOnLoad(){ alert("I am outside the Wall!"); } </script> <body onload="fnOnLoad();"> <script type="text/javascript"> alert("I am inside the Wall.."); </script> </body> //先弹出“I am inside the Wall..”; //后弹出“I am outside the Wall!”
body的onload事件触发条件是body内容加载完成,而body中的js代码会在这一事件触发之前运行。
DOM和BOM
JavaScript、ECMAScript、DOM以及BOM的关系:
核心(ECMAScript):定义了脚本语言的所有对象,属性和方法。文档对象模型(DOM):HTML和XML应用程序接口。浏览器对象模型(BOM):对浏览器窗口进行访问操作。
DOM分为针对于XML的DOM即DOM Core和针对HTML的DOM HTML:
<div id="container"> <span>hello world</span> </div>
注意:div和span之间的间隔(换行)也被认为是文本节点。还有就是文本节点和文本的区别,取到文本节点在取它的innertext才能获取到文本。
JavaScript利用DOM和html元素交互的例子:
<html> <head> <title>DOM Test</title> <script type="text/javascript"> window.onload = function () { var li = document.getElementsByTagName("li"); for (var i = 0; i < li.length; i++) { li[i].style.border = "1px solid #000"; } var every = document.getElementById("everywhere"); every.parentNode.removeChild(every); }; </script> </head> <body> <h1> DOM Test</h1> <p class="test"> DOM Test</p> <ul> <li id="everywhere">It can be found everywhere.</li> <li class="test">It's easy to use.</li> <li class="test">It can help you to find what you want ,realy quickly.</li> </ul> </body> </html>
BOM:
BOM与浏览器紧密结合,这些对象也被称为是宿主对象,即由环境提供的对象。这里要强调一个奇怪的对象Global对象,它代表一个全局对象,Javascript是不允许存在独立的函数,变量和常量,如果没有额外的定义,他们都作为Global对象的属性或方法来看待.像parseInt(),isNaN(),isFinite()等等都作为Global对象的方法来看待,像Nan,Infinity等"常量"也是Global对象的属性。像Boolean,String,Number,RegExp等内置的全局对象的构造函数也是Global对象的属性.但是Global对象实际上并不存在,也就是说你用Global.NaN访问NaN将会报错。实际上它是由window来充当这个角色,并且这个过程是在javascript首次加载时进行的。
面向对象
一个简单的例子:
<html> <head> </head> <body onload="fnOnLoad();"> <script type="text/javascript"> function Lecture(name,teacher) { this.name = name; this.teacher = teacher; } Lecture.prototype.display = function () { return this.teacher + " is teaching " + this.name; }; function Schedule(lectures) { this.lectures = lectures; } Schedule.prototype.display = function () { var str = ""; for (var i = 0; i < this.lectures.length; i++) { str += this.lectures[i].display() + " "; } return str; }; var mySchedule = new Schedule([ new Lecture("zhangsan", "zjd"), new Lecture("lsis", "zjd2"), new Lecture("wangwu", "zjd3") ]); alert(mySchedule.display()); </script> </body> </html>
浏览器输出:
一些概念
引用(reference)
”引用“指向对象实际位置的指针。但实际对象肯定不会是引用,也就是引用不能指向引用。
属性(property)
一个对象可以包含一系列属性,属性就是指向其他对象的引用。
函数重载(overload)
函数重载依赖两件事:判断传入参数的数量和判断传入参数的类型。JavaScript每个函数都有一个仅在函数范围内有作用的变量,称为参数(argument),它是包含所有传入的参数的伪数组(pseduo-arrary),只能访问不能修改该数组,它具有属性length可以用来校验传入参数的个数。一个函数重载的例子:
<html> <head> </head> <body> <script type="text/javascript"> function sendMessage(msg, obj) { if (arguments.length == 2) { obj.handleMsg(msg); } else { alert(msg); } } sendMessage("Hello World!"); sendMessage("How are you!", { handleMsg: function (msg) { alert("This is a custom message:" + msg); } }); </script> </body> </html>
作用域(scope)
在JavaScript中作用域是以函数划分的,而不是由块(block)划分(比如while,if,for语句中间)。JavaScript中全局变量的在 全局作用域里,其实所有的全局变量都是window对象的属性:
<script type="text/javascript"> var test = "test"; alert(window.test === test)//true </script>
和作用有关的一个非常难理解的概念是闭包。
闭包( closure)
闭包的概念在很多语言中都有(包括C#,关于C#中的闭包可以看这里),先看下我认为的一个最权威的解释:
在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
从栈的角度理解的:
闭包函数返回时,该函数内部变量处于激活状态,函数所在栈区依然保留。
结合JavaScript的理解:
闭包意味着内层的函数可以引用存在于包围它的函数内的变量,即使外层函数执行已经终止。
网上还有一个人的说法我认为很有深度,角度也很独到:
(1,5) 是一个区间,但对这个区间做分析、计算什么的,经常会用到1和5这两个不属于这个区间的值,[1,5]就是(1,5)的闭包。
在生活上,我们办事情,找A部门,A部门说,你先得找B部门盖个章,B部门说,你先得找C部门盖个章,C部门说,这个东西不是我们的职权范围…… 踢皮球,这就是非闭包。闭包就是负责到底,你找到A部门,A部门接待的那个人负责到底,他/她去协调B部门和C部门。
在工程上,闭包就是项目经理,负责调度项目所需要的资源。老板、客户有什么事情,直接找项目经理即可,不用再去找其它的人。
在程序语言中,闭包就是一种语法糖,它以很自然的形式,把我们的目的和我们的目的所涉及的资源全给自动打包在一起,以某种自然、尽量不让人误解的方式让人来使用。至于其具体实现,我个人意见,在不影响使用的情况下,不求甚解即可。在很多情况下,需要在一段代码里去访问外部的局部变量,不提供这种语法糖,需要写非常多的代码,有了闭包这个语法糖,就不用写这么多代码,自然而然的就用了。……”
以及某个大牛关于理解闭包的忠告:
(1)闭包是一种设计原则,它通过分析上下文,来简化用户的调用,让用户在不知晓的情况下,达到他的目的;
(2)网上主流的对闭包剖析的文章实际上是和闭包原则反向而驰的,如果需要知道闭包细节才能用好的话,这个闭包是设计失败的;
(3)尽量少学习。
个人认为理解闭包的一个前提是弄清楚作用域,因为不同的语言中作用域的定义是不一样的,否则很容易混淆。比理解闭包是什么更重要的是理解闭包能做什么和闭包可能带来的问题:
使代码更清晰:
<script type="text/javascript"> setTimeout(function () { alert("setTimeout"); }, 1000); //利用闭包封装setTimeout function delayedAlter(msg, time) { setTimeout(function () { alert(msg); }, time); } delayedAlter("delayedAlter", 5000); </script>
函数生成器:
<script type="text/javascript"> function addGenerator(num) { return function (toAdd) { return num + toAdd; }; } var addFive = addGenerator(5); alert(addFive(10));//15,5已经脱离了它的作用域,但是这里仍然能引用它。 </script>
避免产生大量多余的全局变量,使用匿名函数来隐藏全局作用域变量:
<script type="text/javascript"> (function () { //变量本应该是全局的 var msg = "Thank you for visiting!"; //将一个新函数绑定到全局变量 window.onload = function () { //这个函数使用了隐藏的msg变量 alert(msg); }; })()//关闭匿名函数并执行 </script>
使用闭包会遇到一个问题,闭包允许你使用父函数中的变量,但提供的值并非该变量创建时的值,而是父函数范围内的最终值。你会看到这样带来的最常见的问题是在for循环中,有一个变量做为循环计数(比如i,记住JavaScript中变量的作用域是以函数划分的),在这个循环里创建新的函数,利用闭包来引用循环计数器。问题时在这个新的闭包函数被调用的时,它的引用计数器的值是最后一次赋值,而不是你期望的那个值。
下面的一个例子是使用匿名函数来激发出创建多个使用闭包的函数所需的作用域:
<html> <head> </head> <body> <div id="test"> 这里是内容。 </div> <script type="text/javascript"> var obj = document.getElementById("test"); var items = ["click", "mouseover"]; for (var i = 0; i < items.length; i++) { (function () { var item = items[i]; obj["on" + item] = function () { alert("Thank for your " + item); }; })(); } </script> </body> </html>
关于这个例子可以理解因为创建了多个匿名函数所以将每次循环的循环变量和多个函数作用域绑定到了一起,所以不会出现上面提到的问题,也就是”激发“
上下文对象(context)
在JavaScript中所有代码都是在一个上下文对象中,即使是全局变量(上面说过全局变量都是window对象的属性),上下文对象是通过this变量体现的。从功能和作用上就理解成C#等面向对象语言中的this或myself就可以了,只是在JavaScript中实现的更为复杂灵活。
<script type="text/javascript"> var obj = { yes: function () { this.val = true; }, no: function () { this.val = false; } }; //我们发现‘obj’对象没有val属性 alert(obj.val == null); //true //执行了yes后,将val属性与‘obj’关联起来了 obj.yes(); alert(obj.val == true); //true //不过现在把window.no指向obj.no并执行 window.no = obj.no; window.no(); //结果是obj的val属性并没变 alert(obj.val == true); //true //而window的属性val跟性了 alert(window.val == false);//true </script>
上面的例子展示了JavaScript中上下文的自动切换,但使得代码不太好理解了,因此有了call和apply两个方法:
<script type="text/javascript"> //一个设置上下文对象颜色的简单函数 function changeColor(color) { this.style.color = color; } //在window中调用此函数会失败因为window没有style属性 changeColor("white"); var main = document.getElementById("main"); //使用call方法将它的颜色设置为黑色。call方法将上下文对象设置为第一个参数,并将其他参数作为原函数的参数。 changeColor.call(main, "red"); //设置body元素颜色的函数 function setBodyColor() { //apply方法将上下文对象设置为第一个参数指定的body元素第二个参数是传给函数的所有参数的数组。 changeColor.apply(document.body, arguments); } setBodyColor("black"); </script>
关于call和apply让我想到了其实C#中实例方法的调用默认的第一个实参就是this。
对象的创建
事件
事件(event)是黏合应用程序中所有用户交互的”胶水“。在设计良好的JavaScript的应用程序中,既有数据来源,又有这些数据来源的视觉表现(在HTML DOM中表现),要在这两个方面之间进行同步就必须通过与用户交互,并据此来更新用户界面。DOM和JavaScript事件组合,是决定现代Web应用程序形态的根本所在。
利用DOM和事件进行交互的例子:
<html> <head> <title>Event Test</title> <script type="text/javascript"> window.onload = function () { var li = document.getElementsByTagName("li"); for (var i = 0; i < li.length; i++) { li[i].onmouseover = function () { this.style.backgroundColor = 'blue'; }; li[i].onmouseout = function () { this.style.backgroundColor = 'white'; } } }; </script> </head> <body> <h1> Event Test</h1> <p class="test"> Event Test</p> <ul> <li id="everywhere">It can be found everywhere.</li> <li class="test">It's easy to use.</li> <li class="test">It can help you to find what you want ,realy quickly.</li> </ul> </body> </html>
JavaScript中的线程
……………………
出处:http://www.cnblogs.com/zhanjindong
个人博客:http://zhanjindong.com
关于:一个程序员而已
说明:目前的技术水平有限,博客定位于学习心得和总结。