函数进阶
1. 函数的定义方式
方式一:函数声明 function 关键字(命名函数)
function fn(){}
方式二:函数表达式(匿名函数)
var fn = function(){}
方式三:实例化函数
执行效率低,也不方便书写,因此较少使用
所有函数都是 Function 的实例(一切皆对象)
注意:函数里参数都必须是字符串格式
var fn = new Function('参数1','参数2'..., '函数体')
var f = new Function('a', 'b', 'console.log(a + b)');
f(1, 2); // 3
2. 函数的调用
// 1. 普通函数
function fn() {
console.log('111');
}
fn();
fn.call();
// 2. 对象的方法
var o = {
sayHi: function() {
console.log('222');
}
}
o.sayHi();
// 3. 构造函数
function Star() {};
new Star(); // 实例化调用
// 4. 绑定事件函数
// btn.onclick = function() {}; // 点击了按钮就可以调用这个函数
// 5. 定时器函数
// setInterval(function() {}, 1000); // 定时器自动1秒钟调用一次
// 6. 立即执行函数
(function() {
console.log('人生的巅峰');
})(); // 页面加载立即执行自动调用
3. 函数内部 this 指向
这些 this 的指向,是当调用函数的时候确定的
调用方式的不同决定了 this 的指向不同,一般指向为调用者
调用方式 | this 指向 |
---|---|
普通的数调用 | window |
构造函数调用 | 实例对象原型,原型也指向实例对象 |
对象方法调用 | 该方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
4. 改变函数 this 指向
以下三种方法都可以改变函数内部的 this 指向
方法名 | 调用函数 | 参数传递 | 应用场景 |
---|---|---|---|
call | 会 | 可多个参数,之间以逗号隔开 | 常做构造函数的继承 |
apply | 会 | 必须是数组(伪数组),接收到为数据原类型 | 常跟数组有关。如:借助于数学对象实现数组最大值最小值 |
bind | 不会 | 可多个参数,之间以逗号隔开 | 如有不调用函数,但想改变 this 指向。如:改变定时器内部的this 指向 |
bind 返回:原函数改变 this 之后产生的新函数
通用语法:函数.方法名(thisArg, [参数])
- thisArg:改变函数内部的 this 指向为指定值
- 参数:向函数内传递的参数(参见表内参数传递)
5. 严格模式
JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)
ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码
注意:严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略
严格模式对正常的 JavaScript 语义做了一些更改:
-
消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为
-
消除代码运行的一些不安全之处,保证代码运行的安全
-
提高编译器效率,增加运行速度
-
禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class,enum,export,extends,import,super 不能做变量名
5.1 开启严格模式
严格模式可以应用到整个(script 标签)脚本或个别函数中
语法:'use strict';
情况一:为脚本开启严格模式:
有的 script 脚本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他
<script>
'use strict';
// 下面的 js 代码就会按照严格模式执行代码
</script>
<script>
(function() {
'use strict';
// 下面的 js 代码就会按照严格模式执行代码
})();
</script>
情况二:为函数开启严格模式
要给某个函数开启严格模式,需把声明语句放在函数体所有语句之前
<script>
// 此时只是给本函数开启严格模式
function fn() {
'use strict';
// 下面的代码按照严格模式执行
}
// 其他区域还是按照普通模式执行
</script>
5.2 严格模式中的变化
严格模式对 Javascript 的语法和行为,做了一些改变
- 变量规定
- 变量名都必须先用
var
声明,然后再使用 - 严禁删除已经声明变量。如:
delete x;
语法是错误的
- 变量名都必须先用
- this 指向问题
- 全局作用域中函数中的
this
是undefined
- 构造函数必须用
new
实例化对象,否则this
会报错 - 定时器 this 还是指向window
- 事件、对象还是指向调用者
- 全局作用域中函数中的
- 函数变化
- 函数不能有重名的参数
- 不能在
if
for
代码块中声明函数 - 可以在函数中声明函数
6. 高阶函数
概念:高阶函数就是接收函数作为参数或将函数作为返回值的函数
函数也是一种数据类型,也可以作为参数,传递给另外一个参数使用
最典型就是作为回调函数,同理函数也可以作为返回值传递回来
// 接收函数作为参数
<script>
function fn (callback) {
callback&&callback ();
}
fn (function () {alert ('hi')}
</script>
// 将函数作为返回值输出
<script>
function fn(){
return function() {}
}
fn();
</script>
7. 闭包
7.1 变量的作用域
变量根据作用域的不同分为两种:全局变量和局部变量
- 函数内部可以使用全局变量
- 函数外部不可以使用局部变量
- 当函数执行完毕,本作用域内的局部变量会销毁
7.2 什么是闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数
简述:一个作用域可以访问另外一个函数内部的局部变量,被访问变量所在的函数就是闭包函数
<script>
function fn1(){
// fn1 就是闭包函数
var num = 10;
function fn2(){
console.log(num); // 10
fn2()
}
fn1();
</script>
7.3 闭包的作用
作用:延伸变量的作用范围
注意:当闭包函数的变量等所有函数调用完毕,变量才会销毁
function fn() {
var num = 10;
function fun() {
console.log(num);
}
return fun;
}
var f = fn();
f();
7.4 闭包的案例
- 利用闭包的方式得到当前 li 的索引号
// 利用for循环创建了4个立即执行函数
for (var i = 0; i < lis.length; i++) {
// 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的 i 这变量
(function(i) {
lis[i].onclick = function() {
console.log(i);
};
})(i);
}
- 三秒钟之后,打印所有 li 元素的内容
for (var i = 0; i < lis.length; i++) {
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000)
})(i);
}
- 计算打车价格
// 打车起步价13(3公里内), 之后每多一公里增加 5块钱,用户输入公里数就可以计算打车价格
// 如果有拥堵情况,总价格多收取10块钱拥堵费
var car = (function() {
var start = 13; // 起步价
var sum = 0; // 总价
return {
price: function(n) { // 正常的总价
if (n >= 3) {
sum = start + (n - 3) * 5;
} else {
sum = start;
}
return sum;
},
jam: function(flag) { // 拥堵后费用
return flag ? sum + 10 : sum;
}
}
})();
console.log(car.price(5)); // 23
console.log(car.jam(1)); // 33
8. 递归
概念:如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
简述:函数内部自己调用自己,这个函数就是递归函数
注意:递归函数的作用和循环效果一样,由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return
下面是应用递归函数的几道例题:
8.1 求 1~n 的阶乘
利用递归函数求 1~n 的阶乘 1 * 2 * 3 * 4 * .. n,用户输入到几就计算到几
function fn(n) {
if (n == 1) {
return 1;
}
return n * fn(n - 1);
}
console.log(fn(3)); // 6
console.log(fn(4)); // 24
// 详解:假如用户输入的是 3
// fn(4) = return 4 * fn(3) = 24
// fn(3) = return 3 * fn(2) = 6
// fn(2) = return 2 * fn(1) = 2
// fn(1) = return 1;
8.2 求斐波那契数列
利用递归函数求斐波那契数列(兔子序列)1、1、2、3、5、8、13、21...
用户输入一个数字 n 就可以求出这个第 n 个的兔子序列值
算法:求出用户输入的 n 的前面两项 n-1和 n-2 就可以计算出第 n 个的兔子序列值
function fn(n) {
if (n == 1 || n == 2) {
return 1;
}
return fn(n - 1) + fn(n - 2);
}
console.log(fn(5)); // 5
console.log(fn(6)); // 8
// 详解:
// fn(5) = return fn(4) + fn(3) = 3 + 2 = 5
// fn(4) = return fn(3) + fn(2) = 2 + 1 = 3
// fn(3) = return fn(2) + fn(1) = 1 + 1 = 2
// fn(2) = return 1
// fn(1) = return 1
8.3 遍历数据
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱',
goods: [{
id: 111,
gname: '海尔'
}], {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰'
}];
// 想要做输入 id 号,就能返回 id 所在的数据对象
function getID(data, id) {
o = {}; // 保存返回结果
data.forEach(function(item) { // 用 forEach 遍历里面的每一个对象
if (item.id == id) { // 如果对象 id 为目标 id
o = item; // 将目标对象返回
// 如果有这个对象并且不为空
} else if (item.goods && item.goods.length > 0) {
o = getID(item.goods, id); // 用递归将目标对象返回
}
});
return o; // 返回结果
}
console.log(getID(data, 1)); // 家电所在的对象
console.log(getID(data, 11)); // 冰箱所在的对象
console.log(getID(data, 111)); // 海尔所在的对象
8.4 深浅拷贝
浅拷贝:拷贝一层,而多层的数据只是引用其内存地址
深拷贝:拷贝多层,每一层的每个数据都会拷贝新的内存地址
- 一层:指简单数据类型及复杂数据类型的最外层
- 多层:指复杂数据类型其中可能包含多种数据或多层嵌套等
浅拷贝 ES6 方法:Object.assign(target, ...sources)
- target:目标对象
- sources:源对象(可有多个,以逗号隔开)
- 返回值:目标对象
// 封装深拷贝函数
// 原理:遍历所有数据,将所有的复杂数据类型重新创建,再将里面的数据再进行遍历,将所有的简单数据类型直接遍历赋值
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;
}
}
}
文章版权归作者所有,未经允许请勿转载。