JavaScript高级
馨er BOSS

1 JS的内存管理和闭包

1.1 内存管理

内存的管理的生命周期

第一步:分配申请你需要的内存(申请)

第二步:使用分配的内存(存放一些东西,比如对象等)

第三步:不需要使用时,对其进行释放

JavaScript通常情况下是不需要手动来管理的

1.2 JS的内存管理

JavaScript会在定义变量时为我们分配内存。

  • 对于基本数据类型内存的分配会在执行时,直接在空间进行分配;

  • 对于复杂数据类型内存的分配会在内存中开辟一块空间,并且将这块空间的指针返回值变量引用;

JS的垃圾回收:常见的GC算法——标记清除(设置一个根对象(root object),垃圾回收器会定期从这个根开始,找所有从根开始有引用到的对象,对于哪些没有引用到的对象,就认为是不可用的对象)

1.3 闭包

一个普通的函数function,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包。从广义的角度来说:JavaScript中的函数都是闭包;从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用域的变量,那么它是一个闭包

1
2
3
4
5
6
7
8
function makeAdder(count) {
var name = "why"
return function(num) {
return count + num;
}
}
var add10 = makeAdder(10)
console.log(add10(5))

内存泄漏:count释放不掉,name未引用,会被释放掉

解决内存泄露:add10 = null

2 JS函数的this指向

2.1 几种绑定

2.1.1 默认绑定

独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用,使用默认绑定(this为undefined)

1
2
3
4
5
6
// 案例一
function foo() {
console.log(this); // undefined
}

foo();
1
2
3
4
5
6
7
8
9
10
11
12
13
// 案例二
function test1() {
console.log(this); // undefined
test2();
}
function test2() {
console.log(this); // undefined
test3();
}
function test3() {
console.log(this); // undefined
}
test1();
1
2
3
4
5
6
7
8
9
10
11
// 案例三
function foo(func) {
func();
}
var obj = {
name: "why",
bar: function () {
console.log(this); // undefined
}
}
foo(obj.bar);

2.1.2 隐式绑定

隐式绑定是通过某个对象进行调用的(this为这个对象)

1
2
3
4
5
6
7
8
function foo() {
console.log(this); // {name: "why", foo: ƒ foo()}
}
var obj = {
name: "why",
foo: foo
}
obj.foo();

2.1.3 显示绑定

隐式绑定有一个前提条件:必须在调用的对象内部有一个对函数的引用(比如一个属性)。如果没有这样的引用,在进行调用时,会报找不到该函数的错误,正是通过这个引用,间接的将this绑定到了这个对象上

如果我们不希望在对象内部包含这个函数的引用,同时又希望在这个对象上进行强制调用,JavaScript所有的函数都可以使用call和apply方法(这个和Prototype有关)。

call和apply都要求两个参数:第一个参数是相同的(要求是一个绑定this的对象),第二个的参数,apply为数组,call为参数列表(可接受多个参数)。以上过程称之为显示绑定

1
2
3
4
5
6
function foo() {
console.log(this);
}
foo.call(window); // window
foo.call({name: "why"}); // {name: "why"}
foo.call(123); // 123

bind:将一个函数显示的绑定在一个对象上

1
2
3
4
5
6
7
8
9
10
function foo() {
console.log(this); // {name: "why"}
}

var obj = {
name: "why"
}

var bar = foo.bind(obj);
bar();

2.1.4 new绑定

JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。

使用new关键字来调用函数时,会执行如下的操作:

1、创建一个全新的对象;

2、这个新对象会被执行prototype连接;

3、这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);

4、如果函数没有返回其他对象,表达式会返回这个新对象;

1
2
3
4
5
6
function Person(name) {
console.log(this); // Person {name: "why", constructor: Object}
this.name = name;
}
var p = new Person("why");
console.log(p); // Person {name: "why", constructor: Object}

2.1.5 内置函数的绑定

1
2
3
setTimeout(function () {
console.log(this); // window
}, 1000);
1
2
3
4
var box = document.querySelector(".box");
box.onclick = function () {
console.log(this === box); // true
}
1
2
3
4
5
var names = ["abc", "cba", "nba"];
var obj = { name: "why" };
names.forEach(function (item) {
console.log(this);// 打印三次{name: "why"}
}, obj)

2.2 规则优先级

1、默认规则的优先级最低

2、显示绑定优先级高于隐式绑定

3、new绑定优先级高于隐式绑定

4、new绑定优先级高于bind(new绑定不能和call、apply一起使用)

2.3 this规则之外

2.3.1 忽略显示绑定

如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则:

1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
console.log(this);
}
var obj = {
name: "why"
}
foo.call(obj); // obj对象
foo.call(null); // window
foo.call(undefined); // window

var bar = foo.bind(null);
bar(); // window

2.3.2 间接函数引用

创建一个函数的间接引用,这种情况使用默认绑定规则。

赋值(obj2.foo = obj1.foo)的结果是foo函数,foo函数被直接调用,那么是默认绑定

1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
console.log(this);
}
var obj1 = {
name: "obj1",
foo: foo
};
var obj2 = {
name: "obj2"
};
obj1.foo(); // obj1对象
(obj2.foo = obj1.foo)(); // window

2.3.3 箭头函数

箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。

箭头函数不会绑定this、arguments属性,箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误)

箭头函数的书写

1
2
3
nums.forEach((item, index, arr) => {

})

():函数的参数,{}:函数的执行体

编写优化

优化一:如果只有一个参数()可以省略

1
nums.forEach(item => {})

优化二:如果函数执行体中只有一行代码, 那么可以省略大括号,并且这行代码的返回值会作为整个函数的返回值

1
2
nums.forEach(item => console.log(item))
nums.filter(item => true)

优化三:如果函数执行体只有返回一个对象,那么需要给这个对象加上{}

1
2
3
4
var foo = () => {
return { name: "abc" }
}
var bar = () => ({name: "abc"})

我们来看一个模拟网络请求的案例:

  • 这里我使用setTimeout来模拟网络请求,请求到数据后如何可以存放到data中呢?

  • 我们需要拿到obj对象,设置data;

  • 但是直接拿到的this是window,我们需要在外层定义:var _this = this

  • 在setTimeout的回调函数中使用_this就代表了obj对象

1
2
3
4
5
6
7
8
9
10
11
var obj = {
data: [],
getData: function () {
var _this = this;
setTimeout(function () {
// 模拟获取到的数据
var res = ["abc", "cba", "nba"];
_this.data.push(...res);
}, 1000);
}
}

以上代码等同于

1
2
3
4
5
6
7
8
9
10
var obj = {
data: [],
getData: function () {
setTimeout(() => {
// 模拟获取到的数据
var res = ["abc", "cba", "nba"];
this.data.push(...res);
}, 1000);
}
}

如果把getData也改为箭头函数,则

1
2
3
4
5
6
7
8
var obj = {
data: [],
getData: () => {
setTimeout(() => {
console.log(this); // undefined
}, 1000);
}
}

2.4 面试题

面试题一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
// 间接引用,不遵从this规则,this为window,则this.name为全局定义的name,即"window"
sss(); // window
// 显示规则,this为person对象, 则this.name为"person"
person.sayName(); // person
// 显示规则
(person.sayName)(); // person
// 间接引用
(b = person.sayName)(); // window
}
sayName();

面试题二

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
var name = "window";
var person1 = {
name: "person1",
foo1: function () {
console.log(this.name);
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name);
}
},
foo4: function () {
return () => {
console.log(this.name);
}
}
};
var person2 = { name: 'person2' }
person1.foo1(); // person1
person1.foo1.call(person2); // person2

person1.foo2(); // window
// 箭头函数,this绑定无效,遵从父元素的this
person1.foo2.call(person2); // window

// f()()执行子函数 f()()()执行孙函数
// return中function的this为window对象
person1.foo3()(); // window
person1.foo3.call(person2)(); // window
person1.foo3().call(person2); // person2

person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1

面试题三

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
var name = "window";
function Person (name) {
this.name = name;
this.foo1 = function () {
console.log(this.name)
}
this.foo2 = () => console.log(this.name)
this.foo3 = function () {
return function () {
console.log(this.name)
}
}
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.foo1(); // person1
person1.foo1.call(person2); // person2

person1.foo2(); // person1
person1.foo2.call(person2); // person1

person1.foo3()(); // window
person1.foo3.call(person2)(); // window
person1.foo3().call(person2); // person2

person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1

面试题四

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
var name = "window";
function Person(name) {
this.name = name;
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name)
}
},
foo2: function () {
return () => {
console.log(this.name)
}
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2

person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj

3 JS函数式编程

3.1 实现apply、call、bind

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
Function.prototype.hyapply = function (thisBings, args) {
thisBings = thisBings ? Object(thisBings) : window
thisBings.fn = this
if (!args) {
thisBings.fn()
} else {
var result = thisBings.fn(...args)
}
delete thisBings.fn
return result
}

Function.prototype.hycall = function (thisBings, args) {
thisBings = thisBings ? Object(thisBings) : window
thisBings.fn = this
var result = thisBings.fn(...args)
delete thisBings.fn
return result
}

Function.prototype.hybind = function (thisBings, args) {
thisBings = thisBings ? Object(thisBings) : window
thisBings.fn = this

return function (...newArgs) {
var args = [...bindArgs, ...newArgs]
return thisBings.fn(...args)
}
}

3.2 arguments

JS面向对象

JS面向对象的继承

异常处理

错误处理方案

对于函数来说,需要对其他人传进来的参数进行验证,否则可能得到的是我们不想要的结果——抛出一个异常

throw语句:throw语句用于抛出一个用户自定义的异常;当遇到throw语句是,当前的函数执行会被停止(throw后面的语句不会执行)

throw关键字

throw表达式就是在throw后面可以跟上一个表达式来表示具体的异常信息

throw关键字可以跟的类型

基本数据类型:number、string、Boolean

对象类型:对象类型可以包含更多的信息

1
2
3
4
5
6
7
// 每次写这么长的对象又有点麻烦,所以我们可以创建一个类:
class HYError {
constructor(errCode, errMessage) {
this.errCode = errCode
this.errMessage = errMessage
}
}

Error类型

JavaScript提供了一个Error类,我们可以直接创建这个类的对象

1
2
3
function foo() {
throw new Error("error message", "123")
}

Error包含三个属性

message:创建Error对象时传入的message

name:Error的名称,通常和类的名字一致

stack:整个Error的错误信息,包括函数的调用栈,当我们直接打印Error对象时,打印的就是stack

Error有一些自己的子类

RangeError:下标值越界时使用的错误类型

SyntaxError:解析语法错误时使用的错误类型

TypeError:出现类型错误时,使用的错误类型

异常的处理

一个函数抛出了异常,调用它的时候程序会被强制终止:

这是因为如果我们在调用一个函数时,这个函数抛出了异常,但是我们并没有对这个异常进行处理,那么这个异常会继续传递到上一个函数调用中,而如果到了最顶层(全局)的代码中依然没有对这个异常的处理代码,这个时候就会报错并且终止程序的运行

异常的捕获

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
throw "coderwhy error message"
}

function bar() {
try {
foo()
console.log("foo后续的代码~")
} catch (error) {
console.log(error)
}
}
bar()

在ES10中,catch后面绑定的error可以省略

当然,如果有一些必须执行的代码,我们可以使用finally来执行。如果try和finally都有返回值,那么会使用finalyy当中的返回值

JavaScript的模块化

什么是模块化

  • 事实上模块化开发最终的目的是将程序划分成一个个小的结构;

  • 这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构;

  • 这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用;也可以通过某种方式,导入另外结构中的变量、函数、对象等;

上面说提到的结构,就是模块。按照这种结构划分开发程序的过程,就是模块化开发的过程

CommonJS规范和Node关系

模块化案例

exports案例

补充

with语句

用于扩展一个语句的作用域链

不建议使用with语句,因为它可能是混淆错误和兼容性问题的根源

1
2
3
4
5
6
7
8
9
var obj = {
name: "hello world",
age: 18
}

with(obj) {
console.log(name)
console.log(age)
}

eval函数

可以将传入的字符串当作JavaScript代码来运行

1
2
3
var evalString = 'var message = "Hello World";console.log(message)'
eval(evalString)
console.log(message)

不建议在开发中使用eval: ①eval代码的可读性非常的差(代码的可读性是高质量代码的重要原则);②eval是一个字符串,那么有可能在执行的过程中被刻意篡改,那么可能会造成被攻击的风险;③eval的执行必须经过JS解释器,不能被JS引擎优化

严格模式

认识严格模式

image-20220504223847515

开启严格模式

可以在js文件中开启严格模式,也可以对某一个函数开启严格模式。使用"use strict"开启

严格模式限制

1、无法意外的创建全局变量

2、严格模式会使引起静默失败(silently fail,注:不报错也没有任何效果)的赋值操作抛出异常

3、严格模式下试图删除不可删除的属性

4、严格模式不允许函数参数有相同的名称

5、不允许0的八进制语法

6、在严格模式下,不允许使用with

7、在严格模式下,eval不再为上层引用变量

8、在严格模式下,this绑定不会默认转成对象

  • 本文标题:JavaScript高级
  • 本文作者:馨er
  • 创建时间:2022-05-23 14:17:26
  • 本文链接:https://sjxbbd.vercel.app/2022/05/23/cc590f552d66/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!