JavaScript语言特性:
语法:很多地方类似于C语言。如if,else,for,while等和c语言一致。不涉及面向对象的部分还是和C语言有不少共同点的。
优势:HTML标准的官方语言。做前端开发的唯一选择。后端开发的选择之一。最基本的面向对象也是最彻底的面向对象。下面从几个小的方面展示一些javascript的特性。
特性1:所有事物都是对象。字符串,数值,数组,函数。
让我们以一份简单json数据为例。
var person=
{
firstname: "Bill",
lastname: "Gates",
id: 5566,
… //其他属性
createPat: function () {
this.pat = { name:"mimi", type: "cat", id: 1111 };
}
…//其他方法
};
//json数据格式
从不同的角度看待:
数据:这个person从数据的角度,可以认为是一份数据。保存了一个人的个人信息。可以用person["id"]访问数据的相关value。在调用person.createPat之前,person数据中并不保存pat即宠物的数据。调用person.createPat之后,可以用person["pat"]访问person的宠物数据(宠物数据也嵌套包含名字id等等)。从这个角度可以看出来json数据定义很灵活,支持的类型也非常多。
对象:person也就是一个人,它具有三个属性,firstname,lastname和id。可以用person.id访问对象属性。使用person.createPat以后,还可以给person增加一个pat的新属性。这是完全面向对象的。从这里可以看出,这一段数据可以被作为普通数据使用。也可以作为一个对象来管理。这就是比较灵活的地方。
基类对象以及基类方法:通过这个简单的person对象,可以继续构造它的子对象。如:
var a =Object.create(person);
a.area ="Asia";
var b =Object.create(person);
b.area ="Africa";
构建了一个a对象,地区在亚洲,和一个b对象地区在非洲。他们都继承了person的其余属性。只在地区这个属性上有所区别。可以被认为是两个不同子对象。
子对象a调用createPat,可以给子对象a添加pat属性。所以createPat可以看做不同子对象都可以调用的基类公共方法。
内部类:另外一种用法如下:
var cat1 = new person.createCat(); 这样的话,createPat不但能看做一个方法,还能看成一个内部类型。通过new关键字可以生成不同的createCat实例。而person就成了多个内部类的聚合,类似于一个命名空间。需要注意的是,这里的cat1和person是一样的,都可以在键值对基础上不断扩展。Cat1也可以成为类似于person这样的内部类聚合体。这样可以不断嵌套,从而构建更加复杂的类型。
总结:上面的例子充分说明了为什么是简单彻底的面向对象。也说明了使用js语言的灵活性。使用js创建类,以及实现继承除了上面提到的,还有多种办法。就不列举了。
下面简单列举Three.js的面向对象方式:
varTHREE = { REVISION: '66' }; //类似于整个THREE的命名空间。 是多个内部类的聚合。
某一个功能模块类的实现:
THREE.CanvasRenderer= function ( parameters ) { //相当于类定义
this.autoClear= true; //类成员变量
this.setSize= function ( width, height, updateStyle ) {。。。}//类方法
}
//通过prototye 和Object.create来实现继承。
THREE.Geometry2.prototype= Object.create( THREE.BufferGeometry.prototype );
特性2:闭包。
function closure(){ //一个方法
var str = "I'm a part variable."; //方法里的局部变量。在方面外面无法访问
return function(){ //方法中的嵌套方法,根据局部变量作用域,这里可以访问str
alert(str); //输出str的值
}
}
var fObj = closure(); //返回一个function
fObj(); //调用function可以获取str的值
来自 <http://baike.baidu.com/view/1518602.htm?fr=aladdin>
上面代码演示了闭包特性。
闭包有两个特性:
1 通过闭包能获取某个方法局部变量的值。如上面执行fObj可以输出closure局部变量str的值。
2 局部变量在闭包调用中不会被释放。而是继续保存。如str变量是局部变量,一般局部变量在方法执行完以后就可以释放了。但这里str因为在内部方法使用,而内部方法被fObj引用。所以fObj导致str不会在closure执行完以后就释放。
闭包用途:
1 方法局部变量只能通过闭包方法这一种途径访问,如closure方法中的局部变量str,只能通过closure返回的fObj来访问。利用这一点保护数据安全性。类似于私有变量。全局变量的缺点在于任何地方都能访问修改不安全,同时起名字容易和别人冲突。利用闭包构建的局部变量是没有这些问题的。
2 局部变量不会被释放。利用这个特点,把方法中的某个局部变量用来保存一些执行过程中的值。如某个局部变量count,利用闭包特性使其自加,由于count不会释放,每次count都加1,可以用来计数等等。而count是局部变量,不是全局变量,全局变量可能会在某个地方被人修改,闭包使用的局部变量根据闭包用途1,只存在一个调用路径,可以方便跟踪数据。
function closure(){
var count= 0;
return function(){
count++;
return count;
}
}
3 使用闭包特性构建面向对象框架。通过下面例子可以看出来。name是一个局部变量,但是由于被getName setName所引用,所以值可以保存。并且可以通过set、get方法来改变。这样使用方式基本就和一个类一样。而且并没有使用new关键字。本质上是利用闭包 的特性把name保存到内存里。
function Person() {
var name = "default";
return {
getName: function () {
return name;
},
setName: function (newName) {
name = newName;
}
}
};
var john = new Person();
john.setName("john");
var jack = Person();
jack.setName("jack");
4 其他使用方法还非常多。上面列举的常用的。
特点3:单线程架构,异步编程:setTimeout。
javascript语言的执行环境是单线程。所以所有javascript语句都不能创建新线程。所有的代码都必须在一个线程里执行。所以对于耗时长的操作必须采用异步方案。
采用异步的几种方案:
1setTimeout
//此操作耗时 开机就执行会阻塞UI显示
functionabc()
{
var a = 1;
};
//这个方法使用setTimeout来延时执行一个操作
functiondelay(func) {
setTimeout(function () {
func();
}, 1000);
};
//对abc执行延时操作
delay(abc);
特点:
1 同一时间内只有一段javascript代码被执行。其他代码会被加入到消息队列。
当前执行的代码如果耗时,就一定会阻塞其他代码执行。因为单线程没法被抢占。
2 setTimeout相当于在x秒钟以后把回调代码加入消息队列。abc这个方法仍然耗时,但是会在前面代码执行完毕以后,即x秒钟以后才会执行。把耗时的时间延后了,可以把时间先分给UI显示等重要操作。
3 由于不会抢占。所以setTimeout定时器到了的时候,如果有其他代码(鼠标键盘事件处理?画面刷新处理?动画处理?其它定时器回调?等等)还在执行,那么定时器绑定的回调函数就被加入队列中等待。也就是不管定时几秒,都没办法保证恰好在定时到达的时间执行。
3 setTimeout外还有一个类似函数setIntervel。机制是一样的。不过这个函数是每隔多少时间就执行一次。如果执行某次发现上次还没有执行就丢弃。当然,比如间隔一秒,也是无法精确保证的。比如第一次执行时候恰好有其他代码(如鼠标事件处理)拖延了半秒,就会导致半秒延迟执行。下一次触发的时候线程空闲,没有延迟。这样这两次操作间隔了半秒。而不是设定值1秒。
4 setTimeout的回调操作如果操作时间太长也会影响体验(单线程导致回调执行时,画控制UI的javascript无法执行)。如回调1秒钟才能执行完毕,那么这一秒钟之内点鼠标,操作键盘等需要UI刷新的javascript也没法执行。所以最好把setTimeout的回调分为多个耗时少的操作。
特点4:真正意义上的工作线程的实现。
1 GoogleGears:谷歌提供的一个浏览器插件工具。可以支持多线程。需要浏览器安装插件。所以不多介绍。
2 WebWorker:HTML5提供的javascript多线程解决方案。
可以使用worker类创建一个新的工作线程。
//worker.js
onmessage =function (evt){
var d = evt.data;//通过evt.data获得发送来的数据
postMessage( d );//将获取到的数据发送会主线程
}
//------------------------------------------------------------------------
var worker =new Worker("worker.js"); //创建一个Worker对象并向它传递将在新线程中执行的脚本的URL
worker.postMessage("helloworld"); //向worker发送数据
worker.onmessage =function(evt){ //接收worker传过来的数据函数
console.log(evt.data); //输出worker发送来的数据
}
来自 <http://www.cnblogs.com/feng_013/archive/2011/09/20/2175007.html>
用途:通过创建工作线程。可以把一些耗时的操作放到线程中执行。
执行完毕后,可以通过postMessage把结果传递过来。主线程的onmessage方法里可以接收结果并处理。
共享工作线程:可以让多个页面共享的处理。不使用onmessage。使用onconnect来定义接收时的处理。
总结
webworker看起来很美好,但处处是魔鬼。
我们可以做什么:
1.可以加载一个JS进行大量的复杂计算而不挂起主进程,并通过postMessage,onmessage进行通信
2.可以在worker中通过importScripts(url)加载另外的脚本文件
3.可以使用 setTimeout(),clearTimeout(), setInterval(), and clearInterval()
4.可以使用XMLHttpRequest来发送请求
5.可以访问navigator的部分属性
有那些局限性:
1.不能跨域加载JS
2.worker内代码不能访问DOM
3.各个浏览器对Worker的实现不大一致,例如FF里允许worker中创建新的worker,而Chrome中就不行
4.不是每个浏览器都支持这个新特性
来自 <http://www.cnblogs.com/feng_013/archive/2011/09/20/2175007.html>
特点5:资源加载。
1.threejs中使用了XMLHttpRequest进行json资源加载。(数据、模型等都可以用json表示)例如加载js模型文件。
varloader = new THREE.JSONLoader();
var callbackMale = function(geometry, materials) {
。。。
};
loader.load("models/xxx.js",callbackMale);
可以看到,loader调用加载后传入一个回调函数。该回调函数会在加载完毕以后被回调。load方法内部是采用XMLHttpRequest实现加载的。所以是真正的多线程加载。加载完毕后会把结果放到单线程消息队列。常用的模型如obj,ply等都是用这种办法。
2 纹理异步加载
loader.load('textures/xxx.jpg', function (texture) {
uniformsTerrain.tDiffuse.value= texture;
});
纹理图片的加载也是类似的。如上面接口当加载完毕后会调用回调函数。纹理的onload实现是靠img标签来实现的。不知道是否是多线程加载。应该也是。