JavaScript 学习笔记

读了《JavaScript高级程序设计》,感觉非常赞,例子的说明很详细。

通常而言浏览器中的 JavaScript = ECMAScript + BOM + DOM

BOM 和 DOM 是宿主容器浏览器给 ECMAScript 提供的扩展,而当容器变为Node时则没有BOM和DOM。

ECMAScript

基本语法篇

数据类型

ECMAScript的数据类型:

  • object
  • function
  • number
  • string
  • boolean
  • undefined

(实际上还应该算上 null, undefined 派生自 null,null == undefined 会返回true)

通过 typeof 操作符可以得到变量的具体类型,基本类型比如 string、 number、 boolean 等用 typeof 就很好用,但是引用类型怎么办? typeof 始终会返回 object。这个时候就需要 instanceof 出场了。

var arr = [];
console.log(arr instanceof Array); // => true

其他类型到Boolean的转换

下面的表格记载其他类型转换到 boolean 的规则,

数据类型 转换为 True 转换为 False
boolean true false
number 非0值(包括无穷大) 0 或者 NaN
string 任何非空字符串 “”
object 任何对象 null
undefined 无法转换 undefined

NaN

NaN 表示不是一个数字,NaN 同时不等于 NaN 自身

isNaN() 方法可以测试一个变量是否是NaN,其工作原理是会尝试将变量转换为number,如果转换失败就返回 NaN

alert(isNaN(NaN)); // => true 
alert(isNaN(10)); // => false(10是一个数值)
alert(isNaN("10")); // => false(可以被转换成数值10)
alert(isNaN("blue")); // => true(不能转换成数值)
alert(isNaN(true)); // => false(可以被转换成数值1)

Number进制转换

parseInt() 方法可以转换字符串中的数组,同时它也只关注数字,其他的非数字字符会略过,如果字符串以数字开头则能够正确parse,如果是非数字开头则返回NaN

parseInt("023") // => 23
parseInt("d23") // => NaN
parseInt("023", 8// => 19
parseInt("0xf", 16) // => 15

另一个类似的函数 parseFloat() 只能够解析十进制数,所以不支持进制转换

JavaScript没有块级作用域

例子1,for 循环中定义的变量,循环外部也能够访问

console.log("start");

for(var i = 0; i < 10; i++) {
    console.log(i);
}

console.log(i); // => 10

那么在没有块级作用域的情况下怎么模拟块级作用域呢?

ECMAScript的作用域只有全局和函数作用域,因此我们可以通过创建一个立即执行的函数作用域来模拟局部作用域,例如下面的例子

例子2

for(var i = 0; i < 10; i++) {
   (function() {
      var count = i;
      console.log("loop: " + i)
   })();
}

console.log(count); // => undefined

创建一个立即执行的函数可以实现块级作用域,因此可以用这样的方法来创建私有变量,比如下面

例子3

var singleton = function() {
   var mem = 0;

   return {
      add: function(addition) {
         mem += addition;
      },
      getMem: function() {
         return mem;
      }
   }
}();

singleton.add(10); // mem should be 10
console.log(singleton.getMem()); // => 10

console.log(singleton.mem); // but there is no way to visit mem directly, return undefined

变量的作用域

当在函数中声明变量时

function foo() {
    var a;
}

那么变量a的作用域就是foo函数内部,仅此而已

当函数声明不写var时

function foo() {
    fooa = 10;
}

foo(); // run this function
console.log(fooa); // => 10

当声明变量不写var时,变量就会成为全局作用域global中的一部分

函数

参数的传递

函数参数传递的是值,而不是引用

当向函数传递参数时,实际上是把变量内存中的值复制过去。对于基础类型变量,传递的是值的拷贝;对于引用类型变量传递的是引用变量引用的内存地址的值。

虽然我们可以通过传递值引用在函数中改变引用对象的属性,但是这并不意味着ECMAScript是按照引用传递的

作用域链

ECMAScript只有两种执行环境类型: 全局函数

延长作用域链的方法

  • 调用方法,即创建新的函数执行环境
  • with 语法
  • try-catch 语法
with(localtion) {
    var url = "";
}

代码中将 location 对象添加到作用域链的最前端,而由于 ECMAScript 中没有块级作用域,所以 with 语句中的变量 url 会被添加到上一级的作用域中

闭包的作用域

闭包: 闭包是一个有权访问另一个函数作用域中变量的函数。

闭包对于外部函数引用的变量是外部函数变量最后的状态,而不能引用外部函数变量某个时刻的值,例如下面的例子

function createFunctions(){
   var result = new Array();
   for (var i=0; i < 10; i++){
      result[i] = function(){
         return i;
      };
   }
   return result;
}

var arr = createFunctions();
for(var idx in arr) {
   console.log(arr[idx]())  // => log 10, 10 times
}

因为闭包函数中是直接引用外部函数的变量i,所以当创建函数时不时拷贝一份 变量i 的值。 更改方案如下

function createFunctions2(){
   var result = new Array();
   for (var i=0; i < 10; i++){
      result[i] = function(num){
         return function(){
            return num;
         };
      }(i);
   }
   return result;
} 

由于ECMAScript中函数变量是按值传递,所以在创建闭包的时候造一个立即调用的函数,将当前变量i的值拷贝下来,传递给内部的闭包,这样闭包创建的时候就相当于绑定好了某个时刻变量i的值

this

this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。

var name = "The Window";
var object = {
   name : "My Object",
   getNameFunc : function(){
      return function(){
         return this.name;
      };
   }
};
console.log(object.getNameFunc()()); //"The Window"(在非严格模式下)

当闭包在执行时环境中的this发生了变化,this变成了 global对象。所以访问得到的是 global.name。 解决方法是在创建闭包的时候,将外部对象的this保存到一个局部变量,好让闭包可以访问到

var name = "The Window";
var object = {
   name : "My Object",
   getNameFunc : function(){
      var that = this;
      return function(){
         return that.name;
      };
   }
};
console.log(object.getNameFunc()()); //"The Window"(在非严格模式下)

(坑先挖到这里,后面继续补)