类型:
基本类型: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继承。
原理:
- if (typeof Object.create !== 'function') {
- Object.create = function(o) {
- function F() { }
- F.prototype = o;
- return new F();
- };
- }
访问属性
假设:
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。