函数介绍:
- 把一段相对独立的具有特定功能的代码块封装起来,形成一个独立实体,就是函数,起个名字(函数名),在后续开发中可以反复调用
- 函数的作用就是封装一段代码,将来可以重复使用
函数特点:
- 函数声明的时候,函数体并不会执行,只要当函数被调用的时候才会执行。函数一般都用来干一件事情,需用使用动词+名词,表示做一件事情
tellStory
sayHello
等一、函数的创建
1-1 函数的定义方式
1-1-1 函数声明
function go(a){
console.log(a)
}
1-1-2 函数表达式
```javascript var foo = function () {
}
<a name="Tew8Z"></a>
#### 1-1-3 函数声明和函数表达式的区别
```javascript
1.函数声明必须有名字
2.函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用
3.函数表达式类似于变量赋值
4.函数表达式可以没有名字,例如匿名函数
5.函数表达式没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用
下面是一个根据条件定义函数的例子:
if (false) {
function f () {
alert(1);
}
}
f();
//函数声明如果写在if else中,不同浏览器会有不同的结果
//谷歌报错 :f is not a function
//ie8 alert(1)
//说明不同浏览器的预解析规则不太一样
不过我们可以使用函数表达式解决上面的问题:
var f
if (false) {
f = function () {
console.log(1)
}
}
//函数表达式不会像函数声明一样被强行提升
1-1-4 new Function()
//所有的函数实际上都是Function的构造函数创建出来的实例对象
var f1 = new Function("num1","num2","return num1+num2");
1-2 函数的返回值
- 返回值就是函数的执行结构,js中函数可以没有返回值,使用return语句之后,返回值后面的语句不会执行
function show(){ return "hello world" console.log(3) } console.log(show()) //输出:hello world
1-3 函数也是对象
function f1(){}
1.所有函数都是 Function 的实例,所以函数也是对象(对象拥有 __proto__ 属性),函数拥有prototype属性
2.console.dir(f1) 打印函数
3.所有的函数实际上都是Function的构造函数创建出来的实例对象
f1.__proto__ == Function.prototype //结果为true,函数的__proto__指向了Function的prototype
4.console.dir(Function) Function既是一个函数也是一个对象,其实var Function = new Function(),这里就不讨论了。
Function.prototype.__proto__ == Object.prototype //Function的prototype中的__proto__最终都指向了Object的原型
5.console.log(Object.prototype.__proto__); //null
二、函数的传参
2-1 函数参数不定参(函数可以不传参或多传参)
函数内部有个arguments 对象,接收函数传递过来的参数,是一个类数组对象
function go(a,b,c){ console.log(a) console.log(a+b) console.log(a+b+c) } go() //不传参 undefined NaN go(10) //一个参数 10 NaN NaN go(10,20,30,50) //多余参数 10,30,60
2-2 默认赋值
预先给函数参数赋值
function go(type="get"){ console.log(type) } go() go("post")
三、重载
3-1 arguments的使用
JavaScript中,arguments对象是比较特别的一个对象,实际上是当前函数的一个内置属性。也就是说所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有的实参。arguments是一个伪数组,因此及可以进行遍历
3-2 模拟重载
- 根据传入参数不一样,动态决定调用哪种方法。
- JavaScript不支持重载,因为重复声明,覆盖掉了,可以用arguments对象模拟重载
```javascript
1.声明覆盖
function go(a,b){
console.log(a+b) } function go(a){ console.log(a) } go(10,20) //10
2.模拟重载 function show(){ if(arguments.length==2){ console.log(arguments[0]+arguments[1]) }else if(arguments.length==1){ console.log(arguments[0]) } } show(10) show(10,20)
<a name="VpQ8B"></a>
## 四、解构赋值
- ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
old:
```javascript
let a = 1;
let b = 2;
let c = 3;
new:
let [a, b, c] = [1, 2, 3];
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
<script>
/* 解构 */
/* var obj={name:"xxx",age:18,sex:"男"}
var {name,age,sex}=obj;
console.log(name) */
var {name,age,sex}={name:"xxx",age:18,sex:"男"}
console.log(name)
console.log(age)
console.log(sex)
//不完全解构
let [a, [b], d] = [1, [2, 3], 4];
console.log(a)
console.log(b)
console.log(d)
</script>
五、箭头函数和回调函数
5-1 箭头函数
5-1-1 箭头函数的使用
var go=x=>x;
console.log(go(10)) //10
var test=y=>console.log(y);
test(20) //20
//两个参数
var getInfo=(x,y)=>{
console.log(x)
console.log(x+y)
}
5-1-2 箭头函数的作用
解决函数内部this关键字指向问题,当函数直接调用时,this指向window
test.onclick=function(){
console.log(this)
go()
}
function go(){
console.log(this) //window调用
}
test.onclick=go; //test调用
用箭头函数解决this指向问题
var test=document.getElementById("test");
test.onclick=function(){
setTimeout(()=>{
console.log(this)
},300)
}
5-1-3 箭头函数的特点
- 普通function的提升在变量提升之前,箭头函数没有函数提升
- 箭头函数没有this,super,函数体内部的this对象就是定义的时候所在的对象而不是使用时所在的对象
- 箭头函数没有arguments对象,如果要用,可以使用rest参数
- 箭头函数不能作为构造函数,不能被new
- 箭头函数没有原型(prototype)
- 不能通过call、bind、apply方法修改箭头函数中的this
- 不可以使用yield命令,因此箭头函数不能做Generator函数
5-2 回调函数
- 就是将函数作为参数传递给另外一个函数
- 作用:可以获取函数内部的值
ajax中,常使用回调函数进行处理,代码就可以进行其他任务,而无需等待
<script>
function http(callback){
var data={"name":"xxx",age:18};
callback(data);
}
function handleData(res){
console.log(res)
}
http(handleData);
</script>
六、this指向
对象函数中的this
var obj={
name:"html",
sayName:function(){
console.log(this.name)
}
}
var skill={
name:"javascript",
saySkill(){
console.log(this.name)
}
}
obj.sayName() //html
skill.saySkill() //javascript
var obj = {
name:"zhangsan",
say:function(){
console.log(this,this.name) //obj對象
}
}
obj.say();
构造函数中的this
function Person(name,age){
//this就是new出來的p對象
this.name = name;
this.age = age;
}
var p = new Person("xx",19);
事件中的this
var btn = document.querySelector("#btn");
btn.onclick = function(event){
console.log(this); //當前綁定事件的元素
}
定时器中的this
var obj2 = {
name:"zhangsan",
say2:function(){
//this是 obj2對象
setTimeout(() => {
console.log(this) //obj2
}, 1000);
},
say3:function(){
//this 是obj2對象
//定時器,如果是普通函數,this是window
setTimeout(function(){
console.log(this) //window
},1000)
}
}
obj2.say2();
obj2.say3()
箭头函数中的this
声明函数时所在作用域的this
闭包中的this:指向window
var obj3 = {
eat:function(){
return function(){
console.log(this,"0000");
}
}
}
obj3.eat()();
6-2 改变函数内部的关键字指向
bind() , call() , apply() , 箭头函数
bind() 改变函数内部this执行的上下文环境
var name="zhangsan"; var obj={ name:"lisi" } var test=function(){ console.log(this.name) }.bind(obj); test() //输出:lisi 不用bind 输出:zhangsan
call() 调用的时候改变this关键字指向问题
call(thisObj,params) //第一个参数指向函数内部的this,之后的参数是需要被传入函数的参数
var li="lisi"; var obj={ name:"zhangsan" } var test=function(){ console.log(this.name) } test.call(obj) //zhangsan function sayName(label){ console.log(label+":"+this.name) } var name="window"; var cheng={ name:"cheng" } var wang={ name:"wang" } sayName() //undefiend:window sayName("wiow") //wiow:window sayName.call(cheng,"chengchao") //chengchao:cheng sayName.call(wang,"wangwu") //wangwu:wang
apply()
fun.apply(thisArg, [argsArray])传递的是数组
function go(name,age){
console.log(this.name+":"+this.age)
}
var zhang={
name:"zhangsan",
age:18
}
var li={
name:"lisi",
age:21
}
go.call(zhang,"zhang",111) //输出:zhangsan:18
go.apply(li,["li",24]) //输出:lisi:21
6-3 三者区别
bind改变函数的上下文执行环境,不会马上执行
而被call和apply绑定的函数会马上执行
七、自调函数
7-1 1.0
(function(){
console.log("自调函数1.0")
})()
7-2 2.0
(()=>{
console.log("自调2.0")
}())
7-3 自调函数的好处
我们自己写的代码不会对外部的js代码有任何影响
//我们在开发的时候会引入很多第三方的js文件,有时候我们并不直到在第三方js中谢了什么函数
//假如第三方js已经写好了一个函数如下:
function foo(){
console.log("aa");
}
//而我们不知情,又写了下面代码
function foo(){
console.log("bb");
}
//这个时候我们自己写的代码就会把第三方的js代码给顶掉
//而如果使用自调用函数则没有这个情况
(function(){
function foo(){
console.log("这是我自己的foo");
}
foo();
}());
foo();
//推荐在以后的开发工多使用自调用函数,把所有的js代码都放在自调用函数中
7-4 自调用案例(沙箱)
<div>这是div</div>
<div>这是div</div>
<div>这是div</div>
<p>这是p</p>
<p>这是p</p>
<p>这是p</p>
<script>
var getTag = 10;
var dvObjs = 20;
var pObjs = 30;
//此处如果不加沙箱,在调用getTag方法的时候会报not function错误,原因是getTag()这个函数会被提升
到script代码块的最前面
(function () {
//根据标签名字获取元素
//自调用函数中的函数是私有的,在外部不可以被访问
function getTag(tagName) {
return document.getElementsByTagName(tagName)
}
//获取所有的div
var dvObjs = getTag("div");
for (var i = 0; i < dvObjs.length; i++) {
dvObjs[i].style.border = "2px solid pink";
}
//获取所有的p
var pObjs = getTag("p");
for (var i = 0; i < pObjs.length; i++) {
pObjs[i].style.border = "2px solid pink";
}
}());
console.log(getTag);
console.log(dvObjs);
console.log(pObjs);
</script>
7-5 自调函数注意点
// 问题1:如果存在多个自调用函数要用分号分割,否则语法错误
// 下面代码会报错
(function () {
}())
(function () {
}())
// 问题2:当自调用函数 前面有匿名函数时,会把自调用函数作为参数(其实会匿名函数和自调用函数中的空格去掉)
var a = function () {
alert('11');
}
(function () {
alert('22');
}())
// 代码规范中会建议在自调用函数之前加上分号
;(function () {
}())
;(function () {
}())
7-6 自调函数中的全局变量
<script>
//沙箱---小环境
(function () {
var num=10;
})();
console.log(num); //无法访问
</script>
//虽然在自调用函数中的变量是局部变量,但是我们仍然可以通过window将自调用函数中的局部变量变为全局变量
<script>
//沙箱---小环境
(function () {
num=10; //这个地方相当于window.num = 10;
})();
console.log(num);
</script>
//虽然在自调用函数中的变量是局部变量,但是我们仍然可以通过window将自调用函数中的局部变量变为全局变量
//将局部变量变成全局变量更常见的做法如下:
(function (window) {
var num=10;//局部变量
window.num=num;//全局变量
})(window);
console.log(num);
7-7 自调函数中封装Random对象
//通过自调用函数产生一个随机数对象,在自调用函数外面,调用该随机数对象方法产生随机数
(function (window) {
//产生随机数的构造函数
function Random() {
}
//在原型对象中添加方法
Random.prototype.getRandom = function (min,max) {
return Math.floor(Math.random()*(max-min)+min);
};
//自调用函数中的普通函数是私有函数,只能够在当前自调用函数内部使用
//要想把这个方法暴露给外部,可以设置给window,或者设置给Random函数
function test1(){
console.log("test1");
}
//把Random对象暴露给顶级对象window--->外部可以直接使用这个对象
window.Random=Random;
})(window);
//实例化随机数对象,这边能够使用Random函数,原因是Random函数已经被绑定到了window对象上
var rm=new Random();
//调用方法产生随机数
console.log(rm.getRandom(0,5));
test1(); //无法访问,test1()是私有函数
八、闭包
闭包:函数中嵌套函数,定义在一个函数内部的函数,静态保存所有了父级作用域的内部函数。
8-1 特点:
- 内部函数可以使用外部函数中声明的变量
- 延长外部函数中变量的作用域链
-
8-2 闭包的分类
函数模式闭包
//1.函数模式的闭包 function f1(){ var num = 10; return function(){ console.log(num); } } var ff = f1(); ff();
对象模式闭包
//2.对象模式的闭包 function f3(){ var num = 10; return { age:num } } var obj = f3(); console.log(obj.age);
8-2 用途:
- 缓存,延长变量的作用域链
- 面向对象中的对象
- 实现封装,防止变量跑到外层作用域中,发生命名冲突 ```javascript //闭包的主要作用:缓存数据,延长变量的作用域链 //1.普通的函数 function f1() { var num = 10; num++; return num; } console.log(f1()); console.log(f1()); console.log(f1());
//2.函数模式的闭包 function f2() { var num = 10; return function () { num++; return num; } } var ff = f2(); console.log(ff());//11 console.log(ff());//12 console.log(ff());//13
//可以发现每次ff执行的时候,num的值发生了累加(其实是对应的内存未被释放)
<a name="1TPJ7"></a>
#### 8-3 危害:
1. 闭包有一个非常严重的问题,那就是内存浪费问题,这个内存浪费不仅仅因为它常驻内存,更重要的是,对闭包的使用不当的话会内存泄漏。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
1. 性能问题,使用闭包时,会涉及到跨作用域访问,由于闭包内部变量优先级高于外部变量,所以多查找作用域链中的一个层次,就会在一定程度上影响查找速度。
<a name="tsMQk"></a>
## 九、函数防抖和函数节流
<a name="8Lkog"></a>
### 9-1 函数防抖
**定义**:函数调用n秒后才会执行,如果函数在n秒内被调用的话则函数不执行,重新计算执行时间。函数去抖主要避免快速多次执行函数(操作DOM,加载资源等等)给内存带来大量的消耗从而一定程度上降低性能问题。
**应用场景**:
1. 监控键盘keypress事件,每当内容变化的时候就向服务器发送请求
1. 在页面滚动的时候监控页面的滚动事件,会频繁执行scroll事件
1. 监控页面的resize事件,拉动窗口改变大小的时候,resize事件被频繁的执行
**函数去抖的封装**:
```javascript
<input type="text" oninput="textInput()">
<script>
//模拟向服务器发送请求的方法
function ajax() {
console.log("发送请求")
}
//去抖函数的封装
function debounce(method, delay) {
var timer = null;
return function () {
var context = this
var args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
method.apply(context, args);
}, 1000);
}
}
var fn = debounce(ajax,1000);
//用户先输入一个 a,再输入一个b
function textInput() {
//fn函数肯定会调用两次
//当第一次调用fn函数的时候,先清除之前的定时器,再开启一个定时器,定时器的作用就是1秒后触发指定的方法
//当第二次调用fn函数的时候,先清除之前的定时器,再开启一个定时器,定时器的作用就是1秒后触发指定的方法
fn();
}
</script>
9-2 函数节流
定义:函数预先设定一个执行周期(或者节流阀),当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。
应用场景:
- 上拉下拉刷新,每拉动一次彻底完毕之后才可以下一次拉动
- 图片轮播动画,每一张图片动画完成之后才开始下一个图片的动画
代码封装:
function throttle(method,duration){
var begin = new Date();
return function(){
var context = this
var args=arguments
var current=new Date();
if(current-begin>=duration){
method.apply(context,args);
begin = current;
}
}
}
function resizehandler(){
console.log(++n);
}
window.onresize=throttle(resizehandler,500);
十、递归函数
10-1 递归执行模型
function fn1 () {
console.log(111)
fn2()
console.log('fn1')
}
function fn2 () {
console.log(222)
fn3()
console.log('fn2')
}
function fn3 () {
console.log(333)
fn4()
console.log('fn3')
}
function fn4 () {
console.log(444)
console.log('fn4')
}
fn1()
10-2 举例:求n个数字的和
//递归实现:求n个数字的和 n=5---> 5+4+3+2+1
//函数的声明
function getSum(x) {
if(x==1){
return 1;
}
return x+getSum(x-1);
}
//函数的调用
console.log(getSum(5));
10-3 递归应用场景
- 遍历DOM树
//从根节点开始遍历 function fromRoot(ele){ //找到根节点的所有孩子 var children = ele.children; //遍历根节点的每个孩子 for(var i=0;i<children.length;i++){ //获取每一个孩子 var c = children[i]; //打印节点名称 console.log(c.nodeName); //如果每一个孩子下面还有子节点,则继续将每一个孩子看成根节点继续遍历孩子下面的子孩子 c.children && fromRoot(c); } } fromRoot(my$("box"));
十一、高阶函数
11-1 函数作为参数
```javascript function f1(fn) { console.log(“f1的函数”); //fn是参数,最后作为函数使用了,函数是可以作为参数使用 fn();//此时fn当成是一个函数来使用的 }
//1.传入匿名函数 f1(function () { console.log(“我是匿名函数”); });
//2.命名函数 function f2() { console.log(“f2的函数”); } //函数作为参数的时候,如果是命名函数,那么只传入命名函数的名字,没有括号 f1(f2);
```javascript
function eat (callback) {
setTimeout(function () {
console.log('吃完了')
callback();//回调
}, 1000)
}
eat(function () {
console.log('去唱歌')
})
function f1(fn) {
setInterval(function () {
console.log("定时器开始");
fn(); //回调
console.log("定时器结束");
},1000);
}
f1(function () {
console.log("好困啊,好累啊,就是想睡觉");
});
11-2 函数作为返回值
function f1() {
console.log("f1函数开始");
return function () {
console.log("我是函数,但是此时是作为返回值使用的");
}
}
var ff=f1();
ff();
11-3 判断和获取变量类型的方式
//1.获取变量类型
var num=10;
console.log(typeof num);//获取num这个变量的数据类型
var obj={};//对象
//2.判断这个对象是不是某个类型的
console.log(obj instanceof Object);
console.log(obj.constructor == Object);//这种方式在函数的原型被改掉后会不适用
//3.获取变量类型的第三种方案
//此时输出的是Object的数据类型 [object Object]
console.log(Object.prototype.toString());
//获取某个对象的数据类型的样子
//Object.prototype.toString.call(对象);//此时得到的就是这个对象的类型,注意点是这个方法无法获取自定义函数的类型
//输出的数组的数据类型 [object Array]
console.log(Object.prototype.toString.call([]));
var arr=[10,20,30];
console.log(Object.prototype.toString.call(arr));
案例:判断某个对象是否是指定类型的对象
//判断某个对象是否是指定类型的对象
function genFun (type) {
return function (obj) {
//Object.prototype.toString.call(obj) 输出obj对象的数据类型
return Object.prototype.toString.call(obj) === type
}
}
var isArray = genFun('[object Array]')
var isObject = genFun('[object Object]')
console.log(isArray([])) // => true
console.log(isObject({})) // => true