四,函数进阶
1,函数定义
-
定义
-
自定义函数(命名函数)
function fn() {};
-
函数表达式(匿名函数)
var fun = function() {};
-
利用 new Function(‘参数1’, ‘参数2’, ‘函数体’)
var fn = new Function('a', 'b', 'console.log(a+b)'); // fn 是 Function 的一个实例 fn(10, 20);
-
Function 里面所有的参数都是字符串
-
了解即可
-
重要: 所有的函数都是 Function 的实例(对象)
console.dir(Function.prototype === fn.__proto__); // true console.log(fn instanceof Object); // true console.log(Function instanceof Object); // true
-
-
2,函数的调用
// 1. 普通函数
function fn() {
console.log('独孤求败');
}
fn();
fn.call();
// 2.对象的方法
var a = {
hello: function() {
console.log('独孤求败');
}
}
a.hello();
// 3. 构造函数
function People() {
this.hello = function() {
console.log(666);
}
}
new People();
// 4. 绑定事件函数
btn.onclick(function() {}); // 按钮点击之后调用
// 6. 定时器函数
setInterval(function() {}, 1000); // 定时器每隔几秒调用
// 5. 立即执行函数
(function() {
console.log('独孤求败');
})()
3,函数的this指向
对象里面的this指向:谁调用了this,this就指向谁
const obj = {
name: 'xyb',
age: 20,
sayHi: function() {
console.log(this);
}, // 谁调用了这个函数,this就指向谁
lookThis: (function() {
console.log(this);
})(), // 因为这是立即执行函数,this的指向是windows
}
var btn = document.querySelector('button');
btn.addEventListener('click', obj.sayHi)
4,改变函数this指向
1,call方法
-
可以调用函数,并且可以改变函数的 this 指向
fn.call(thisArg, arg1, arg2...); // 例子 var obj = {}; fn.call(obj, 1, 2...); // 改变函数内部 this 指向
-
实现组合继承
function Father(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } function Son(name, age, sex) { Father.call(this, name, age, sex); }
2,apply方法
-
调用函数,改变函数的 this 指向
var obj = {}; var fn = function(a, b, c) { console.log('独孤求败'); console.log(a); console.log(b); console.log(c); console.log(this); } fn.apply(obj, [1, 2, 3])
-
apply方法的主要应用
// 求一个数组的最大值 var res = Math.max.apply(Math, [4, 2, 6, 8, 9, 10]) console.log(res);
3,bind方法
和上面两者最大的区别就是,bind方法不会调用函数,返回值是一个原函数的拷贝
-
改变 this 指向
var o = { name: 'xyb' }; function fn(a, b) { console.log(a + b); } var f = fn.bind(o, 1, 2); // 改变 this 指向 f()
- 注意:bind 之后返回的是一个改变 this 指向新函数
案例
有一个按钮,点击之后 3s内 禁用,之后再开启
var btn = document.querySelector('button'); btn.addEventListener('click', function() { this.disabled = true; setTimeout(function() { this.disabled = false; }.bind(this), 3000) // bind改变函数内的this指向 })
4,三个方法总结
都可以改变函数内部的 this 指向
-
不同点:
- call 和 apply 会调用函数,并且会改变函数内部的 this指向
- call 和 apply 传递的参数不一样,call 传递参数 arg1,arg2的形式,apply为数组的形式
- bind 不会调用函数,但是可以改变函数内部指向
-
主要应用场景
- call 经常用来做继承
- apply 经常和数组有关系,比如借助数学对象实现数组最大值最小值
- bind 不调用函数,但是还想改变 this 指向,比如定时器的 this 指向
5,严格模式
-
严格模式和普通模式区别
- 消除了 JS 语法的一些不合理,不严谨之处,减少了一些怪异行为(变量必须申明才能使用)
- 消除代码一些不安全的地方,增加运行速度
- 提高编译效率,增加运行速度
- 禁用了 ECMAScript 在未来可能会定义的一些语法,如一些保留字 class enum export等等不能作变量名
-
开启严格模式
-
为脚本开启严格模式
<script> 'use strict' // 为整个 script 标签开启严格模式 </script>
-
为函数开启严格模式
<script> (function() { 'use strict' // 为立即执行函数单独开启严格模式 })() function fn() { 'use strict' // 为该函数单独开启严格模式 } </script>
-
-
严格模式的变化
'use strict'; // 1. 变量必须先申明再使用 // num = 10; // 报错 var num = 10; // 2. 不能删除已经声明好的变量 // delete num; // 报错 // 3. 严格模式下全局作用域中函数的 this 是 underfined function fn() { console.log(this); // underfined } fn(); // 4. 如果构造函数不用new创建实例,会报错 function People(name) { this.name = name; } // People(); // 报错 new People(); // 5. 定时器里的this还是指向window setInterval(function() { console.log(this); }, 3000); // 6. 严格模式下函数里卖弄的参数不能重名 function(a, a) { console.log(a + a); }
6,高阶函数
高阶函数是对其他函数进行操作的函数,他接收函数作为参数或将函数作为返回值输出
-
接收函数为参数
function fn(callback) { callback && callback() } fn(function() { alert(666) })
-
返回值为函数
function fn() { return function() { alert(666) } } fn()();
7,闭包
-
什么是闭包? 外层函数包裹内层函数,内层函数能访问外层函数的局部作用域
function fn() { var x = 10; function func() { console.log(x); } func(); } fn()
案例4:点击li打印当前所引
// 1. 以前的写法
var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = function() {
// console.log(i);
}
}
// 因为for循环是同步任务,点击事件是异步任务,当按钮点击时候,i的值早就被加到了4
// 所以以前我们都是给每个li添加一个index的动态属性的
var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
var lis[i].index = i;
lis[i].onclick = function() {
console.log(this.index);
}
}
// 2. 使用闭包完成
var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
(function(i) {
lis[i].onclick = function() {
console.log(i);
}
})(i);
}
// 作用域里 i = 1 函数里面有作用域,每次第一个按钮被点击,回调函数回来找i会去拿自己作用域里面的i
(function(i) {
lis[i].onclick = function() {
console.log(i);
}
})(i);
// 立即执行函数的作用就是开辟了一个新的作用域,这个作用域里面有他自己的i的值,每次回调都会先去自己作用域里面找i,因为立即执行函数在循环的时候就已经执行并且形成了自己的作用域,所以不会受最外面i的影响
// 这样会造成内存泄漏问题!!!
案例5: 3s后打印li元素里面的内容
// 1. 错误写法,setTimeout是异步任务,等到他执行完了,i早就过头了
var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 1000)
}
// 2. 利用立即执行函数 + 闭包
var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 1000)
})(i)
}
// 只要setTimeout没有结束,创建的立即执行函数作用域就会一直存在
案例6:计算计程车价格
var car = (function() {
var start = 13; // 起步价,局部变量
var total = 0; // 总价,局部变量
return {
// 正常的价格
price: function(n) {
if (n <= 3) {
total = start;
} else {
total = start + (n - 3) * 5;
}
return total
},
// 拥堵之后的费用
yd: function(flag) {
return flag ? total + 10 : total
}
}
})();
console.log(car.price(3));
console.log(car.yd(true))
8,递归函数
函数自己调用自己
var num = 1;
function fn() {
console.log('递归');
num++;
if (num >= 10) {
return
}
fn();
}
fn();
案例7:利用递归求阶乘
function fn(num) {
if (num == 1) {
return 1;
}
return num * fn(num - 1)
}
console.log(fn(3));
// 思路
// return 3 * fn(3 - 1)
// return 3 * fn(2)
// return 3 * (2 * fn(2 - 1))
// return 3 * (2 * 1)
// return 3 * 2 * 1
案例8:求斐波那契数列
function fn(num) {
if (num === 1 || num === 2) {
return 1;
}
return fn(num - 1) + fn(num - 2);
}
console.log(fn(4));
// return fn(4 - 1) + fn(4 - 2)
// return fn(3) + fn(2)
// return fn(2) + fn(1) + fn(2)
// 1 + 1 + 1 = 3
案例9:用递归遍历数据
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
name: '冰箱'
}, {
id: 16,
name: '洗衣机'
}, {
id: 25,
name: '衣服'
}]
}, {
id: 66,
name: '电脑'
}]
// 我们输入id号,就能够返回数据对象
// 1. 利用 forEach 去遍历里面的每一个对象
function getID(json, id) {
json.forEach(function(item) {
if (item.id === id) {
console.log(item.name);
} else if (item.goods && item.goods.length > 0) {
getID(item.goods, id)
}
})
}
getID(data, 66);
// 我们输入id号,就能够返回对象!!!
// 1. 利用 forEach 去遍历里面的每一个对象
function getID(json, id) {
var o = {};
json.forEach(function(item) {
if (item.id === id) {
o = item;
} else if (item.goods && item.goods.length > 0) {
o = getID(item.goods, id);
}
})
return o;
}
var res = getID(data, 112);
console.log(res);
9,浅拷贝深拷贝
// 浅拷贝只是拷贝一层,更深层次对象级别的只能深拷贝
var obj = {
id: 1,
name: 'xyb',
msg: {
age: 20
},
msg2: {
money: 2000
}
};
var o = {};
// 1. 浅拷贝
for (var k in obj) {
// 对象[属性] = 属性值
o[k] = obj[k];
}
obj.msg.age = 999
console.log(o);
// ----------------------
// 浅拷贝的语法糖
Object.assign(o, obj)
console.log(o);
// 2. 深拷贝
function deepCopy(newobj, oldobj) {
for (var k in oldobj) {
// 判断属性值属于哪种数据类型
// 1. 获取属性值 oldobj[k]
var item = oldobj[k];
// 2. 判断这个值是否是数组
if (item instanceof Array) {
newobj[k] = [];
deepCopy(newobj[k], item)
} else if (item instanceof Object) {
// 3. 判断这个值是否是对象
newobj[k] = {};
deepCopy(newobj[k], item)
} else {
// 4. 属于简单数据类型
newobj[k] = item;
}
}
}
deepCopy(o, obj)
obj.aaa = 666
console.log(o);
console.log(obj);