(转 Uncle Tom )深入理解javascript(1)学习笔记
1、最小全局变量(Minimizing Globals)
Javascript 通过函数管理作用域,在函数内部声明的变量只能在内部使用,函数外面不可用。
所谓“全局变量”:
1、任何在函数外面声明的变量
2、未声明直接简单使用的(隐式全局变量)
所谓“全局对象”window
每个javascript环境有一个全局对象,当你在任意的函数外面使用this时可以访问到。
你创建的每一个全局变量都成为这个全局对象的属性。
在浏览器中,该全局对象有个附加属性叫:window。通常window指向该全局对象本身。
下面的代码片段显示如何在浏览器环境中创建和访问的全局变量:
myglobal="hello" console.log(myglobal); //不推荐写法 console.log(window.myglobal); console.log(window["myglobal"]); console.log(this.myglobal);
2、全局变量的问题
第三方js库,广告方脚本代码,不同类型组件等,均会定义全局变量,有可能造成命名冲突
3、应尽量避免使用全局变量
function sum(x, y) { // 不推荐写法: 隐式全局变量 result = x + y; return result; }
1) 经验法则是始终使用var声明变量,正如改进版的sum()函数所演示的:
function sum(x, y) {
var result = x + y;
return result;
}
但是b
确实全局变量,这可能不是你希望发生的:
// 反例,勿使用
function foo() {
var a = b = 0; // 不推荐写法: b为隐式全局变量
var c = (d = 0);// 不推荐写法: d为隐式全局变量
}
正确写法:
function foo() {
var a, b;
// ... a = b = 0; // 两个均局部变量
}
4、忘记var的副作用(Side Effects When Forgetting var)
隐式全局变量和明确定义的全局变量间有些小的差异,就是通过delete
操作符让变量未定义的能力。
- 通过var创建的全局变量(任何函数之外的程序中创建)是不能被删除的。
- 无var创建的隐式全局变量(无视是否在函数中创建)是能被删除的。
这表明,在技术上,隐式全局变量并不是真正的全局变量,但它们是全局对象的属性。属性是可以通过delete
操作符删除的,而变量是不能的:
// 定义三个全局变量 var global_var = 1; global_novar = 2; // 反面教材 (function () { global_fromfunc = 3; // 反面教材 }()); // 试图删除 delete global_var; // false delete global_novar; // true delete global_fromfunc; // true // 测试该删除typeof global_var; // "number" typeof global_novar; // "undefined" typeof global_fromfunc; // "undefined"
在ES5严格模式下,未声明的变量(如在前面的代码片段中的两个反面教材)工作时会抛出一个错误。
5、访问全局对象(Access to the Global object )
1、全局对象使用window属性访问
2、自己写代码创建全局对象,作用是写自己的js库时,通过此种方法获取全局对象
var global=(function(){ return this; }());
单var形式(Single var Pattern)
在函数顶部使用单var语句是比较有用的一种形式,其好处在于:
1、提供一个单一的地方寻找所有局部变量
2、防止变量在定义之前使用的逻辑错误(先声明,后使用)
3、帮助你记住全局变量
单var形式长得就像下面这个样子:
function func() {
var a = 1,
b = 2,
sum = a + b,
myobject = {},
i,
j;
// function body...
}
function updateElement() {
var el = document.getElementById("result"),
style = el.style;
// 使用el和style干点其他什么事...
}
预解析:var散布的问题(Hoisting: A Problem with Scattered vars)
预解析(hosting): javascript中,你可以在函数的任何位置声明多个var语句,并且它们就好像在函数顶部声明一样发挥作用。这种行为叫预解析。当你使用了一个变量,之后在函数中又重新声明,就可以产生逻辑错误。只要你的变量在同一作用域(函数)内,它都被当成声明的。即使是它在var声明前使用。
// 反例
myname = "global"; // 全局变量
function func() {
alert(myname); // "undefined"
var myname = "local";
alert(myname); // "local"
}
func();
为什么第一次alert myname值为:undefined?
由于预解析(func函数中有变量myname,因此myname变量声明当被悬置到函数的顶部),myname被当成函数局部变量.
上面的代码片段执行的行为可能就像下面这样:
myname = "global"; // global variable
function func() {
var myname; // 等同于 -> var myname = undefined;
alert(myname); // "undefined"
myname = "local";
alert(myname); // "local"}
func();
结论:由于javascript默认预解析函数内所有变量,并将其声明悬挂在函数顶端,
因此建议函数内最好预先声明好所有要使用的变量
for循环(for Loops)
// 次佳的循环
for (var i = 0; i < myarray.length; i++) {
// 使用myarray[i]做点什么
}
如果myarrary.length值每次通过计算获取,则建议使用var max存储使用
特别当myarrary 为htmlCollection时,这种建议就更有必要了
记住Dom操作是比较昂贵的
修改后:
function looper() {
var i = 0,
max,
myarray = [];
// ...
for (i = 0, max = myarray.length; i < max; i++) {
// 使用myarray[i]做点什么
}
}
for-in循环(for-in Loops)
for-in
循环应该用在非数组对象的遍历上,使用for-in
进行循环也被称为“枚举”。
所以最好数组使用正常的for循环,对象使用for-in循环。
有个很重要的hasOwnProperty()
方法,当遍历对象属性的时候可以过滤掉从原型链上下来的属性
思考下面一段代码:
// 对象
var man = {
hands: 2,
legs: 2,
heads: 1
};
// 在代码的某个地方
// 一个方法添加给了所有对象
if (typeof Object.prototype.clone === "undefined") {
Object.prototype.clone = function () {};
}
在这个例子中,我们有一个使用对象字面量定义的名叫man的对象。在man定义完成后的某个地方,在对象原型上增加了一个很有用的名叫 clone()的方法。此原型链是实时的,这就意味着所有的对象自动可以访问新的方法。为了避免枚举man的时候出现clone()方法,你需要应用hasOwnProperty()
方法过滤原型属性。如果不做过滤,会导致clone()函数显示出来,在大多数情况下这是不希望出现的。
// 1.
// for-in 循环
for (var i in man) {
if (man.hasOwnProperty(i)) { // 过滤
console.log(i, ":", man[i]);
}
}
/* 控制台显示结果
hands : 2
legs : 2
heads : 1
*/
// 2.
// 反面例子:
// for-in loop without checking hasOwnProperty()
for (var i in man) {
console.log(i, ":", man[i]);
}
/*
控制台显示结果
hands : 2
legs : 2
heads : 1
clone: function()
*/
另外一种使用hasOwnProperty()
的形式是取消Object.prototype上的方法。像是:
for (var i in man) {
if (Object.prototype.hasOwnProperty.call(man, i)) { // 过滤
console.log(i, ":", man[i]);
}
}
其好处在于在man对象重新定义hasOwnProperty情况下避免命名冲突。也避免了长属性查找对象的所有方法,你可以使用局部变量“缓存”它。
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
if (hasOwn.call(man, i)) { // 过滤
console.log(i, ":", man[i]);
}
}
严格来说,不使用
hasOwnProperty()
并不是一个错误。根据任务以及你对代码的自信程度,你可以跳过它以提高些许的循环速度。但是当你对当前对象内容(和其原型链)不确定的时候,添加hasOwnProperty()
更加保险些。
格式化的变化(通不过JSLint)会直接忽略掉花括号,把if语句放到同一行上。其优点在于循环语句读起来就像一个完整的想法(每个元素都有一个自己的属性”X”,使用”X”干点什么):
// 警告: 通不过JSLint检测
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) if (hasOwn.call(man, i)) { // 过滤
console.log(i, ":", man[i]);
}
(不)扩展内置原型((Not) Augmenting Built-in Prototypes)
不增加内置原型是最好的。你可以指定一个规则,仅当下面的条件均满足时例外:
- 可以预期将来的ECMAScript版本或是JavaScript实现将一直将此功能当作内置方法来实现。例如,你可以添加ECMAScript 5中描述的方法,一直到各个浏览器都迎头赶上。这种情况下,你只是提前定义了有用的方法。
- 如果您检查您的自定义属性或方法已不存在——也许已经在代码的其他地方实现或已经是你支持的浏览器JavaScript引擎部分。
- 你清楚地文档记录并和团队交流了变化。
如果这三个条件得到满足,你可以给原型进行自定义的添加,形式如下:
if (typeof Object.protoype.myMethod !== "function") {
Object.protoype.myMethod = function () {
// 实现...
};
}
避免隐式类型转换(Avoiding Implied Typecasting )
JavaScript的变量在比较的时候会隐式类型转换。这就是为什么一些诸如:false == 0 或 “” == 0 返回的结果是true。为避免引起混乱的隐含类型转换,在你比较值和表达式类型的时候始终使用===和!==操作符。
var zero = 0;
if (zero === false) {
// 不执行,因为zero为0, 而不是false
}
// 反面示例
if (zero == false) {
// 执行了...
}