//函数表达式:
//使用函数声明创建函数:
function functionName(arg0,arg1,arg2){
console.log();
}
console.log(functionName.name); //函数名.name:返回函数名。functionName
sayHi(); //函数声明提升:在执行代码之前会先读取函数声明。所以可以把函数声明放在调用它的语句后面。
function sayHi() {
console.log("hi!");
}
//使用函数表达式创建函数:
//函数表达式不同于声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫匿名函数(本章重点)。
sayHi(); //Error:函数还不存在。因为用了匿名函数,没有进行函数声明提升。
sayHi = function() {
console.log("hi!");
};
if(true) { //不可以使用这样的条件表达式。
function sayHi() {
console.log("hi!");
}
} else {
function sayHi() {
console.log("no!");
}
}
sayHi(); //"no!"本质上是错误的,每种浏览器处理方式不一样。
if(true) { //但可以使用这样的条件表达式。
sayHi = function() {
console.log("hi!");
}
} else {
sayHi = function() {
console.log("no!")
};
}
sayHi(); //"hi!"
//这样不会发生意外,不同的函数根据条件被赋给sayHi
//匿名函数实例:创建比较器的函数。
function createComparisonFunction(propertyName) {
return function(object1,object2) {
var val1 = object1[propertyName];
var val2 = object2[propertyName];
if(val1<val2) {
return 1;
} else if(val1>val2) {
return -1;
} else {
return 0;
}
};
}
function functionName () {
return arguments.callee.name;
}
console.log(functionName()); //functionName:使用函数的.name属性获取了函数名。
//以前写过的例子:递归函数应该始终使用arguments.callee来递归地调用自身,降低耦合性。
function factorial(num) {
if(num==1)
return 1;
else
return num*arguments.callee(num-1); //降低耦合性。
}
console.log(factorial(6));
//注意:严格模式下不能使用arguments.callee函数,会发生错误。
//解决方法:使用命名函数表达式(给匿名函数命名)
var factorial1 = function f(num) { //用f表示匿名函数,即使将函数名改变,函数也不会出错。
if(num==1)
return 1;
else
return num*f(num-1);
}
var factorial2 = factorial1;
console.log(factorial1(6)); //720
console.log(factorial2(6)); //720,改了名仍然有效。
//闭包:有权访问另一个函数作用域中变量的函数(通过外部函数中的变量创建内部函数)。
//例:
function createComparisonFunction(propertyName) {
return function(object1,object2) {
var val1 = object1[propertyName];
var val2 = object2[propertyName]; //这两行代码访问了外部函数中的变量。
if(val1<val2) {
return 1;
} else if(val1>val2) {
return -1;
} else {
return 0;
}
};
}
var fun = createComparisonFunction("name");
console.log(fun.propertyName);
*/
/*
* 对闭包的理解:
* 一般函数在被调用时,会创建一个执行环境,执行环境指向了一组指针:作用域链
* 作用域链中优先级为 函数活动对象→全局对象。
* 而闭包被调用时,有闭包的作用域链为 闭包活动对象→外部函数活动对象→全局变量;
* 外部函数的作用域链为 外部函数活动对象→全局对象。
* 由于外部函数的活动对象一直存在于闭包的作用域链中。
* 所以即使外部函数执行完毕以后,其活动对象也不会被销毁(因为一直在被闭包的作用域链引用)。
* 而是一直被匿名函数的作用域链所指向从而留在内存中,直到匿名对象被销毁。
* 解决:少用闭包,在绝对必要时才用。用完以后手动解除其和外部函数活动对象之间的关联(理解:《JavaScript高级程序设计》 P180 图7-2)
*/
//手动解除关联:
var compareNames = createComparisonFunction("name"); //将函数创建的闭包函数记录以调用
var result = compareNames({ name: 'Steve'},{ name: 'Erison'}); //调用函数
//使用完毕后:
compareNames = null; //解除对匿名函数的引用,释放内存
//以便垃圾收集器释放闭包活动对象
//然后其作用域链中的其他活动对象(除了全局作用域)也被安全地销毁。
//闭包的副作用:闭包只能取的外部函数中任何变量的最后一个值
function createFunctions() { //目的:通过闭包创建十个函数,十个函数的返回值为从0到9。
var result = new Array();
for(var i=0;i<10;i++) {
result[i] = function() {
return i;
}
}
return result;
}
var result = createFunctions();
for(var i=0;i<result.length;i++) {
console.log(result[i]()); //返回了10个10!
}
//原因:闭包的作用域链中包含外部函数,所以闭包中的i是通过作用域链在外部函数中搜索来的。
//在最后return result时,i是10,故10个函数的返回值都是10.
//解决:
function createFunctions() {
var result = new Array();
for(var i=0;i<10;i++) {
result[i] = function(num) {
return function() {
return num;
}
}(i); //用定义匿名函数的方式给数组赋予函数。最后传入一个参数i。由于函数参数是按值传递的,所以i会被赋值给参数num。
}
return result;
}
var result = createFunctions();
for(var i=0;i<result.length;i++) {
console.log(result[i]()) //返回了0到9
}
//闭包与this对象
var name = "The Window";
var object = {
name: "My Object",
get: function() { //get:返回一个匿名函数
return function() {
return this.name;
};
}
};
console.log(object.get()()); //The Window:返回的匿名函数中的this指向外部作用域。
//!*这里我没搞懂。书上的解释是:内部函数在搜索this和argument变量时,
//只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。
//让闭包访问外部函数对象:
var name = "The Window"
var object = {
name: "My Object",
get: function() {
var that = this;
return function() {
return that.name;
}
}
};
console.log(object.get()()); //My Object:在函数内定义了that指向object,起到过渡作用,使内部函数访问到object。
//细微区别导致this改变:
var name = "The Window";
var object = {
name: "My Object",
getName: function() {
return this.name;
}
}
console.log(object.getName()); //My Object:因为this.name就是object.name。
console.log((object.getName)()); //My Object:将object中的函数当作一个独立的函数运行,但this的值得到了维持。
//因为object.getName()和(object.getName)()的定义是相同的。
console.log((object.getName = object.getName)()); //The Window:
//!*这里也没太看懂。书上的解释:因为这个赋值表达式的值是函数本身。 //所以this的值不能得到维持。
//(因为将object中的函数重新赋予,this没有维持,所以改变了作用域?)
//IE中的BUG:如果闭包的作用域链中保存着一个HTML元素,该元素无法被销毁。
//因为IE中队JS对象和COM对象使用的不同的垃圾收集例程(JS:标记清除/COM:引用计数)
function assignHandler() {
var element = document.getElementById("someElement");
element.onclick = function() {
alert(element.id);
};
}
//由于匿名函数一直保存着对assignHandler活动对象的引用,导致活动对象一直存在,element的引用数至少是1,所占用的内存永远不会被回收。
//解决:不让闭包直接访问HTML属性,而是创建一个副本让闭包访问。
function assignHandler() {
var element = document.getElementById("someElement");
var id = element.id; //创建闭包要访问的属性的副本。
element.onclick = function() {
alert(id); //将内部函数解除对element的引用(变为引用所需要的element对象属性的副本)。
};
element = null; //解除对element的引用。
}
//由于被闭包访问的整个活动对象会一直存在,所以即使闭包引用的是id,包含函数中还是会存在一个对element的引用。所以要手动解除对element的引用。
//模仿块级作用域:js中没有块级作用域,语句块中的变量实际上在函数中,所以可以用匿名函数来模仿块级作用域。
function output() {
for(var i=0;i<10;i++) {
}
var i; //js中,后续声明一个已存在的变量会被无视,i还是10。
console.log(i); //后面的输出结果说明了,这里可以访问到i。
}
output(); //10:由于没有块级作用域,导致所有变量都在函数中。所以在函数内的for语句之外也可以访问到i。
(function(){
//块级作用域:创建一个匿名函数并立即执行。
for(var i=0;i<10;i++) {
}
})();
console.log(i); //错误:因为i在匿名函数模仿的块级作用域中,所以外部访问不到。
//用途:经常会用在全局作用域中的函数外部,为了不向全局作用域中添加过多的数据,增强封装型,满足程序员们的强迫症。
//私有变量
//构造函数方法
function Person(name) { //通过在构造函数内部定义两个特权方法,定义了私有变量和函数
this.getName = function() { //由于没有显式地定义name属性,除了通过这两个方法以外,没有其他方法可以访问name属性。
return name;
};
this.setName = function(value) {
name = value;
};
}
var person = new Person("Steve");
console.log(person.getName());
//通过构造函数定义特权变量缺点:像创建对象的构造函数模式一样,无法实现方法的复用。
//静态私有变量
//通过在私有作用域(通过匿名函数实现)中定义私有变量或函数,创建特权方法。
(function {
//私有变量和私有函数
var privateVariable = 10;
function privateFunction() {
return false;
}
//构造函数
MyObject = function() {};
//公有/特权方法
MyObject.prototype.publicMethod = function() {
privateVariable++;
return privateFunction;
};
})();
//与构造函数中定义特权方法的区别:将方法定义在原型上,原型上的方法是共享的。因为不管是哪个实例中的此方法都包含着对作用域的引用。
(function() {
var name = "";
Person = function(value) { //不使用var关键字:确保Person为全局函数
name = value;
}
Person.prototype.getName = function(){
return name;
}
Person.prototype.setName = function(value) {
name = value;
}
})();
var p1 = new Person("Steve");
console.log(p1.getName());
var p2 = new Person("Erison");
console.log(p1.getName()); //Erison
console.log(p2.getName()); //Erison 由于变量的共享,都被改成了Erison
//模块模式:为单例添加私有变量和特权方法。
var application = function(){ //创建了一个含有指定方法的实例:在私有域中定义方法和属性,并添加方法,然后立刻返回一个单例。
//私有变量和函数。
var components = new Array();
//初始化
component.push(new BaseComponent());
//公共方法
return { //用字面量定义法返回一个单例
getComponentCount: function(){
return component.length;
},
registerComponent: function(component){
if(typeof conponent == "object") {
components.push(conponent);
}
}
}(); //这种单例以Object形式存在(因为由对象字面量方法定义)。
//增强的模块模式。
//在返回对象之前加入对其增强的代码。适合单例必须是某种类型的实例。
//使用构造函数而不是字面量方法来返回对象
//修改后的上述代码:
var application = function(){
//私有变量函数
var components = new Array();
//初始化
components.push(new BaseComponent());
//创建局部副本:相比模块模式加强的地方,因为返回的对象必须是BaseComponent的实例!
var app = new BaseComponent();
//再为局部副本添加能够访问私有变量的公共方法
app.getComponentCount = function() {
return components.length;
};
app.registerComponent = function(component) {
if(typeof component == "object"){
components.push(component);
}
}
//返回局部副本,此时返回以后便是指定类型的单例了。
return app;
}();