定义:函数表达式区别于函数声明,也是一种定义函数的方式,形似与变量赋值,这个值就是函数体,例如:数组
var a = function(){}; // 函数表达式之匿名函数 var a = function fn(){}; // 函数表达式之具名函数 (function(){})(); // 匿名函数之当即执行函数 // 目前知道的是这三种形式, 但愿高人补充
定义:在一个函数中调用自身,递归必需要有结束条件阶乘闭包
// fibonacci数列 function fibonacci(n){ if(n == 1 || n == 2){ // 结束条件 return 1; }else{ var num = fibonacci(n-1) + fibonacci(n-2); // 递归调用 return num // 每一层递归都返回和 } }; console.log(fibonacci(6)); // 8
特色:
1 . 调用匿名函数表达式自身,为了便于维护,能够经过arguments.callee(指向当前函数的指针)来调用当前函数,这样作的好处是当递归函数换名称时不用更换内部的函数名称函数
function fibonacci(n){ if(n == 1){ return 1; }else{ var num = arguments.callee(n-1) * n ; return num } }; let a = fibonacci; fibonacci = null; console.log(a(6)); // 函数内在再次调用fibonacci就会报错 Uncaught TypeError: fibonacci is not a function
一变this
function fibonacci(n){ if(n == 1){ return 1; }else{ var num = arguments.callee(n-1) * n ; return num } }; let a = fibonacci; fibonacci = null; console.log(a(6)); // 720 可是在严格模式下回报错 Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed
二变prototype
'use strict'; let a = (function fibonacci(n){ if(n == 1){ return 1; }else{ var num = fibonacci(n-1) * n ; return num } }); let b = a; a=null; console.log(b(4)); // 24 ,这里至关于包了一层,若是外边的变量改变是不会影响函数内部调用的
注:在严格模式下arguments.callee会报错,能够经过命名函数表达式来实现指针
特色:关于闭包的特色都得先理解执行环境、做用域、活动对象的概念code
执行环境: 函数被调用时会建立当前函数的执行环境,能够想象成一个对象,对象包含一个属性,该属性指向做用域(做用域链表)
做用域,也能够当作是做用域链表,一个list,list的元素是指向活动对象,包括本做用域内的活动对象的指向和对上级的指向,当前函数执行完毕做用域就消失了
活动对象,包含当前函数内的全部属性,当没有做用域链引用时活动对象被销毁
注:指向能够理解为引用,像 a=[], a就指向了内存中的一个数组对象
{ scope: scopeList ----| } | 执行环境(context) | [ | activeObj1: activeObjects1, --|--| activeObj2: activeObjects2, --|--|--| ... | | | ] | | | 做用域链(scopeList) <----| | | { | | var1: 1, | | var2: 1, | | var1: [], | | } | | 活动对象(activeObjects1) <--| | { | _var1: 1, | _var2: 1, | _var1: [], | } | 活动对象(activeObjects2) <--| // 能够看出执行环境和做用域链是一对一的, 因此当执行完函数后执行环境就没了,做用域没有被引用了就也没了,可是活动对象和做用域链是多对多的(途中只展现了一对多) ,因此就算做用域没了,当前做用域的活动对象也可能被其它做用域引用(例如闭包),因此仍然存在于内存中
特色:闭包对外层活动对象是引用不是复制(也能够说是复制了引用),这里写一个亲身经历的笔试题递归
var nAdd = null; function f(){ let n = 99; nAdd = () => { ++n; } return () => { console.log(n); } }; var f1 = f(); var f2 = f(); nAdd(); f1(); f2(); nAdd(); f1(); f2();
我认为这个题挺有意思。这里不给答案,读者能够本身先猜一下,而后本身跑一下和本身的猜测对对。
我认为这个题目的关键在nAdd是在f函数的外层,也就是每次实例化这个f函数都会对nAdd从新赋值,从新赋值后执行环境中n会不一样,屡次赋值取最后一个,只要能搞清楚执行环境、做用域、活动对象的关系其实不难,不会也不要紧,一开始看到我也懵。内存
特色:特色和定义息息相关,this和执行环境绑定,只要执行完毕执行环境不存在了就,例如:
var name = 'china,hebei'; var province = { name: 'hebei', getName: function(){ return function(){ return this.name; }; } }; console.log(province.getName()()); // china,hebei
从结果来看this指的是全局的那个this,这个和常规理解不太同样,按说getName属于province这个对象,this指向province才对,想一想定义就明白了,province.getName()这个执行了getName函数,并返回了一个函数体,再次执行这个函数体的时候getName()已经执行完了,执行完了执行环境固然就不存在了,this就返回给执行的环境(全局环境),那如何改为指向province呢?
var name = 'china,hebei'; var province = { name: 'hebei', getName: function(){ let that = this; return function(){ return that.name; }; } }; console.log(province.getName()()); // hebei
很容易理解,that在getName执行完毕后并不会消失,由于它所在的活动对象还被最后返回的函数的做用域链引用着,因此最后输出的就是hebei
定义:经过建立一个当即执行函数来模仿模块做用域的效果,普通的{}没有块级概念
for(var i=0; i<2; i++){ console.log(i); } console.log(i) // 2
块级做用域,很简单,经过函数做用域封装一层便可,例如
(function(){ for(var i=0; i<2; i++){ console.log(i); } })() console.log(i) // Uncaught ReferenceError: i is not defined
定义:在函数定义的变量和方法均可以当作是私有变量,能够经过在函数建立闭包实如今函数外部访问私有变量,称之为共有方法(特权方法),例如:
function Student(){ var name = 'jiang'; this.getName = function(){ return name; }; }; var xiaoming = new Student(); console.log(xiaoming.getName()); // jiang 只有这种特权方法能够访问到
特色:私有变量只能经过特权方法在函数外部被访问
解决的问题:加强函数的封装性,函数做用域内得变量只能经过特权方法访问
带来的问题:每一个实例都会从新建立一个特权方法
定义: 在私有做用域内(当即执行函数)定义函数内的私有变量和全局的(变量没有声明就赋值时)匿名函数,为匿名函数添加原型方法,原型方法内访问函数内的变量,这样在函数外部能够能够经过变量名称直接访问全局的匿名函数上的原型方法,方法内部能够访问函数私有变量
(function(){ let name = 'jiang'; student = function(){}; student.prototype.getName = function(){ return name; }; })() console.log(student.prototype.getName()); // jiang
单例模式,例如
var a = {}; var a = {}; // 老是只有一个a对象
var a = (function(){ var name = 'jiang'; // 私有变量 return{ // 单例模式 getName: function(){ return name; } } })() console.log(a.getName());
定义:将函数内返回的对象经过构造函数的方式声明,而后为其添加特权方法和属性,而后将对象返回,这样的对象就能够经过instanceof确认其类型了
var a = (function(){ var name = 'jiang'; function Student(){}; var xiaoming = new Student(); xiaoming.getName = function(){ return name; }; return xiaoming; })()