函数进阶
函数的定义和调用
函数的定义方式
- 函数声明方式 function 关键字(命名函数)。
function fn(){ }
- 函数表达式(匿名函数)。
var fun = function(){ }
- new Function(‘参数1’,‘参数2’,‘函数体’)(Function() 是一个构造函数。)
var f = new Function('console.log(123)')
注意:- Function 里面都必须是字符串格式。
- 第三种方式执行效率低,也不方便书写,因此较少使用。
- 所有函数都是 Function 的实例(对象)。
- 函数也属于对象。
函数的调用方式
- 普通函数
function fn(){
console.log('人生的巅峰')
}
// 调用方式:fn() 或者 fn.call()
- 对象的方法
var o = {
sayHi: function(){
console.log('人生的巅峰')
}
}
// 调用方式:o.sayHi()
- 构造函数
function Star(){
console.log('人生的巅峰')
}
// 调用方式:new Star()
- 绑定事件函数
btn.onclick = function(){
console.log('人生的巅峰')
}
// 调用方式:触发事件(点击按钮)。
- 计时器函数
setInterval(function(){},1000)
// 调用方式:每隔一段时间(这个时间可以自己设置,在上面这个案例是每隔 1s 调用一次)调用一次。
- 立即执行将函数
(function(){
console.log('人生的巅峰')
})()
// 立即执行函数是自动调用。
this 的指向问题
函数内 this 的指向
这些 this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了 this 的指向不同,一般指向我们的调用者。
调用方式 | this 指向 |
---|---|
普通函数调用 | window |
构造函数调用 | 实例对象 原型对象里面的方法也指向实例对象 |
对象方法调用 | 该方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
改变函数的 this 指向
JavaScript为我们专门提供了一些函数方法来帮我们更优雅地处理函数内部 this 的指向问题,常用的有:bind()、call()、apply()三种方法。
- call 方法
参考:构造函数中的继承 - apply()
apply()方法调用
一个函数。简单理解为调用函数的方式,但是他可以改变函数的 this 指向。
- 语法
fun.apply(thisArg,[argsArray])
(1) thisArg:在 fun 函数运行时指定的 this 值。
(2) argsArray:传递的值,必须包含在数组
里面。
(3) 返回值就是函数的返回值,因为它就是调用函数。
(4) apply 的主要应用:利用 apply 借助于数学内置对象求最大值
var arr = [41,878,451,844,811,585]
Math.max.apply(Math,arr) // 求最大值。
- bind 方法
bind() 方法不会调用函数,但是可以改变函数内部的 this 指向。
- 语法
fun.bind(thisArg,arg1,arg2,......)
- thisArg:在 fun 函数运行时指定的 this 值。
- arg1,arg2:传递的其他参数。
- 返回由指定的 this 值和初始化参数造成的
原函数拷贝
。 - 如果有的函数不需要立即调用,但是又想改变这个函数内部的 this 指向,此时用bind
- 我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒之后再开启这个按钮。
<body>
<button>点击</button>
<script>
var btn = document.querySelector('button')
btn.onclick = function(){
this.disabled = ture
setTimeout(function(){
this.disabled = false
}.bind(this),3000)
}
</script>
</body>
总结
call()、apply()、bind()
- 相同点:
都可以改变函数内部的 this 指向。 - 不同点:
- call 和 apply 会调用函数,并且改变函数内部的 this 指向。
- call 和 apply 传递的参数不一样,call 传递参数 aru1,aru2…形式,而apply 必须数组形式[arg].
- bind 不会调用函数,可以改变函数内部 this 指向。
- 主要应用场景
1)call 经常做继承。- apply 跟数组有关系,所以经常借助数学对象实现数组的最大值和最小值。
- bind 不调用函数,但是还想改变 this 指向。比如改变定时器内部的 this 指向。
严格模式
什么是严格模式
- JavaScript 除了提供正常模式外,还提供了
严格模式(strict mode)
。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行JS代码。 - 严格模式在 IE 10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
- 严格模式对正常模式的 JavaScript 语义做了一些修改:
- 消除了 JavaScript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
- 消除代码运行的一些不安全之处,保证代码运行的安全。
- 提高编译器效率,增加运行速度。
- 禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 JavaScript 做好铺垫。比如一些保留字如:class、enum、export、extends、import、super 不能做变量名。
开启严格模式
严格模式可以应用到整个脚本
或个别函数
中,因此在使用时,我们可以将严格模式分为为脚本开启严格模式
和为函数开启严格模式
两种情况。
- 为脚本开启严格模式
- 为整个脚本开启严格模式,需要
在所有语句之前放一个特定语句"use strict"(或者'use strict')
。因为"use strict"加了引号,所以老版的浏览器会把它当作一行普通字符串而忽略。
"use strict"
console.log("这是严格模式")
- 有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。
(function (){
"use strict"
var num = 10
function fn(){}
})()
- 为函数开启严格模式
要给某个函数开启严格模式,需要把"use strict"(或者'use strict')声明放在函数体所有语句之前。
// 此时只是给 fn 函数开启严格模式。
function fn(){
'use strict'
// 下面的代码按照严格模式执行。
}
function fun(){
//里面的代码还是按照普通模式执行。
}
严格模式中的变化
严格模式对 JavaScript 的语法和行为,都做了一些改变。
-
变量规定
- 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用 var 命令声明,然后再使用。
- 严禁删除已经声明变量。例如,
delete x
语法是错误的。
-
严格模式下 this 的指向问题
- 以前在全局作用域函数中的 this 指向 window 对象。
- 严格模式下全局作用域函数中的 this 是 undefined 。
- 以前构造函数不加 new 也可以调用,当普通函数,this 指向全局对象。
- 严格模式下,如果构造函数不加 new 调用,this 会报错。
- new 实例化的构造函数指向创建的对象实例。
- 定时器 this 还是指向 window 。、
- 事件、对象还是指向调用者。
-
函数变化
- 函数不能有重名的参数。
- 函数必须声明在顶层新版本的 JavaScript 会引入“块级作用域”(ES6中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数。
高阶函数
高阶函数
是对其他函数进行操作的函数,它接收函数为参数
或将函数作为返回值输出
。
// 接收函数作为参数。
function fn(callback){
callback&&callback()
}
fn(function(){alert('hi!')})
// 将函数作为返回值输出。
function fn(){
return function(){}
}
fn()
- 此时 fn 就是一个高阶函数。
- 函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是回调函数。
闭包
- 变量作用域
- 变量分为全局变量和局部变量。
- 函数内部可以使用全局变量。
- 函数外部不可以使用局部变量。
- 当函数执行完毕,本作用域内的局部变量会销毁。
- 什么是闭包?
闭包
(closure)是指有权访问
另一个函数作用域中变量
的函数。简单理解就是,一个作用域可以访问另外一个函数内部的局部变量。
function fn(){
var num = 10
function fun(){
console.log(num)
}
fun()
}
fn()
// fn 就是一个闭包函数。
- 闭包的主要作用:延伸了变量的作用范围。
闭包的案例
- 循环注册点击事件
// 利用闭包的方式得到当前小 li 的索引号。
for(var i=0;i<lis.length;i++){
// 利用 for 循环创建了4个立即执行函数
// 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的 i 这变量
(function(i){
// console.log(i) 输出0、1、2、3四个数
lis.onclick = function(){
cosole.log(i)
}
})(i)
}
- 循环中的 setTimeout()
// 3秒之后,打印所有 li 元素的内容
for(var i=0;i<lis.length;i++){
(function(i){
setTimeout(function(){
function(){
cosole.log(lis[i].innerHTML)
},3000)
})(i)
}
递归
- 什么是递归?
如果一个函数在内部可以调用其本身
,那么这个函数就是递归函数
。简单理解:函数内部自己调用自己,这个函数就是递归函数。
var num = 1
function fn(){
console.log('我要打印6句话。')
if(num == 6){
return // 递归函数必须加退出条件。
}
num++
fn()
}
fn()
递归函数的作用和循环效果一样
由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return
。
利用递归求数学题
- 求123…*n 阶乘。
function fn(){
if(n == 1){
return 1
}
return n * fn(n - 1)
}
fn()
- 求斐波那契序列(兔子序列)。
function fb(){
if(n == 1 || n == 2){
return 1
}
return fb(n - 1) + fb(n - 2)
}
- 利用递归求:根据 id 返回对应的数据对象
var date = [{
id:1,
name:'家电',
goods:[{
id:11,
gname:'冰箱',
},{
id:12,
gname:'洗衣机',
}]
},{
id:2,
name:'服饰',
}];
// 我们想要做输入 id 号,就可以返回的数据对象。
// 1. 利用 foreach 去遍历里面的每一个对象
function getID(json,id){
var o = {}
json.forEach(function(item){
console.log(item) // 2个数组元素
if(item.id == id){
console.log(item)
o = item
return item
// 我们想要得到里层的数据 11 12 可以利用递归函数
// 里面应该有 goods 数组并且数组的长度不为0
}else if(item.goods && item.goods.length > 0){
o = getID(item.goods,id)
}
});
return o
}
console.log(getID(date,1))
浅拷贝和深拷贝
- 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用。
- 深拷贝拷贝多层,每一级别的数据都会拷贝。
- Object.assign(target,…sources) ES6 新增方法可以浅拷贝。
var obj = {
id:1,
name:'andy',
msg:{
age:18
},
color:['pink','red']
};
// 浅拷贝
var o = {}
for(var k in obj){
// k是属性名,obj[k]属性值
o[k] = obj[k]
}
// 浅拷贝2
Object.assign(o,obj)
console.log(o)
// 深拷贝
// 封装函数
function deepCopy(newobj,oldobj){
for(var k in oldobj){
// 判断我们的属性值属于哪种数据类型。
// 1. 获取属性值
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
}
}
}
deepCopy(o,obj)
console.log(o)
// 深拷贝不会对原来的值造成影响,但是浅拷贝会。