目录
3. 严格模式
JavaScript除了提供正常模式外,还提供了严格模式(strict mode)。ES5的严格模式是具有限制性JavaScript变体的一种方式,即在严格的条件下运行js代码
严格模式在IE10以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略
严格模式对正常的JavaScript语义做了一些更改:
- 消除了JavaScript语法的一些不合理、不严谨之处,减少了一些怪异行为
- 消除代码运行的一些不安全之处,保证代码运行的安全
- 提高编译器效率,增加运行速度
- 禁用了在ECMAScript的未来版本中可能会定义的一些语法,为未来新版本的JavaScript做好铺垫。比如一些保留字符,如:class、enum、export、import、super不能做变量名
3.2 开启严格模式
严格模式可以应用到整个脚本或个别函数中,因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况
1.为脚本开启严格模式
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句 "use strict"; (或 'use strict';)
<script>
'use strict';
console.log("这是严格模式");
</script>
因为'use strict'加了引号,所以老版本浏览器会把它当作一行普通字符串而忽略
有的script基本是严格模式,有的script脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中,这样独立创建一个作用域而不影响其他script脚本文件
<script>
(function() {
"use strict";
var num = 10;
function fn() {}
})();
</script>
<body>
<!-- 为整个脚本(script标签)开启严格模式 -->
<script>
'use strict'
// 下面的js代码就会按照严格模式执行代码
</script>
<script>
(function () {
'use strict'
})();
</script>
</body>
2.为函数开启严格模式
要给某个函数开启严格模式,需要把"use strict";(或'use strict';)声明放在函数体所有语句之前
<script>
function fn() {
'use strict';
// 下面的代码按照严格模式执行
}
function fn() {
// 里面的还是按照普通函数执行
}
</script>
3.4 严格模式中的变化
严格模式对Javascript的语法和行为,都做了一些改变
1.变量规定
1.在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用
<script>
'use strict';
// 1.变量必须先声明再使用
num = 10;
console.log(num);
</script>
2.严禁删除已经声明的变量。例如,delete x;语法是错误的
<script>
'use strict';
// 2.不能删除已经声明好的变量
var num = 10;
delete num;
</script>
2.严格模式下this指向问题
1.以前在全局作用域函数中的this指向window对象
2.严格模式下全局作用域中的this是undefined
3.以前构造函数时不加new也可以调用,当普通函数,this指向全局对象
4.严格模式下,如果构造函数不加new调用,this会报错
5.new实例化的构造函数指向创建的对象实例
6.定时器this还是指向window
7.事件、对象还是指向调用者
3.函数变化
1.函数不能有重名的参数
2.函数必须声明在顶层,新版本的JavaScript会引入“块级作用域”(ES6中已引入),为了与新版本接轨,不允许在非函数的代码块(比如说if、for)内声明函数
更多严格模式要求参考:MDN严格模式
4. 高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出
<script>
function fn(callback) {
callback&&callback();
}
fn(function() {alert('hi');});
</script>
<script>
function fn() {
return function() {}
}
fn();
</script>
此时fn就是一个高阶函数
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用,最典型的就是作为回调函数
高阶函数的应用:回调函数
<script>
// 高阶函数- 函数可以作为参数传递
function fn(a, b, callback) {
console.log(a + b);
callback && callback(); //执行完上面的语句再执行回调函数
}
fn(1, 2, function () {
console.log('我是最后调用的');
});
</script>
<style>
div {
position: absolute;
width: 100px;
height: 100px;
background-color: pink;
}
</style>
<body>
<div></div>
<script>
$("div").animate({
left: 500
}, function () {
$("div").css("backgroundColor", "red");
});
</script>
</body>
5. 闭包
5.1 变量作用域
变量根据作用域的不同分为两种:全局变量和局部变量
1.函数内部可以使用全局变量
2.函数外部不可以使用局部变量
3.当函数执行完毕,本作用域内的局部变量会销毁
5.2 什么是闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数
简单理解就是,一个作用域可以访问另外一个函数内部的局部变量
<script>
// 闭包(closure)指有权访问另一个函数作用域中变量的函数。
// 闭包:fun()这个函数的作用域 访问了fn()的局部变量
function fn() { //num这个变量所在的函数就是一个闭包函数
var num = 10;
function fun() {
console.log(num); // 10
}
fun();
}
fn();
</script>
注意:num这个变量所在的函数就是一个闭包函数
num这个变量所在的函数就是一个闭包函数
在chrome里打个断点看闭包:
<script>
// 闭包(closure)指有权访问另一个函数作用域中变量的函数。
// 闭包:fun()这个函数的作用域 访问了fn()的局部变量
// 外面的作用域也可以访问fn()内部的局部变量
function fn() {
var num = 10;
function fun() {
console.log(num);
}
return fun;
}
var f = fn();
f();
// 类似于
// var f = function fun() {
// console.log(num);
// }
</script>
<script>
// 闭包(closure)指有权访问另一个函数作用域中变量的函数。
// 闭包:fun()这个函数的作用域 访问了fn()的局部变量
// 外面的作用域也可以访问fn()内部的局部变量
function fn() {
var num = 10;
return function () {
console.log(num);
};
}
var f = fn();
f();
// 类似于
// var f = function {
// console.log(num);
// }
</script>
闭包的主要作用:延伸了变量的作用范围
本身num局部变量在使用完就应该销毁,而现在又在fn函数外面或者是里面被调用,所以要等所有的作用域使用完了再销毁
5.5 闭包案例
1.循环注册点击事件
以前的做法:利用自定义属性
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
// 闭包应用-点击li输出当前li的索引号
// 1. 我们可以利用动态添加属性的方式
var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
lis[i].setAttribute('index', i);
lis[i].onclick = function () {
//不能这么写,因为function是个异步任务,只有点击了才会执行,而for循环是同步任务,会直接执行,当点击li输出i的值的时候,for循环已经执行完了,此时i等于4(lis的长度是4,i的值等于3之后,i++ 等于4了 停止循环),所以此时不管点击哪个li输出的都是4
// console.log(i);
console.log(this.getAttribute('index'));
}
}
</script>
</body>
利用闭包的方式得到当前li的索引号:
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
// 闭包应用-点击li输出当前li的索引号
// 2.利用闭包的方式得到当前li的索引号
var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
// 立即执行函数也称为小闭包,因为立即执行函数里的每一个函数都可以使用它的i变量
(function (i) {
lis[i].onclick = function () {
console.log(i);
}
})(i);
}
</script>
</body>
2.循环中的setTimeout()
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
// 闭包应用-3秒钟之后,打印所有li元素的内容
var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
(function (i) {
setTimeout(function () {
console.log(lis[i].innerHTML);
}, 3000);
})(i);
}
</script>
</body>
闭包思考题:
<script>
// 思考题 1:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
console.log(object.getNameFunc()())
// var f = object.getNameFunc();
// // 类似于
// var f = function() {
// return this.name;
// }
// f();
</script>
关于这里this的指向:
f();调用函数就相当于一个匿名函数 function(){ this } ();被调用,匿名函数的this指向的是window,所以输出的结果是“The Window”,此时没有闭包的产生
<script>
// 思考题 2:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
var that = this;
return function () {
return that.name;
};
}
};
console.log(object.getNameFunc()())
// 相当于
// function () {
// return that.name;
// }();
// 在getNameFunc()里this赋值给了that,而这个this指向的是getNameFunc()的调用者object对象。所以输出的结果是object对象里的name My Object
</script>
5.6 闭包总结
1.闭包是什么?
闭包就是一个函数(一个作用域可以访问另外一个函数的局部变量)
2.闭包的作用是什么?
延伸变量的作用域
6.递归
6.1 什么是递归
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
简单理解,函数内部自己调用自己,这个函数就是递归函数
递归函数的作用和循环效果一样
由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return
<script>
// 递归函数 : 函数内部自己调用自己, 这个函数就是递归函数
var num = 1;
function fn() {
console.log('打印六次');
if (num == 6) return; //递归函数必须有return;
num++;
fn();
}
fn();
</script>
6.2 利用递归求数学题
1.求1*2*3*...*n
<script>
// 利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
function fn(n) {
if (n == 1) {
return 1;
}
return n * fn(n - 1);
}
console.log(fn(3)); //3
// 详细思路 假如用户输入的是3
//return 3 * fn(2)
//return 3 * (2 * fn(1))
//return 3 * (2 * 1)
//return 3 * (2)
//return 6
</script>
2.求斐波那契序列
<script>
// 利用递归函数求斐波那契数列(兔子序列) 1、1、2、3、5、8、13、21...
// 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值
// 我们只需要知道用户输入的n 的前面两项(n-1 n-2)就可以计算出n 对应的序列值
function fb(n) {
if (n == 1 || n == 2) {
return 1;
}
return fb(n - 1) + fb(n - 2);
}
console.log(fb(6)); //8
</script>
6.3 利用递归求:根据id返回对应的数据对象
<script>
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
name: '冰箱',
}, {
id: 12,
name: '洗衣机'
}]
}, {
id: 2,
name: '服饰'
}];
// 我们想要做输入id号,就可以返回的数据对象
// 1. 利用 forEach 去遍历里面的每一个对象
function getData(json, id) {
json.forEach(function (item) {
if (item.id == id) {
console.log(item.name);
// 如果想要得到里层的11,12,应该使用递归
// 里面item里面应该有goods这个数组,并且goods的长度应该大于0
} else if (item.goods && item.goods.length > 0) {
getData(item.goods, id);
}
});
}
getData(data, 1);
getData(data, 11);
getData(data, 12);
</script>
想要把获取到的数据保存起来,而不只是输出。。。。。。。。。。。。。。。。。
<script>
var data = [{
id: 1,
name: '家电',
goods: [{
id: 11,
gname: '冰箱',
goods: [{
id: 111,
gname: '海尔',
},
{
id: 112,
gname: '美的',
}
]
}, {
id: 12,
gname: '洗衣机'
}]
}, {
id: 2,
name: '服饰'
}];
function getData(json, id) {
var o = {}; //用来保存筛选出来的数据
json.forEach(function (item) {
if (item.id == id) {
// console.log(item.name);
// 将外层的item对象对o进行覆盖
o = item;
// 返回外层的item给里层的递归调用的函数
return item;
} else if (item.goods && item.goods.length > 0) {
// 得到外层返回的item之后对o进行覆盖,如果不覆盖的话就获得不到里层的数据
o = getData(item.goods, id);
}
});
return o;
}
console.log(getData(data, 1));
console.log(getData(data, 2));
console.log(getData(data, 11));
console.log(getData(data, 12));
console.log(getData(data, 111));
console.log(getData(data, 112));
</script>
6.4 浅拷贝和深拷贝
- 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用
- 深拷贝拷贝多层,每一级别的数据都会拷贝
- Object.assign(target,...sources) es6新增方法可以浅拷贝 语法糖 推荐使用
如果是浅拷贝,遇到更深层次的对象,只是拷贝了它的地址,如果修改了拷贝了的对象的复杂数据类型的属性值,被拷贝的对象的复杂数据类型的属性中同样会发生改变
<script>
// 浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用(地址).
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
}
};
var o = {};
for (var k in obj) {
// k是属性名 obj[k]是属性值
o[k] = obj[k];
}
console.log(o);
o.msg.age = 20;
o.msg.id = 2;
console.log(obj);
</script>
<script>
// 浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用.
// 深拷贝拷贝多层, 每一级别的数据都会拷贝.
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
}
};
var o = {};
// Object.assign是语法糖
// 也是浅拷贝
Object.assign(o, obj);
console.log(o);
</script>
深度拷贝:将被拷贝对象(obj)里的复杂数据类型里面的数据完全赋值一份给拷贝给的对象(o),如果改变里o里复杂数据类型里的属性,不会影响被拷贝的对象(obj)
深拷贝时,遍历被拷贝对象里面的属性,如果是简单类型,就直接将值拷贝给要拷贝到的对象里,如果是复杂数据类型,比如说对象和数组,就要判断这个属性是对象类型还是数组类型,再通过递归,把对象/数组里的属性拷贝到要拷贝到的对象内的对象/数组里
注意:我们递归调用的时候,是把值拷贝给了属性 deepCopy(newObj[k], item); item是旧的属性值,newObj[k]是新的对象的属性
注意:我们先判断是不是属于数组,再判断是不是属于对象,因为数组也属于对象
<script>
// 深拷贝拷贝多层, 每一级别的数据都会拷贝.
var obj = {
id: 1,
name: 'andy',
msg: {
age: 18
},
color: ['pink', 'red']
};
var o = {};
// 封装函数
function deepCopy(newObj, oldObj) {
// 遍历oldObj(obj)里的属性
for (var k in oldObj) {
// 获取属性值
var item = oldObj[k];
// 判断属性值obj[k]属于什么类型
if (item instanceof Array) {
// 1、判断这个值是不是数组类型
newObj[k] = []; //使得新对象(o)里的属性等于一个空数组,相当于o.color = [];
deepCopy(newObj[k], item);
} else if (item instanceof Object) {
// 2.判断这个值是不是对象类型
newObj[k] = {};
deepCopy(newObj[k], item);
} else {
// 3.判断这个值是不是简单数据类型
newObj[k] = item;
}
}
}
deepCopy(o, obj);
console.log(o);
o.msg.age = 20;
console.log(obj);
</script>