概念
把一个相对独立的完整功能的代码块封装起来
使程序变得更剪短清晰
有利于程序维护
可以提高程序开发效率
提高代码复用性
匿名函数
函数名部分可以不写,被当做匿名函数,函数为匿名函数的时候可以自调用
<script> (function () { console.log("自调用成功") }) </script>
形参实参
在函数声明时候写的参数,形式参数,简称形参。位于函数内部相当于局部变量。
在函数调用的时候,往里面传入的参数,实际参数,简称实参。
一一对应接收,可以有多个
arguments:天然存在于函数里,当前函数执行时,传入实参的集合列表,是一个类数组(伪数组)。具有数组的长度和下标,但是没有数组的方法。适合在参数个数不确定的情况下使用。
函数定义
函数声明方式
<script> function fn1(a,b) { return a+b; } </script>
函数表达式方式
<script> var fn2=function(a,b){ return a+b; } </script>
全局变量和局部变量
函数内部或外部,赋值表达式(没有var),默认在全局生成一个隐式的全局变量
扫描二维码关注公众号,回复: 1485497 查看本文章防止两个函数相互影响,各自加各自的形参,此时形参相当于局部变量。
全局变量在函数外部能用,在函数内部也可以调用
局部变量在外部使用会报错,有自己的局部作用域
递归
函数在自身中调用自身,一定要加条件在某一刻停止递归,否则会变成死递归
<script> var i=0; function fn1() { console.log("fn1"); i++; if(i<10) { fn1(); } } fn1(); </script>
回调
把一个函数当做参数传入另一个函数内部 这个参数函数就是回调函数
<script> var num=10; var fn=function() { console.log("fn"); } function fn1(a) { a(); } fn1(fn); </script>
三个重要内容
arguments
是函数调用的时候实参的集合,伪数组
实际应用于参数个数不确定的时候
return
可以返回任何数据类型;不明写的默认返回undefined;返回返回值之后,后续代码不执行,直接终止函数。
返回值需要接收
this
指向很重要
函数进阶
函数的定义方式
函数的声明
函数的声明function foo ( ) {}
预解析的时候,同名函数和变量以函数为准
<script> console.log(typeof fn) // function var fn = 123 function fn() { console.log('hello') } </script>
执行的时候对 fn 重新赋值,因为函数 fn 已经被解析过,所以这里不再执行,所以最终输出结果是 number
<script> var fn=123 function fn() { console.log('hello') } console.log(typeof fn) </script>
函数的表达式
函数的表达式 var foo=function ( ) {}
函数表达式类似于普通变量赋值,函数
<script> console.log(typeof fn) var fn =function() { console.log('hello') } </script>
函数声明与函数表达式的异同
函数声明必须有名字
函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用
函数表达式类似于变量赋值
函数表达式可以没有名字,例如匿名函数
函数表达式没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用
拓展;在 JavaScript 中没有块作用域,花括号就是块,if 块中声明的变量成员都存在于执行 if 语句所处的作用域环境,这里的代码一些比较老的浏览器会先进行函数提升,下面的语句,只要后面调用 fn1() fn2() 在 IE10 及以下的浏览器中两个函数都会执行
<script> var condition=true if(condition) { function fn1 () { console.log('hello') } }else { function fn2 () { console.log('world') } } </script>
对于上面的方式,使用函数表达式来处理就可以了,因为函数表达式没有函数提升
new Function方式
函数表达式或者函数声明,都是 Function 的实例,类似于{}和 new Object()
这种写法很少用,因为它的执行效率比较低,因为它是在执行阶段根据传递的字符串来创建函数,所以效率很低
函数的调用方式
普通函数
构造函数
对象方法
函数也是对象
所有函数都是Function的实例
Function的实例是Function自己
call、apply、bind
call
fun.call(thisArg[,arg1[,arg2[,...]]])
thisArg在fun函数运行时指定的this值
如果制定了null或者undefined则内部this指向window
arg1,arg2指定的参数列表
函数的call方法可以实现改变内部this的指向,然后调用函数
实例
定义一个数组
定义一个函数handle,传入参数i
功能是打印输出键值对
循环遍历对象数组
循环中自调用这个函数
用call指定this指向animal的第i项
把i当参数的原因是i会时时改变,如果不传入当参数,只会得出一个最终结果
<script> var animals = [ { species: 'Lion', name: 'King' }, { species: 'Whale', name: 'Fail' } ]; var handle = function (i) { // this = animals[i] this.print = function () { console.log('#' + i + ' ' + this.species + ': ' + this.name); } } for (var i = 0; i < animals.length; i++) { ;(function (i) { this.print = function () { console.log('#' + i + ' ' + this.species + ': ' + this.name); } }).call(animals[i], i) } console.log(i) </script>
call方法的其他使用场景
这是一个伪数组,伪数组不能用forEach,但是可以借用call
<script> var obj={ 0:'a', 1:'b', 2:'c', length:3 } Array.prototype.forEach.call(obj,function(item,index) { console.log(item,index) }) </script>
这里获取的divs是一个伪数组对象,要调用forEach的方法,可以先用一个空数组调用,然后指向这个伪数组就可以了
getElementsByTagName获取的没有forEach方法
querySelectorAll 的方法原型中有forEach方法
<script> var divs=document.getElementsByTagName('div') ;[].forEach.call(divs,function(item) { console.log(item) }) </script>
apply
fun.apply(thisArg,[argsArray])
thisArg在fun函数运行时指定的this值,如果制定了null或者undefined则内部this指向window
argsArray包含多个参数的数组
apply必须传数组,会把数组给展开,然后一一对应地传递给fn方法,只能处理一个数组参数,其他的不处理
bind
bind( )函数会创建一个新的函数(绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体。
当目标函数被调用的时候this值绑定到bind( )的第一个参数,该参数不能被重写。
绑定函数被调用时,bind( )也接收预设的参数提供给原函数。
一个绑定的函数也能使用new操作符创建对象,这种行为就像把原函数当成构造器。
提供的this值被忽略,同时调用的参数被提供给模拟函数
fun.bind(thisArg[,arg1[,arg2[,...]]])
返回值:由指定的this值和初始化参数改造的原函数拷贝
call和apply的方法是不绑定的,直接调用
bind方法不调用,而是返回一个指定this环境的新的函数
总结
call和apply特性一样
都是用来调用函数,而且是立即调用。可以在调用函数的同时,通过第一个参数指定函数内部this的指向。如果第一个参数指定了null或者undefined则内部this指向window。
call( )方法接收的是若干个参数的列表,参数之间用逗号隔开。
apply( )方法接收的事一个包含多个参数的数组
bind
可以用来指定内部this的指向,然后生成了一个改变了this指向的新的函数。不会调用。
支持传递参数,bind的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部
函数的其他成员
arguments 实参集合
caller 函数的调用者
length 形参的个数
name 函数的名称
高阶函数
函数可以作为参数
<script> function eat (callback) { setTimeout(function () { console.log('吃完了') callback() },100) } eat(function () { console.log('去唱歌') }) </script>
函数可以作为返回值
<script> var isArray = generateCheckTypeFn('[object Array]') var isObject = generateCheckTypeFn('[object Object]') var isString = generateCheckTypeFn('[object String]') var isNumber = generateCheckTypeFn('[object Number]') function generateCheckTypeFn(type) { // 没调用一次 generateCheckTypeFn 内部就要创建一个函数作为返回值 var checkType = function (obj) { return Object.prototype.toString.call(obj) === type } return checkType } </script>
函数闭包
作用域链
定义三个变量abc在不同的位置,fn1中找a,先在自己身上找,找不到,跳到上一级,fn中找,找不到,跳到全局,找到了。找b和c同理,fn2中找c,自己身上没有,父级fn中没有,全局中也没有,所以会报错。fn2不能跑到fn1中找fn1的变量,这就是作用域链
<script> var a=10 function fn() { var b=20 function fn1() { var c=30 console.log(a+b+c)//60 } function fn2() { var d=40 console.log(c+d)//报错 } fn1() fn2() } fn() </script>
函数的作用域链以函数定义时的位置为准,不是调用时
闭包
就是能够读取其他函数内部变量的函数,由于在JavaScript语言中,只有函数内部的子函数才能读取变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”,所以在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
在getCount中获取外部的count,之后在setCount中进行设置,之后再输出的时候,count已经发生了变化
<script> function fn () { var count=0 return { getCount:function () { console.log(count) }, setCount:function () { count++ } } } var fns=fn() fns.getCount()//0 fns.setCount() fns.getCount()//1 </script>
在函数内部,getFoo就是一个闭包环境(闭包函数)
<script> var getFoo=function() { return foo } </script>
闭包函数的核心就是作用域链
实例
定义一个数组,循环遍历这个数组,for循环中i数全局变量,自执行匿名函数中包含一个闭包函数
<script> var arr = [10, 20, 30] for (var i = 0; i < arr.length; i++) { arr[i] = (function (i) { return function () { console.log(i)//0 1 2 } })(i) } arr.forEach(function (item, index) { item() }) </script>
实例
定时器永远会放到普通代码之后才执行
for循环的时候不会等定时器,直接进行下一次循环
<script> console.log(111) for (var i = 0; i < 3; i++) { setTimeout((function (i) { return function () { console.log(i) } })(i), 0) } console.log(222) </script>
沙箱
利用匿名函数自执行保护内部成员不会被外部修改或访问,但是在函数内部可以访问修改
<script> ;(function () { var age = 3 function F () {} F.prototype.getAge = function () { return age } F.prototype.setAge = function (val) { if (val < 18) { return console.error('age 不能小于18岁') } age = val } window.F = F })() var f = new F() console.log(f.getAge()) </script>
思考
函数内部定义的函数,this指向window
inner是在函数内部定义的,有自己的指向,是window,所以不走作用域链
<script> var name = "The Window"; var object = { name: "My Object", getNameFunc: function () { var inner = function () { console.log(this.name)//The Window } return inner return function () { return this.name; }; } }; console.log(object.getNameFunc()())//undefined </script>
思考
<script> var name = "The Window"; var object = { name: "My Object", getNameFunc: function () { var that = this; // 除了 this // 其它成员,按照定义时所处的作用域环境沿着作用域链查找 return function () { return that.name; }; } }; console.log(object.getNameFunc()())//My Object </script>
递归
DOM树
<body> <div> 132 <div> 456 <p>123</p> <p>123</p> <p>123</p> <span>dsadsa</span> </div> <div> <div> <div> <p>1</p> <p>2</p> <p>3</p> <p>4</p> </div> </div> </div> </div> <script> // 把当前网页中所有的 DOM 节点元素获取到放到一个数组中 // 1. 先获取根节点 // 2. 然后拿到根节点的子节点 var rootNode = document.getElementsByTagName('html')[0] var domTree = [] function getDomTree(children) { for (var i = 0; i < children.length; i++) { var element = children[i] domTree.push(element) if (element.children) { getDomTree(element.children) } // 如果 element 还有 children 则继续遍历 element.children } } getDomTree(rootNode.children) console.log(domTree) </script> </body>
尾调用
尾调用:在一个函数执行的最后一步return调用另一个函数
<script> function f(x) { return g(x) } function g(x) { return x } console.log(f(10)) </script>
f(x)执行到最后一步,f(x)弹栈,g(x)进来,占用f(x)刚才的位置,相当于替换
下面三种都不是尾调用
第一个,在调用g之后还有赋值操作,不属于尾调用
第二个,调用后还有加操作,不属于尾调用
第三个,没有返回,不属于尾调用,相当于下图,返回一个undefined
<script> function f(x) { let y=g(x); return y; } function f(x) { return g(x)+1; } function f(x) { g(x) } </script>
尾调用和函数调用
函数调用会在内存形成一个调用记录,又称调用帧,保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用帧上方,还会形成一个B的调用帧。等到B运行结束,将结果返回到A,B的调用帧才会消失。如果函数B内部还调用函数C,那就还有一个C的调用帧,以此类推。所有的调用就形成一个调用帧。
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
try-catch
try-catch用来捕获异常
发生错误之后,try 后面的语句不执行,catch 后面的语句执行,finally 后面的语句执行
没有发生错误,try 后面的语句执行,catch 后面的语句不执行,finally 后面的语句执行
catch中传入的参数就是错误e,e.message表示错误短语,e.stack是具体错误堆栈信息,包含具体的消息,文件,行号等
Error是内置函数
EvalError 创建一个error实例,表示错误的原因,与eval()有关
InternalError 创建一个代表 JavaScript 引擎内部错误的异常跑出实例。如:递归太多
RangeError 创建一个 error 实例,表示错误的原因:数值变量或参数超出其有效范围
ReferenceError 创建一个error实例,表示错误的原因:无效引用
SyntaxError 创建一个error实例,表示错误的原因:eval()在解析代码的过程中发生语法错误
TypeError 创建一个error实例,表示错误的原因:变量或参数不属于有效类型
URIError 创建一个error实例,表示错误的原因,给encodeURI()或decodeURI()传递的参数无效