1 函数表达式
创建函数有两种方式 函数声明 与 函数表达式
函数声明
// 函数声明 函数声明会有函数提升即可以先使用,后声明
f();
function f(){
console.log('函数提升')
}
函数表达式
函数表达式有几种方式,最常用的是函数字面量形式
1.1 递归
递归式一个函数通过名字调用自身的情况下构成的
这是一个求阶乘的递归函数
function factorial(num){
if(num <= 1){
return num
} else {
return num * factorial(num - 1)
}
}
console.log(factorial(5))
但是这样却可能报错
let anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(3)) // error: factorial is not a function
这是由于factorial已经等于null了,而递归函数内部仍然调用的是 num * factorial(num - 1)
// argumrnts.callee指向正在执行的函数的指针
function factorial(num){
if(num <= 1){
return num;
} else {
return num * arguments.callee(num-1)
}
}
// 这样就不会报错了
let anotherFactorial = factorial;
factorial = null;
console.log(anotherFactorial(3))
1.2 闭包
闭包是指有权访问另一个函数作用域中的变量 的函数
// 闭包
let dad = {
age: 10
}
let mom = {
age: 40
}
function createComparisonFunction(propertyName){
// 返回一个匿名函数
return function(object1 , object2){
let value1 = object1[propertyName]
let value2 = object2[propertyName]
if(value1 > value2){
return ">"
}
return '<='
}
}
// 当匿名函数被返回后,他的作用域链会包含createComparisonFunction的活动对象和全局变量对象
let compare = createComparisonFunction('age') // 这时候compare就是返回的匿名函数
let result = compare(dad , mom) // compare里还可以访问createComparisonFunction 里面的 propertyName
console.log(result)
// 由于闭包会携带包含它的函数的作用域,所以会比其他函数占用更多的内存
1.2.1 闭包与变量
作用域链的这种配置会引出一个缺陷,即闭包只能取得包含函数中任何变量的最后一个值
// 作用域链的而这种配置机制引出一个副作用,即闭包只能取得包含函数中任何变量的最后一个值
function createFunction(){
let result = [];
for(var i = 0 ; i < 10 ; i++){
result[i] = function(){
return i;
}
}
return result
}
// 表面上看,返回的是一个函数数组,数组里的每个元素都会返回i
// 但实际上每个都会返回10
// 因为var声明的变量不存在块级作用域,实际上每个函数都包含 createFunction()的活动对象,他们引用的都是同一个变量i,而当createFunction()返回后,i的值是10
console.log(createFunction()1) // 10
console.log(createFunction()2) // 10
// 我们可以通过创建一个匿名函数强制让强制让闭包的行为符合预期
function createFunction(){
let result = [];
for(var i = 0 ; i < 10 ; i++){
result[i] = function(num){
return function(){
return num
}
}(i)
}
return result;
}
console.log(createFunction()[1]())
1.2.2 关于this对象
this对象是在运行时基于函数的运行环境绑定的
在全局函数中,this等于window
而当函数作为某个对象的方法调用时,this等于那个对象
不过匿名函数的执行环境具有全局性,因此this通常指向window,
但有时候编写闭包的方式不同,这一点可能不那么明显
let name = 'my window'
let obj = {
name: 'my obj',
say: function(){
return this.name
}
}
console.log(obj.say()) // 'mu obj'
1.2.3 内存泄漏
function assinHandler(){
let element = document.getElementById("dom")
element.onclick = function(){
alert(element.id)
}
}
assinHandler()
// 以上代码创建了一个作为element元素事件处理的闭包,而这个闭包又创建了一个循环引用。由于匿名函数保存了一个对assinHandler的活动对象
// 的引用,所以无法减少element的引用数。只要匿名函数存在,element的引用至少为1,所以它占用的内存就用愿不会被回收
// 闭包会包含函数的整个活动对象,所以即使闭包不直接引用element,包含的活动对象中仍然会保存一个引用
// 修改
function assinHandler(){
let element = document.getElementById("dom");
let id = element.id; // element.id 保存在一个变量中
element.onclick = function(){
alert(id)
}
element = null; // 解除对element对象的引用,
}
1.3 模仿块级作用域
es6已有块级作用域的概念
1.4 私有变量
严格讲,js中没有私有成员的概念,所有对象属性都是公有的。
不过在函数中定义的变量,都可以认为是私有变量。因为在函数外部不能访问他们。
我们把有权访问私有变量和私有函数的共有方法称为特权方法,而创建特权方法的的方式有两种
1.4.1 在构造函数中定义特权方法
// 通过构造函数创建特权方法
function Person(name){
this.getName = function(){
return name;
}
this.setName = function(value){
name = value
}
}
let p = new Person('jack')
console.log(p.getName())
// 通过在私有作用域中定义私有变量或函数,也能创建特权方法
(function(){
var name = 'wu';
Person = function(value){ // 未经声明的变量,总会创建一个全局变量
name = value
}
Person.prototype.getName = function(){
return name;
}
Person.prototype.setName = function(value){
name = value
}
})()
let p = new Person('wwww');
console.log(p.getName())
1.4.2 模块模式
前面的模式是用于为自定义类型创建私有变量和特权方法的,这里的模块是为单例创建私有变量
单例:指的是只有一个实例的变量,通常js中用对象字面量的方式来创建单例对象
let singleObj = {
name: 'singlr',
method: function(){
consol.log('sss')
}
}
如果必须创建一个对象,并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式
let app = function(){
let component = new Array();
return {
setCom: function(item){
component.push(item)
},
getCom: function(){
return component
}
}
}()
console.log(app.getCom())
app.setCom('sss')
console.log(app.getCom())
1.4.3 增强的模块模式
在返回对象之前加上对其增强的代码。这种增强模式适合那些单例必须是某种类型的实例,同时还必须还添加某些属性或方法对其加以增强的方法。
// 增强模块
function APP(){
}
let singleton = function(){
let component = new Array(); // 私有变量
let app = new APP(); // 创建对象 这个对象是APP的实例
app.getCom = function(){
return component
}
app.setCom = function(val){
component.push(val)
}
return app;//返回这个对象
}()
singleton.setCom('new');
console.log(singleton.getCom())
1.5 小结
函数表达式无需对函数命名,从而实现动态编程。
匿名函数,是一种使用js编程的强大方式,一下是函数表达式的特点。
- 函数表达式不同于函数声明,函数声明需要名字,函数表达式不需要。没有名字的函数表达式也叫匿名函数。
- 在无法确定如何引用函数的情况下,递归函数就会变得复杂
- 递归函数应该始终使用arguments.callee(),因为函数名可能会变化
当在函数内部创建其他函数时,就创建了闭包,闭包有权访问 包含它的函数 内部的所有变量,原理如下: - 在后台执行环境中,闭包的作用域包含 自己的作用域 包含它的函数作用域 全局作用域
- 通常,函数作用域及其所有变量都会在函数执行结束后被销毁。
- 但是,当函数返回了一个闭包时,这个函数的作用域会在内存中保存到闭包不存在为止
使用闭包可以在js中模仿块级作用域,要点如下: - 创建并立即执行一个函数,这样既能执行其中代码,又不会在内存中留下对该函数的引用
- 结果就是函数内部所有变量会立即销毁–除非将某些变量赋值给了 包含作用域(即外部作用域)中的变量
闭包还可以用来在对象中创建私有变量,相关概念如下:
- 即使js中没有正式的私有对象属性的概念,但可以用闭包来实现公有方法,而通过公有方法可以访问包含在函数作用域中定义的变量
- 有权访问私有变量的方法叫特权方法
- 可以使用构造函数模式 原型模式来实现自定义类型的特权方法,也可以使用模块模式,增强的模块模式来实现单例的特权方法