1 类型,值 和 变量

一 类型,值 和 变量

类型:

基本类型:number,string, boolean,null, undefined,其中number,string,boolean都有wrapper objects,所以他们有对应的方法可以调用,但是null和undefined就没有。

其余的都是object类型。

普通的object类型是一个无序的key-value集合.

array,function,date,regular,error等是特殊的对象,js对这些特殊对象提供了额外的支持。


数字:

数学运算在js中是不报错的,比如除0,负数开方等,而是会返回特殊的结果。

js中任何数都是用二进制浮点数(binary floating-point numbers))表示的,所有无法分解为1/2(n)形式的小数都无法精确表示,比如0.1

console.log(0.3-0.2);//无法精确表示


String:

js使用utf-16编码来表示字符串


null undefined:

null是关键字,而undefined只是一个全局变量,在ECMA5之前undefined值是可以被改变的


Wrapper Objects:

三种基本类型:number,string和boolean都有对应的wrapper object,访问基本类型的属性或方法时,会自动进行装箱操作。比如string a="a"; a.substring(1);,调用substring方法时,会自动使用包装类将a装换成一个临时的String对象,即进行了如下的操作:var a = new String(a); 所以才可以调用a.substring方法,使用完毕后销毁。类似于java中的自动装箱。


var s="a";
s.len=4;
console.log(s.len);     //undefined


理解上述代码,因为第二行代码执行时,由于访问了s的属性,此时s不是第一行中的s二是一个临时的String对象,自然在第三行中赋值时会出错。

也可以自己手动装箱:var s = new String(s);

同样,装箱有对应的拆箱操作,在必要的时候会把String对象拆箱为s。


immutable & mutable:

五种基本类型都是不可变的:number,boolean,string,undefined,null, 任何对他们的改变本质都是创建了一个新的变量。

基本类型的比较操作都是根据其值的,只有string不同,是根据其长度和每个index的字符。

object类型默认是比较引用的,比如Array。


类型转换:

对象转换成基本类型有如下两个方法:toString(), valueOf(), 一个是转成string,一个是转成数字。


function scope:

js的作用域是function scope,也就是函数内声明的局部变量在整个函数内部都是可以访问的,区别于很多语言的block scope.

用var声明的局部对象是不可以delete的。

hoisting 特性:函数内的所有变量都相当于自动在函数头部声明(注意,只是声明被移到头部,赋值部分位置不变)。所以有些js程序员喜欢在函数开头声明所有变量是有道理的。

function(){
     return a;
     var a=1;
}
//以上代码等价于以下代码
function(){
     var a;
     return a;
     a=1;
}

//看看另一个代码
a = 1;
function f(){
     console.log(a);
     var a = 2;
}
f();     //输出undefined,而不是输出1,等价转换一下就明白了

//上述代码等价一下代码
a = 1;
function f(){
     var a;
     console.log(a);
     a=2;
}
f();     //所以就很明显该输出undefined。



eval, delete

直接调用eval(string),作用域就是当前代码的作用域,在很多实现中(除ie外),如果使用另一个名字调用eval,则作用域会自动变为全局作用域。

ie中有一个execScript和eval类似,但是作用域会变成全局作用域。

delete操作无法删除var定义的变量,delete只适合于删除某一对象的属性,但是delete操作是不会报错的。

delete不会影响父类的属性

在ECMA5 strict模式下,delete任何不能被delete的变量都会报错,并且无法删除configurable为false的属性


function

调用任何一个function时都有两种参数:arguments 和 context(隐式传递,即函数中的this);

四种调用方式:

1,函数调用, a(1);

2,方法调用,o.a(1);

3, 构造函数调用,new a(1);

4, 简介调用,a.call(this, 1); a.apply(this, 1);

函数调用和方法调用的区别是context的不同,函数调用的context一般是global(在E5 strict模式下是undefined),方法调用的context是o.

构造函数var a = new A(1); context是a, 如果A返回一个object类型,则a就是返回值,如果A返回的不是object类型,则返回值被忽略。

js不会检查函数的参数类型和参数个数,js中也不存在函数重载。

function hoisting

function声明和局部变量声明一样,也会被hoisting到当前作用域的顶部,赋值部分位置不变,所以:

var a=function(){};     //在此之前无法使用a(),因为只有var a;被升到顶部,而赋值部分位置不变

function a(){};     //在此之前就可以使用a,因为整个语句被升到顶部了。

method chaining

如果method总是返回this,那么可以进行链式操作,如jQuery。


closure

嵌套的函数声明即为闭包。


闭包的原理:函数的作用域是函数定义时的作用域,而不是函数调用时的作用。

闭包的实现:因为每产生一个函数都会生成一个新的scope chain, 一般情况函数结束后就没有对这个scope chaine的引用,因此scope chain被销毁,但是如果有闭包的存在,因为内部函数的scope chain引用了外部函数的scopechain,所以外部函数的scope chain不会被销毁。


如果调用多次外部函数,则会创造多个scope chain,因此每一个闭包访问的外部scope chain都是独立的:

function f(){
var a = 1;
return  function(){return ++a;};
}
f()(); f()();//都会返回2,因为调用了两个f。
var c= f();
c();c();//返回2,3,因为他们的scope chain是一样的。


一个经典的错误:

function f(){
     var funs = [];
     for(var i=0;i<10;i++){
          funs.push(function(){return i;});;
     }
     return funs;
}
var funs = f();
funs[5]();     //总是返回10


因为10个fun共享同一个i,当函数f调用结束后,i == 10;

正确的做法是,利用闭包构造多个不同的scope chain,这样每一个fun都有自己独立的scope chain。

正确的写法如下:

function f(){
     var funs = [];
     for(var i=0;i<10;i++){
          (function(x){
               funs.push(function(){return x;});;
          })(i);
     }
     return funs;
}
var funs = f();
funs[5]();     //总是返回10



闭包可以用作产生block作用域,因为js是函数作用域的。严格来说这不算闭包的特性。

(function(){

//put your code here.

}());

注意最外层的()不要省略了,省略之后就是函数定义,加上()之后才是函数表达式。


利用闭包还可以实现bind函数,将一个函数的this绑定到一个对象上:

function bind(f,o){
     return function(){
          f.apply(o, arguments);
     };
}



闭包内的函数可以直接访问外部函数的作用域,但是,内部函数的this指针并不继承自外部函数,如果想使用外部函数的this,可以使用上面的bind函数,也可以直接使用闭包,一般如下:

function out(){
     var self = this;     //存储this到一个局部变量,因为内部函数可以访问这个局部变量
     function inner(){
          self.xxx;     //使用外部函数的this
     }
}



memoization

memoization 即函数返回值的缓存。如果用同样的参数调用同一个函数多次,那么从第二次开始就可以从缓存中直接取返回结果而不需计算。此例用到了闭包,当然也可以用构造函数做。

function memoize(f){
     var cache = {};
     return function(){
          var key = arguments.length + Array.prototype.join.call(arguments, ",");
          if(key in cache) return cache[key];
          else return cache[key]=f.apply(this, arguments);
     };
}


for in

for(var name in object) 其中每次遍历出来的是object中属性的name 而不是value。

for in 循环只能遍历出enumerable的属性,

built-in method 和 built object不能被遍历出来,例如toString等。

所有用户自定义的属性都可以被遍历出来,用户自定义的继承属性也可以被遍历出来

一般遍历属性的顺序和这些属性声明的顺序是相同的,即先声明的属性先被遍历出来。

在ECMA5标准中,用户可以自定义属性是否是enumerable



native objects, hosting objects

native objects:由ECMAScript定义的对象,如:arrays, functions, dates, regular expressions.

hosting objects: 由宿主环境定义的变量,如dom element.


create objects

三种方法:

1,字面量:

var a = {a:1,b:2}; 

a.__proto__ == Object.prototype;

如果key中含有特殊字符或者key是保留字,可以用引号。

2, 构造函数

var a = new Constructor(a, b);

不同于字面量,a.__proto__ == Constructor.prototype;

3, Object.create

ECMAScript5中引入的新方法

var a = Object.create(Object.prototype); //等价于var a = new Object();

创建一个对象,第一个参数是prototype,可选的第二个参数是属性。如果不设置prototype,不会从Object继承。

原理:

  1. if (typeof Object.create !== 'function') {  
  2.     Object.create = function(o) {  
  3.         function F() { }  
  4.         F.prototype = o;  
  5.         return new F();  
  6.     }; 
  7.  } 



访问属性

假设:

a={a1:"a"};

b={b1:"b"};

b.__proto__ = a;

也就是b有自己的属性b,同时继承了a中的a1属性,那么:

1,b.b1, b.a1, 能正确的获取b和a中对应的属性值

2,b.b1 = "c" 能正确的修改属性值, 但是b.a1=“c”,不是修改了a的属性,而是添加了一个b.a1属性,从而影藏了a.a1,如果在执行delete b.a1 则此时b.a1 == a.a1;

3, 如果a.a1是只读的,那么b.a1="c"会出错,既不会在b上新增一个a1属性,也无法修改a.a1的值。

总结就是:get操作会对父类进行查询,set操作不会影响父类的属性值(最多就是因为重名而使父类属性被影藏了)


property attribute & object attribute

在ECMAScript5标准中,可以为property配置attribute。

每个property有四个attribute:value, writable, enumerable, configurable.

如果是个accessor property,则没有value和writable, 而对应的是get和set。

Object.defineProperty(obj, "p",{
     value:1,
     writable:true,
     enumerable:true,
     configurable:true
}
);


同E3中对属性的的访问,如果没有p属性,则是添加操作,此时没有定义的attribute默认为false;如果p已经存在,则是修改操作,此时未定义的attribute值保持不变。


每个object都有三个attribue:prototype, class, extensible

prototype:访问方法:

1,obj.constructor.prototype 

2,在firefox,safari和chrome中都可以 obj.__proto__直接读写prototype,非标准不建议使用

3,E5中可以使用Object.getPrototypeOf(obj)

class:定义了obj的type,但不是不同于typeof。在E3和E5中都没有直接方法可以访问到,只能间接地通过toString方法来读取。如果下方法利用toString来获取class。

function classof(o){
     if(o === null) return "Null";
     if(o === undefined) return "Undefined";
     return Object.prototype.toString.call(o).slice(8,-1);     //因为有些js库会污染toString方法,所以这样调用,而不是用o.toString()
}


extensible:

object是否可以被add/delete property,在E3中任何对象都是可以的,在E5中可以自定义。

Object.isExtensible(o);

Object.preventExtensible(o);一旦调用此方法之后,没有方法可以再变回去。

Object.seal():同时设置extensible false,并且改对象所有property.configurable=false。注意如果property是可写的,仍然可以改变其值,只是无法删除也无法添加新的。

Object.freeze():在seal基础上同时设置所有property为read-only。

注意:上述方法都只对object本身起作用,不影响其原型链上的东西。


JSON & serializing object

https://github.com/douglascrockford/JSON-js/blob/master/json2.js 文件中定义了JSON.stringify和JSON.parse函数,这两个函数现在在E5中是内置函数了,chrome,firefox,safari等高级浏览器都提供了原生的JSON对象。


数组

array是一个特殊的object,特殊在一下三个地方

1, 其index 是一种特殊的 object property,特殊在其值只能是32bit的整数。

2,从Array.prototype继承了一些方法

3,会自动维护一个length属性。如果设置任何一个index>=length的元素,则length=index+1;如果设置length,则删除所有index>=length的元素。

除此之外,array和object没什么区别。

所以可以理解js中的array没有out of bound错误,因为查询一个大于length-1的index时,会被当做一个普通的property来对来,自然会返回undefined而不是报错。


对象 === 关联数组 === hashmap

在js中,对象就是关联数组(或者理解为map),其本质就是key->value的无序集合。

稀疏数组 sparse array

sparse array是指index不是0开始并连续的数组,和element为undefined的数组是不同的。

var a = new Array(5);     //没有元素,但是length为5,这是稀疏数组。

var a = {,,,,,}; //有5个元素,值为undefined, 这是普通数组,不同于稀疏数组。


很多数组方法对于array-like object,比如novelist都是适用的,通过Array.prototype.xxx.call(nodelist,x)的方式来调用。

所谓arra-like object,就是有index和有length属性的object,并且能用[]操作符来取值。

在E5中,string也是array-like的,不过是一个read-only array-like。