JavaScript语法: 函数
- 上下文:函数总有上下文,可以通过this关键字引用其上下文;与java不同,函数的上下文未必是对象.如果函数挂栽于一个对象,则该函数被叫做对象的方法,该对象成为该函数本次调用的上下文.
- 构造函数:初始化一个新创建的对象.
- 函数=对象:函数有自己的属性,甚至有自己的方法.
- 嵌套定义:函数中可以定义函数,子函数可以访问所处作用域中的任何变量.
- 闭包:嵌套定义的副产品,给JavaScript带来了非常强劲的变成能力.
function关键字
函数定义表达式
- 只出现在它作为一个大表达式的一部分的时候
- 可以子表达式的方式出现在JavaScript代码的任何地方
- 函数名是可选的
- 定义后可立即调用: var tensquared = (function(x) {return x * x;} (10));
函数声明语句
- 被提前到外部脚本或外部函数作用域的顶部
并非真正的语句: 不能出现在循环,条件,try/cache/finally以及with语句中
函数名称是函数内部的一个局部变量
- 无return语句的函数返回undefined
调用一个JavaScript函数
任何一种函数,都严格遵守以下调用原则.
作为一个例子,嵌套在内部的函数并不会因为其外层函数是方法而变成方法.
作为函数
被调用的函数不是任何对象的属性.
- 调用上下文是全局对象,如浏览器JavaScript中的window
全局函数是作为全局对象属性出现的,所以这样规定调用上下文是自然的.
第二,全局对象没有名字,标准的引用方式是使用this关键字.window是浏览器全局对象的一个属性,它包含对自身的引用. - 在严格模式下,调用上下文是undefined
- 以函数形式调用的函数通常不使用this关键字
作为方法
函数是一个对象的属性或者数组中的一个元素.
- this关键字引用其隶属对象
作为构造函数
当函数或方法调用之前带有new关键字时,函数成为构造函数.
- 若构造函数没有形参,调用该构造函数时可省略小括号
- 构造函数会创建新的对象,新对象继承自构造函数的prototype属性
- 构造函数以新对象为其上下文
- 除非显示返回一个对象,否则构造函数总是返回新对象
间接调用
通过调用函数的call()和apply()方法,可以间接调用函数.
- 这两个方法可以显示指定this的值
- call()方法使用它自由的实参列表,apply()方法则要求以数组形式传入实参.
实参和形参
JavaScript不对传入的参数做类型检查,也不检查传入参数的个数.
- 个数匹配:形参被依次赋与实参的值,多余的形参被赋值为undefined,多余的实参不能通过形参直接访问
- 实参对象:arguments这个类数组对象保存了全部实参
- 访问调用栈:callee引用当前正在执行的函数,caller引用调用callee的函数
- trick:将对象属性用作实参,这样你就不必记住实参的顺序
命名空间
JavaScript没有块级作用域.
也就是说,JavaScript变量的作用域只有两种:全局的/函数内部的.因此,为了让一个变量的作用域局限于一个代码块内,我们将这个代码块用匿名函数包围起来:
(function(){
//statements
}());
闭包
作用域链
-
调用对象/声明上下文对象
- 局部变量是某个神秘对象的属性,该对象被称为调用对象.调用对象是一种对我们不可见的内部实现,然而对我们的编程非常重要.
于是:
- 变量总是对象的属性
- 对象总是另一个对象的属性
这样,按照隶属关系,JavaScript中的全局对象和所有调用对象最终构成了一颗树,就叫作用域树
好了.
-
作用域链
- 在JavaScript的作用域树上,从某一节点开始向根结点上溯,这条路径上的所有对象(调用对象/全局对象)构成作用域链.
变量解析:当JavaScript需要查找变量x的值的时候,它会从作用域链中的第一个对象开始查找,如果此对象有个名为x的属性,则会直接引用这个属性的值,否则,JavaScript会向上查找链中的下一个对象.如果作用域链上不存在x,则抛出EeferenceError.
当定义一个函数时,新函数保存其创建者的作用域链.当新函数被调用时,一个新的调用对象被创建并连接到已保存的作用域链上.
你理解上面这两段话吗?
/*
* <javascript权威指南> P183
*/
var scope = "global";
function checkscope() {
var scope = "local";
function f() {return scope;}
return f;
}
checkscope()(); //"local"
最后,作用域链的重叠部分是共享的: 如果一段代码修改了其作用域链上的某个调用对象的属性(也就是一个变量),那么这种改变对所有引用该调用对象的代码可见.
闭包
函数的定义后,它的作用域链随之”定型“,并永远与这个函数关联在一起.于是,函数的每个调用对象都会连接至已保存的作用域链的末端,因此我也说:函数位于这条作用域链的末端.
与之不同的是,函数的this并没有在定义时”定型”.函数this的值随着调用栈的变化而变化.
这样,我们就可以定义闭包:
-
闭包
- 位于作用域链末端的函数
这样,JavaScript中的每个函数都是一个闭包.但是,通常,闭包指的是嵌套在函数内部的函数.
函数是个对象
- 函数名只不过是个引用了函数对象的普通变量而已
- 函数拥有属性,甚至拥有方法和一个构造器Function()
下面是函数的属性,方法和构造函数
-
length
- 函数形参的个数 prototype
- 对一个对象的引用,这个对象叫做”原型对象”, 每个函数都引用不同的原型对象, 此外,当函数用作构造函数的时候,新对象会从原型对象上继承属性 call()和apply()
- 间接调用函数.第一个参数是调用上下文,剩下的参数传递到原函数
call()函数一个一个地传递参数,apply()函数则使用数组传递参数:
f.call(obj,1,2);
f.apply(obj,[1,2]);
-
bind()
- 将函数绑定到一个对象.
bind()函数可以这样模拟:
/*
* JSTDG P191
*/
if (!Function.prototype.bind) {
Function.prototype.bind = function(o, /* args */) {
var self = this, boundArgs = arguments;
return function() {
var args = [], i;
/*
* 将原函数的调用参数传入args
*/
for(i = 1; i < boundArgs.length; ++i) args.push(boundArgs[i]);
/*
* 新函数的调用参数将覆盖原函数的调用参数
*/
for(i = 0; i < arguments.length; i++) args.push(arguments[i]);
return self.apply(o, args);
}
}
}
这样,下面的代码就好理解了:
/*
* JSTDG P191
*/
var sum = function(x,y) {return x + y};
var succ = sum.bind(null, 1);
succ(2) // 3
function f(y,z) {return this.x + y + z};
var g = f.bind({x:1}, 2);
g(3) // 6
但是,真正的bind()方法有一些特性是上述代码无法模拟的: JSTDG P192