第七章 函数表达式
1. 创建函数两种方法
函数表达式特征
1,函数声明:
特征: 函数声明提升,意思执行代码前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。
function functionName(arg0, arg1, arg2) {
// 函数体
}
sayHi();
function sayHi() {
consoel.log('Hi');
}
<!-- 这个例子不会报错 -->
2,函数表达式(匿名函数(anonymous function),拉姆达函数),匿名函数的函数名称是空字符串
var fName = function(arg){
// 函数体
}
<!-- 创建一个函数并将它赋值给变量 fName -->
3, 函数声明 和 函数表达式区别
- 理解函数提升的关键,就是理解函数声明与函数表达式之间的区别
函数声明执行顺序: 先获取声明 -》 在执行
函数表达式: 先获取值 -》 在执行
标题 | 顺序 | 备注 |
---|---|---|
函数声明 | 获取声明 -》 执行 | null |
函数表达式 | 获取值 -》在执行 | null |
通过例子理解
<a href="h-1.html">函数提升</a>
// 函数声明
fDefine('!')
function fDefine(arg0) {
alert('函数声明' + arg0)
}
// 函数表达式
fExpression('!')
var fExpression = function(arg0) {
alert('函数表达式' + arg0)
}
2. 递归
递归函数是一个函数通过该名字调用自身的情况构成的
例子1
var result = factorial(3)
// 3 * (3-1) * (2-1) = 6
console.log(result)
function factorial(num){
if (num <= 1){
return 1;
} else {
return num * factorial(num-1);
}
}
例子2 - 改进
arguments.callee 是一个指向正在执行的函数的指针,因此可以用它来实现对函数 的递归调用
function factorial(num){
if (num <= 1){
return 1;
} else {
return num * arguments.callee(num-1);
}
}
但在严格模式下,不能通过脚本访问 arguments.callee,访问这个属性会导致错误。
例子3 - 改进
通过命名函数表达式实现相同结果
var factorial = (function f(num -1){
if (num <=1) {
return 1
} else {
return num * f(num -1)
}
})
这种方式在严格模式和 非严格模式下都行得通。
拓展
题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
var fib = function (n){
if(n == 1){
return 1;
}else if(n==2){
return 2;
}else if(n>2){
return fib(n-1) + fib(n-2);
}
}
console.log(fib(4));
当我们从第 n-1 阶楼梯爬到第 n 阶楼梯时,需要1步;
当我们从第 n-2 阶楼梯爬到第 n 阶楼梯时,需要2步.也就是说 到达第 n 阶楼梯的方法数等于到达第 n-1 阶楼梯的方法数加上到达第 n-2 阶楼梯的方法数,即f(n) = f(n - 1) + f(n - 2)},其正好符合斐波那契通项。
3. 闭包
闭包是一个有权访问另一个函数变量的的函数, 创建闭包的常见方式,就是在一个函数内部创建另一个函数
通过例子理解:
function compare(value1, value2) {
if (value1 > value2) {
return -1;
} else {
return -2
}
}
var result = compare(10, 15)
上面例子发生了什么?
1, 定义了compare函数,在全局调用了它
2,调用compare() 会创建包含arguments、value1 和 value2 的活动对象
3,全局环境的变量对象(包含result 和 compare)在compare() 执行环境处于第二位。
arguments 是一个对应于传递给函数的参数的类数组对象。
<img src="images/zuoyongyu.png">
后台的每个执行环境都有一个表示变量的对象——变量对象。
作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),但是闭包情况有所不同
--
function createComparisonFunction(age){
return function(obj1, obj2){
var value1 = obj1[age]
var value2 = obj2[age]
if(value1 > value2) {
return -1;
} else {
return 1
}
}
}
函数内部的匿名函数可以访问外部函数中的变量age,之所以可以访问外部变量,是因为内部函数作用域链包含了createComparisonFunction的作用域。
1,当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链。 然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象(activation object)
2,但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,......直至作为作用域链终点的全局执行环境。
var compare = createComparisonFunction("name");
var result = compare({ name: "Nicholas" }, { name: "Greg" });
在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。
<img src="images/zuoyongyu1.png">
(1)闭包与变量
闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量。
下面这段代码想要循环延时输出结果 0 1 2 3 4,请问输出结果是否正确,如果不正确,请说明为什么,并修改循环内的代码使其输出正确结果
var result = function() {
for (var i = 0; i < 5; ++i) {
setTimeout(function() {
console.log(i + " ");
}, 100);
}
}
result()
输出结果为 5 5 5 5 5
原因:
1,js 运行环境为单线程,setTimeout 注册的函数需要等到线程空闲时才能执行,此时 for 循环已经结束,i 值为 5
2, 又因为循环中 setTimeout 接受的参数函数通过闭包访问变量 i,所以 5 个定时输出都是 5。
修改方法:将 setTimeout 放在立即执行函数中,将 i 值作为参数传递给包裹函数,创建新闭包
可以通过创建另一个匿名函数强制让闭包的行为,符合预期
var result = function() {
for (var i = 0; i < 5; ++i) {
(function (i) {
setTimeout(function() {
console.log(i + " ");
}, 100);
})(i)
}
}
result()
闭包内逻辑: (闭包(num){ return next闭包() { return num } })(i) push => i 也就是num
例子2
function add() {
var x = 1;
console.log(++x);
}
add(); //执行输出2,
add(); //执行还是输出2
使用闭包,怎样才能使每次执行有加 1 效果呢?
function add() {
var x = 1
return function() {
console.log(++x)
}
}
var result = add()
result()
result()
(2)this对象
1, this 对象是在运行时基于函数的执行环境绑定的,
2, this 对象是在运行时基于函数的执行环境绑定的:在全局函数中,this 等于 window,而当函数被作为某个对象的方法调用时,this 等 于那个对象。
3, 匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。
名称 | 描述 | |
---|---|---|
局部函数 | 基于函数的执行环境绑定 | |
全局函数 | this 等于 window | |
匿名函数 | 匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window |
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
} };
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)
每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。
外部作用域中的 this 对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this
return function(){
return that.name;
};
} };
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)
3. 模仿块级作用域
JavaScript 没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
alert(i); //计数
}
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
var i; //重新声明变量
alert(i); //计数 }
JavaScript 从来不会告诉你是否多次声明了同一个变量;遇到这种情况,它只会对后续的声明视而不 见
(function(){
//这里是块级作用域
})();
JavaScript 将 function 关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。
4.私有变量
function add(num1, num2){
var sum = num1 + num2;
return sum;
}
私有变量有哪些?
num1, num2, sum
1.私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
2.把有权访问私有变量和私有函数的公有方法称为特权方法。
特权方法?
1,在构造函数中定义特权方法。
function MyObject(){
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function (){
privateVariable++;
return privateFunction();
};
}
能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的 所有变量和函数。
作用?
利用私有和特权成员,可以隐藏那些不应该被直接修改的数据
function Person(name){
this.getName = function(){
return name;
};
this.setName = function (value) {
name = value;
};
}
var person = new Person("Nicholas");
alert(person.getName()); //"Nicholas"
person.setName("Greg");
alert(person.getName()); //"Greg"
4.1 静态私有变量
通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法
(function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//构造函数
MyObject = function(){ };
//公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();
1,这个模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法
2,在私有作用域中, 首先定义了私有变量和私有函数,然后又定义了构造函数及其公有方法。
3,公有方法是在原型上定义的, 这一点体现了典型的原型模式。
这个模式和构造函数定义特权方法区别?
就在于私有变量和函数是由实例共享的。由于 特权方法是在原型上定义的,因此所有实例都使用同一个函数。
(function(){
var name = "";
Person = function(value){
name = value;
};
Person.prototype.getName = function(){
return name;
};
Person.prototype.setName = function (value){
name = value;
};
})();
var person1 = new Person("Nicholas");
alert(person1.getName()); //"Nicholas"
person1.setName("Greg");
alert(person1.getName()); //"Greg"
var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"
以这种方式创建静态私有变量会因为使用原型而增进代码复用,但每个实例都没有自己的私有变 量。到底是使用实例变量,还是静态私有变量,最终还是要视你的具体需求而定。
4.2 模块模式
模块模式(module pattern)是为单例创建私有变量和特权方法。所谓单例(singleton),指的就是 只有一个实例的对象 。 按照惯例,JavaScript 是以对象字面量的方式来创建单例对象的。
var singleton = {
name : value,
method : function () {
//这里是方法的代码
}
};
模块模式通过为单例添加私有变量和特权方法能够使其得到增强
var singleton = function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权/公有方法和属性
return {
publicProperty: true,
publicMethod : function(){
privateVariable++;
return privateFunction();
}
};
}();