JS高级第三天

JS高级第三天

一、函数的定义和调用

1、函数的定义方式

  • 函数的定义方法:

    ​ 1. 函数声明

    ​ 2. 函数表达式

    ​ 3. 利用 Function 构造函数

1
2
3
4
5
6
7
8
9
10
11
12
//  1. 函数声明
function fn(a, b) {
return a + b;
};
// 2. 函数表达式
var fn1 = function() {
console.log('123');
};
// 3. 利用 Function 构造函数
var fn2 = new Function('a', 'b', 'console.log(a+b)');
fn2(1, 2);
console.dir(fn2);

2、函数调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 1. 普通函数
function fn1() {
console.log(123);
}
fn1();
// 2. 对象中的函数调用
var obj = {
say: function() {
console.log('hello');
}
};
obj.say();
// 3. 构造函数的调用
function Student(name, age) {
this.name = name;
this.age = age;
}
var xm = new Student('小明', 12);
console.log(xm);
// 4. 绑定事件的处理函数
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
console.log('点击了');
});
// 5. 定时器的处理函数
window.setTimeout(function() {
console.log('345');
}, 1000);
// 6. 立即执行函数
var num = 10;
!(function() {
// 提供了一个局部作用域,和外界的不冲突
var num = 10;
console.log('hi');
})()

二、this指向

1、函数内部this指向

  • 普通函数 指向window
  • 对象中的函数调用 指向调用者
  • 构造函数的调用 指向实例对象
  • 绑定事件的处理函数 指向绑定者
  • 定时器的处理函数 指向window
  • 立即执行函数 指向window
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<div>hello</div>
<button>点击</button>
<script>
// 1. 普通函数
function fn1() {
console.log(this); //window
}
fn1();
// 2. 对象中的函数调用
var obj = {
say: function() {
console.log('hello');
}
};
obj.say(); //this 指向obj
var foo = obj.say;
foo(); //this指向window
// 3. 构造函数的调用
var that;
function Student(name, age) {
that = this;
this.name = name;
this.age = age;
}
var xm = new Student('小明', 12);
console.log(xm);
console.log(xm === that);
// 4. 绑定事件的处理函数
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
console.log('点击了');
console.log(this); //指向btn
});
// 5. 定时器的处理函数
window.setTimeout(function() {
console.log(this); //指向window
}, 1000);
// 6. 立即执行函数
var num = 10;
!(function() {
var num = 10;
console.log(this); //指向window
})()

2、call改变this指向

  • fn.call([thisArg, arg1, arg2…])
  • 作用:调用一个函数,并指定调用时 this 的值
  • 参数: thisArg 函数中 this 的指定值; arg1, arg2…可选的参数列表
  • 返回值:函数调用的结果

注意:第一个参数 thisArg 如果不传或是 null、undefined,默认函数内 this 指向 window

3、apply 方法

  • fn.apply(thisArg,[ argsArray])
  • 作用:调用一个函数,并指定调用时 this 的值
  • 参数: thisArg 函数中 this 的指定值; argsArray 可选的参数数组(!!)
  • 返回值:函数调用的结果

和call方法区别

  • call方法从第二个传递的是每个独立参数,而apply传递的是参数数组
  • 在可以使用 call 的情况下, 都可以使用 apply 替代
1
2
3
4
5
6
7
8
9
10
11
function fn(a, b) {
console.log(this);
console.log(a, b);
}
fn.call({}, 2, 3);
fn.apply({}, [2, 3]);

// 1、使用apply去判断一组数的最大值
var arr = [1, 254, 545, 12, 5];
var res = Math.max.apply(null, arr);
console.log(res);

4、 bind方法

  • var newFn = fn.bind(thisArg,arg1, arg2, …)
  • 作用:基于原函数创建一个新函数,这个新函数的 this 被指定为第一个参数, 其余参数作为实参传递给新函数
  • 参数:thisArg 是新函数内 this 的预设值; arg1, arg2 是新函数预设传入的参数
  • 返回值: 新函数(本身不会调用函数)
1
2
3
4
5
6
7
8
function fn(a, b) {
console.log(this);
console.log(a + b);
}
// bind创建一个函数,并且可以为新函数预先指定传入实参
var newFn = fn.bind({}, 3, 4);
newFn(); //新函数中的this已经被修改为{},并且传入新的参数没用,bind已经预设了
fn(3, 4) //原函数中的this 让然指向window

三、严格模式

1、定义

采用具有限制性JavaScript变体的一种方式,摆脱了以前的松散模式

​ 1. 消除了 js 代码的不合理和不严谨地方,减少怪异行为

​ 2. 消除了代码的不安全地方,保证代码安全运行

​ 3. 提高编译器的效率,增加运行的速度

​ 4.禁用 ECMAScript 未来版本中可能会定义的一些语法 class extends super 等

  • 使用:在scrip代码块最前面添加’use strict’;

2、严格模式的变化

  • 变量未声明不能直接赋值
  • 变量必须先声明再使用
  • 函数内的 this 默认指向 undefined
  • 非函数内的 this 默认指向 undefined
  • 构造函数和类只能加 new 进行使用
  • 函数中的参数名不能重名
  • 不允许在非函数的代码块内声明函数 (chrome 浏览器没有实现)

四、高阶函数

对其他函数进行操作的函数,主要有两种类型的高阶函数:

​ 1. 把函数作为参数的函数

  1. 把函数作为返回值的函数

五、闭包

1、定义:

内部函数访问外部函数声明的变量,这种组合方式就是闭包

2、JS 中的垃圾回收机制(GC)

垃圾回收机制会定期(周期性)找出那些不再引用到的内存(变量),然后释放其内存

3、闭包产生的原因

(1) 当一个函数内的声明的变量没有被其他函数引用时,那么调用完这个函数后,所有的局部变量就会被垃圾回收机制清除;

​ (2) 一旦这个变量被另外一个函数所引用,这个变量的值会始终保存在内存中,不会被垃圾回收机制回收,从而形成了闭包

4、案例:

  • 需求: 点击每个按钮弹出当前按钮的索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>
<button>按钮</button>

<script>
// 闭包的应用: 在异步任务中使用同步任务下的变量
// 需求: 点击每个按钮弹出当前按钮的索引
var btns = document.querySelectorAll('button');
for (var i = 0; i < btns.length; i++) {
btns[i].index = i;
btns[i].onclick = function() {
console.log(this.index);
}
}
// 闭包应用
for (var i = 0; i < btns.length; i++) {
// 使用立即执行函数去构建闭包,叫做小闭包
(function(i) {
// 注意:这里的i就是我们在闭包中去保存的变量,保存了五份
btns[i].onclick = function() {
console.log(i);
}
})(i)
}
  • 计算打车的价格

打车起步价 8(3公里内),之后每多一公里增加 5 块钱,用户输入公里数就可以得出打车价格,如果有拥堵的情况,在之前价格的基础上再多收取 10 块钱拥堵费

封装一个求正常时打车价格 和 拥挤时打车价格的对象, 并全局中不能访问到起步价和总价

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj = (function() {
var total = 0;
var start = 8;
return {
price: function(km) {
total = km <= 3 ? start : (km - 3) * 5 + start;
return total;
},
busyPrice: function(isBusy) {
total = isBusy ? total + 10 : total;
return total;
}
}
})()
console.log(obj.price(20));
console.log(obj.busyPrice(true));

六、递归函数

  • 函数内部自己调用自己,作用和循环效果类似
  • 递归很容易发生 “栈溢出” 错误,所以和 while 循环类似必须要添加中断条件
1
2
3
4
5
6
7
8
9
//求 n 到 m 两个数之间的阶乘   5, 10 ===> 5 * 6 * ... * 10
function fn2(n, m) {
if (n == m) {
return m;
}
return n * fn2(n + 1, m);
}
var res2 = fn2(1, 5);
console.log(res2);
1
2
3
4
5
6
7
8
9
10
11
// 利用递归函数求斐波那契数列(兔子序列) 1, 1, 2, 3, 5, 8, 13, 21...
// 用户输入一个数字 n 就可以求出这个数字对应的斐波那契数列值

function fn(n) {
if (n == 0) return 0;
if (n == 1) return 1;
return fn(n - 1) + fn(n - 2);
// return n==0||n==1?n:fn(n - 1) + fn(n - 2);
}
console.log(fn(4));
//fn(3)+fn(2)==>fn(2)+fn(1)+fn(2)==>fn(1)+fn(0)+fn(1)+fn(2)==>fn(1)+fn(0)+fn(1)+fn(1)+fn(0)==>1+0+1+1+0=3

七、拷贝

简单数据类型都是直接拷贝,不区分深浅拷贝

1、浅拷贝

只拷贝对象一层的数据,复杂数据类型只拷贝内存地址值(引用同一个对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj = {
id: 1,
name: '小明',
data: {
id: 2,
age: 19
}
}
var obj2 = {};
// obj2 = obj;
// 遍历原对象
for (var key in obj) {
// obj2对象中添加属性
obj2[key] = obj[key]
}
obj.data.id = 10; //修改原对象data属性下的id值,obj2中的id也会改变
//这里的data属性值是相同的内存地址
console.log(obj2.data.id);

2、深拷贝

  • 拷贝对象多层的数据,遇到复杂数据类型会继续新建一个空间,拷贝里面每一层的属性和值
  • 和浅拷贝的区别
    • 深拷贝后的对象和原对象是完全隔离的,各自互不影响, 而浅拷贝中所有复杂数据类型的值,都是共同引用的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
var obj1 = {
id: 1,
name: '小明',
data: {
id: 2,
age: 19,
goods: {
id: 10
}
},
arr: [1, 2, 3]
}
var obj2 = {}
function deepClone(obj1, obj2) {
// 遍历数组
for (var key in obj1) {
var temp = obj1[key];

// 分三种情况讨论
// 数组==>在obj2中新建数组,再去拷贝
if (Array.isArray(temp)) {
obj2[key] = [];
deepClone(temp, obj2[key]);
// 对象==>在obj2中新建对象,再去拷贝
} else if (temp instanceof Object) {
obj2 = {};
deepClone(temp, obj2[key]);
// 简单数据类型,直接拷贝到obj2中
} else {
obj2[key] = temp;
}
}
};
deepClone(obj1, obj2);

obj1.data.id = 10;
obj1.arr[0] = 100;

// obj2 中的所有数据和obj1中的所有数据完全隔离, 互不影响
console.log(obj2.data.id);
console.log(obj2.arr[0]);

3、补充

  • 浅拷贝快速实现
1
2
3
4
5
6
7
8
9
10
11
12
var obj = {
a: 1,
b: 2,
c: {
d: 4
}
}
obj.c.d = 10;
var newObj = {...obj
};
console.log(newObj);

  • 深拷贝快速实现
  • JSON.parse(JSON.stringify(obj))
  • JSON.stringify()可以把复杂数据类型转成字符串
  • JSON.parse()可以把字符串再转成对象
1
2
3
var obj2 = JSON.parse(JSON.stringify(obj))
obj.c.d = 20;
console.log(obj2); //obj2中的属性值不会产生影响

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!