内容来源于从网上看到的一些文章。
主要介绍一些 javascript 比较混乱的语法和一些不太常用的用法。
基本语法
Js 是弱类型动态语言,所以任意变量名都可随时改变数据类型,在js 里面一切都是对象,一切都是数据。
函数是js 的一等公民。
Ojbect 是所有对象的父,遍历prototype 对象时最终一定会到达Object.prototype
变量的声明
• JS 的作用域只有全局和局部
• 所有运行于浏览器的js 解释器的全局作用域都是window ,其它的脱离浏览器的js 解释器都有各自的全局对象名称
• 所有全局变量都是全局对象的属性
• 函数也是一种数据类型,所以全局作用域的函数也是全局对象的属性
• var 关键字是指在当前作用域声明变量
//eg. Test.js
var a;// 在全局作用域声明一个变量
function f(){
var a;// 在函数f 作用域声明一个变量
}
• 使用一个变量时如果没有在当前作用域链中找到就会在全局作用域查找。如果这个全局变量存在就使用,如果不存在就认为是undefined 。如果当前是赋值语句就自动创建一个全局变量
//eg. Test.js
a;// 在全局作用域声明一个全局变量
function f(){
a=1;// 在函数f 为全局变量a 赋值
b;//undefined
c= 'c';// 在函数f 内声明一个全局变量
}
• 使用function fname (){} ,如果当前作用域是全局的,就声明一个全局函数
如果当前作用域是在函数内容,就声明一个内部函数
与var fname=function(){}区别如下
function f1() {
f1();//出错
f2();//调用成功
var f1=function(){};//看过的一本老外的书上推荐这么写
function f2(){}//不推荐这么写
f1();//可调用成功
f2();//可调用成功
}
• 由于任意类型都是数据,函数也不例外,可以把一个函数的定义作为变量值赋给一个变量
function global(){// 在全局作用域声明一个全局函数
function global2(){}// 在函数内声明一个内部函数
var fn=function(){};
// 在函数内声明一个内部函数,分号不能丢
var fn2=function fn3(){
fn3();// 成功
};
fn3();//undefined
}
• Js 解析器会自动提升变量和函数声明到当前作用域的最前面,但是相应的赋值语句位置不变
• Js 变量只有函数作用域和全局作用域,没有块作用域。也就是在if for switch while 内声明的变量也是在整个函数内可见的
alert(a);//undefined
var f=function(){alert(a);};
var a=1;
f();// 可访问 变量a
上面的代码相当于
var a;
alert(a);//undefined
var f=function(){alert(a);};
a=1;
f();// 可访问 变量a
// 另一个例子
function f(){
for(var i=0;i<10;i++){var a=0;}
alert(i+''+a);//10 0
}
function f(){
for(var i=0;i<10;i++){
setTimeout(function(){alert(i);},500);
// 一共alert10 个10
}
}// 应该是下面的样子
function f(){
for(var i=0;i<10;i++){
setTimeout((function(i){
return function(){alert(i);};
})(i),500);
}
}
// 或者是
function f(){
for(var i=0;i<10;i++){
(function(i){
setTimeout(function(){alert(i);},500);
})(i);
}
}
变量的作用域链
-
Js 有两个变量作用域链
-
一个是闭包上下文作用链
-
一个是prototype 继承作用域链
-
一个Function 对象如果是另一个对象的成员这个Function 对象就是它的拥有者的方法,如果一个Function 对象是独立的它就是一个函数
-
对于方法来说会首先在当前对象的prototype 查找变量,如果找不到就从它的父的prototype 查找,如果一直找不到会一直追溯到Object.prototype
-
Object.prorotype===null;//true
-
如果当前作用域是一个函数,会首先在当前函数作用域查找变量,如果找不到会判断当前函数是不是闭包,如果不是就查找全局作用域。如果是闭包,就在创建闭包的函数内查找,直到全局作用域。
this 的引用
- this 一定会有一个引用它不可能是 undefined 或 null
- 如果使用 this 的 Function 对象是一个函数,不论这个函数是不是闭包, this 一定会引用全局对象。
- 在一个对象成员方法内通过 this 获取属性会查找 prototype 继承链
- 在构造器内, this 引用新创建的对象
function con(){
this.a=1;
// 创建一个新属性, this 引用 new con();
}
con.prototype={f:function(){alert(this.b);},b:2};
new con().f();//2
call 和 apply
- 它们会临时改变一个函数的 this 引用
- this 引用 call 和 apply 的第一个参数
function foo(arg){
alert(this.a);
alert(arg);
}
var o={a:1};
foo.call(o,2);//1 2
foo.apply(o,[2]);//1 2
new 的用法
使用 new 调用一个函数时,这个函数默认返回新创建的对象
function f(){}
var o=new f();//o 的值实际是 new f 的返回值
等价于 var o=new f;
function f1(){return 1;}
var o=new f1();//(o===1)===true
不使用 new 调用一个函数,函数默认返回 undefined
function f(){}
var a=f();//undefined
prototype
- Js 采用 prototype 继承方式
- Object.prototype===null;//true
- 一个构造器创造的所有实例都会引用同样的 prototype 对象,而不会为每一个新对象创建一个新 prototype 对象。
function Asian(){};
Asian.prototype={haircolor: 'black'};
new Asian().haircolor= 'white';
new Asian().haicolor;// 'white'
constructor
返回一个对象的构造器引用
function Asian(){};
new Asian().constructor===Asian;//true
constructor 是 prototype 的一个属性,如果 A 对象的 prototype 是 B 对象的引用,那么 A 、 B 两个对象的constructor 是一样的。这时我们需要这么做:
function A(){}
function B(){}
A.prototype=new B();
A.prototype.constructor=A;
''.constructor===String//true
[].constructor===Array;//true
1..constructor===Number;//true ,第一个点是小数点
(1).constructor===Number;//true
true.constructor===Boolean;//true
var foo=function(){};
new foo().constructor===foo;//true
- 我们可以用这种方式精确判断一个对象的类型,比 typeof 和 instanceof 好的多
- typeofof 只有‘ undefined ’ , ‘ object ’ , ’ string ’ , ’ boolean ’ , ’ number ’ , ’ function ’ 6 个值,而 instanceof 的做法与 java 的 instanceof 一样
arguments
arguments 是 js 函数参数对象。
它的属性如下:
length ——调用时传入函数的参数数目
callee ——函数本身的引用
arguments[INTEGER_INDEX] ——获取相应的实参值,实参索引与传入函数时的位置一一对应。INTEGER_INDEX>=0&&INTEGER_INDEX<arguments.length
arguments.callee 会强迫 js 解释器查找当前函数的自身的引用
function foo(){}
// 如果 foo 是局部函数, caller 是 undefined
// 如果 foo 是全局函数, caller 是全局对象
// 如果 foo 是一个对象的方法, caller 与 foo 内的 this 引用相同
这两个属性会引起时间效率的问题
但在有些时候会很方便。我们看下面的例子
setTimeout(function(){
setTimeout(arguments.callee,0);
},0);
// 在需要递归调用一个匿名函数的时候使用 arguments.callee 会很方便
同时可利用 callee 和 caller 在运行时动态创建或修改函数对象的属性值
var foo=function(){
var callee=arguments.callee;
if(!callee.invokeCount){callee.invokeCount=0;}
callee.invokeCount++;
var caller=callee.caller;
if(caller.invokeFoo){caller.invokeFoo=0;}
caller.invokeFoo++;
};
判断一个函数是不是 new 调用
// 现在利用前面说过的 this callee caller 特性判断一个函数是不是在调用时使用了 new
function foo(){
if(this.constructor===arguments.callee){
// 是用 new 调用的
}else{
// 不是,此时 (constructor===undefined)===true
}
}// 我们可以用这种方式使用一个 Function 对象即能做函数又能做构造器
Function 的 prototype
任何 function 声明的对象都是 Function 对象,我们可以利用这一特性完成一些特殊应用
Function.prototype.inherit=function(name,fun){
if(arguments.length===2){
this.prototype[name]=fun;
}else{
this.prototype=name;
}
return this;
}
var o=(function(){}).inherit({a:1}).inherit('name' ,function(){});
new o().name();
new o().a;
基于闭包的偏函数应用
var fn=function(args){
// 初始化工作
// 这部分代码只执行一次
return (fn=function(args){
// 需要重复执行的代码,
// 会用到前面初始化的一些变量
})(args);
}// 这种用法可以有以下应用场景
// 第一次调用时传入比较多的参数,把一部分参数设为固定值,以后再调用时可传入比较少的参数。
防止递归溢出的方法
var bounce=function(recursion){
for(;recursion.constructor===Function;
recursion=recursion());
return recursion;
};
var recursion=function(arg){
return function(){return arguments.callee(arg);};
};
bounce(recursion());
// 这是个无穷递归,然后被改造成了无限循环。
//chrome 递归层次最少只有 2000 多,所以可以增加一个递归计数器,在计数器达到 2000 时采用这种方式
其它
- hasOwnProperty 与 in 关键字
- if(VAR_NAME in OBJ){} 会判断 VAR_NAME 是不是存在于 OBJ 的 prototype 链上
- for(var n in obj){} 会遍历 obj 整个 prototype 继承链
- 为了判断属性名是 obj 自身的属性还是 prototype 的属性应该使用 obj.hasOwnProperty(n)
- propertyIsEnumerable 表示属性名是不是可枚举的
- 如果它返回 true ,在 for in 循环中就会被遍历到
- 如果要为一个对象声明的属性名是 js 保留字,应该这么做: foo[ ‘ class ’ ]= ‘ val ’ ;
function foo(a){// 可用这种方式指定默认值
a=a|| 'defaultVal';
}
function foo(a){
var b=(a && a.constructor===Number && a*10)||0;
// 可用这种方式初始化变量,省去复杂的 if 判断
}
事件模型
- Js 是基于事件驱动的。包括 setTimeout 和 setInterval 也是
- setInterval 会每隔指定时间触发事件,把 interval 函数置于事件队列尾,不论之前的 interval 函数有没有执行完或有没有执行
- setTimeout 会在到达指定时间时触发且仅触发一次 timeout 函数
- Ajax 会在响应结束后触发事件,并把响应函数置于事件响应队列尾
- 所有事件响应函数都在同一个事件队列里面依次执行
- <!--[if !supportLists]--><!--[endif]-->
- 而事件队列属于界面线程
原生对象构造器
Function
- Function 是所有函数对象的构造器。
- 实际上我们使用 function 关键字定义一个函数时 js 解释器调用的就是 new Function 创建一个新的函数对象
- 当然并不推荐在 js 编程实践中出现 new Function 这样的代码,一方面会降低代码可读性,另一方面会在加载时增加传输字节数,第三会降低 js 解析效率
- 至于第三条的原因,个人猜想应该是 new Function 会强迫 js 解释器在执行上下文中再次调用 js 语法分析器,这样当然不如直接使用 function(){} 声明,在 js 加载完成时一次分析完成所有代码构造出所有对象来的快。
Array
- 由于 Array 构造器的参数比较混乱,个人猜想应该是 js 解释器内建多个重载的 Array 构造器
- new Array(1,2,3) <==>[1,2,3]
- new Array(len) <==>[]//len 可以任意整数
- new Array( ‘ 1 ’ ) <==>[ ‘ 1 ’ ]
- 建议任何时候都用数组字面量表示法定义数组
Boolean String Number 等
value | class | typeof |
"foo" | String | string |
new String("foo") | String | object |
1.2 | Number | number |
new Number(1.2) | Number | object |
true | Boolean | boolean |
new Boolean(true) | Boolean | object |
/abc/g | RegExp | object (function in Nitro/V8) |
new RegExp("meow") | RegExp | object (function in Nitro/V8) |
- 每次用 new 创建 Number String Boolean 对象实际上又为实际值包装了一层引用,类似 java 的 new String(“”)
- 建议只把它们用作类型转换函数,比如:
Number(‘1’)
Boolean(‘true’)
由于js是弱引用类型,在进行计算时, js解释器总是试图把同一表达式内不同类型的值进行自动类型转换, 而不会抛出错误
下面的列表足以说明类型转换的混乱,如果应用了这些特性,会增加调试与维护代码的难度,和出错的可能。
"" == "0" // false
0 == "" // true
0 == "0" // true
false == "false" // false
false == "0" // true
false == undefined // false
false == null // false
null == undefined // true
" \t\r\n" == 0 // true
"" === "0" // false
0 === "" // false
0 === "0" // false
false === "false" // false
false === "0" // false
false === undefined // false
false === null // false
null === undefined // false
" \t\r\n" === 0 // false
=== !== 与 == != 就像孪生的兄弟姐妹,一半天使一半恶魔
有时我们需要显式类型转换,前面已经提到可用 Number Boolean 等进行显式转换下面还有一些类型转换方式
转化成数字 —— 不推荐使用
+'010' === 10
parseInt('010', 10) === 10 // 用来转换为整数
+'010.2' === 10.2
parseInt('010.2', 10) === 10
parseInt('1a',10)===1;// 这是糟糕的特性
转化字符串
'' + 10 === '10'; // true
['aaa',1234,true].join('');// 连接字符串 ,ie 比较快
var repeatString=function(strToRepeat,repeat){return new Array(repeat+1).join(strToRepeat)};
// 可用这种方式构造重复 repeat 次的字符串,这是 new Array 的惟一用处
转化成 Boolean ,这种方式更快
!!'foo'; // true
!!''; // false
!!'0'; // true
!!'1'; // true
!!'-1' // true
!!{}; // true
!!true; // true !!undefined ===false !!null===false !!0===false !!1===true !!-1===true
!!function(){} //true
分号
Js 解析器只能解释以分号结束的语句,如果没有加上分号解析器会尝试自己加上分号再解析。所以对于没有分号的js 可能会出现不符合预期的结果。
为了降低调试难度,应该每条语句都以分号结束。
return所在的行必须有分号,返回值不能在return的下一行,比如:
return
1;//unreachable
return {A:1
};//valid
eval setTimeout setInterval
eval 调用 js 解析器将字符串参数解析为 js 语句并执行,造成的性能影响与前面说过的 new Function 问题一样
setTimeout setInterval 的第一个参数既可以是 function 也可以是字符串,当传递字符串时也会发生同样的事情。
所以强烈不建议使用 eval ,也不要向 setTimeout 和 setInterval 传递字符串
良好编程规范
1. 不用 for in 遍历数组,因为 for in 会遍历原型对象
2. 不定义全局变量。
3. 每条语句以分号结束
4. 尽量不用保留字
5. 不用 typeof 和 instanceof
6. 不用 parseInt
7. 不使用 == 和 !=
8. 不使用 with 表达式
9. 不用 eval new Function
10. 不向 setTimeout setInterval 传递字符串
11. 少用 continue 会提升性能
12.for if else while 一律使用 {} 包含语句块
13. 使用 var fn=function(){}, 代替 function fn(){}
因为后者会定义全局函数
14. 不使用 new String
new Boolean
new Number
new Array
15. 尽量少用 callee caller
16. 尽量减少闭包层次和继承层次
17. 对于使用两次以上的对象属性和闭包变量,一定先使用局部变量取得属性值,减少对象属性访问次数
18. 如果必须使用全局对象,一定先用局部变量引用全局对象
19. 不要修改原生对象
生僻浏览器 dom api
1.Window.navigator// 浏览器与系统信息只读
2.document.getElementsByTagName//NodeList 对象,结果集会随着 dom 树的改变而改变
3.UserAgent ,浏览器内核与版本信息
4.attachEvent||addEventListener
// 向 dom 对象添加事件响应函数
detachEvent||removeEventListener
// 从 dom 对象移除事件响应函数
5.
var css=dom.currentStyle||window.getComputedStyle(dom,null);// 获取 css 文件定义的样式
var bind=function(dom,e,f){dom.attachEvent?dom.attachEvent('on'+e,f):dom.addEventListener(e,f,null);};
var unbind=function(dom,e,f){dom.detachEvent?dom.detachEvent('on'+e,f):dom.removeEventListener(e,f,null);};
var load=(function(fn){
return function(e){
e=e||window.event;
var tar=e.target||e.srcElement;
if(e.readyState || e.readyState==='loaded'|| e.readyState==='completed'){
unbind(tar,'load',load);
unbind(tar,'readystatechange',load);
fn(e);
}
};
})(function(){});
bind(dom,'load',load);
bind(dom,'readystatechange',load);
Ie 内存泄漏
1. 循环引用
var myGlobalObject;
function SetupLeak() { // First set up the script scope to element reference
myGlobalObject = document.getElementById("LeakedDiv");
// Next set up the element to script scope reference
document.getElementById("LeakedDiv").expandoProperty = myGlobalObject;
}
//myGlobalObject 引用了 LeakedDiv,LeakedDiv 又引用了 myGlobalObject
function Encapsulator(element) { // Set up our element
this.elementReference = element; // Make our circular reference
element.expandoProperty = this;
}
function SetupLeak() { // The leak happens all at once
new Encapsulator(document.getElementById("LeakedDiv"));
}
function BreakLeak() {
document.getElementById("LeakedDiv").expandoProperty = null;
}
//Encapsulator 的对象引用了 element , element 又引用了 Encapsulator
造成泄漏
2. 闭包
function AttachEvents(element) {
// This structure causes element to ref ClickEventHandler
element.attachEvent("onclick", ClickEventHandler);
function ClickEventHandler() {}// This closure refs element
}
function SetupLeak() { // The leak happens all at once
AttachEvents(document.getElementById("LeakedDiv"));
}
// ClickEventHandler 里面引用了 element, element onclick 事件又引用了 ClickEventHandler
// 这也是循环引用的一种
// 在事件响应函数内部应该使用 e.srcElement 取得事件触发对象
// 同时应该使用 attachEvent 绑定事件响应函数
3. 添加子节点
function LeakMemory() {
var hostElement = document.getElementById("hostElement");
// Do it a lot, look at Task Manager for memory response
for(i = 0; i < 5000; i++) {
var parentDiv = document.createElement("<div onClick='foo()'>");
var childDiv = document.createElement("<div onClick='foo()'>");
// This will leak a temporary object
parentDiv.appendChild(childDiv);
hostElement.appendChild(parentDiv);
hostElement.removeChild(parentDiv);
parentDiv.removeChild(childDiv);
parentDiv = null;
childDiv = null;
}
hostElement = null;
}
// 先向父节点内添加子节点,最后把父节点添加到根会造成泄漏,把顺序反过来添加顺序就不会
4.Dom 的 text 属性
<head>
function LeakMemory() { // Do it a lot, look at Task Manager for memory response
for(i = 0; i < 5000; i++) {
hostElement.text = "function foo() { }";
}
}
</head>
<body> <button οnclick="LeakMemory()">Memory Leaking Insert</button>
<script id="hostElement">function foo() { }</script>
</body>
<!-- 不断修改一个 dom 的 text 属性会造成内存泄漏-->