▊ 函数的定义
// 命名函数
function fun() {};
// 匿名函数
var fun = function() {};
// 对象方式
var fun = new Function('参数1', '参数2', '函数体'); // 注意全是字符串形式
// 前两种比较常用;第三种效率较低
// 但从第三种书写方式中,我们要理解这样的思想:所有函数都是Function的实例对象
▊ this指向
记住一句话就行:this指向的是函数的调用者
对于普通函数,定时器函数,立即执行函数,调用者为window
JavaScript提供了三种优雅的方法改变this的指向:call()
apply()
bind()
func.call(新的this指向, 其他参数...) // 调用函数,改变指向。
func.apply(新的this指向, [其他参数...]) // 调用函数,改变指向。传入的参数是数组(伪数组)
func.bind(新的this指向, 其他参数...) // 不调用函数,改变指向。返回一个新的函数。
// call的典型用法
function Son(uname) {
Father.call(this, uname); // 实现继承
}
// apply的典型用法
Math.max.apply(Math, arr); // 利用数学内置对象的方法,来求数组的最大值 —— 调用者不改变,但参数改为数组
Math.min.apply(Math, arr); // 同理
// bind的典型用法
// 使用场景:有的函数我们并不想在改变this指向的同时立即调用
// 比如:有一个按钮,按下后禁用3s后再次可用
btn.addEventListener('click', function() {
this.disabled = true;
setTimeout(function() {
this.disabled = false; // 提前把this存在that也行,但没有这种写法优雅
}.bind(this), 3000) // 将this指针绑定在了btn上,但没有立即调用,而是依旧由计时器控制
}) // 注意bind里的this是在函数之外的,指向的是btn
// 补充:多个按钮的情况
for(var i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function() {
this.disabled = true;
setTimeout(function() {
this.disabled = false; // 这里千万不要企图使用btns[i]——点击btns[2]是开启计时器,2s过后这个btn[i]不一定是btn[2]!!!
}.bind(this), 3000)
})
}
tip:通过其英文含义理解它们:call【调用】,apply【应用】, bind【绑定】
▊ 严格模式(strict mode)
严格模式对正常的JavaScript语义做了一些修改
严格模式分为为脚本开启严格模式和为函数开启严格模式
<!-- 给整个脚本开启严格模式 -->
<script>
(function() {
'use strict'; // 开启严格模式
// ...
})() // 通过立即执行函数开辟一个独立空间,防止变量名冲突(常用写法)
</script>
<!-- 给函数开启严格模式,就在函数的第一行加上'use strict' -->
// 严格模式('use strict')下的变化
n = 12; // (×)变量必须先声明
delete n; // (×)不能随意删除已经声明好的变量
function fun() {
console.log(this); // 全局作用域中的this不再指向window,而是undefined
}
function Dog() {
this.age = 3;
}
Dog(); // 报错。必须加new,否则构造函数被当做普通函数调用,这个age属性显然不能给undedined(非严格模式下给了window)
setTimeout(function() {
console.log(this); // 定时器内,this仍旧是指向window的
}, 1000)
function fun(a, a) {
console.log(a + a);
}
func(1, 2); // 非严格模式下,会输出4;这种覆盖显然很奇怪,因此严格模式下禁止形参重名
for(var i = 0; i < 10; i++) {
function() {}; // 报错
}
if(true) {
function() {}; // 报错
}
function fun1() {
function fun2() {}; // 合法。总结来说,就是因为ES6引入了块级作用域,因此严格模式下[禁止在非函数的代码块中声明后函数]
}
▊ 高阶函数
高阶函数是对其他函数进行操作的函数。
特点是:把函数作为传入的参数或返回值。
▊ 闭包(closure)
闭包指有权访问另一个函数作用域中局部变量的函数。
function fun1(){ // fun1是一个典型的闭包——fun1的内部(包括内部的函数的内部),都能使用变量n
var n = 12;
function fun2(){
console.log(n); // 因为fun2函数的作用域内访问了另一个作用域(fun1)的变量
}
fun2();
}
fun1();
function fun() { // fun函数就是一个闭包
var n = 10;
return function() { // 可以看出,这里的闭包是一个高阶函数
console.log(n);
}
}
var f = fun();
f(); // 在fun的作用域外,依旧访问了n
结合这个典例,理解闭包的主要作用:在时间和空间上延伸了变量的作用范围
闭包案例1(点击小li后,输出该小li的索引号)
var lis = document.querySelector('.vtb').querySelectorAll('li');
// 传统写法
for (var i = 0; i < lis.length; i++) {
lis[i].index = i;
lis[i].addEventListener('click', function() {
console.log(this.index);
// 因为for循环是同步任务(仅仅立即循环一次),function是异步任务(任务队列),因此不能直接打印i(i是循环一次后的最终值)
})
}
// 闭包写法
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
// 这些立即执行函数都是闭包——因为立即函数内部都能使用传入的i
(function(i) {
lis[i].addEventListener('click', function() {
console.log(i); // 与上面的写法相比,可以直接打印i(通过闭包延长了i的作用范围)
})
})(i); // 立即把i传入每个立即函数的独立作用域内
}
闭包案例2(3s后,打印所有li元素的内容)
// 错误写法
for (var i = 0; i < lis.length; i++) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000)
}
// 分析:
// for是同步任务,计时器内的function是异步任务,进入任务队列;此时for循环在主线程内已经执行完毕,i = 4,此时打印lis[i]只能得到undefined
// 正确写法(闭包)
for (var i = 0; i < lis.length; i++) {
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000)
})(i);
}
// 分析:仍旧是典型的、利用立即执行函数传入i形成闭包,来延长i的作用范围的思路
闭包案例3
// 计算打车价格
// 打车起步价13块钱(3公里内),之后每多一公里增加5块钱
// 如果遇到拥堵,总价格多收取10元拥堵费
var car = (function() {
var start = 13;
var total = 0; // 这个立即函数内都可以使用这两个变量,因此是闭包
return { // 返回值是一个包含两个方法的对象
getPrice: function(n) {
if (n <= 3) {
total = start;
} else {
total = start + (n - 3) * 5;
}
return total;
},
isCrowed: function(flag) {
return flag ? total + 10 : total;
}
}
})();
console.log(car.getPrice(5)); // 这样去调用
console.log(car.isCrowed(true)); // 因为car返回是个[包含两个方法的对象]
▊ 浅拷贝和深拷贝
var oldObj = {
id: 233, // 值为简单数据类型
name: 'loli',
color: ['pink', 'skyblue'], // 值为数组
msg: { // 值为对象
age: 12
}
}
var newObj = {}; // 先声明出一个空的新对象,之后作为参数传入
// 浅拷贝
function shallowCpoy(newObj, oldObj) {
for (var k in oldObj) {
newObj[k] = oldObj[k];
}
}
shallowCopy(newObj, oldObj);
oldObj.id = 255; // newObj.id不受影响
oldObj.color[0] = 'deeppink'; // newObj.color[0]受到影响
oldObj.msg.age = 13; // newObj.mag.age受到影响
// 分析:浅拷贝只拷贝一层(即简单数据类型这一层);而对象级别的(数组,对象)只是拷贝引用,也就是说,oldObj和newObj的color和msg指向同一个对象
// 其实,浅拷贝仅仅在键值对的层面上,拷贝了"值",而这个值不一定就是简单数据类型,还有可以又是一个键值对(数组或对象),也即引用
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;
}
}
}
deepCopy(newObj, oldObj);
oldObj.id = 255; // newObj.id不受影响
oldObj.color[0] = 'deeppink'; // newObj.color[0]不受影响
oldObj.msg.age = 13; // newObj.msg.age不受影响
// 深拷贝拷贝多层,每一级都被拷贝,都是独立的
// 补充
Object.assign(newObj, oldObj); // 浅拷贝有语法糖
☀ Loli & JS
♫ Suki