1、函数内 this 的指向
2、严格模式
1、什么是严格模式
JavaScript 除了提供正常模式外,还提供了 严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。
严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:
1、消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
2、消除代码运行的一些不安全之处,保证代码运行的安全。
3、提高编译器效率,增加运行速度。
4、禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class, enum, export, extends, import, super 不能做变量名
2、开启严格模式
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
2.1 为脚本开启严格模式
为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句:在所有语句之前放一个特定语句 "use strict"
<script>
'use strict';
console.log('严格模式已开启');
<script/>
有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。
<script>
(function (){
"use strict";
var num = 10;
})();
</script>
2.2 为函数开启严格模式
要给某个函数开启严格模式,需要把 "use strict";
声明放在函数体所有语句之前。
将 "use strict"
放在函数体的第一行,则整个函数以 “严格模式” 运行。
function fn(){
"use strict";
return "这是严格模式。";
}
function foo() {
console.log("这不是严格模式");
}
3、 严格模式中的变化
3.1 变量规定
1、在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用 var (let、const)
命令声明,然后再使用。
2、严禁删除已经声明变量。例如:delete x;
语法是错误的。
3.2 严格模式下 this 指向问题
1、以前在全局作用域函数中的 this 指向 window 对象。
2、严格模式下全局作用域中函数中的 this 是 undefined。
3、以前构造函数时不加 new 也可以调用,当普通函数,this 指向全局对象
4、严格模式下,如果构造函数不加 new 调用, this 指向的是 undefined 如果给他赋值则会报错
5、new 实例化的构造函数指向创建的对象实例。
6、定时器 this 还是指向 window。
7、事件、对象还是指向调用者。
3.3 函数变化
1、函数不能有重名的参数。
2、函数必须声明在顶层。新版本的 JavaScript 会引入 “块级作用域”( ES6 中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数比如if或者for语句。
3、垃圾回收机制
Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存
原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存
通常情况下有两种实现方式:
1、标记清除
2、引用计数
1、标记清除
JavaScript最常用的垃圾收回机制
1、当变量进入执行环境是,就标记这个变量为“进入环境“。进入环境的变量所占用的内存就不能释放,当变量离开环境时,则将其标记为“离开环境“
2、垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文(理解为作用域)中的变量,以及被在上下文中的变量引用的变量的标记去掉
3、剩下有标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了
4、随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存
2、引用计数
语言引擎有一张**“引用表”**,保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放
如果一个值不再需要了,引用数却不为0,垃圾回收机制无法释放这块内存,从而导致内存泄漏
比如:
const arr = [1, 2, 3, 4];
console.log('hello world');
上面代码中,数组[1, 2, 3, 4]是一个值,会占用内存。变量arr是仅有的对这个值的引用,因此引用次数为1。尽管后面的代码没有用到arr,它还是会持续占用内存
如果需要这块内存被垃圾回收机制释放,只需要设置如下:
arr = null
通过设置arr为null,就解除了对数组[1,2,3,4]的引用,引用次数变为 0,就被垃圾回收了
3、总结
有了垃圾回收机制,不代表不用关注内存泄露。那些很占空间的值,一旦不再用到,需要检查是否还存在对它们的引用。如果是的话,就必须手动解除引用
4、闭包
1、变量作用域
变量根据作用域的不同分为两种:全局变量和局部变量。
1、函数内部可以使用全局变量。
2、函数外部不可以使用局部变量。
3、当函数执行完毕,本作用域内的局部变量会销毁。
2、闭包的概念
闭包(closure)指有权访问另一个函数作用域中变量的函数。
简单理解就是一个函数和对其周围状态的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
function init() {
var name = "Mozilla"; // name 是一个被 init 创建的局部变量
function displayName() { // displayName() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
displayName();
}
init();
displayName()
没有自己的局部变量。然而,由于闭包的特性,它可以访问到外部函数的变量
3、闭包的作用
如何在 fn1() 函数外面访问 fn1() 中的局部变量 x ?
function fn1() {
let x = 10;
// fn2 是一个闭包
function fn2() {
console.log(x);
}
return fn2;
}
let f = fn1(); //f也是一个函数,正常调用就行
f(); // 10
闭包作用:延伸变量的作用范围。
4、闭包应用-点击li输出当前索引号
// 有以下节点:
<ol>
<li>Banana</li>
<li>Apple</li>
<li>Peach</li>
</ol>
错误示例:
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
console.log(i);
// function是一个异步任务,点击之后才会执行,但是for循环是同步任务,立马去执行,一直输出最后索引+1的索引号
}
}
方案1:设置 index 属性
for (var i = 0; i < lis.length; i++) {
lis[i].index = i; //动态添加属性
lis[i].onclick = function () {
console.log(this.index);
}
}
方案2:闭包
立即执行函数也称为小闭包,因为立即执行函数里面的任何一个函数都可以使用它的i变量
for (var i = 0; i < lis.length; i++) {
//利用for循环创建了4个立即执行函数
(function (i) { //接受参数
// 每一个点击事件的函数成为一个闭包,点击事件内部访问到了来自立即执行函数的变量 i
lis[i].onclick = function () {
console.log(i); //这个函数里面用到的值是立即执行函数传过来的,形成闭包
}
})(i); //传递参数
}
5、闭包应用-3s后打印所有li内容(延时调用)
for (var i = 0; i < lis.length; i++) {
(function (i) {
setTimeout(function () {
console.log(lis[i].innerHTML);
}, 3000);
})(i);
}
5、递归
1、什么是递归
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
简单理解:函数内部自己调用自己, 这个函数就是递归函数。
递归函数的作用和循环效果一样。
由于递归很容易发生 “栈溢出” 错误(stack overflow),所以必须要加退出条件 return。
2、利用递归求阶乘
<body>
<script>
function fn(n) {
if (n == 0 || n == 1) {
return 1;
} else {
return n * fn(n - 1);
}
}
console.log(fn(8));
</script>
</body>
2、递归求斐波那契数列
<body>
<script>
function fb(n) {
if (n == 0 || n == 1) {
return 1;
} else {
return fb(n - 1) + fb(n - 2);
}
}
console.log(fb(10));
</script>
</body>
6、深浅拷贝
1、浅拷贝
1、浅拷贝,指的是创建新的数据,这个数据有着原始数据属性值的一份精确拷贝
2、如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址
3、即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址
4、下面简单实现一个浅拷贝
<body>
<script>
function shallowCopy(obj) {
const newObj = {};
//prop是属性名
for (let prop in obj) {
//hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
if (obj.hasOwnProperty(prop)) { //
newObj[prop] = obj[prop];
}
}
return newObj
}
var obj = {
id: '1',
name: 'andy'
};
console.log(shallowCopy(obj));
</script>
</body>
Object.assign(newObj,obj) //语法糖
2、深拷贝
深拷贝开辟一个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
<body>
<script>
let arr = [5, 4, 9, 8];
let obj1 = {
name: 'xxx',
sex: '男',
like: ['红色', '蓝色'],
book: {
title: 'js程序',
price: '88'
}
};
//手写深拷贝
function deepClone(obj) {
// 查看要拷贝的是数组还是对象,如果数组创建空数组,是对象创建空对象
const newObj = obj instanceof Array ? [] : {};
for (let k in obj) {
//K属性名 obj[k]值
//判断当前每个元素是否是对象或者数组
//如果还是对象,继续递归拷贝
//是值,直接添加到新创建的对象或者数组里
if (typeof obj[k] === 'object') {
//实现一个递归拷贝
newObj[k] = deepClone(obj[k])
} else { //否则是值, 直接添加到新建的 newObj中
newObj[k] = obj[k];
}
}
return newObj;
}
console.log(deepClone(obj1));
</script>
</body>
方法二:使用JSON字符串实现深拷贝
// JSON.stringify 简单粗暴
var origin_data = { a: 1, b: 2, c: [1, 2, 3] }
var copy_data = JSON.parse(JSON.stringify(origin_data))
console.log(copy_data)
7、正则表达式
1、什么是正则表达式
正则表达式是一种用来匹配字符串的强有力的武器
它的设计思想是用一种描述性的语言定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的
在 JavaScript中,正则表达式也是对象,构建正则表达式有两种方式:
1、字面量创建,其由包含在斜杠之间的模式组成
var regexp = /表达式/;
2、通过调用 RegExp 对象的构造函数创建
var regexp = new RegExp(/表达式/);
2、测试正则表达式 test
test()
正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。
regexObj.test(str);
1、regexObj 是写的正则表达式
2、str 我们要测试的文本
3、检测 str 文本是否符合我们写的正则表达式规范
3、正则表达式中的特殊字符
3.1 正则表达式的组成
一个正则表达式可以 由简单的字符构成,比如 /abc/
,也可以是 简单和特殊字符的组合,比如 /ab*c/
。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+
等。
3.2 边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符。
如果 ^
和 $
在一起,表示必须是精确匹配(不能多不能少,只能是这些)。
let regexp = /^he$/;
console.log(regexp.test('hello')); // flase
console.log(regexp.test('he')); // true
3.3 字符类
字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。
1 [] 方括号
/[abc]/.test('andy')
// true
正则含义:后面的字符串只要包含 abc 中任意一个字符,都返回 true。
2 [ - ] 方括号内部 - 范围符
/^[a-z]$/.test('c')
// true
含义:方括号内部加上 - 表示范围,这里表示 a 到 z 26个英文字母都可以。
3 [^] 方括号内部 取反符 ^
/[^abc]/.test('andy')
// false
方括号内部加上 ^ 表示 取反,只要包含方括号内的字符,都返回 false 。
4 字符组合
/[a-z1-9]/.test('andy')
// true
方括号内部可以使用字符组合,这里表示包含 a 到 z 的 26 个英文字母和 1 到 9 的数字都可以。
3.4 量词符
量词符用来设定某个模式出现的次数。
3.5 括号总结
1、大括号:量词符。里面表示重复次数。
2、中括号:字符集合。匹配方括号中的任意字符。
3、小括号:表示优先级。
3.6 预定义类
3.7 正则案例
1、手机号码:/^1[3|4|5|7|8][0-9]{9}$/
2、QQ:[1-9][0-9]{4,}
(腾讯QQ号从10000开始)
3、昵称是中文:^[\u4e00-\u9fa5]{2,8}$
4. 正则表达式中的替换
1、replace 替换
replace()
方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。
stringObject.replace(regexp/substr,replacement)
1、第一个参数:被替换的字符串 或者 正则表达式
2、第二个参数:替换为的字符串
3、返回值:一个替换完毕的新字符串
2、正则表达式参数
当 replace 中第一个参数为正则表达式的时候,还有一个 switch 参数可选。
/表达式/[switch]
switch
(也称为修饰符)按照什么样的模式来匹配. 有三种值:
1、g:全局匹配
2、i:忽略大小写
3、gi:全局匹配 + 忽略大小写
举例:
let msg = 'what\'s the fuck? Damn it!';
msg = msg.replace(/fuck|damn/gi, '****');
console.log(msg);
// what's the ****? **** it!