【JavaScript进阶】函数进阶
本次主要学习内容:
- 函数的多种定义和调用方式
- 函数内部this的指向
- 严格模式的特点
- 高阶函数把函数作为参数和返回值传递
- 闭包的作用
- 递归的两个条件
- 深拷贝和浅拷贝的区别
一、函数的定义和调用
01. 定义
-
(1)函数声明方式
function
关键字(命名函数)function fn(){ }
-
(2)函数表达式(匿名函数)
var fn = function(){ }
-
(3)利用
new Function()
var fn = new Function("参数1", "参数2", "函数体");
- 参数必须是字符串格式
- 但第三种方式执行效率低,也不方便书写,因此较少使用
- 另外,所有函数都是
Function
的实例(对象),因此函数也属于对象
02. 调用
-
普通函数
this
指向window
fn(); fn.call();
-
对象的方法
this
指向obj
obj.fn();
-
构造函数1
this
指向实例对象new Person();
-
绑定事件函数
this
指向btn
,函数的调用者btn.onclick = function(){ };
-
定时器函数
this
指向window
setInterval(function(){ },1000);
-
立即执行函数(自动调用)
this
指向window
(function(){ })();
二、this
指向的改变
call()
、apply()
、append()
(1)通过call()
方法
-
语法结构:
call(thisarg, arg1, arg2)
-
thisarg
即为改变的this
的指向对象 -
可以调用函数
-
可以实现继承
function func(arg1, arg2){ } func(arg1, arg2); //调用函数,传递参数 func.call(thisArg, arg1 ,arg2); //调用函数,改变this,传递参数
-
(2)通过apply()
方法
-
语法结构:
apply(thisArg, [])
-
可以调用函数
-
改变函数内部
this
的指向,可以为空null
-
参数必须是数组,且参数的数据类型和数组的数据类型一致
-
应用:
-
可以利用
Math.max
和apply()
求数组中的最大值var arr=[1,3,5,7,8,9]; // Math.max(arg1, arg2, aeg3, ...) Math.max(1,3,5,7,8,9); // 结果:9 // .apply(Math,arr) // 即将参数以数组形式传入,但函数仍以其原有接受参数形式进行接收 var max = Math.max.apply(Math,arr); //等价于上面的Math.max(...)
-
-
(3)通过bind()
方法
-
语法结构:
fn.bind(thisArg, arg1, arg2);
-
bind()
方法不会调用函数 -
但能改变原来函数内部
this
的指向 -
会返回一个新函数
-
应用:
btn.onclick = function(){ this.disabled = true; setTimeout(function(){ //一般情况下,定时器里面的this指向window this.disabled = false; }.bind(this),3000) //通过bind() 让定时器内部的 this指向 改变为事件函数的调用者 }
-
(4)总结call()、apply()、bind()
-
相同点
- 都可以改变函数内部的
this
指向
- 都可以改变函数内部的
-
区别点:
-
call
和apply
会调用函数,并且改变函数内部this
指向 -
call
和apply
传递的参数不一样call
传递参数单个形式:arg1,arg2
apply
传递参数数组形式:[arg1,arg2]
-
bind
不会调用函数,但也能改变函数内部this
指向
-
-
主要应用场景:
-
call
:经常在继承中使用在子构造函数中调用父构造函数的构造方法
-
apply
:经常搭配数组使用比如借助于数学对象实现数组最大值最小值
-
bind
:不需要调用函数,但是还想改变this
指向时适用比如改变定时器内部的
this
指向
-
三、严格模式(strict mode)
01.基本概念
- 消除了
Javascript
语法的一些不合理、不严谨之处,减少了一些怪异行为 - 消除代码运行的一些不安全之处,保证代码运行的安全
- 提高编译器效率,增加运行速度
- 禁用了在
ECMAScrip
t的末来版本中可能会定义的一些语法,为末来新版本的Javascript
做好铺垫。- 比如一些保留字,如:
class
、enum
、export
、extends
、import
、super
不能做变量名
- 比如一些保留字,如:
02.使用方法
(1)为脚本开启严格模式
- 在所有语句之前放置特定语句:
"use strict";
(2)为函数开启严格模式
- 在函数内部之前放置语句:
"use strict";
03.严格模式下的变化
(1)变量
- 变量必须先声明,再使用
- 不能随意删除已经声明的变量
(2)this
指向
- 有改变的:
- 全局作用域中函数的
this
不指向window
, 而是指向undefined
- 构造函数中,不加
new
调用,this
会报错
- 全局作用域中函数的
- 没有变化的:
new
实例化的构造函数还是指向对象实例- 定时器函数
this
还是指向window
- 事件对象还是指向调用者
(3)函数
-
函数必须声明在顶层,不允许在非函数的代码块内声明函数
如,函数声明不能在
if、for、while
中 -
函数的参数不能重名
四、高阶函数
- 高阶函数是对其他函数进行操作的函数
- 可以接收函数作为参数或将函数作为返回值输出
五、闭包
01.关于变量作用域
- 全局变量和局部变量
02.闭包的基本概念
-
闭包(
closure
)指:有权访问另—个函数作用域中变量的函数 -
一个作用域可以访问另外—个函数内部的局部变量,局部变量所在函数就称为:闭包
function father(){
var num = 24; //num可以被son函数访问到,num所在函数就产生了闭包
function son(){ //这个son函数可以访问father函数下的num
console.log(num);
}
son();
}
function father(){
var num = 24;
function son(){
console.log(num);
}
return son;
}
var fun = father(); //这时,fun就等于son,可以调用
fun(); //调用fun,仍然可以产生闭包,可以访问num
- 闭包的作用:延伸了变量的作用范围
for(var i=0;i<lis.length;i++){
//闭包可以延伸i的作用范围,这样可以实现点击li打印对应索引号
(function(i){
lis[i].onclick = function(){
console.log(i);
}
})(i);
}
六、递归
-
递归函数:函数内部自己调用自己,这个函数就是递归函数(类似于循环)
-
递归里面必须加退出条件
return
(否则死递归)
-
应用:数学递归、数据递归
function factorial(n){ if(n == 1){ return 1; }else{0 return n * factorial(n-1); } } console.log(factorial(5));
七、浅拷贝、深拷贝
-
浅拷贝:只拷贝一层,更深层次对象级别的只拷贝【引用(地址)】
-
深拷贝:可拷贝多层,每一级别的数据都会拷贝
可用栈+堆的思维方式理解:
浅拷贝只拷贝栈上的值,因此对于存放在堆里面的对象数据只能拷贝栈上的引用,而无法拷贝具体数据
深拷贝可拷贝所有的值,包括栈和堆里的数据
-
ES6的浅拷贝方法:
assign
Object.assign(目标对象, 原对象); //把原对象拷贝给目标对象,这个拷贝是浅拷贝
-
ES6的深拷贝方法:(递归)
var oldObj = {}; var newObj = {}; function deepCopy(newObj, oldObj){ for(var k in oldObj){ var item = oldObj[k]; if(item instanceof Array){ //如果这个属性是数组,注意数组要在对象前,因为数组也是对象 newObj[k] = []; //让目标对象的这个属性也是数组 deepCopy(newObj[k],item);//然后进入递归 }else if(item instanceof Object){ //如果这个属性是对象 newObj[k] = {}; //让目标对象的这个属性也是对象 deepCopy(newObj[k],item);//然后进入递归 }else{ //只有在既不是数组、也不是对象时,才可以直接赋值 newObj[k] = item; } } }