[ES6]Day09—函数的定义和调用、this、闭包、递归

ECMAScript 6 入门 (Day09)

接上篇:[ES6]Day08—Object对象的拓展

在这里插入图片描述

9.1 函数的定义和调用

9.1.1 函数的定义方式
1、自定义函数(命名函数)

即利用function关键字声明函数

function fn(){
    
    };
2、函数表达式(匿名函数)

即利用函数表达式声明函数

const fn=function(){
    
    };

() => {
    
    };
3、new Function( ) 构造函数

语法:var fn = new Function('arg1',' arg2','...argN','func')

参数 含义
’ arg1’, ‘arg2’,’…argN’ 为调用func函数需要传入的参数,每个参数都要单引号括起来,需要传字符串类型,用逗号分隔,
func 函数体,需要单引号括起来,需要传字符串类型
const fn = new Function('a','b','console.log(123);console.log(a+b)');
fn(1,2);
console.log(fn instanceof Object); //true
  • Function里面参数都必须是字符串格式
  • 执行效率低,不方便书写,比较少使用
  • 所有函数都是Function 的实例对象
  • 函数也属于对象,JS里万物皆对象

在这里插入图片描述

9.1.2 函数的调用方式
1、普通函数
function fn(){
    
    
	console.log(123);
};

fn();
//fn.call(); 
2、对象内的函数
const obj={
    
    
	say:function(){
    
    
		console.log(123);
	}
}

obj.say()
3、构造函数
function Star(){
    
    
	console.log(123);
}	
new Star();
4、绑定事件函数
 btn.onclick=function(){
    
    console.log(123)}; //点击了按钮就能调用
5、定时器函数
setInterval(function(){
    
    
	console.log(123);
},1000)

//每隔一秒自行调用
6、立即执行函数
()()
(function(){
    
    
	console.log(123); 
})()    // 自动调用

9.2 this

9.2.1 函数内的this指向

函数内的this指向,是当我们调用函数时确定的,调用方式的不同决定了this 的指向不同,一般指向我们的调用者

调用方式 this 指向
普通函数调用 window
构造函数调用 实例对象 原型对象里的方法也指向实例对象
对象方法调用 该方法所属对象
事件绑定方法 绑定事件的对象
定时器函数 window
立即执行函数 window
9.2.2 改变函数内部this指向

bind()call()apply() 三种函数可以改变this 指向

1、call()

语法:func.call(thisArg, arg1, arg2, ...argN)

参数 含义
thisArg this 的指向对象
arg1, arg2, ...argN 为调用func函数需要传入的参数,全都用逗号分隔,直接放到后面

作用:调用 func 函数,并改变函数运行时的this指向

例子:

function fn(x,y){
    
    
	console.log(this);
	console.log(x+y);
}

var o={
    
    
	name:'sam'
}
fn.call(o,3,8) 

在这里插入图片描述

2、apply()

语法:func.apply(thisArg, [argsArray])

参数 含义
thisArg 必选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
[argsArray] 可选的。但是必须是一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。

作用:apply() 方法调用一个参数为数组(或类似数组对象),给定this值的函数。

例子:

function fn(x,y){
    
    
	console.log(this);
	console.log(x+y);
} 
var o={
    
    
	name:'sam'
}
fn.apply(o,[3,8])  //输出 {
    
    name:'sam'}  11 
fn.apply(o,3,8)    //报错 ,因为第二个参数不是数组或类数组

在这里插入图片描述

var arr=[1,54,78,3,6,85,100]
Math.max.apply(Math,arr); //不改变this指向,求数组最大值
//100
3、bind()

语法:let boundFunc = func.bind(thisArg, arg1, arg2, ...argN)

参数 含义
thisArg this 的指向对象
arg1, arg2, ...argN 调用func函数需要传入的参数,全都用逗号分隔,直接放到后面,返回一个函数(闭包),函数延迟执行,需要调用才执行
return 返回由指定的this值和初始化参数改造后的原函数拷贝,相当于返回一个新的函数

作用:bind()不会调用函数,但是能改变函数内部的this指向。 如果有的函数不需要立即调用,但是又想改变这个函数内部的this指向此时用bind()

例子:

function fn(x,y){
    
    
	console.log(this);
	console.log(x+y);
}

var o={
    
    
	name:'sam'
}
fn.bind(o,3,8)  // 输出fn 函数,不会执行
fn.bind(o,3,8)() //执行 fn 函数, 输出 {
    
    name:'sam'}  11 

在这里插入图片描述

实例: 有一个按钮,当我们点击之后,禁用按钮,3秒钟后开启按钮

var btn =document.querySelector('button');
btn.onclick=function(){
    
    
	this.disabled=true; //这个this 指向btn 按钮
	console.log(this);//定时器函数里面的 this 指向 btn
	
	//setTimeout(function(){
    
    
	//	console.log(this);//定时器函数里面的 this 指向window 
	//},3000)

	setTimeout(function(){
    
    
	    console.log(this);//加了bind(),定时器函数里面的 this 指向 btn
	 }.bind(this),3000) //this 在定时器外面,指向的是btn对象 ,将btn对象绑定到定时器上
	}
}

多个按钮
在这里插入图片描述
在这里插入图片描述
改变定时器中的this指向,原来是指向Window,改变之后指向btn ,而且不会立即调用函数

4、call() 、bind() 、 apply() 区别:
var obj={
    
    
    name:'张三',
    age:18,
    myFun:function(fm,to){
    
    
        console.log(this.name+" 年龄"+this.age,fm + "去往" + to);
    }  
}
var db={
    
    
    name:'德玛',
    age:99
}

obj.myFun.call(db,'成都','上海');   //德玛 年龄99 成都去往上海

obj.myFun.apply(db,['成都','上海']);   //德玛 年龄99 成都去往上海  

obj.myFun.bind(db,'成都','上海'); // 返回一个函数,这里就是 bind  call/apply 的区别之一,bind 的时候不会立即执行

obj.myFun.bind(db,'成都','上海')();    //德玛 年龄99 成都去往上海

obj.myFun.bind(db,['成都','上海'])();  //德玛 年龄99 成都,上海去往undefined

相同点

1、都可以改变上下文 this 指向的,第一个参数都是 this 的指向对象
2、三者的参数不限定是string类型,允许是各种类型,包括函数 、 object

不同点

1、call()apply() 会调用函数,并且改变函数内部this指向。

2、传递参数形式不同call() 后面的参数全都用逗号分隔 ,apply() 后面的参数必须传一个数组

 obj.myFun.call(db,'成都', ... ,'string' );
 obj.myFun.apply(db,['成都', ..., 'string' ]);

3、bind()不会调用函数,除了返回的是原函数改变this之外产生的新函数以外,它的参数和call()一样。如果有的函数不需要立即调用,但是又想改变这个函数内部的this指向此时用bind()方法
    
4、应用对象不同call()apply() 是直接使用在函数上,而 bind() 绑定 this 后返回执行上下文被改变的函数(闭包),不会立即执行,需要()才会执行。

5、主要应用场景不同call()常用在继承;apply() 经常与数组有关,比如借助于数学对象实现数组最大值最小值;bind()不调用函数,但是还想改变this指向。比如改变定时器内部的this指向。

Array.prototype.sum=function(){
    
    
	var sum=0;
	for(let i = 0; i < this.length ; i++){
    
    
		sum+=this[i];
	}
	return sum;
}

var arr=new Array(1,2,3)
arr.sum() //6

//打印 Array.prototype 

9.3 严格模式

9.3.1 什么是严格模式

在这里插入图片描述

9.3.2 开启严格模式

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

9.3.3 严格模式下的变化
1、变量必须先声明再使用
num=20
console.log(num) //undefined

在这里插入图片描述

2、不能随意delete 已经声明好的变量
var num=10;
console.log(num);
delete num; //false

在这里插入图片描述

3、严格模式下全局作用域中函数的this是undefined

在正常的全局作用域中 this 指向 window对象

function fn(){
    
    
	console.log(this);
} 
fn(); //window

在这里插入图片描述
严格模式下全局作用域中函数中的 thisundefined
在这里插入图片描述

4、严格模式下,构造函数必须 new 调用
5、new 实例化的构造函数指向创建的对象实例
function Star(){
    
    
	this.sex='男';
}
var yy= new Star();
console.log(yy.sex); //

在这里插入图片描述

6、定时器 this 还是指向 window
setTimeout(function(){
    
    
	console.log(this); //window
},10000)
7、事件、对象中的 this 还是指向调用者
8、严格模式下,函数中的参数不允许有重名
'use strict'
function fn(a,a){
    
    
	console.log(a+a)
}
fn(1,2)

在这里插入图片描述

9、严格模式禁止不在脚本或者函数层面上的函数声明。
"use strict";
if (true) {
    
      //非函数、非脚本
  function f() {
    
     } // !!! 语法错误
  f();
}

for (var i = 0; i < 5; i++) {
    
    
  function f2() {
    
     } // !!! 语法错误
  f2();
}

function baz() {
    
     // 合法
  function eit() {
    
     } // 同样合法
}

想了解更详细看这里 严格模式-MDN

9.4 高阶函数

什么是高阶函数?

高阶函数是对其他函数进行操作的函数,至少有这两大特征之一:

  • 接收函数作为参数
  • 将函数作为返回值输出

函数也是一种数据类型,同样可以作为参数,传递给另一个参数使用,最典型的就是作为回调函数。

//接收函数作为参数
function fn(callback){
    
    
	callback&&callback();
}

fn( function(){
    
     alert('hi') } ) 
//将函数作为返回值输出
function fn(){
    
    
	return function (){
    
    }
}
fn();

9.5 闭包

9.5.1 变量作用域

变量根据作用域的不同分为两种:全局变量和局部变量。

  1. 函数内部可以使用全局变量。
  2. 函数外部不可以使用局部变量。
  3. 当函数执行完毕,本作用域内的局部变量会销毁
9.5.2 什么是闭包

闭包是指有权访问另一个函数作用域中变量函数

简单来说,一个作用域可以访问另外一个函数内部的局部变量

闭包的作用:延伸了变量的作用范围

例子:

function fn(){
    
    
	var num = 10;
	function fun(){
    
    
		console.log(num);//访问 fn 函数中的 num 变量
	} 
	fun();//打印num
}

//思考:怎么在外面能够访问到fn 函数中的 num 

解决思路:

fun 函数作为fn 函数的返回值,用一个变量f在外部接收,由于返回值为一个函数,所以调用 f() 就可以得到 num 的值

function fn(){
    
    
	var num = 10 ;
	//function fun(){
    
    
	//	console.log(num); //访问fn 内部的局部变量
	//}
	//return fun;
	
	return function fun(){
    
    // 直接 return
		console.log(num); //访问fn 内部的局部变量
	}
}
var f = fn();

//类似于
//var f= function fun(){
    
    
//	console.log(num);
//}

f(); //调用 f(), 就可以访问 fn 内部的局部变量num了
9.5.3 闭包的应用
1、循环注册点击事件

在这里插入图片描述
异步函数必须调用才会执行,所以在 for 循环 创建 立即执行函数 ()(i) 存当前循环的 i 值,在立即执行函数中写 点击事件。

在这里插入图片描述

但是闭包会产生内存泄漏问题,因为创建了 立即执行函数 ()(i) ,应该要马上销毁变量 i,但是这里要等点击事件执行了才能销毁变量 i

2、循环中的setTimeout()

在这里插入图片描述
setTimeout() 也是异步任务的一种,要等3秒到了才会执行

3、计算打车价格

// 打车起步价13(3公里内),之后每多一公里增加5块钱,用户输入公里数就可以计算打车价格
//如果有拥堵情况,总价格多收取10块拥堵费

在这里插入图片描述

//调用 
car.price(7) //7公里 7-3*5+13=33
car.yd(true)  // 33+10=43 
4、思考题1
var name = "The Window";
var object = {
    
    
	name:"My Object",
	getNameFunc : function(){
    
    
		return function(){
    
      //匿名函数
			return this.name;
		} 
	} 
}
console.log(object.getNameFunc()) //返回一个函数
console.log(object.getNameFunc()()) //执行this.name 

这道题重点是 要弄清楚 this 指向
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//相当于立即执行函数,this指向了window
function(){
    
      console.log(this) } () 

所以执行下面这段代码:

function(){
    
      console.log(this.name) } ()  //结果是 window.name = "The Window"

综上所述这道题 没有局部变量的访问,不存在闭包(有点坑,哈哈哈),最终答案为"The Window"。

5、思考题2
var name = "The Window";
var object = {
    
    
	name:"My Object",
	getNameFunc : function(){
    
    
		var that = this;
		return function(){
    
      //匿名函数
			return that.name;
		} 
	} 
}
console.log(object.getNameFunc()) //返回一个函数
console.log(object.getNameFunc()()) //执行that.name 

在这里插入图片描述

这里的 that 指向 object 这个对象,所以 that.name =“My Object” ,这道题存在一个闭包就是getNameFunc()

9.5.4 闭包的总结
1、闭包是什么?

闭包是一个作用域可以访问另外一个函数的局部变量的函数

2、闭包的作用是什么?

延伸变量的作用范围

9.6 递归

9.6.1 什么是递归

如果一个函数在内部可以调用其自身,那么这个函数 就是 递归函数,简单来说,就是函数内部自己调用自己。

递归函数的作用与循环一样,容易发生“栈溢出” 错误(stack overflow),所以必须要加上退出条件 return
在这里插入图片描述

//正确写法
var num=1;
function fn(){
    
    
	console.log(num) //1,2,3,4
	if(num==4) return;
	num++;
	fn();
}
fn();
9.6.2 递归的应用
1、求 1 * 2 * 3 … * n 阶乘
function fn(n){
    
    
	if(n==1) return 1;
	return n*fn(n-1)  
}

fn(6) //720

在这里插入图片描述

2、求斐波那契数列(兔子数列)1,1,2,3,5,8,13,21 …

用户输入一个数字n ,可以得到这个数字对应的兔子序列值

1+1=2,1+2=3,2+3=5,3+5=8,5+8=13 … 相邻两个数相加等于第三个数

第三个数=前两项之和 n=(n-1)+(n-2)

function fb(n){
    
     
	if(n===1 ||n===2){
    
    
		return 1;
	}
	return fb(n-1) +fb(n-2);
}
console.log(fb(3)) //3个是  2
console.log(fb(6)) //6个是  8
3、根据 id 找数据对象

输入商品id ,找到商品信息

var arr=[
{
    
    
	id:0,
	name:'水果',
	goods:[{
    
    
		id:11,
		gname:'西瓜' ,
		goods:[
			{
    
    
				id:111,
				gname:'妈妈牌'  
			}
		]
	},{
    
    
		id:12,
		gname:'草莓' 
	}]  
},
{
    
    
	id:1,
	name:'服饰',
	goods:[{
    
    
		id:13,
		gname:'上衣' 
	},{
    
    
		id:14,
		gname:'裙/裤' 
	}]  
}];

var o ={
    
    }
function getObjItem(id,arr){
    
    
	//1、用forEach 遍历数组
	arr.forEach(item=>{
    
    
		if(item.id==id){
    
     
			o = item  //这里只能得到第一层的
			return item
		}else if( item.goods && item.goods.length>0){
    
    
		//2、取到里层的数据,使用递归
		   o = getObjItem(id,item.goods)
		}  
	});
	return o 
} 

getObjItem(111,arr)

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/iChangebaobao/article/details/106936276
今日推荐