Javascript权威指南学习笔记02

时间:2022-08-25 16:26:03

可枚举属性 enumerable  , for(p in obj)
可配置属性 configurable , delete obj.attr
自有属性 obj.hasOwnProperty(prop)

keys(o) 返回对象可枚举的自有属性
function keys(o){
    //if(typeof o !=='object') return;
    if(typeof o !=='object') throw 'not an object';
    var result=[];
    for(var prop in o){
        if(o.hasOwnProperty(prop)){
            result.push(prop);
        }
    }
    return result;
}

ES5 有2个枚举属性名称的函数 Object.keys() , Object.getOwnPropertyNames()
------
getter,setter定义的属性 '存取器属性' accessor property,
数据属性 data property,只是一个简单的值

若属性同时具有getter和setter方法 则为可读可写的属性

对象直接量语法定义存取属性 accessor property
var o={
    data_prop:value, //数据属性
    //存取器属性都是成对定义的函数
    get accessor_prop(){...},
    set accessor_prop(){...}
};

inherit(o,p)
//一个简单的继承函数
function inherit(o){
    var subObj={};
    for(var prop in o){
        subObj[prop]=o[prop];
    }
    return subObj;
}

var p={ //point
    //x,y是普通的可读写的数据属性
    x:1.0,
    y:1.0,
    //r是可读写的存取器属性 它有getter和setter方法
    get r(){return Math.sqrt(this.x * this.x + this.y * this.y)},
    set r(newValue){
        var oldValue=Math.sqrt(this.x * this.x + this.y * this.y);
        var ratio=newValue/oldValue;
        this.x*=ratio;
        this.y*=ratio;
    },
    //theta只读存取器属性
    get theta(){return Math.atan2(this.y,this.x);}
}

存取器属性页可以被继承
var q=inherit(p);
q.x=1,q.y=1;
console.log(q.r);
console.log(q.theta);

//产生严格自增的序列号
var serialnum(){
    $n:0, //$符号暗示是一个私有属性
    get next(){return this.$n++;},//返回当前值后自增

    set next(n){
        if(n>this.$n) this.$n=n;
        else throw "所设置序列号的值不能比当前值小";
    }
};
-----
对象的属性除了包含名/值外,还包含一些表示该属性可写、可枚举和可配置的特性 ~~属性的特性
ES3中 对象的属性都是可写 可枚举 可配置的,并且不能对这些特性做修改;ES5 提供相应API修改这些属性的特性

可以认为一个属性包含1个名字和4个属性:
数据属性:它的值value 可写性writable 可枚举性enumerable 可配置性configurable
存取器属性:读取get 写入set 可枚举enumerable 可配置configurable
-----
ES5提供属性描述符对象,该对象的属性和属性的4特性同名
数据属性的描述符对象 包含的属性 value writable enumerable configurable
存取器属性的描述符对象 包含的属性: get set enumerable configurable


Object.getOwnPropertyDescriptor({x:1},"x");//获取对象某个自有属性的描述符对象 {value:1,writable:true,enumerable:true,configurable:true}

Object.getPrototypeOf()
//设置属性的特性
Object.defineProperty()

var o={};
Object.defineProperty(o,"x",{value:1,writable:true,enumerable:false,configurable:true});//参数:对象 对象属性 属性的描述符对象

//o.x不可枚举

Object.defineProperty(o,"x",{writable:false}); //o.x 变成只读属性

o.x=2; //false 执行失败

Object.defineProperty(o,"x",{value:2}); //o.x仍然是可配置的
console.log(o.x); //2

Object.defineProperty(o,"x",{get function(){return 0;}}); //o.x从数据属性变为存取器属性
o.x; //0

//同时修改或创建多个属性
Object.defineProperties( {},
    x:{value:1,writable:true,enumerable:true,configurable:true},
    y:{value:2,writable:false,enumerable:false,configurable:true}
)


//Object.prototype增加一个不可枚举的方法extend()
//作为参数传入的对象,其属性及属性特性都会被复制    

Object.defineProperty(Object.prototype,"extend",
    {
        writable:true,
        enumerable:false,
        configurable:true,
        value:function(0){
            var names=Object.getOwnPropertyNames(o);
            for(var i=0;i<names.length;i++){
                if(names[i] in this) continue;
                var desc=Object.getOwnPropertyDescriptor(o,names[i]);
                Object.defineProperty(this,names[i],desc);
            }
        }
    }
)

------
查询和设置getter setter的老式API
__lookupGetter__ __lookupSetter__ __defineGetter__ __defineSetter__

两条下划线前缀  两条下划线后缀表明是非标准方法

--------------------------------------------------------
对象的三个属性:
每个对象都有 原型prototype 、类class、可扩展性extensible 三个属性
------
原型属性:用于属性的继承 , 对象直接量的原型是Object.prototype 如: var o={x:1}
ES5: Object.getPrototypeOf(o); //可以查询对象o的原型

//判断一个对象是否另一个对象的原型
p.isPrototypeOf(o); //检测p是否o的原型

Mizilla实现的javascript对外暴露了一个专门命名的属性 __proto__ 用以直接查询和设置对象的原型 , safari,chrome支持 __proto__; ie 和 Opera没实现

------
类属性:用以表示对象的类型信息    不能设置,可以间接访问 toString()方法返回的结果 包含类信息

//返回任意对象的类
function classOf(o){
    if(o===null) return 'null';
    if(o===undefined) return 'undefined';
    return Object.prototype.toString.call(o).slice(8,-1);
}

通过内置构造函数(Function Array Object Number String...)创建的对象包含 "类属性"
对象直接量 自定义构造函数的"类属性" 是 Object

------
可扩展性:表示是否可以增加新属性
内置对象 自定义对象 宿主对象

ES5: Object.esExtensible(o) //判断对象是否可扩展的
Object.preventExtensions(o) //将对象转换为不可扩展的

可扩展性属性的目的是将对象锁定
Object .seal() ; //类似 Object.preventExtensions()
Object.isSealed();

Object.freeze(); //'冻结' 更严格的锁定对象
Object.isFrozen();

对象序列化 serialization:指将对象的状态转换为字符串,反序列化则相反
ES5: JSON.stringfy(obj); //序列化 只能序列化可枚举的自有属性
ES5: JSON.parse(str); //反序列化

JSON的语法是javascript语法的子集。

----------
对象的方法:
js对象都从Object.prototype继承属性 如: hasOwnProperty(),propertyIsEnumerable(),isPrototypeOf()

Object.prototype对象里的方法:
-----
toString() 对象出现在希望使用字符串的地方,则调用对象的toString()方法
返回的信息少 如[Object Object] [Object Function] ,对检测对象的类型非常有用

很多内置类都重写一个自己的toString()方法 如: Array.toString(), Function.toString(),Date.toString()

----
toLocalString(); //返回对象的本地化字符串 主要用于Date,Number类
----
toJson(); //返回序列化的结果

valueOf(); //对象出现在需要原始值而非字符串时,调用该方法做转换;个别内置类 定义了自己的valueOf()方法 如 Date.valueOf()

---------------------------------------------------------------------
数组
数组:值的有序集合 使用数字索引,数组元素可以是任意类型
js的数组时稀疏的,即数组索引不一定连续的 如 var a=[1,2]; a[5]=23;
length=最大索引+1

Js数组是js对象的特殊形式,区别在于索引是数字

Array.prototype对象中定义的方法 对数组和类数组对象都有效。
字符串 ~~ 只读的字符数组
--------
创建数组:
直接量法: var a=[1,2,4]; var a=[]; var a=[1,"dd",null];
var base=1024;
var c=[base,base+1,base+2];
构造函数法:
var k=new Array();
var arr=new Array(2); //指定长度
var arr=new Array(2,3,'dd'); //指定初始值
-----------
数组元素的读写
arr[i]
arr[i+3]

~~数组是对象的特殊形式 arr[1] => arr["1"] 属性 "1"的值
普通对象也可以用数字做属性名
var o={};
o[1]='ok';

数组中属性名为0~2^23之间的整数才是索引,其他的属性名跟普通对象的属性名一样,数组是一种特殊的对象,可创建任意名字的属性
如: arr={0:'zz',1:'aa',2:'bb',x:'fine',y:true,length:3}
数组的属性名包括:数组索引和普通属性名
a[1.23]=true; //创建名为 "1.23"的属性
a["100"]=0; // ==a[100]
a[1.00]=2; // ==a[1]
--------
稀疏数组:索引不连续的数组 length=最大索引+1
delete 操作符会产生稀疏数组。如: var a=[1,2,3]; delete a[1]; //a=[1,,3];

在数组直接量中省略值不会产生稀疏数组,省略值为undefined 如 var a=[0,,2,3];//== [0,undefined,2,3]
元素不存在和元素存在但值为undefined有细微的差别,可以用in来检测
var a1=[,,]; // [undefined,undefined,undefined]非稀疏数组
var a2=new Array(3); //稀疏数组
0 in a1; //true 索引0处有元素
0 in a2; //false 索引0处没元素
-----
数组长度 length 数组长度总是大于它的最大索引值
var a=[1,2,3,4];
a.length=2; //[1,2] 将删除后面的元素 让长度刚好为2
a.length=10; //变成稀疏数组

Object.defineProperty()让数组a的长度属性只读
Object.defineProperty(a,"length",{writable:false});
---------
数组元素的添加
var arr=[];
arr[0]=100;

arr.push('newValue'); //相当于 a[a.length]='newValue';
arr.push('one','two');

arr.unshift('headele'); //数组首部插入元素

var a=[1,2,3];
delete a[0];
0 in a ; //false 数组元素被删除了
a.length; //3

a.pop();
a.shift();

a.splice()
--------
数组遍历
for()

var keys=Object.keys(o);
var values=[];
for(var i=0;i<keys.length;i++){
    var key=keys[i];
    values[i]=o[key];
}

for(var i=0,len=keys.length;i<len;i++){...}

for(var i=0,len=a.length;i<len;i++){
    if(a[i]==undefined) continue; //跳过值为null,undefined和不存在的元素
    ...
}

for(var i=0,len=a.length;i<len;i++){
    if(!(i in a)) continue; // 跳过不存在的元素
}


for/in 遍历数组,特别对遍历稀疏数组比较有效,每次循环 数组的索引和普通属性名会被赋值给循环变量
for(var index in sparseArray){
    var value=sparseArray[index];
    ...
}

for/in 能够枚举继承的属性 如在Array.prototype中定义的方法,如不希望枚举继承属性,可以用 hasOwnProperty()过滤一下
for(var i in arr){
    if(!arr.hasOwnProperty(i)) continue; //跳过 继承的属性
    ...
}

for(var i in arr){
    if(String(Math.floor(Math.abs(Number(i)))))!==i) continue; //跳过非负整数的i
    ...
}

ES5: forEach()  , arr.forEach(fn);
----------
多维数组,2维数组(元素为数组的数组) a=[[1,2],[3,4]] a[1][1]=1 实际上形成了一个矩阵

二维数组做的一个九九乘法表
var table=new Array(10);
for(var i=0;i<table.length;i++){ //每个元素赋值为一个数组
    table[i]=new Array(10);
}
//给二维数组赋值
for(var m=0;m<table.length;m++){
    for(var n=0;n<table[m].length;n++){
        table[m][n]=m*n
    }

}

//使用
var product=table[3][9]; //27;

function print99table(){ //输出九九乘法表
    var tblHtml='<table border="1" style="border-collapse:collapse;border:1px solid #eee;">';
    for(var m=1;m<10;m++){
        tblHtml+='<tr>';
        for(var n=1;n<10;n++){
            tblHtml+='<td style="padding:5px;border:1px solid #eee;">'+table[m][n]+'</td>';
        }
        tblHtml+='</tr>';
    }
    tblHtml+='</table>';
    document.body.innerHTML+=tblHtml;
}

window.onload=print99table;
--------------
数组的方法:
Array.prototype原型对象中定义数组的公共方法
-------
join();

var a=new Array(9); //长度为9的数组 没有初始化
a.join('-'); //生成8个重复的字符-
var a1=[1,2,3];
a1.join(); //1,2,3 默认逗号连接各个元素
a1.join(''); //123

join() , split() 为相反操作的两个方法
---------
reverse() //返回倒序后的数组
var a=[1,2,3];
a.reverse().join(); //'3,2,1'
---------
sort() //返回排序后的数组
var arr=new Array('apple','banana','cherry');
arr.sort().join(', ') //默认按字母表顺序排序 'apple, banana, cherry'
可以给sort指定排序函数,排序函数包含2个参数 若函数返回值为负则 参数1在前;函数返回值为正 则参数1在后
比较函数 通常用匿名函数即可 function(a,b){return a-b} a>b,返回正,a排在后面; a<b,返回负,a排在前面 即升序
function(a,b){return b-a} 倒序
var a=[1,2,3];
a.sort(function(a,b){return a-b}); // [1,2,3]
a.sort(function(a,b){return b-a;}); //[3,2,1]

var a=['ant','Bug','cat','Dog'];
a.sort(); //['Bug','Dog','ant','cat'] 区分大小写的排序 大写字母的ASCII码都小于小写字母的
a.sort(
function(a,b){ //排序函数 实现不区分大小写的排序
    var a1=a.toLowerCase();
    var b1=b.toLowerCase();
    //return a1-b1; 因为不是数值所以不能直接相减 比较大小
    if(a1>b1) return 1;
    if(a1<b1) return -1;
    });
---------
Array.concat(arr1) //创建并返回新合并数组 参数可以是一个数组 或者一些元素;参数是数组 合并的数组的元素,若数组的元素还是数组,concat()并不会进一步取它的元素

var a=[1,2,3];
a.concat([4,5]); //[1,2,3,4,5] 参数为数组
a.concat('a','b'); //[1,2,3,'a','b']  参数为元素
a.concat([6,[4,5]]); //[1,2,3,6,[4,5]] 没进一步扁平化元素数组

---------
Array.slice(startPos[,endPos])  //返回数组的一个片段或子数组 startPos endPos支持负数 表示从末尾开始的位置
var a=[1,2,3,4,5];
a.slice(2); //[3,4,5]
a.slice(2,4); //[3,4]
a.slice(-1,3); //[] 开始位置大于结束位置 所以没元素
a.slice(-4,3); //[3]
---------
Array.splice(startPos[,eleNum],RepEleList); // 删除、插入或替换数组元素,
splice方法不同于 slice() concat(),它会修改调用的数组。自修改性
splice删除或插入元素后,后面的元素索引值会减少或增加 保持索引的连续性 保持为稠密数组
splice的返回值为被删除的元素组成的数组 若没元素被删除 则返回空数组

var a=[1,2,3,4,5,6];
a.splice(3); // a==[1,2,3] 返回[4,5,6]
a.splice(1,2); // a==[1] 返回[2]
a.splice(1,0,'a','b'); // a==[1,'a','b']; 插入元素 返回[]
---------

push() 和 pop()  // 跟splice() 一样具有自修改性 修改被调用的函数
push() //数组当做栈  数组末尾增加一个或多个元素 返回新长度
pop() //删除数组最后一个元素 并返回这个元素

var a=[1,2,3,4];
a.push(15,16); //a==[1,2,3,4,15,16] 返回6
a.pop() //a==[1,2,3,4,5] 返回 6
---------
unshift() 和 shift() 类似于 push() 和 pop() ,只不过是在数组头部操作
shift() // 删除数组头部第一个元素,并返回这个元素,后面的元素索引减少填补空缺
unshift() //数组头部增加一个或多个元素 返回新长度 后面元素索引值增加
---------
toString() 和 toLocalString()
toString() 数组的每个元素调用它们自身的toString转换为字符串,然后用逗号连接各字符串
[1,2,3].toString() //1,2,3  toString()不带参数和 join()方法一样效果
[1,2,3].join() // 1,2,3

toLocalString() 转化为本地化字符串,主要用于时间 数值采用本地化的格式
--------------------
ES5中的数组方法
遍历  映射  过滤  检测   简化  和搜索
---------
forEach(fn(arrEle[,arrEleIdx,arrSelf]){} [,objForThisInFn])

var data=[1,2,3,4,5]
//数组元素求和
var rtn=0;
data.forEach(function(eleVal){rtn+=eleVal;});
alert(rtn); //15

//数组元素的值+1
data.forEach(function(ele,idx,arr){arr[idx]=ele+1;}); // [2,3,4,5,6]
forEach() 无法再遍历完成前,提前终止,若要提前终止 只有用 抛出错误的方式
-----
map(fn) //每个元素传给指定函数 该函数返回的结果形成新数组
var a=[3,4,5];
var mapArr=a.map(function(x){return x*x;}); //[9,16,25]

-----
filter(fn) // 每个元素传入指定函数 若能返回真 则返回这部分元素组成新数组(源数组的子集) 返回的总是稠密数组
var a=[5,4,3,2,1];
bigger=a.filter(function(x){return x>3}); //[5,4]
evenSubArr=a.filter(function(x){return x%2==0}) //[4,2]
//由于filter遍历时会跳过空缺数组,所以可利用filter()压缩稀疏数组的空缺
a.filter(function(x){renturn true;});
//利用filter压缩空缺和删除undefined和null元素
a.filter(function(x){return x!==undefined && x!==null})

-----
every(fn) 和 some(fn) //数组的逻辑判断
every(fn) //若数组每个元素都在fn中返回true  则返回true
some(fn) //若数组中存在元素在fn中返回true 则返回true
var a=[1,2,3,4];
a.every(function(x){return x>0;}) //true
a.some(function(x){return x%2==0;}) //true
every() 和 some() 都是短解析的,every()一旦碰到有元素在fn中返回false则可确定 整个表达式返回false,停止继续遍历元素;some()遇到有元素在fn中返回true 也将停止遍历 直接返回true

---
reduce(fn[,initVal]) 和 reduceRight(fn[,initVal]) //简化函数 fn函数的作用是把两个值组合或化简为1个值 可称为"折叠",因此需要2个参数,1个是初始值或上一次简化的值,1个是目前遍历的元素, 没指定初值时,第一个元素将作为初值
var a=[1,2,3,4,5];
var sum=a.reduce(function(x,y){return x+y;},0); // 15 数组求和
var product=a.reduce(function(x,y){return x*y;},1); //数组求积
var max=a.reduce(function(x,y){return x>y?x:y;}); //数组求最大值

reduceRight() 的作用和 reduce() 一样,只是从右到左化简的区别
var a=[2,3,4];
//计算 2^(3^4)
var big=reduceRight(function(x,y){return Math.pow(y,x);});

Function.bind() //将函数绑定到对象 变成它的方法
function union(o1,o2){
    var obj={};
    for(var i in o1){
        if(o1.hasOwnProperty(i)) obj[i]=o1[i];
    }

    for(var j in o2){
        if(o2.hasOwnProperty(j)){
            if(!obj.hasOwnProperty(j)) obj[j]=o2[j];
        }
    }
    return obj;
}
var objects=[{x:1},{y:2},{z:3}];
var merged=objects.reduce(union);
console.log(merged); //{x:1,y:2,z:3}

-----
indexOf(searchVal[,stratPos]) 和 lastIndexOf(searchVal[,startPos]) //startPos可以是负数  和字符串的indexOf lastIndexOf基本一样
var a=[1,2,0,1,3];
a.indexOf(1); //0
a.lastIndexOf(1); //3
a.indexOf(12); //-1 没找到
a.indexOf(1,2); //3

-----
数组是具有特殊行为的对象
判断一个对象是否数组的方法: typeof 操作符只是简单的返回Object(typeof对函数以外的对象检测都返回object)
instanceof 运算符可以判断一个对象是否为数组 如:
var a=[];
console.log(a instanceof Array) //true;
比较可靠的检测方法是 isArray() //ES5 支持方法 Array.isArray() ,ES4中也可以自己定义这个方法
var isArray=Array.isArray||function(o){
    return typeof o ==='object' && Object.prototype.toString.call(o)==='[Object Array]';
}

----
类数组对象:具有length属性 和 对应非负整数属性的对象,最常见的是 arguments对象 ,类数组对象完全可以用遍历数组的方式进行遍历
for(var i=0;i<arguments.length;i++){...}
var a={"0":"a","1":"b","2":"c","length":3}; //类数组对象
Array.prototype.join.call(a,"+"); //a+b+c
Array.prototype.slice.call(a,0); //["a","b","c"] 转换为真正的数组对象
Array.peototype.map.call(a,function(x){return x.toUpperCase();}); //["A","B","C"] 转换为大写的真正数组对象

----
作为数组的字符串
字符串的行为类似只读数组,ES5中除了可以用 charAt() 访问某个字符 还可以方括号方式直接访问
var s="test";
s.charAt(0)//"t";
s[1]; //"e"
通用的数组方法可用到字符串上
var s='javascript';
Array.prototype.join.call(s,' '); //'j a v a s c r i p t'
Array.prototype.filter.call(s,function(x){return x.match(/[^aeiou]/);}).join(' ');// 'j v s c p t'
注意:字符串的值是不可改变的,只能看做只读数组,那些自修改性质的数组方法(push,sort,reverse,splice)是无效的

------------------------------------------------------------------------
函数

子例程subroutine 过程procedure
形参 实参,形参是函数体内的局部变量
调用函数时,都有一个上下文对象 this

直接调用是作为普通函数调用(this指向全局对象或undefined[严格模式下]),作为对象的方法调用(this指向该对象)
用于初始化一个新创建对象的函数调用 构造函数
js中,函数也是对象,可以把它复制给变量,让它作为参数传入其他函数 设置属性(prototype,or other pro),调用它的方法(call apply)

函数可以嵌套 在函数内部定义的函数可以访问父函数作用域内的所有变量,构成了一个"闭包"
"闭包":一个在函数执行完后,没有被释放的内存堆区或栈区,函数内的变量仍然能被外部访问
----------
函数的定义:定义表达式方式 声明语句方式
var square=function(x){return x*x;}; //函数定义表达式 定义一个匿名函数并赋值给变量
var f=function factory(x){if(x<=1)return 1; else return x*factory(x-1)}; //函数定义表达式也可以包含函数名,这在递归时很有用
(function(x){return x*x})(10);  //自执行的匿名函数

var sum=function(x,y){return x+y;} //实际上是声明一个变量,并将一个函数对象赋值给该变量,声明语句会"被提前"到作用局顶端,但赋值语句不会提前

函数定义表达式特别适合定义只会用到一次的函数

function sum(x,y){ //函数声明语句方式定义函数,该声明语句会被提前到作用域的顶端,也可理解为函数对象的直接量写法
    return x+y;
}

$ _ 是除了英文字母和数字外两个合法的js标识符
以表达式方式定义的函数在定义之前无法被调用 因为这种定义方式语句不会被提前
嵌套函数 作用域链 嵌套在内部的函数可访问父函数作用域内的任何变量
----------
函数调用
js函数有4中调用方式:
1.作为普通函数
2.作为对象的方法
3.作为构造函数
4.通过函数对象,通过call,apply方法间接调用
//定义并调用一个函数来确定当前脚本是否运行在严格模式
var strict=(function(){return !this;}());
sum(2,3); //普通函数调用
---------
作为方法调用
o.m=f;
o.m(); //作为方法调用

var calculator={ //对象直接量
    operaAnd1:100,
    operaAnd2:200,
    add:function(){
        this.result=this.operaAnd1+this.operaAnd2;
    }
};
caculator.add() // this指向caculator这个对象 给其设置了属性result
caculator.result; //300
方法函数return this,可以实现方法的链式调用 $('<p/>').html('<span>hi</span>').css("color":"blue");

注意:this是一个关键字 不是变量也不是属性名 不能给它赋值。
调用嵌套函数时,this不会指向外层函数的上下文对象,如需访问外层函数的上下文对象 应在它的作用域内把this赋值给变量 如:self=this

var o={
    m:function(){
        var self=this;
        console.log(this===o); // true
        f();
        function f(){
            console.log(this===o); //false;
            console.log(self===o); //true
        }
    }
};
o.m();

-------
构造函数调用
var o = new Object(); //若构造函数不带参数 也可以省略圆括号
var o=new Object; //新的空对象 这个对象继承了构造函数的prototype属性
var a=new Array;
构造函数一般不需要显示的返回值,它默认返回新的对象,若显示的return 一个对象,那么结果就为这个对象,若显示return;或return 一个原始值;则还是会返回构造函数初始化的新对象

------
间接调用
函数也是对象,跟其他对象一样也包含方法,其中2个方法 call,apply可间接地调用这个函数。
call,apply显示地执行函数的上下文对象,让函数好像是该对象的方法一样被调用
call(thisObj,originalArgumentList) //call第一个参数后为它的自有实参列表
apply(thisObj,arguments) //apply第一个参数后,为数组形式的自有实参列表

---------
函数的形参和实参
函数定义不指定形参的数据类型,函数调用时可以传入任意个实参,不要求和形参个数匹配,传入的实参保存在类数组对象arguments中
arguments也可称为实参对象
可选形参
当实参比形参少时,多余的形参的值为undefined,通常我们为省略的参数设置一个默认值 如 e=e||document; 可省略的参数必须放到列表的后面,因为我们无法省略中间的参数 如: function sum(x,y,z){y=y||2;z=z||3; ...} 调用 sum(x),不能sum(x,,9);需要省略中间某个参数时,可用占位符null, sum(x,null,9)

通常某参数若省略的话,则采用其默认值。我们需要在函数体内为可省略参数设置默认值。
function getPropertyNames(o,/*optional*/ a){ //把对象o中的可枚举属性追加到数组a中 并返回数组a
    a=a||[]; //a若省略 则默认为空数组
    for(var property in o)    a.push(property);
    return a;
}

省略参数的占位符可用null或undefined,推荐用null.

可变长的实参列表:实参对象
当实参个数多于形参个数时,函数体内要获得多出来的实参的值,需要通过arguments对象 arguments[index]
function sum(a,b,c){
    //a, arguments[0]指向同一内存地址,都可以引用到第一个实参的值
    //arguments[3]引用 第4个实参的值,此时没有同样引用该值的形参变量
    //形参就是函数体内的局部变量
}
sum(1,2,3,4)
arguments.length //实参的个数
arguments.callee.length //函数定义形参的个数
用实参对象arguments来验证传入的参数个数是否和定义的形参个数一致
function f(x,y,z){
    if(arguments.length!==arguments.callee.length) throw new Error('传入的参数个数和期望的参数个数不一致');
}
函数定义时可以没有形参 如 function sum(){var rtn=0; for(var i=0;i<arguments.length;i++) rtn+=arguments[i]; return rtn;.} 调用时,用arguments实参对象来获取传入的实参值

arguments的callee和 函数的caller属性
callee 当前正在函数
caller 引用执行当前函数的父函数,caller可以访问调用栈
function f(){
    console.log(arguments.callee);
    console.log(arguments.callee.caller);
    console.log(f.caller); //和上一句等价
}
callee常被匿名函数用来递归调用自身 如:
var factorial=function(x){
        if(x<=1) return 1;
        return x*arguments.callee(x-1);
};

实参都保存在对象属性中,整个对象作为1个实参传入函数体
名/值对的形式传入参数,参数顺序无关紧要。
function easyCopy(args){
    arrayCopy(args.from,args.fromStart||0,args.to,args.toStart||0,args.length)
}
var a=[1,2,3,4],b[];
easyCopy({from:a,to:b,length:4})

-------------
实参的类型
对实参做严格的类型检查,有利于减少运行时错误,更容易确定问题所在
function sum(a){
    if(isArrayLike(a)){
        var total=0;
        for(var i=0;i<a.length;i++){
            if(a[i]==null) continue;
            if(isFinite(a[i])) total+=a[i];
            else throw new Error('元素不能为无穷数')
        }
        return total;
    }
    else throw new Error('参数必须是一个数组或类数组');
}
---------------
作为值的函数
函数可以定义,可以调用,这是函数最重要的特性。函数的定义和调用是javascript和大多数变成语言的词法特性。
然而,函数也是一种对象,具有属性和方法,可以作为值赋值给变量,作为参数传入函数 作为数组的元素,对象的属性
function square(x){return x*x;} 变量square指向一个函数对象,准确的说变量square保存了函数对象的首地址
var s=square; //把函数的首地址赋给变量s ,此时 s,square都指向该函数对象
s(2)=4;
square(2)=4;

var o={square:function(x){return x*x;}};
o.square(2); //4

var a=[function(x){return x*x;},5];
a[0]([1]);//25

//函数作为参数传入
function add(x,y){return x+y;}
function subtract(x,y){return x-y;}
function multiple(x,y){return x*y;}
function devide(x,y){return x/y;}

function operate(operator,operand1,operand2){
    return operator(operand1,operand2);
}
//(2+3)+(4*5)
var i=operate(add,operate(add,2,3),operate(multiple,4,5));

var operators={
    add:function(x,y){return x+y;},
    subtract:function(x,y){return x-y;},
    multiple:function(x,y){return x*y;},
    devide:function(x,y){return x/y;},
    pow:Math.pow
};

var operate2=function(operator,operand1,operand2){
    if(typeof operators[operator]==='function'){ //若操作符在操作符对象中存在,并且是一个函数
        return operators[operator](operand1,operand2);
    }
    else{
        throw "unknown operator";
    }    
};

函数作为参数传入的例子还有 Array.sort(),Array.map()

//------------
自定义函数属性
保存函数的执行状态,可以用全局变量,也可以用函数对象的自定义属性,显然后者更优

//每次调用返回唯一整数
uniqueInteger.counter=0;
function uniqueInteger(){ //声明语句定义函数,语句提前
    return uniqueInteger.counter++;
}

//把函数当做数组来对待
function factorial(n){
    if(! (n in factorial)){ //若函数对象尚不存在属性n
        factorial[n]=n*factorial(n-1);
    }
    return factorila[n];
}
factorial[1]=1;

//------------
作为命名空间的函数
函数是javascript中唯一具有作用域的结构,函数体内定义的变量都是局部变量。我们通常用立即执行的匿名函数来创建命名空间,让全局命名空间不被污染,提高函数的独立性
(function(){ //注意行首的括号不可省略,不然js解释器会把function当做函数声明语句,括号内的function被解释为函数定义表达式
....
}())

//把对象参数列表中,后面对象参数的属性复制到第一个对象参数中
//ie中有一个bug 若对象参数中 具有同名的可枚举属性和不可枚举属性 则 for/in循环会忽略该可枚举属性
var extend=(function (){ //立即执行的匿名函数 后面对象参数个数不定,用arguments对象访问传入的实参
    for(var prop in {toString:null}){
        //若执行进来这里 说明非ie,返回一个简单版的extend函数
        return function extend(o){
            for(var i=1;i<arguments.length;i++){
                var soure=arguments[i];
                for(var p in source) o[p]=source[p];
            }
            return o;
        };

    }

    //若前面没有return,执行到了这里 说明是ie 返回一个补丁版的extend函数
    return function patchedExtend(o){
        for(var j=0;j<arguments.length;j++){
            var source=arguments[j];
            for(var prop in source) o[prop]=source[prop]; //先把不存在同名不可枚举属性的复制
            for(var i=0;i<protoprops.length;i++){
                prop=protoprops[i];
                if(source.hasOwnProperty(prop)) o[prop]=source[prop];
            }
            return o;
        }
        var protoprops=["toSring","valueOf","constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString"];
    };    

}())


//-------------------
闭包
javascript采用词法作用域,函数执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是调用时决定的。

函数对象通过作用域链互相联系起来
大多数,定义函数时的作用域链在函数调用时依然生效的;当定义函数的作用域链和调用函数的作用域链不一致时就产生了闭包。
嵌套函数把内部定义的函数返回,即形成闭包

var scope='global scope';
function checkScope(){
    var scope='local scope';
    return function(){return scope;}; //返回一个闭包
}
checkScope()(); //local scope

函数执行用到了作用域链,作用域链是在函数定义时创建的。词法作用域
作用域链可以描述为一个对象列表。当函数执行时会创建一个新对象来保存局部变量,这个变量绑定对象添加到作用域链中,当函数返回时,这个变量绑定对象也被删除,如果存在嵌套函数,每个嵌套的函数都有一个作用域链,这个作用域链指向父函数调用时的变量绑定对象。如果嵌套函数被返回,那么它就会有外部引用指向这个返回的嵌套函数,父函数的变量绑定对象也不会被回收,称为闭包函数的执行上下文。(或者理解为闭包函数作用域链的上级链)

闭包和垃圾回收的关系

//用闭包重写uniqueInteger函数
var uniqueInteger=(function(){ //定义立即执行的匿名函数
    var counter=0; //只有闭包函数才能访问到的 私有变量。
    return function(){return counter++;}
}());

//多个嵌套函数 多个闭包
function counter(){
    var n=0;
    return {
        count:function(){return n++;},
        reset:function(){n=0;}
    }
}

//getter setter局部变量存取器
function addPrivateProperty(o,name,predicate){
    var value;
    o['get'+name]=function(){return value;};
    o['set'+name]=function(v){
        if(predicate & !predicate(v)){
            throw new Error('set'+name+' invalid value :'+v);
        }
        else{
            value=v;
        }
    }
}
var o={};
addPrivateProperty(o,'name',function(x){return typeof x=='string'})
o.setname('frank');
console.log(o.getname());
o.setname(0); //试图设置value=一个非字符串值 抛出异常

function constFunc(v){
    return function(){return v;};
}

var fns=[]
for(var i=0;i,10;i++) fns[i]=constFunc(i); //利用循环创建了10个闭包 闭包之间互不影响
fns[3](); //3

function constFunc(){
    var fns=[];
    for(var i=0; i<10; i++){ //利用for循环创建了10个闭包,闭包都访问同一个局部变量i,函数返回时它作用域内的i=10
        fns[i]=function(){return i};
    }
    return fns;
}
console.log(constFunc()[3]()); //10