基础总结深入

引用类型

  • Object: 任意对象

  • Function: 一种特别的对象(可以执行)

  • Array: 一种特别的对象(数值下标,内部数据是有序的)

    判断数据类型

    三种方法判断数据类型:typeof、instanceof、===

  • typeof:
    可以判断: undefined/ 数值 / 字符串 / 布尔值 / function

  • 不能判断: null与object object与array*

  • instanceof:
    判断对象的具体类型

  • ===
    可以判断: undefined, null

typeof的返回值是一个字符串,如果要检查一个变量a是不是undefined,要用typeof判断,应该写console.log(typeof(a) === ‘undefined’)
typeof(null)的值是object,因此如果要检查一个变量是不是null,只能用===

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var b1 = {
b2: [1, 'abc', console.log],
b3: function () {
console.log('b3')
return function () {
return 'xfzhang'
}
}
}
console.log(b1 instanceof Object, b1 instanceof Array) // true false
console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) // true true
console.log(b1.b3 instanceof Function, b1.b3 instanceof Object) // true true

console.log(typeof b1.b3==='function') // true

console.log(typeof b1.b2[2]==='function')
b1.b2[2](4) //调用函数演示
console.log(b1.b3()()) //调用函数演示

undefined与null

区别:

  • undefined代表定义未赋值
  • null定义并赋值了, 只是值为null

什么时候给变量赋值为null:

  • 初始赋值, 表明将要赋值为对象
  • 结束前, 让对象成为垃圾对象(被垃圾回收器回收)

内存、数据、变量

内存:

  • 内存条通电后产生的可储存数据的空间
  • 产生和死亡: 内存条(电路板)->通电->产生内存空间->存储数据->处理数据->断电->内存空间和数据都消失
  • 存放的数据:内部存储的数据、地址值
  • 分类:栈: 全局变量/局部变量;堆: 对象

变量:

  • 可变化的量, 由变量名和变量值组成
  • 每个变量都对应的一块小内存, 变量名用来查找对应的内存, 变量值就是内存中保存的数据

内存,数据, 变量三者之间的关系:

  • 内存用来存储数据的空间
  • 变量是内存的标识

JS引擎管理内存的方式

内存生命周期

  • 分配小内存空间, 得到它的使用权
  • 存储数据, 可以反复进行操作
  • 释放小内存空间

释放内存

  • 局部变量: 函数执行完自动释放
  • 对象: 赋值null成为垃圾对象->垃圾回收器回收

对象

必须使用[‘属性名’]的情况

  1. 属性名包含特殊字符: - 空格
  2. 属性名不确定
1
2
3
4
5
6
7
8
9
10
//情况一
p['content-type'] = 'text/json'
console.log(p['content-type'])

//情况二
var propName = 'myAge'
var value = 18

p[propName] = value
console.log(p[propName])

对象字面量和构造函数

var obj = {} 等价于 var obj = new Object(),
所以用对象字面量创建的对象,是Object函数的实例,

函数

call和apply在JS基础上已经记过笔记

回调函数

  1. 回调函数满足:
  • 自己定义
  • 没有立即调用
  • 最终它在某个时刻或某个条件下执行了
  1. 常见的回调函数
  • dom事件回调函数 ->发生事件的dom元素
  • 定时器回调函数 ->window
  • ajax请求回调函数(后面讲)
  • 生命周期回调函数(后面讲)

原型

博客/自我总结

可以参考以下三篇博客:
JavaScript中Function和Object的原型和原型链

JS中 Object.prototype 原型和原型链 详解

彻底理解什么是原型链,prototype和__proto__的区别(最清晰)

自己总结一下:

  • 在函数对象中存在原型对象prototype,在普通对象中没有prototype,但存在__proto__,所有对象都有__proto__属性
  • function定义的对象有prototype属性,使用new生成的对象没有prototype属性,存在__proto__
  • 特殊的是, Function.prototype没有prototype属性,在控制台输出undefined
  • 如果认为普通对象A是对象B的实例,A的__proto__属性等价于B的prototype属性
  • Object.__proto__指向Function.prototype(HBuilder的控制台输出结果为function Empty() {}),但是Object.prototype.__proto__指向null

prototype是函数对象自带的一个属性,比如console.log(Object.prototype),控制台打印[object Object];console.log(Function.prototype),控制台打印function Empty() {}。给人的感觉是,打印 自己的“本质”。prototype后又可以继续访问它的__proto__属性,如console.log(Object.prototype.proto),控制台打印null。

而__proto__是普通对象自带的一个属性,普通对象.__proto__即为函数对象.prototype。__proto__给人的感觉是,要追溯它的下一级,比如用构造函数得到的对象,它的__proto__就是new后边的函数,又比如Object其实是一个函数对象,那么console.log(Object.proto)会在控制台打印function Empty() {},即它可等价为Function.prototype。

constructor

每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
原型对象中有一个属性constructor, 它指向函数对象

console.log(Date.prototype.constructor===Date)  //true
console.log(Date.prototype.constructor)    //function Date() { [native code] }

关于construcor,可以看这篇博客:
javascript 对象中的 constructor属性的作用

显式原型与隐式原型

  1. 每个函数function都有一个prototype,即显式原型(属性)
  2. 每个实例对象都有一个__proto__,可称为隐式原型(属性)
  3. 对象的隐式原型的值为其对应构造函数的显式原型的值
  4. 内存结构(图)
  5. 总结:
  • 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象(该“空”指的是没有程序员指定的属性或方法,不是什么都没有)
  • 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
  • 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)

内存结构图:
在这里插入图片描述

原型链

在这里插入图片描述

执行上下文和作用域

执行上下文

代码分类(位置)

  • 全局代码
  • 函数(局部)代码

全局执行上下文

  • 在执行全局代码前将window确定为全局执行上下文
  • 对全局数据进行预处理
  • var定义的全局变量->undefined, 添加为window的属性
  • function声明的全局函数->赋值(fun), 添加为window的方法
  • this->赋值(window)
  • 开始执行全局代码

函数执行上下文

  • 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)
  • 对局部数据进行预处理
  • 形参变量->赋值(实参)->添加为执行上下文的属性
  • arguments->赋值(实参列表), 添加为执行上下文的属性
  • var定义的局部变量->undefined, 添加为执行上下文的属性
  • function声明的函数 ->赋值(fun), 添加为执行上下文的方法
  • this->赋值(调用函数的对象)
  • 开始执行函数体代码

执行上下文对象个数=调用函数的次数+1(多出的1是window,即全局执行上下文)

1
2
3
4
5
6
7
8
9
10
11
12
13
<script type="text/javascript">
var a = 10;
var bar = function (x) {
var b = 5;
foo(x + b);
};
var foo = function (y) {
var c = 5;
console.log(a + c + y);
}
bar(10);//调用bar算一次,bar中还要调用foo再算一次,共2次
bar(10);//同理,共2次
</script>

这一段代码中共4+1=5个执行上下文对象

执行上下文栈

  1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
  2. 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
  3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
  4. 在当前函数执行完后,将栈顶的对象移除(出栈)
  5. 当所有的代码执行完后, 栈中只剩下window
  1. 依次输出什么?
    gb: undefined
    fb: 1
    fb: 2
    fb: 3
    fe: 3
    fe: 2
    fe: 1
    ge: 1
  2. 整个过程中产生了几个执行上下文? 5
1
2
3
4
5
6
7
8
9
10
11
12
console.log('gb: '+ i);
var i = 1;
foo(1);
function foo(i) {
if (i == 4) {
return;
}
console.log('fb:' + i);
foo(i + 1) //递归调用: 在函数内部调用自己
console.log('fe:' + i);
}
console.log('ge: ' + i);

作用域链

理解

  • 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)
  • 查找变量时就是沿着作用域链来查找的

查找一个变量的查找规则

  • 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2
  • 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3
  • 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常

    区别与联系

    区别1
  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了,而不是在函数调用时
  • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
  • 函数执行上下文是在调用函数时, 函数体代码执行之前创建

区别2

  • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
  • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放

联系

  • 执行上下文(对象)是从属于所在的作用域
  • 全局上下文环境->全局作用域
  • 函数上下文环境->对应的函数使用域

闭包

彻底理解JS中的闭包

闭包,看这一篇就够了——带你看透闭包的本质,百发百中

定义

  • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
  • 闭包是嵌套的内部函数,是包含被引用变量(函数)的对象
  • 闭包存在于嵌套的内部函数中,执行函数定义就会产生闭包(不用调用内部函数)
  • 产生闭包的条件:
    * 函数嵌套
    * 内部函数引用了外部函数的数据(变量/函数)
    

常见闭包

将函数作为另一个函数的返回值

1
2
3
4
5
6
7
8
9
10
11
function fn1() {
var a = 2;
function fn2() {
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f(); // 3
f(); // 4

将函数作为实参传递给另一个函数调用

1
2
3
4
5
6
function showDelay(msg, time) {
setTimeout(function () {
alert(msg);
}, time);
}
showDelay('atguigu', 2000);

闭包的作用

  1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中,延长了局部变量的生命周期(类比static)
  2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

问题:

  1. 函数执行完后, 函数内部声明的局部变量一般不存在, 存在于闭包中的变量才可能存在
  2. 在函数外部不能直接访问函数内部的局部变量吗, 但可以通过闭包让外部操作它

闭包的生命周期

产生: 在嵌套内部函数定义执行完时产生(仅是完成了定义,不是调用)
死亡: 在嵌套的内部函数成为垃圾对象时

1
2
3
4
5
6
7
8
9
10
11
12
13
function fn1() {
//此时闭包已经产生(函数提升, 内部函数对象已经创建了)
var a = 2;
function fn2 () {
a++;
console.log(a);
}
return fn2;
}
var f = fn1();
f() // 3;
f() // 4;
f = null; //闭包死亡(包含闭包的函数对象成为垃圾对象)

闭包的应用

定义JS模块

  • 具有特定功能的js文件
  • 将所有的数据和功能都封装在一个函数内部
  • 只向外暴露一个包含n个方法的对象或函数
  • 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能

对象创建模式

工厂模式

(这部分在JS基础中讲过,此处是为了和下方的自定义构造函数放在一起)

  • 套路: 通过工厂函数动态创建对象并返回
  • 适用场景: 需要创建多个对象
  • 问题: 对象没有一个具体的类型,都是Object类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createPerson(name, age) { //返回一个对象的函数===>工厂函数
var obj = {
name: name,
age: age,
setName: function (name) {
this.name = name;
}
}
return obj;
}
// 创建2个人
var p1 = createPerson('Tom', 12);
var p2 = createPerson('Bob', 13);

// p1和p2是Object类型

自定义构造函数模式

  • 套路: 自定义构造函数, 通过new创建对象
  • 适用场景: 需要创建多个类型确定的对象
  • 问题: 每个对象都有相同的数据,浪费内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Person(name, age) {
this.name = name;
this.age = age;
this.setName = function (name) {
this.name = name;
}
}
var p1 = new Person('Tom', 12);
p1.setName('Jack');
console.log(p1.name, p1.age);
console.log(p1 instanceof Person);
//p1确定的类型是Person

function Student (name, price) {
this.name = name;
this.price = price;
}
var s = new Student('Bob', 13000);
console.log(s instanceof Student);
//p2确定的类型是Student
var p2 = new Person('JACK', 23);
console.log(p1, p2);

构造函数+原型 的组合模式

  • 套路: 自定义构造函数,属性在函数中初始化, 方法添加到原型上
  • 适用场景: 需要创建多个类型确定的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name, age) { 
//属性在函数中初始化
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name) {
//向原型中添加方法
this.name = name;
}

var p1 = new Person('Tom', 23);
var p2 = new Person('Jack', 24);
console.log(p1, p2);

继承模式

原型链继承

(这一块很重要,笔试/面试可能会考察)
套路

  1. 定义父类型构造函数
  2. 给父类型的原型添加方法
  3. 定义子类型的构造函数
  4. 创建父类型的对象赋值给子类型的原型
  5. 将子类型原型的构造属性设置为子类型(参考:阮一峰的博客
  6. 给子类型原型添加方法
  7. 创建子类型的对象: 可以调用父类型的方法

关键:子类型的原型为父类型的一个实例对象

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
//1.定义父类型构造函数
function Supper() {
this.supProp = 'Supper property';
}

//2.给父类型的原型添加方法
Supper.prototype.showSupperProp = function () {
console.log(this.supProp);
}
//3. 定义子类型的构造函数
function Sub() {
this.subProp = 'Sub property';
}
//4. 创建父类型的对象赋值给子类型的原型
Sub.prototype = new Supper();

//5. 将子类型原型的构造属性设置为子类型
Sub.prototype.constructor = Sub;

//6. 给子类型原型添加方法
Sub.prototype.showSubProp = function () {
console.log(this.subProp);
}

//7. 创建子类型的对象: 可以调用父类型的方法
var sub = new Sub();
sub.showSupperProp(); //"Supper property"
// sub.toString();
sub.showSubProp(); //"Sub property"

console.log(sub); // Sub
/*
Sub
subProp: "Sub property"
__proto__: Supper
*/

借用构造函数继承

套路:

  1. 定义父类型构造函数
  2. 定义子类型构造函数
  3. 在子类型构造函数中调用父类型构造(call,或直接用this)

关键:在子类型构造函数中通用call()调用父类型构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 定义父类型构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}

//2. 定义子类型构造函数
function Student(name, age, salary) {
//3.在子类型构造函数中调用父类型构造
Person.call(this, name, age) ;
// 相当于: this.Person(name, age);
this.salary = salary;
}
var s = new Student('Tom', 20, 14000);
console.log(s.name+" "+s.age+" "+s.salary); //Tom 20 14000

组合继承

原型链+借用构造函数的组合继承

  1. 利用原型链实现对父类型对象的方法继承
  2. 利用call()借用父类型构建函数初始化相同属性
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
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.setName = function (name) {
this.name = name;
}

function Student(name, age, price) {
Person.call(this, name, age); //为了得到属性
this.price = price;
}
Student.prototype = new Person(); //为了能使用父类型的方法
Student.prototype.constructor = Student;
//修正constructor属性(这一句可以没有)

Student.prototype.setPrice = function (price) {
this.price = price;
}
//这里可以去掉,然后在var s下边一行加:s.__proto__.setPrice = ...
//或者直接s.setPrice...

var s = new Student('Tom', 24, 15000);
s.setName('Bob');
s.setPrice(16000);
console.log(s.name+' '+s.age+' '+ s.price);

线程机制与事件机制

进程与线程

进程:程序的一次执行,它占有一片独有的内存空间

线程: CPU的基本调度单位, 是程序执行的一个完整流程

进程与线程:

  • 一个进程中一般至少有一个运行的线程: 主线程
  • 一个进程中也可以同时运行多个线程,,称程序是多线程运行的
  • 一个进程内的数据可以供其中的多个线程直接共享
  • 多个进程之间的数据是不能直接共享的

单进程浏览器:firefox、老版IE
多进程浏览器:chrome、新版IE

如何查看浏览器是否是多进程运行:任务管理器->进程

浏览器都是多线程运行的

浏览器内核

浏览器内核:支持浏览器运行的最核心的程序

不同浏览器的内核:Chrome, Safari: webkit;firefox: Gecko;IE: Trident;360,搜狗等国内浏览器: Trident + webkit

内核由很多模块组成:

  • html,css文档解析模块 : 负责页面文本的解析
  • dom/css模块 : 负责dom/css在内存中的相关处理
  • 布局和渲染模块 : 负责页面的布局和效果的绘制
  • 定时器模块 : 负责定时器的管理
  • 网络请求模块 : 负责服务器请求(常规/Ajax)
  • 事件响应模块 : 负责事件的管理

定时器引发的思考

定时器并不能保证真正定时执行,一般会延迟一丁点(可以接受), 也有可能延迟很长时间(不能接受)

1
2
3
4
5
6
7
8
9
10
11
12
13
document.getElementById('btn').onclick = function () {
var start = Date.now();
console.log('启动定时器前...');
setTimeout(function () {
console.log('定时器执行了', Date.now()-start);
}, 200);
console.log('启动定时器后...');

// 做一个长时间的工作,发现定时器执行的时间远远超过200ms
for (var i = 0; i < 1000000000; i++) {
}
}
//修改i之后的时间,先ctrl+s保存,在浏览器中刷新,再点击按钮,才会显示新的运行时间

定时器回调函数在主线程执行,js是单线程的

定时器是如何实现的:事件循环模型

JS是单线程的

如何证明js执行是单线程的?

  • setTimeout()的回调函数是在主线程执行的
  • 定时器回调函数只有在运行栈中的代码全部执行完后才有可能执行

为什么js要用单线程模式, 而不用多线程模式?

  • JavaScript的单线程,与它的用途有关。
  • 作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。

代码的分类:

  • 初始化代码
  • 回调代码

JS引擎执行代码的基本流程:

  • 执行初始化代码: 包含一些特别的代码、回调函数(异步执行)
  • 设置定时器
  • 绑定事件监听
  • 发送ajax请求
  • 后面在某个时刻才会执行回调代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
setTimeout(function () {
console.log('timeout 2rd');
alert('Second');
}, 2000)
setTimeout(function () {
console.log('timeout 1st');
alert('First');
}, 1000)
setTimeout(function () {
console.log('timeout() 0');
}, 0)
function fn() {
console.log('fn()');
}
fn();
console.log('alert()之前');
alert('------');
//暂停当前主线程的执行, 同时暂停计时, 点击确定后, 恢复程序执行和计时
console.log('alert()之后');

该程序执行的次序:

  1. 执行fn函数,在控制台输出fn()
  2. 控制台输出alert()之前
  3. 弹出’——‘
  4. 控制台输出alert()之后
  5. 控制台输出timeout() 0
  6. 控制台输出timeout 1st 弹出First
  7. 控制台输出timeout 2nd 弹出Second

事件循环模型

所有代码分类

  • 初始化执行代码(同步代码): 包含绑定dom事件监听, 设置定时器, 发送ajax请求的代码
  • 回调执行代码(异步代码): 处理回调逻辑

JS引擎执行代码的基本流程:初始化代码->回调代码

模型的2个重要组成部分:

  • 事件(定时器/DOM事件/Ajax)管理模块
  • 回调队列

模型的运转流程

  • 执行初始化代码,将事件回调函数交给对应模块管理
  • 当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
  • 只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function fn1() {
console.log('fn1()')
}
fn1()
document.getElementById('btn').onclick = function () {
console.log('点击了btn')
}
setTimeout(function () {
console.log('定时器执行了')
}, 2000)
function fn2() {
console.log('fn2()')
}
fn2()

控制台依次打印:
fn1()
fn2()
定时器执行了

点击按钮,打印:点击了btn

Web Workers

这一部分暂时先不学习

参考:阮一峰的ES6教程和尚硅谷教程

let

特性

  • 变量不能重复声明
  • 块级作用域,{}
  • 不存在变量提升
  • 不影响作用域链
    1
    2
    3
    4
    5
    6
    7
    8
    {
    let school = '尚硅谷';
    function fn(){
    console.log(school);
    }
    fn();
    //在fn函数内部找不到school,沿着作用域链找到了let school = '尚硅谷'
    }

    和var的比较

    遍历并绑定事件:
  1. 用var(正确示范):
    1
    2
    3
    4
    5
    for(var i = 0;i<items.length;i++){
    items[i].onclick = function(){
    this.style.background = 'pink';
    }
    }
  2. 用var(错误示范):
    1
    2
    3
    4
    5
    6
    for(var i = 0;i<items.length;i++){
    //此处的var换成let即可正常运行
    items[i].onclick = function(){
    items[i].style.background = 'pink';
    }
    }
    js先执行初始化代码,每个回调函数是赋给了items[i],即items[0],items[1]和items[2]都赋上了回调函数,但是由于i为全局变量,循环进行完后i的值为3,回调函数在执行的时候,function内的语句都变成了 items[3].style.background = ‘pink’; 又因为items[3]越界,所以就会报错。

而用let时,相当于创建了一个个块级作用域,每个i只在自身的块级作用域内有效,就避免了这个问题。

const

特性

  • 一定要赋初始值
  • 一般常量使用大写(潜规则)
  • 值不能修改
  • 块级作用域
  • 对于数组和对象的元素修改(比如增删对象属性和方法,增删数组元素), 不算做对常量的修改, 不会报错

变量的解构赋值

数组/对象都可用,但是数组要求顺序一致,对象要求花括号内的名称必须和对象内元素/方法的名称一致

第一个例子,因为H在let中是第二个元素,对应原数组的第二个元素,所以控制台输出Yan而不是Han

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Dorm214 = ['Cui','Yan','Han','Dong'];
let[Y,H,D] = Dorm214;
console.log(H);//Yan

var Danmo = {
name:"Danmo",
age:"不详",
school:function(){
console.log("此人在关山口男子职业技术学院读书");
}
}
let {school} = Danmo;
//解构调用
school();
//可以直接用方法的名字调用,不需要再写Danmo.school

模板字符串(反引号)与字符串拼接

单引号’’和双引号“”的字符串内,不能有换行符

而ES6新引入的反引号``,它建立的字符串中可以换行

字符串拼接,只能在模板字符串中使用,格式是${变量名} ←花括号

1
2
3
4
5
6
7
8
9
10
11
//还记得JS练习3增删员工吗,这是以前的代码:
tr.innerHTML = "<td>"+name+"</td>"+
"<td>"+email+"</td>"+
"<td>"+salary+"</td>"+
"<td><a href='javascript:;'>Delete</a></td>";

//用模板字符串:
tr.innerHTML = `<td>${name}</td>
<td>${email}</td>
<td>${salary}</td>
<td><a href='javascript:;'>Delete</a></td>;`

对象简化写法

为了书写简洁,ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let name = 'HUST';
let change = function(){
console.log('来华科当单身狗!');
}

const school = {
name,//不需写name = name,
change,//不需写change = change,
improve(){
console.log("提高单身姿势!");
}
//不需写improve = function(){...}的“=function”
}
console.log(name);//HUST
school.change();//来华科当单身狗!
school.improve();//提高单身姿势!

箭头函数

ES6 允许使用「箭头」(=>)定义函数

特性

  • this 是静态的. this 始终指向函数声明时所在作用域下的 this 的值
  • 不能作为构造实例化对象(不能var 对象 = new 箭头函数)
  • 不能使用 arguments 变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    function getName(){
console.log(this.name);
}
let getName2 = () => {
console.log(this.name);
}

//设置 window 对象的 name 属性
window.name = '华中科技大学';
const school = {
name: "HUST"
}
//由于箭头函数是在全局下声明的,this就永远为window(静态this无法被修改)
getName.call(school);//HUST
getName2.call(school);//华中科技大学

简写

  • 当形参只有一个时,可以省略小括号
  • 当代码体只有一行语句时,可以省略花括号,如果这一条语句是return,此return也必须省略,语句的执行结果就是函数的返回值
    1
    2
    let pow = n => n * n;
    console.log(pow(8));//64

    应用

    需求1: 点击 div 2s 后颜色变成『粉色』
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //获取元素
    let ad = document.getElementById('ad');
    //绑定事件
    ad.addEventListener("click", function(){
    //定时器
    setTimeout(() => {
    //修改背景颜色
    //此处的this,是ad
    this.style.background = 'pink';
    console.log(this);
    }, 2000);
    });
    如果不用箭头函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //获取元素
    let ad = document.getElementById('ad');
    //绑定事件
    ad.addEventListener("click", function(){
    //保存 this 的值,如果不这么做,直接在定时器使用this,那么this的值为window
    let _this = this;
    //定时器
    setTimeout(function(){
    _this.style.background = 'pink';
    }, 2000);
    });

需求2:从数组中返回偶数的元素
filter方法——菜鸟教程

1
2
3
4
5
6
7
8
9
10
11
12
 const arr = [1,6,9,10,100,25];
const result = arr.filter(function(item){
if(item % 2 === 0){
return true;
}else{
return false;
}
});

const arr = [1,6,9,10,100,25];
const result = arr.filter(item => item % 2 === 0);
console.log(result);

函数参数默认值设置

ES6 允许给函数参数赋值初始值

形参初始值 具有默认值的参数, 一般位置要靠后(潜规则)

1
2
3
4
5
function add(a,b,c=10) {
return a + b + c;
}
let result = add(1,2);
console.log(result);//13

可与解构赋值结合:

1
2
3
4
5
6
7
8
9
10
11
12
function connect({host="1037", username,password, port}){
console.log(host); //1037(没传参数,使用默认值)
console.log(username); //root
console.log(password); //root
console.log(port); //3306
}
connect({
//host: 'hust',
username: 'root',
password: 'root',
port: 3306
})

rest参数

ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments

rest获取实参和arguments的区别:前者把参数存入数组,后者是类数组但不是数组

1
2
3
4
5
//ES5 获取实参的方式
function date(){
console.log(arguments);
}
date('白芷','阿娇','思慧');

1
2
3
4
5
//rest 参数
function date(...args){
console.log(args);
}
date('阿娇','柏芝','思慧');
1
2
3
4
5
6
7
//rest 参数必须要放到参数最后
function fn(a,b,...args){
console.log(a);//1
console.log(b);//2
console.log(args);//剩下参数全存入数组,(4)[3,4,5,6]
}
fn(1,2,3,4,5,6);

扩展运算符

特性

『…』 扩展运算符能将『数组』转换为逗号分隔的『参数序列』

对于下例,即把[‘易烊千玺’,’王源’,’王俊凯’]变为’易烊千玺’,’王源’,’王俊凯’

1
2
3
4
5
6
7
const tfboys = ['易烊千玺','王源','王俊凯'];
// 声明一个函数
function chunwan(){
console.log(arguments);
}
chunwan(...tfboys);
// 等价于chunwan('易烊千玺','王源','王俊凯')

1
2
3
4
5
6
7
const tfboys = ['易烊千玺','王源','王俊凯'];
// 声明一个函数
function chunwan(){
console.log(arguments);
}
chunwan(tfboys);
//不加拓展运算符

应用

数组的合并

1
2
3
4
5
const kuaizi = ['王太利','肖央'];
const fenghuang = ['曾毅','玲花'];
// const zuixuanxiaopingguo = kuaizi.concat(fenghuang);
const zuixuanxiaopingguo = [...kuaizi, ...fenghuang];
console.log(zuixuanxiaopingguo);

数组的复制

1
2
3
const sanzhihua = ['E','G','M'];
const sanyecao = [...sanzhihua];// ['E','G','M']
console.log(sanyecao);

将伪数组转化为真正数组

1
2
3
const divs = document.querySelectorAll('div');
const divArr = [...divs];
console.log(divArr);

Symbol——第七种数据类型

概述

创建:

1
2
3
4
5
6
7
8
9
10
//创建Symbol
let s = Symbol();
let s2 = Symbol('尚硅谷');
let s3 = Symbol('尚硅谷');
//s2===s3报false

//Symbol.for 创建
let s4 = Symbol.for('尚硅谷');
let s5 = Symbol.for('尚硅谷');
//s4===s5报true

Symbol不能与其他数据进行运算

作用

Symbol表示独一无二的值

可以简单、高效、安全地向对象中添加属性

1
2
3
4
5
let game = {
name:'俄罗斯方块',
up: function(){},
down: function(){}
};

现在想要向这个对象中添加up和down方法(不知道会重名),则用Symbol较为安全

1
2
3
4
5
6
7
8
9
10
let methods = {
up: Symbol(),
down: Symbol()
};
game[methods.up] = function(){
console.log("我可以改变形状");
}
game[methods.down] = function(){
console.log("我可以快速下降");
}

直接把Symbol作为属性名:

1
2
3
4
5
6
7
8
9
let game = {
name:"狼人杀",
[Symbol('say')]: function(){
console.log("我可以发言")
},
[Symbol('zibao')]: function(){
console.log('我可以自爆');
}
}

Symbol的一些方法:控制对象在特定场景下的表现

像上方的.hasInstance,.match,它们都是Symbol的属性,而Symbol.hasInstance,又作为对象的属性,控制对象在特定场景下的表现(或者说拓展对象的功能)

1
2
3
4
5
const arr = [1,2,3];
const arr2 = [4,5,6];
arr2[Symbol.isConcatSpreadable] = false;
//Symbol.isConcatSpreadable属性,表示数组合并时是否展开
console.log(arr.concat(arr2));

如图,Array(3)没有展开

迭代器

for of

演示:
for…of遍历输出键值,而for…in遍历输出键名

1
2
3
4
5
6
7
8
const xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
//使用 for...of 遍历数组
for(let v of xiyou){
console.log(v);//唐僧 孙悟空 猪八戒 沙僧
}
for(let v in xiyou){
console.log(v);//0 1 2 3
}

使用条件和原理

使用迭代器的条件:对象拥有Symbol.iterator属性


条件

迭代器的工作原理:

  • 创建一个指针对象,指向当前数据结构的起始位置
  • 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
  • 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
  • 每调用 next 方法返回一个包含 value 和 done 属性的对象
1
2
3
const xiyou = ['唐僧','孙悟空','猪八戒','沙僧'];
let iterator = xiyou[Symbol.iterator]();
console.log(iterator);

打印Symbol.iterator属性对应函数的返回值

继续调用next方法:

1
2
3
4
5
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

返回具有value和done属性的对象,遍历完成后done为true

自定义遍历数据

Symbol.iterator要返回对象,该对象内需要有next方法,next需要返回一个含value和done(必须叫value和done)的对象

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
const banji = {
name: "终极一班",
stus: [
'xiaoming',
'xiaoning',
'xiaotian',
'knight'
],
[Symbol.iterator]() {
let index = 0;
let _this = this;
//使得_this指向banji,此处可以用箭头函数替代
return {
next: function () {
if (index < _this.stus.length) {
const result = { value: _this.stus[index], done: false };
index++;
return result;
}else{
return {value: undefined, done: true};
}
}
};
}
}
for (let v of banji) {
console.log(v);//xiaoming xiaoning xiaotian knight
}

生成器函数

简介、传参

生成器是一个特殊的函数,在function和括号之间要加星号,它是实现异步编程的一种方式

它不会立即执行,而需要先获取迭代器对象,用next方法一步一步执行,每一次next都执行到下一个yield为止(yield为分隔符,yield后可以是数字,字符串…)

可以向next中传参,其参数会成为上一个yield的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function * gen(arg){
console.log(arg);
let one = yield 111;//one=='BBB'
console.log(one);
let two = yield 222;//two=='CCC'
console.log(two);
let three = yield 333;//three=='DDD'
console.log(three);
}
//执行获取迭代器对象
let iterator = gen('AAA');
console.log(iterator.next());
//AAA
//{value:111,done:false}
console.log(iterator.next('BBB'));
//BBB
//{value:222,done:false}
console.log(iterator.next('CCC'));
//CCC
//{value:333,done:flase}
console.log(iterator.next('DDD'));
//DDD
//{value:undefined,done:true}

应用

需求:使用定时器,1s 后控制台输出 111,2s后输出 222,3s后输出 333

传统方案:

1
2
3
4
5
6
7
8
9
setTimeout(() => {
console.log(111);
setTimeout(() => {
console.log(222);
setTimeout(() => {
console.log(333);
}, 3000);
}, 2000);
}, 1000);

当计时器不断增多,缩进不断向右,形成“回调地狱”

改进:

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
function one(){
setTimeout(()=>{
console.log(111);
iterator.next();
},1000)
}
function two(){
setTimeout(()=>{
console.log(222);
iterator.next();
},2000)
}
function three(){
setTimeout(()=>{
console.log(333);
iterator.next();
},3000)
}
function * gen(){
yield one();
yield two();
yield three();
}
//调用生成器函数
let iterator = gen();
//此处第一次调用
iterator.next();

需求:依次获取到用户数据 订单数据 商品数据(异步)

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
function getUsers(){
setTimeout(()=>{
let data = '用户数据';
//调用 next 方法, 并且将数据传入
//第二次调用next方法,参数data传给第一次yield的返回值,即users='用户数据'
iterator.next(data);
}, 1000);
}

function getOrders(){
setTimeout(()=>{
let data = '订单数据';
//第三次调用next方法,参数data传给第二次yield的返回值,即orders='订单数据'
iterator.next(data);
}, 1000)
}

function getGoods(){
setTimeout(()=>{
let data = '商品数据';
iterator.next(data);
}, 1000)
}

function * gen(){
let users = yield getUsers();
let orders = yield getOrders();
let goods = yield getGoods();
}
//调用生成器函数
let iterator = gen();
//第一次调用next方法
iterator.next();

Promise

基本用法

对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

我的理解:Promise内部的函数执行,和用then调用是可以不同时进行的(像生成器gen和next方法),所以使用Promise是一个异步的过程,这些ES6中新的异步特性,大多都是为了避免“回调地狱”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//实例化 Promise 对象
const p = new Promise(function(resolve, reject){
setTimeout(function(){
let data = '数据库中的用户数据';
resolve(data);
//执行resolve后就不会再执行下方代码了
//如果把上边两行注了,状态变为失败,调用then的时候会在控制台输出'数据读取失败'
let err = '数据读取失败';
reject(err);
}, 1000);
});

//调用 promise 对象的 then 方法
p.then(function(value){
console.log(value);//'数据库中的用户数据'
}, function(reason){
console.error(reason);
//.error和.log类似,只不过输出的是红色的错误信息
})

then中可以只传一个函数作为参数(只写成功函数,或只写失败函数)

读取封装文件

此处和node.js有联系,某些地方暂时看不懂也没关系

Node.js 文件系统

同理,这是一个异步的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//1. 引入 fs 模块
const fs = require('fs');

//2. 使用 Promise 封装
const p = new Promise(function(resolve, reject){
fs.readFile("./resources/为学.md", (err, data)=>{
//判断如果失败,比如把文件路径写错
if(err) reject(err);
//如果成功,文件路径正确且可读
resolve(data);
});
});

p.then(function(value){
console.log(value.toString());
//value的值是一个buffer
//调用.toString方法后,就能把文件内容打印在控制台
}, function(reason){
console.log("读取失败!!");
});

封装Ajax请求

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
// 接口地址: https://api.apiopen.top/getJoke
const p = new Promise((resolve, reject) => {
//1. 创建对象
const xhr = new XMLHttpRequest();

//2. 初始化
xhr.open("GET", "https://api.apiopen.top/getJ");

//3. 发送
xhr.send();

//4. 绑定事件, 处理响应结果
xhr.onreadystatechange = function () {
//判断
if (xhr.readyState === 4) {
//判断响应状态码 200-299
if (xhr.status >= 200 && xhr.status < 300) {
//表示成功
resolve(xhr.response);
//不用Promise会这样写:console.log(xhr.response)
//外边这个function是回调函数,即依靠回调函数来实现异步
} else {
//如果失败
reject(xhr.status);
//不用Promise会这样写:console.error(xhr.status)
}
}
}
})

//指定回调
p.then(function(value){
console.log(value);
}, function(reason){
console.error(reason);
});

以前在回调函数中执行回调,现在在then方法中执行回调,结构更清晰,也没有回调地狱

Promise.then方法详解

调用 then 方法,then方法的返回结果是 Promise 对象, 对象状态由回调函数的执行结果决定

  1. 如果回调函数中返回的结果是 非 promise 类型的属性, 状态为成功, 返回值为对象的成功的值(如果不写return,返回值为undefined,为非promise类型属性,状态仍为成功)
  2. 如果回调函数中返回的结果是promise类型属性,状态和该promise的状态一致,对象的成功/失败值也和该promise一致
  3. 如果没有返回值,而是抛出错误,则状态为失败,对象的失败值和抛出错误的内容一致
1
2
3
4
5
6
7
8
9
10
11
12
13
const p = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve('用户数据');
// reject('出错啦');
}, 1000)
});
const result = p.then(value => {
console.log(value);
throw new Error('出错啦!');//扔出错误1
//throw '出错啦!'; //扔出错误2
}, reason=>{
console.warn(reason);
});

扔出错误1

扔出错误2

由于then的返回结果是Promise对象,因此可以链式调用

1
2
3
4
5
p.then(value=>{

}).then(value=>{

});

用Promise合并三个文本

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
//首先需要声明一个promise对象
const p = new Promise((resolve, reject) => {
fs.readFile("./resources/为学.md", (err, data) => {
resolve(data);
});
});
//通过then方法形成链式结构
//then的返回值是promise对象,先执行函数体内的语句,
//该promise的value就等于成功值,即resolve括号内的值
p.then(value => {
return new Promise((resolve, reject) => {
fs.readFile("./resources/插秧诗.md", (err, data) => {
resolve([value, data]);
//value是上一个成功值,即为学,data是插秧诗
//这里新建了一个数组,并把为学和插秧诗放在这个数组内
});
});
}).then(value => {
return new Promise((resolve, reject) => {
fs.readFile("./resources/观书有感.md", (err, data) => {
//value是之前创造的数组,data是观书有感
//把观书有感作为元素压入这个数组
value.push(data);
resolve(value);
//现在得到一个数组,元素是三个文本
});
})
}).then(value => {
console.log(value.join('\r\n'));
// \r是回车(回到本行行首),\n是换行(到下一行同一个位置处)
//用回车符和换行符来分隔三个文本
});

.catch方法

Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

是ES6提供的一个语法糖,专门指定状态为失败时的回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const p = new Promise((resolve, reject)=>{
setTimeout(()=>{
//设置 p 对象的状态为失败, 并设置失败的值
reject("出错啦!");
}, 1000)
});

// p.then(function(value){}, function(reason){
// console.error(reason);
// });

p.catch(function(reason){
console.warn(reason);
});

集合Set

简介与API

set规定元素不能重复,如果重复会自动去重

set支持拓展运算符,和for…of

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//声明一个 set
let s = new Set();//声明一个空集合
let s2 = new Set(['大事儿','小事儿','好事儿','坏事儿','小事儿']);
//集合接受数组为参数,用来初始化

//元素个数
console.log(s2.size);//4
//添加新的元素
s2.add('喜事儿');
//删除元素
s2.delete('坏事儿');
//检测
console.log(s2.has('糟心事'));//false
//清空
s2.clear();
console.log(s2);//Set(0)

for(let v of s2){
console.log(v);
}

实践

  • 数组去重:先把数组转化为集合,再用…将集合转化为数组
  • 交集:使用数组filter方法,把其中的一个数组转化为集合(去重提高效率),再判断该集合中有无该元素(filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素)
  • 差集:对交集取反即可
  • 并集:先用…合并数组,转化为集合去重,再转化为数组
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
let arr = [1,2,3,4,5,4,3,2,1];
//1. 数组去重
let result = [...new Set(arr)];
console.log(result);
//2. 交集
let arr2 = [4,5,6,5,6];
let result = [...new Set(arr)].filter(item => {
let s2 = new Set(arr2);// 4 5 6
if(s2.has(item)){
return true;
}else{
return false;
}
});
//以下为简略写法:
let result = [...new Set(arr)].filter(item => new Set(arr2).has(item));

console.log(result);

//3. 并集
let union = [...new Set([...arr, ...arr2])];
console.log(union);

//4. 差集
let diff = [...new Set(arr)].filter(item => !(new Set(arr2).has(item)));
console.log(diff);

Map

Map是一个升级版的对象,传统Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果需要“键值对”的数据结构,Map 比 Object 更合适。

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
//声明 Map
let m = new Map();

//添加元素
m.set('name','尚硅谷');
m.set('change', function(){
console.log("我们可以改变你!!");
});
let key = {
school : 'ATGUIGU'
};
m.set(key, ['北京','上海','深圳']);

//size
console.log(m.size);

//删除
m.delete('name');

//获取
console.log(m.get('change'));
console.log(m.get(key));

//清空
m.clear();
//console.log(m); //Map(0){}

//遍历
for(let v of m){
console.log(v);
}
console.log(m);

控制台信息

class

简介

为了让JS更有面向对象内味,ES6加入了class,其绝大部分功能ES5都可以实现

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
//ES5通过原型对象添加方法
function Phone(brand, price){
this.brand = brand;
this.price = price;
}

//添加方法
Phone.prototype.call = function(){
console.log("我可以打电话!!");
}

//实例化对象
let Huawei = new Phone('华为', 5999);
Huawei.call();
console.log(Huawei);

//class
class Shouji{
//构造方法 名字不能修改
constructor(brand, price){
this.brand = brand;
this.price = price;
}

//方法必须使用该语法, 不能使用 ES5 的对象完整形式(call:function(){})
call(){
console.log("我可以打电话!!");
}

let onePlus = new Shouji("1+", 1999);
console.log(onePlus);

静态成员

ES5引例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Phone(){

}
Phone.name = '手机';
Phone.change = function(){
console.log("我可以改变世界");
}
//这些添加的属性和方法,只属于函数对象,不属于实例对象,称为静态成员
Phone.prototype.size = '5.5inch';
//如果想要实例对象也有这些属性或方法,必须添加给原型对象
let nokia = new Phone();

console.log(nokia.name); //undefined
nokia.change();//报错,change函数未定义
console.log(nokia.size);//5.5inch

ES6:静态成员同理,只属于函数但不属于实例

1
2
3
4
5
6
7
8
9
10
11
class Phone{
//静态属性
static name = '手机';
static change(){
console.log("我可以改变世界");
}
}

let nokia = new Phone();
console.log(nokia.name);//undefined
console.log(Phone.name);//手机

类继承

ES5的组合继承:

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
//手机
function Phone(brand, price){
this.brand = brand;
this.price = price;
}

Phone.prototype.call = function(){
console.log("我可以打电话");
}

//智能手机
function SmartPhone(brand, price, color, size){
Phone.call(this, brand, price);
this.color = color;
this.size = size;
}

//设置子级构造函数的原型
SmartPhone.prototype = new Phone;
SmartPhone.prototype.constructor = SmartPhone;

//声明子类的方法
SmartPhone.prototype.photo = function(){
console.log("我可以拍照")
}

SmartPhone.prototype.playGame = function(){
console.log("我可以玩游戏");
}

const chuizi = new SmartPhone('锤子',2499,'黑色','5.5inch');

console.log(chuizi);

ES6的类继承:
声明子类时,用extends 父类,在子类中想要获取父类的属性,使用super(),其相当于父类的constructor

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
class Phone{
//构造方法
constructor(brand, price){
this.brand = brand;
this.price = price;
}
//父类的成员属性
call(){
console.log("我可以打电话!!");
}
}

class SmartPhone extends Phone {
//构造方法
constructor(brand, price, color, size){
super(brand, price);// Phone.call(this, brand, price)
this.color = color;
this.size = size;
}

photo(){
console.log("拍照");
}

playGame(){
console.log("玩游戏");
}

call(){
console.log('我可以进行视频通话');
}
}

const xiaomi = new SmartPhone('小米',799,'黑色','4.7inch');
// console.log(xiaomi);
xiaomi.call();
xiaomi.photo();
xiaomi.playGame();

set和get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Phone{
get price(){
console.log("价格属性被读取了");//当读取price的值时,执行该函数
return 'iloveyou';//return的值会赋给price
}
set price(newVal){//此处必须传参,否则报错
console.log('价格属性被修改了');//设置price的值时,执行该函数
}
}

//实例化对象
let s = new Phone();
console.log(s.price);
s.price = 'free';

数值拓展

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
//0. Number.EPSILON 是 JavaScript 表示的最小精度
//EPSILON 属性的值接近于 2.2204460492503130808472633361816E-16
function equal(a, b){
if(Math.abs(a-b) < Number.EPSILON){
return true;
}else{
return false;
}
}
console.log(0.1 + 0.2 === 0.3);//false
console.log(equal(0.1 + 0.2, 0.3))//true

//1. 二进制和八进制
let b = 0b1010;
let o = 0o777;
let d = 100;
let x = 0xff;
console.log(x);

//2. Number.isFinite 检测一个数值是否为有限数
console.log(Number.isFinite(100));
console.log(Number.isFinite(100/0));
console.log(Number.isFinite(Infinity));

//3. Number.isNaN 检测一个数值是否为 NaN
console.log(Number.isNaN(123));

//4. Number.parseInt Number.parseFloat字符串转整数
console.log(Number.parseInt('5211314love'));
console.log(Number.parseFloat('3.1415926神奇'));

//5. Number.isInteger 判断一个数是否为整数
console.log(Number.isInteger(5));
console.log(Number.isInteger(2.5));

//6. Math.trunc 将数字的小数部分抹掉
console.log(Math.trunc(3.5));

//7. Math.sign 判断一个数到底为正数 负数 还是零
console.log(Math.sign(100));//1
console.log(Math.sign(0));//0
console.log(Math.sign(-20000));//-1

对象方法拓展

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. Object.is 判断两个值是否完全相等
//它和===几乎一样,唯一区别在于判断NaN时,等号认为NaN不能和任何值相等,而is认为可以
console.log(Object.is(120, 120));//true
console.log(Object.is(NaN, NaN));//true
console.log(NaN === NaN);//false

//2. Object.assign 对象的合并
const config1 = {
host: 'localhost',
port: 3306,
name: 'root',
pass: 'root',
test: 'test'
};
const config2 = {
host: 'http://atguigu.com',
port: 33060,
name: 'atguigu.com',
pass: 'iloveyou',
test2: 'test2'
}
console.log(Object.assign(config1, config2));
//如果键名相同,后面的覆盖前面的

//3. Object.setPrototypeOf 设置原型对象 Object.getPrototypeof
const school = {
name: '尚硅谷'
}
const cities = {
xiaoqu: ['北京','上海','深圳']
}
Object.setPrototypeOf(school, cities);
//虽然可行,但是通过Object.create()方法设置原型对象效率更高
console.log(Object.getPrototypeOf(school));
console.log(school);