导读
◆能够说出函数的多种定义和调用方式
◆能够说出和改变函数内部this的指向
◆能够把函数作为参数和返回值传递
◆能够说出闭包的作用
◆能够说出递归的两个条件:
◆能够说出深拷贝和浅拷贝的区别
◆函数的定义和调用
1.1函数的定义方式
1 | // 1. 自定义函数(命名函数) |
1.函数声明方式function关键字(命名函数)
2.函数表达式(匿名函数)
3.new Function()
1 | var fn = new Function('参数1', '参数2'...., ' 函数体' ) |
Function 里面参数都必须是字符串格式
第三种方式执行效率低,也不方便书写,因此较少使用
所有函数都是Function的实例(对象)
函数也属于对象
1.2 函数的调用方式
1.普通函数 : fn(); fn.call()调用
2.对象的方法 : 对象.调用
3.构造函数 : new实例化对象的时候就会调用
4.绑定事件函数 :事件被触发的时候,比如点击按钮 回调函数
5.定时器函数 : 每个定时器时间间隔自动调用 回调函数
6.立即执行函数:立即执行函数是自动调用
1 | // 1. 普通函数 |
◆this
2.1 函数内this的指向
这些this的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this的指向不同-般指向我们的调用者。
| 调用方式 | this指向 |
|---|---|
| 普通函数调用 | window |
| 构造函数调用 | 指向实例对象 ,原型对象里面的方法也指向实例对象 |
| 对象方法调用 | 该方法所属对象 |
| 事件绑定方法 | 绑定事件对象 |
| 定时器函数 | window |
| 立即执行函数 | window |
2.2 改変凾数内部this指向
JavaScript为我们提供了一些函数方法来帮我们更优雅地处理函数内部 this 的指向问题 ,常用的有 bind()、call()、apply() 三神方法。
1) call方法
call() 方法凋用一个対象。简单理解为调用函数的方式,但是它可以改变函数的this指向。
1 | fun.call(thisArg, arg1, arg2, ...) |
1 | var o = { |
2)apply方法
apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的this指向。但是他的参数必须是数组(伪数组) 。
1 | fun.apply (thisArg, [argsArray] ) |
- thisArg :在fun函数运行时指定的this值
- argsArray :传递的值,必须包含在数组里面
- 返回值就是函数的返回值,因为它就是调用函数
apply 的主要应用 :比如说我们可以利用 apply 借助于数学内置对象求数组最大值
1 | var arr = [1, 66, 3, 99, 4]; |
3)bind方法
bind()方法不会调用函数(需要把改变之后的函数赋给另一个函数)。但是能改变函数内部this指向
1 | fun.bind (thisArg, arg1, arg2, .. .) |
thisArg :在fun函数运行时指定的this值
arg1 , arg2 :传递的其他参数
返回由指定的this值和初始化参数改造的原函数拷贝
应用场景:如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向,此时用bind。因此bind()和 回调函数很般配。
1)比如我们需要一个计时器控制按钮禁用3秒,因为计时器里面的this是指向windows,但是我们希望在计时器里面设置按钮的disabled属性,此时就需要改变this的指向。
2)比如点击事件里面,我们不仅需要对 btn 的dom对象操作,还需要对实例化对象进行操作,函数里面的 this 指向的是 dom 对象,如果通过 .bind (this) 也只能指向 实例化对象,无法两全。但是,我们依然可以使用 bind 方法,.bind(this.btn , this)把 this(指向document)当做一个参数传给函数。
1 | this.btn.onclick = this.addTab.bind(this.btn, this); |
2.3 call apply bind 总结
相同点:
都可以改变函数内部的this指向
区别点:
- call 和 apply 会调用函数,并且改变函数内部this指向。
- call 和 apply 传递的参数不样, call传递参数aru1, aru2…形式,apply必须数组形式[arg]
- bind 不会调用函数 , 可以改变函数内部this指向.
主要应用场景:
- call 经常做继承
- apply 经常跟数组有关系, 比如借助于数学对象实现数组最大值最小值
- bind 不调用函数,但是还想改变 this 指向,比如改变定时器内部的this指向
◆严格模式
3.1什么是严格模式
JavaScript 除了提供正常模式外,还提供了严格模式( strictmode )。ES5的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行JS代码。
严格模式在IE10以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。严格模式对正常的 JavaScript 语义做了一些更改 :
1.消除了 Javascript 语法的一些不合理、不严谨之处,减少了-些怪异行为。
2.消除代码运行的一些不安全之处,保证代码运行的安全。
3.提高编译器效率,增加运行速度。
4.禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如: class, enum, export, extends, import, super不能做变量名
3.2 开启严格模式
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
1.为脚本开启严格模式
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句 "usestrict";( 或'usestrict';) 。
1 | <script> |
2.为函数开启严格模式
要给某个函数开启严格模式,需要把 "usestrict" ;(或'use strict';)声明放在函数体所有语句之前。
1 | // 此时只是给fn函数开启严格模式 |
3.4 严格模式中的变化
严格模式对 Javascript 的语法和行为,都做了一些改变。
1.变量规定
①在正常模式中,如果一个变量没有声明就赋值 ,默认是全局变量。严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用。
②严禁删除已经声明变量。例如, delete x;语法是错误的。
2.严格模式下this指向问题
以前在全局作用域函数中的 this 指向 window 对象。严格模式下全局作用域中函数中的 this 是 undefined.
以前构造函数时不加 new 也可以调用,当普通函数, this 指向全局对象 , 严格模式下,如果构造函数不加 new 调用,this 会报错.
new 实例化的构造函数指向创建的对象实例。
定时器this还是指向window。
事件、对象还是指向调用者。
3.函数变化
函数不能有重名的参数。
函数必须声明在顶层新版本的 JavaScript 会引入“块级作用域”( ES6 中已引入)。为了与新版本接轨 , 不允许在非函数的代码块内声明函数。
更多严格模式要求参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
◆高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。

此时fn就是一个高阶函数
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。
◆闭包
5.1 变量作用域
变量根据作用域的不同分为两种:全局变量和局部变量。
1.函数内部可以使用全局变量。
2.函数外部不可以使用局部变量。
3.当函数执行完毕,本作用域内的局部变量会销毁。
5.2 什么是闭包
闭包( closure )指有权访问另一个函数作用域中变量的函数。
简单理解就是一个作用域可以访问另外 一个函数内部的局部变量。闭包可以说是一种现象,一个函数可以访问另一个函数里面的局部变量,那个这个局部变量所在的函数就是闭包,比如下面的 fn ,它里面的 num 这个变量被fun这个函数访问。
通过chrom的Scope可以查看闭包。
闭包的主要作用: 延伸了变量的作用范围,如下面的代码,num 是 fn 的局部棉量,但是当 fn 函数执行完毕之后仍然不会销毁,因为 f 函数调用了它,只有当 f 函数执行完毕才会被销毁。
1 | //在 fn 外面的作用域可以访问 fn 内部的局部变量 |
***闭包的案例(面试经常会问到)
案例一:点击 li 输出索引号
1 | for (var i = 0; i < lis.length; i++) { |
代码如上,但是报错了 。。。。因为 i++ 是同步任务,而 onclick 这个函数是异步任务,页面加载的时候首先会执行任务栈同步任务,在点击之后才会执行onlick函数(此时的 i 已经是5,超出了数组的边界)。
为什么会这样呢
因为在 ES5 中 for 和 if 是没有块级作用域的,i 自动实现了累加。在 es6 中 let 变量就具有块级作用域,for 有块级作用域之后,每次循环都是一个作用域快,i 的值外部是无法改变的。
我们以前的做法是:在点击函数之前把 i 保存起来。
1 | lis[i].index = i; //所以我们以前是先把i值存起来 |
现在我们可以用闭包来解决这个问题
利用for循环创建了4个立即执行函数,立即执行函数形成一个小闭包,通过立即执行函数把 i 作为参数传递给函数内部,这样函数内部可以使用 i 。
每次循环都会创建一个立即执行函数,占用空间内存,但是比较经典
1 | for (var i = 0; i < lis.length; i++) { |
学习了 ES6 之后,可以使用 let 关键字
1 | for (let i = 0; i < lis.length; i++) { |
案例二 :3秒之后打印所有 li 元素的内容
setTimeout要求第1个参数是一个函数,这样等第2个参数规定的时间到了之后,开始执行第1个参数定义的函数。对于下面的函数,当3秒到了,就会开始执行里面的函数。
1 | for (var i = 0; i < lis.length; i++) { |
再看看下面这段代码,在这种情况下,setTimeout第1个参数不是一个函数,而是一个表达式,也就是说会立即执行的函数,它不会等到计时器起作用才执行,而是只要一碰到就会执行,所以表现形式就是直接打出了0,1,2,3,4。
1 | for (var i = 0; i < olis.length; i++) { |
案例三 : 计算打车的价格
3公里以内,起步价是13元,3公里以后,每一公里5元。若交通拥堵的时候,需要增加10元。
总结:
- 如果有一个函数需要立即调用,可以用立即调用函数
- 函数内部可以 return 函数,函数内部又可以 return 变量
闭包的判断
1 | var name = "The Window"; |
object.getNameFunc()()可以理解为function(){this} ();,在立即执行函数里面,this是指向windows的。但是上面的函数是没有外包的,因为里面的函数调用的全局变量,而不是局部变量。
1 | var name = "The Window"; |
5.3 闭包总结
1.闭包是什么?
闭包是一个函数(一个作用域可以访问另外一个函数的局部变量)
2.闭包的作用是什么?
延伸变量的作用范围
◆递归
6.1 什么是递归 ?
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
简单理解:函数内部自己调用自己,这个函数就是递归函数
递归函数的作用和循环效果一样
由于递归很容易发生“栈溢出” 错误( stack overflow) , 所以必须要加退出条件return。
6.2 递归案例
- 计算1-n的阶乘
- 斐波那契序列
利用递归函数求斐波那契数列(兔子序列) 1、1、2、3、5、8、13、21…
递归需要从第 n 项总结出规律,比如上面的序列,我们只需要知道用户输入的 n 的前面两项 (n-1 n-2) 就可以计算出 n 对应的序列值。
用递归遍历复杂数据,比如这种对象里面又包含对象的数据类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱',
goods: [{
id: 111,
gname: '海尔'
}, {
id: 112,
gname: '美的'
}, ]
}, {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰'
}];
6.4 浅拷贝和深拷贝
1.浅拷贝只是拷贝一层更深层次对象级别的只拷贝引用(地址);
2.深拷贝拷贝多层,每一级别的数据都会拷贝;
原生js怎么实现浅拷贝?
可以通过遍历,把对象的每一个数组传给另一个数据
Object.assign( target, ..sources)es6 新增方法可以浅拷贝- target是需要拷贝元素的对象,sources是被拷贝的对象,
Object.assign(o, obj);
- target是需要拷贝元素的对象,sources是被拷贝的对象,
原生js怎么实现深拷贝?
封装一个函数,当遇到对象里面有对象数据类型的时候,通过递归再次进行copy