《JS高级程序设计-第3版》的读书笔记关于函数
1、js的数据类型(这里用js代指ECMAScript)
js共有6种数据类型。其中基本数据类型5个为Undefined、Null、Boolean、Number、String。引用类型1个Object,Object本质是由一组无序的键值对构成。其中所有类型的构建都可以用var(let?)关键字来声明。这样统一的声明方式来源于js的变量是松散类型,及每个变量只是一个用于保存值的占位符。当不用var时即为全局变量,归Global管,浏览器都是将Global对象作为window的一部分实现,即window可认为是Global的实现与扩展。
2、类型的区分与简介
js的六种基本类型都可以用typeof区分。typeof的运算结果可以为
"undefined"——值未定义;在变量声明后默认为undefined。与未定义不同。派生自null
"boolean"——值为布尔值;js中所有类型的值都有与true和false等价的值。例如非0,0,"","1".
"string"——值为字符串;16位Unicode组成。其中length表明的是有几个16位。所以可能与实际有偏差。
"number"——值为数值;js只有整形和浮点型。其中-0和+0相等。还有NaN,(-)Infinity 无穷。
"object"——值为对象或null;测试null时返回object,因为null值表示的是一个空对象的指针。
"function"——值为函数;多种声明方式,按值传参。函数名也只是个指针,可以复制别的变量。
其中函数在js中是Function类的对象,但是返回function。typeof测试null返回object,是空对象的指针。
3、js中的函数
js的函数用function关键字来声明,格式如下。js中使用var时没有块级作用域(if,for这样的花括号括起来的部分中声明的变量和在外面声明是一样的),但函数有函数作用域(即局部作用域)。当使用let声明变量后会存在块级作用域,也附带了许多限制。
function functionName(arg0, arg1, ... ,argN){ //这种是函数声明 statements } var sum = function(num1, num2){ //这种是函数表达式,等号右边为匿名函数 return num1+num2; }; //这有个分号。
函数声明的方式,在代码执行前,解析器会有函数声明提升的过程,即把函数提升到代码书的顶部(这样可以先使用后定义,对于变量也同样适用,变量的声明可以提升,先使用后定义,但是对于var x=1;这样的表达式不能提升。变量会被提升的更高。函数形参-->函数声明-->变量声明。只有声明会提升,var x;会提升,其余都不变。),函数表达式的方式没有(可以放在判断语句中,条件性的定义函数,因为不会提升),因为它只是一条初始化语句。还有一种函数的定义方式,是使用Function类的构造函数,该构造函数可以接收任意数量的参数,(原因在下一个代码段下面)但是最后一个参数被看作函数体,前面的参数则代表了新创建函数实例的形参。所以其声明创建的格式如下。
var sum = new Function("num1", 'num2', "return num1+num2"); //不推荐
这种方式的结果与上面两个相同,但是会解析两次代码,效率降低。第一次解析js的代码,第二次解析传入构造函数的字符串。js中变量的定义,都是使用var(let?),所以在形参部分直接命名就行,这些形参就是函数的命名参数。js中函数是对象,从最后一个函数构造的例子中可以很明显的看出。其中函数名即为该函数的指针。可以直接赋值给别的变量,例如:
function sayHello(name){ alert("Hello "+name); } var x = sayHello; x("123");
函数中的特殊对象(1)arguments:在js中的函数的参数有一个特点。即js的函数可以接受任意多个实参,不论是什么类型。即便在定义的时候设定了几个参数,在使用的时候都可以传递多于设定的参数或少于设定的参数,甚至不传参数。原因是在js中,参数在内部的是用一个数组表示,而函数接收到的是这个数组,实参到形参的传递只是给数组赋值的过程。但命名变量任然存在,与该数组拥有各自的内存,但命名参数于该数组中的值是同步的,修改任一个,另一个都会改变。在函数体内可以通过arguments对象来访问这个数组。arguments对象只是与数组类似(它并不是Array的实实例),但可以使用"[]"语法来访问,它的length属性可以确定传进来参数的数量。arguments.length即可。
function sayHello(){ alert("Hello "+arguments[0]+arguments[1]); }
在定义时不定义任何的形参,但是在使用的时候可以这样:sayHello("123","456");这次的函数调用说明了,函数的命名参数(形参)只是为了在函数内能够方便访问到参数,但是不是必须的。在函数的调用时也不会进行函数签名的验证,所以可以传入数量变化的参数。这使得js以另一种方式实现了重载的功能。但js中的函数没有重载的功能。说明如下
var sum = new Function("num", 'return num+10'); sum = new Function('num', "return num+20");
函数的重载其实只是改变了sum中保存的指针的值。
函数的这个arguments对象的另外一个值得注意的地方是一个属性callee(不是函数的属性),该属性是一个指针,指向拥有这个arguments对象的函数。或者说指向正在执行的函数。请看下面这个非常经典的阶乘函数。
function factorial(num){ if(num<=1){ return 1; }else{ return num*factorial(num-1); } }
定义结成函数一般要用到递归算法;如上面的代码所示,在函数有名字,而且名字以后也不会变的情况下,这样的定义没有问题。但问题是这个函数的执行与函数名factorial紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以像下面这样使用arguments.callee。
function factorial(num){ if(num<=1){ return 1; }else{ return num*arguments.callee(num-1); } }
摘抄结束。这样,就可以将保存函数指针的变量由factorial替换为别的变量,而不发生错误。
函数中的特殊对象(2)this:this引用的是函数据以执行的环境对象。全局作用域中就是window。或者说,哪个对象调用的这个函数,那么这个函数中的this就代表了这个对象,简单的判断就是看函数前的对象,如果没有就是window,也就是说在函数1中调函数2,如果函数2是直接调用的,那么函数2中的this指向window。但是无论那个对象调用这个函数,其实函数名所指的函数是同一个。this的例子如下。
window.color = "red"; var o = {color: "blue"}; //用对象字面量定义一个对象 function sayColor(){ alert(this.color); } sayColor(); //red o.sayColor = sayColor; o.sayColor(); //blue
函数作为值(回调函数):因为函数名是变量,所以函数可以作为值使用,即可作为参数,可作为返回值。例子如下。
function callSomeFunction(someFunction, someArgument){ return someArgument(someArgument); }
这样定义了一个把函数名作为参数的的函数,把第一个参数作为函数名。因为函数名在没有加括号时是变量,可以直接访问。在加括号后为函数调用,所以使用的例子如下。
function add(num){ return num+10; } var result = callSomeFunction(add, 10); alert(result); //20
以上是将函数作为参数传递,return中也只是函数的调用,下面是将函数作为返回值,类似于java中的匿名函数。例子如下。
function createComparisonFunction(propertyName) { return function(object1, object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2){ return -1; } else if (value1 > value2){ return 1; } else { return 0; } }; } var data = [{name: "Zachary", age: 28}, {name: "Nicholas", age: 29}]; data.sort(createComparisonFunction("name")); alert(data[0].name); //Nicholas data.sort(createComparisonFunction("age")); alert(data[0].name); //Zachary
这样,将函数返回前,可以动态的改变排序的参数。sort()默认按字符集的先后排序。即字母顺序。在因为存在函数中调用函数,js也存在着一个访问外层函数的属性caller,该属性保存着调用当前函数的函数的引用。在全局作用域中它的值为null。
function inner(){ alert(inner.caller); //alert(arguments.callee.caller); 显示与上一行相同 } function outer(){ inner(); } outer(); //打印outer()函数的源码
上例不能在严格模式下使用。
函数的属性和方法(与上文的特殊对象不同)js中的函数是对象,所以函数存在属性与方法。
每个函数都有两个属性:length和prototype。length属性表示函数希望接收的命名参数的个数,也就是命名参数的个数。上面例子可以作为调用,outer.length,这里其值为0。prototype属性保存了引用类型的所有实例方法。例如toString(),valueOf()等都是在prototype名下,只不过是通过各自对象的实例访问罢了。
每个函数都有两个非继承而来的方法:apply()和call()。这两个方法都是在特定的作用域中调用函数,相当于设置函数内this对象的值。apply接收两个参数,一个是作用域,一个是参数数组。对于第二个参数,可以是Array实例或arguments对象。
function sum(num1, num2){ return num1 + num2; } function callSum1(num1, num2){ return sum.apply(this, arguments); //传入arguments 对象 } function callSum2(num1, num2){ return sum.apply(this, [num1, num2]); //传入数组 } alert(callSum1(10,10)); //20 alert(callSum2(10,10)); //20
两个函数均在全局作用域中调用,所以this为window对象。call()方法域apply()方法相同,区别在于接受参数的方式不同。对于call()第一个参数为this,其余的参数需要直接传递给call(),如下例子:
function sum(num1, num2){ return num1 + num2; } function callSum(num1, num2){ return sum.call(this, num1, num2); //只有参数传递方式的区别 } alert(callSum(10,10)); //20
这两者的使用,主要在于扩充函数的作用域。也就是修改this,如下
window.color = "red"; var o = { color: "blue" }; function sayColor(){ alert(this.color); } sayColor(); //red sayColor.call(this); //red 因为在全局作用调用this指向window,看传参的位置所以是全局 sayColor.call(window); //red sayColor.call(o); //blue
ECMAScript5中还有一个bind()方法,该方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的返回值,如下。
window.color = "red"; var o = { color: "blue" }; function sayColor(){ alert(this.color); } var objectSayColor = sayColor.bind(o); objectSayColor(); //blue,即便在全局作用域中调用
每个函数继承的toLocaleString()和toString()方法始终返回函数的代码。格式因浏览器而异。(另外一个继承的valueOf()方法同样也只返回函数代码。括号内笔者未验证)
补充:在js中有一个功能强大的方法eval()。它类似于一个完整的js解析器。只接受一个参数就是要执行的js的字符串。
eval("alert('hi')");
/*相当于*/
alert('hi');
当解析器发现代码中调用eval()方法时,他会将传入的参数作为js语句解析,然后把执行结果插入到原来的位置。并且同通过eval()执行的代码被认为是包含在该次调用的执行环境的一部分,因此被执行的代码具有与该执行环境相同的作用链。所以传入的参数可以引用更大范围作用域的变量。如下。
var msg = "hello world"; eval("alert(msg)");
反之,在eval()中定义的变量在外部不一定能够访问。在非严格模式下可以,在严格模式下则不行。另外,在eval()的变量或函数不会被提升。因为在执行到它之前,作为参数的js代码都只是字符串。那这个函数的用武之地在哪?个人已知的是,在早期json被使用的时候,作为了json-->js对象、变量、数组的转换工具,显然这是不安全的。
补充:基本类型保存的是变量的值,引用类型保存的是在内存中对象的引用。两者的操作会有区别如下。
var person = new Object(); person.name = "Nicholas"; alert(person.name); //"Nicholas"
以上代码创建了一个对象并保存在了变量person中,然后又为其添加了一个名为name的属性,然后赋值,访问。如果这个对象不被销毁或者这个属性不被删除,则该属性一直存在。但是对于基本类型则不行。
var name = "Nicholas"; name.age = 27; alert(name.age); //undefined
这样写虽然不会报错,但是却没有任何作用。

浙公网安备 33010602011771号