# JavaScript_Advance
**Repository Path**: hengyudu/JavaScript_Advance
## Basic Information
- **Project Name**: JavaScript_Advance
- **Description**: JS高级部份,含正则表达式和ES6部分类相关语法,跟着黑马某不知名老师学习,2021年11月19日学习完毕,视频地址:https://www.bilibili.com/video/BV1WE411t7kL?share_source=copy_web
- **Primary Language**: HTML
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2022-01-24
- **Last Updated**: 2022-01-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# JavaScript 高阶部分
### - 目标
- 理解面向对象开发思想
- 掌握JavaScript面向对象开发相关模式
- 掌握在JavaScript中使用正则表达式
### - 案例演示
- 贪吃蛇
# 一、回顾
### (1)重新介绍JavaScript
#### JavaScript是什么
- 解析执行:轻量级解释型
- 解释执行:解释一行执行一行(慢)
- 编译执行:java、c#,一次性把代码编译成可执行的代码,再逐行执行(快)
- 语言特点:动态,头等函数(First-class Function)
- 又称函数是JavaScript中的一等公民
- 执行环境:在宿主环境(Host environment)下运行,浏览器是最常见的JavaScript宿主环境。
- 但是在很多非浏览器环境中也使用JavaScript,例如node.js
#### JavaScript的组成
- ECMAScript - 语法规范
- 变量、数据类型、类型转换、操作符
- 流程控制语句:判断、循环语句
- 数组、函数、作用域、预解析
- 对象、属性、方法、简单类型和复杂类型的区别
- 内置对象:Math、Date、Array,基本包装类型String、Number、Boolean
- Web APIs
- BOM
- onload页面加载事件,window顶级对象
- 定时器
- location、history
- DOM
- 获取页面元素,注册事件
- 属性操作,样式操作
- 节点属性,节点层级
- 动态创建元素
- 事件:注册事件的方式、事件的三个阶段、事件对象
#### JavaScript 可以做什么
> 阿特伍德定律:
>
> Any application that can be written in JavaScript, will eventually be written in JavaSript.
>
> 阿特伍德——stackoverflow的创始人之一
### (2)浏览器是如何工作的
[^User Interface]: 用户界面,我们看到的浏览器。
[^Browser engine]: 浏览器引擎,用来查询和操作渲染引擎。
[^*Rendering engine]:渲染引擎,用来显示请求的内容,负责解析HTML、CSS,并把解析的内容显示出来。
[^Networking]:网络,负责发送网络请求
[^*JavaScript Interpreter]:JS解析器,负责执行JS代码
[^UI Backend]:UI后端,用来绘制类似组合框和弹出窗口
[^Data Persistence]:数据持久化,数据存储 cookie、HTML5中的sessionStorage
### (3)JavaScript的执行过程
JavaScript 运行分为两个阶段:
- 预解析
- 全局预解析(所有变量和函数声明都会提前:同名的函数和变量,函数的优先级高)
- 函数内部预解析(所有的变量、函数和形参都会参与预解析)
- 函数
- 形参
- 普通变量
- 执行:
先预解析全局作用域,然后执行全局作用域中的代码,在执行全局代码的过程中遇到函数调用就会先进行函数预解析,然后再执行函数内代码。
# 二、JS面向对象编程
### (1)面向对象介绍
#### 什么是对象
> Everything is object.
对象到底是什么,我们可以从两个层次来理解:
**(1) 对象是单个事物的抽象**
一本书、一辆汽车、一个人可以是对象,一个数据库、一个网页、一个与远程服务器的连接也可以是对象。当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程。
**(2) 对象是一个容器,封装了属性(property)和方法(method)**
属性是对象的状态,方法是对象的行为(完成某种任务)。比如,我们可以把动物抽象为animal对象,使用“属性”记录具体是哪一种动物,使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)。
在实际研发中,对象是一个抽象的概念,可以将其简单理解为:**数据集或功能集**。
ECMAScript-262 把对象定义为:**无序属性的集合,其属性可以包含基本值、对象或者函数**。严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。
提示:每个对象都是基于一个引用类型创建的,这些类型可以是系统内置的原生类型,也可以是开发人员自定义的类型。
#### 什么是面向对象
面向对象编程——Object Oriented Programming,简称OOP,是一种编程开发思想。它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。
因此,面向对象编程具有**灵活、代码可复用、高度模块化**等特点,容易维护和开发,比起一系列函数或指令组成的传统的过程式编程(Procedural Programming),更适合多人合作的大型软件项目。
#### 面向对象与面向过程
- 面向过程就是亲历亲为,事无巨细,面面俱到,步步紧跟,有条不紊
- 面向对象就是找一个对象,指挥得结果
- 面向对象将执行者转变为指挥者
- 面向对象不是面向过程的替代,而是面向过程的封装
#### 面向对象的特性
- 封装性
- 继承性
- [多态性] 抽象
#### 演示面向对象
```js
// 1. 面向过程
// 1.1 记录学生的成绩
var stu1 = {name:'Aa',subject:'English',socre:90}
var stu2 = {name:'Bb',subject:'English',socre:80}
// 1.2 打印学生的成绩
console.log(stu1.name, stu1.subject, stu1.socre)
console.log(stu2.name, stu2.subject, stu2.socre)
// 2. 面向对象
// 创建一个模板,用于创建对象(创建实例instance)
// 在JavaScript中创建对象的模板是构造函数
// 而在其他语言中创建对象的模板是类,ES6新增了class
function Student(name,subject,score){
this.name = name
this.subject = subject
this.score = score
this.printScore = function(){
console.log(this.name, this.subject, this.score)
}
}
var stu1 = new Student('Aa','English',90)
var stu2 = new Student('Bb','English',80)
stu1.printScore()
stu2.printScore()
```
### (2)创建对象的方式
#### 1. new Object( )
```js
var hero = new Object()
hero.blood = 100
hero.name = '刘备'
hero.weapon = '剑'
hero.attack = function(){
console.log('使用' + this.weapon + '攻击敌人')
}
```
#### 2. 对象字面量
```js
// var hero = {} // 空对象
var hero1 = {
blood:100,
name:'刘备',
weapon:'剑',
attack: function(){
console.log('使用' + this.weapon + '攻击敌人')
}
}
var hero2 = {
blood:120,
name:'关羽',
weapon:'大刀',
attack: function(){
console.log('使用' + this.weapon + '攻击敌人')
}
}
// ...hero3 ...hero4
// 当创建单个对象,可用对象字面量。当创建多个对象会产生很多冗余。
```
#### 3.工厂函数 创建多个对象
```js
function createHero(name,blood,weapon){
var o = new Object()
o.name = name
o.blood = blood
o.weapon = weapon
o.attack = function(){
console.log('使用' + o.weapon + '攻击敌人')
}
return o
}
var hero1 = createHero('刘备',100,'剑')
var hero2 = createHero('关羽',120,'刀')
// 工厂函数创造出的对象无法使用instanceof,而typeof无法辨别复杂对象
console.log(hero1 instanceof ??)
console.log(typeof hero1) // object
var arr = []
console.log(typeof arr) // object
```
#### 4.构造函数
1. 会在内存中创建一个空对象
2. 设置构造函数的this,让this指向刚刚创建好的对象
3. 执行构造函数中的代码
4. 返回对象
```js
function Hero(name,blood,weapon){ // 函数名大写
this.name = name // 无需手动创建对象
this.blood = blood
this.weapon = weapon
this.attack = function(){
console.log('使用' + this.weapon + '攻击敌人')
}
} // 无需手动return
var hero1 = new Hero('刘备',100,'剑') // 使用 new
var hero2 = new Hero('关羽',120,'刀')
// 【1】无法使用 typeof获得具体的对象类型
// 【2】constructor 构造器 - 获取对象的具体类型 - 不建议
console.log(hero1.constructor) // Hero
var arr = [] // new Array()
console.log(arr.constructor) // Array
// 【3】instanceof 判断某个对象是否是某个构造函数的实例/对象
console.log(hero1 instanceof Hero) // true
var arr = []
console.log(arr instanceof Array) // true
```
### (3)静态成员和实例成员
#### 静态成员
1. 直接使用对象来调用
2. 使用场景:工具中使用,如内置对象Math、自定义的MyMath.PI、MyMath.max()
```js
// 利用对象封装自己的数学对象 里面有PI、最大值和最小值
var myMath = {
PI: 3.141592653,
max: function () {},
min: function () {}
}
```
#### 实例成员
1. 构造函数中的成员就是实例成员
2. 使用场景:当有很多个对象的时候,使用构造函数的形式来创建对象
### (4)原型:解决sayHi存储多份的问题
多个对象时,会存储多个相同的sayHi方法:
```js
function Student(name, age, sex){
this.name = name
this.age = age
this.sex = sex
this.sayHi = function (){
console.log("Hello, I'm" + this.name) // 谁调用,this就指向谁
}
}
var s1 = new Student('A',12,'male')
var s2 = new Student('B',12,'female')
console.log(s1.sayHi === s2.sayHi) // false
```
解决方式1——放在外层:
```js
function Student(name, age, sex){
this.name = name
this.age = age
this.sex = sex
this.sayHi = sayHi
}
function sayHi(){
console.log("Hello, I'm" + this.name)
}
```
解决方式2——原型:
每一个构造函数都有一个属性:原型/原型对象
```js
function Student(name,age,sex){
this.name = name
this.age = age
this.sex = sex
}
Student.prototype.sayHi = function () {
console.log("Hello, I'm" + this.name)
}
// 通过Student构造函数,创建的对象,可以访问Student.prototype中的成员
var s1 = new Student('A',12,'male')
var s2 = new Student('B',12,'female')
s1.sayHi()
s2.sayHi()
console.log(s1.sayHi === s2.sayHi) // true,说明占用的是同一块儿内存空间
```
### (5)实例对象的原型
当调用对象的属性或者方法的时候,先去找对象本身的属性或方法;如果本身没有,此时去调用原型中的属性或方法;如果原型中也没有,会报错。
- `s1.__proto__`(实例对象的__proto__)等于构造函数Student的prototype
- `__proto__`属性是非标准的属性
```js
console.log(s1.__proto__ === Student.prototype) // true
console.dir(s1.__proto)
console.dir(Student.prototype)
```
在实例对象的原型对象(`s1.__proto__`,即`Student.prototype`)中有一个属性`constructor`,记录了创建该对象的构造函数。
```js
console.log(s1.constructor === Student) // true
var arr = []
console.log(arr.constructor === Array) // true
// 实际上调用的是Array.prototype.constructor
```
### (6)原型三角关系
### (7)原型链
s1对象的原型对象的原型对象 = Object构造函数的原型对象
```js
console.log(s1.__proto__.__proto__ === Object.prototype) // true
```
- 属性查找、对象查找规则:先从实例对象身上找,如果没有然后就沿原型链向上找,**就近原则**。
- 属性设置规则:如果test属性在原型对象上,给实例对象设置test属性时并不会搜索原型链,而是直接给该对象新增一个test属性(不妨碍其他实例对象查找原型对象上的test属性)。
```js
Student.prototype.test = 'abc'
s1.name = 'xxx'
s1.test = '123xxx'
console.log(s1.test) // '123xxx'
console.log(s2.test) // 'abc'
```
- 注意点:当我们改变构造函数的prototype的时候,**需要重新设置constructor属性**,来模仿原来的prototype,本质上,原来的原型对象也只是一个普通的object而已。
```js
Student.prototype = {
constructor:Student,
sayHi: function (){ // 我们想新增的属性
console.log('Hi!')
},
}
```
### (8)扩展内置对象
数组或String中的prototype默认是不可以直接赋值修改的,因为里面已经内置了大量方法,但可以新增方法:
```js
// 新增一个偶数求和的方法
Array.prototype.getSum = function () {
var sum = 0
for (var i = 0; i Person类型
#### 1. 对象的拷贝
复制对象成员给另一个对象(对象的拷贝),并不是真的继承:
```js
function extend(parent,child){
for(var key in parent){
if(child[key]){
continue;
}
child[key] = parent[key]
}
}
```
#### 2. 原型继承
原型继承:无法设置函数的参数(缺点,不实用)
```js
function Person(){ // 父类型
this.name = 'Zhang'
this.age = 18
this.sex = '男'
}
function Student(){ // 子类型
this.score = 100
}
Student.prototype = new Person()
Student.prototype.constructor = Student
// 当改变prototype时,一定要给constructor赋值
var s1 = new Student()
console.log(s1.constructor) // Student
console.dir(s1);
```
#### 3. 借用构造函数
- call(this指向, 其他参数)
- 作用:改变函数的this,并直接调用函数
```js
function fn(x,y){
console.log(this);
console.log(x + y);
}
var o = { name:'zs' }
fn.call(o, 1, 2) // o 3
```
借用构造函数,即利用call,在子类型中调用父类型,可按需输入不同的参数
```js
// 借用构造函数
function Person(name,age,sex){ // 父类型
this.name = name
this.age = age
this.sex = sex
}
Person.prototype.sayHi = function(){
console.log(this.name);
}
// 子类型
function Student(name, age, sex, score){ // 子类型
Person.call(this,name,age,sex)
this.score = score
}
var s1 = new Student('zs',18,'male',100)
console.dir(s1)
```
#### 4. 组合继承
组合继承:借用构造函数 + 原型继承
```js
function Teacher(name,age,sex,salary){
// 1. 借用构造函数
Person.call(this, name, age, sex)
this.salary = salary;
}
// 2. 通过原型,让子类型继承父类型中的方法,而访问不到其他子类型添加的方法
Teacher.prototype = new Person()
Teacher.prototype.constuctor = Teacher
var t1 = new Teacher('王五',30,'男',10101010)
console.dir(t1); // 输出结果见下图
```
- 组合继承,原型三角图:
- 将贪吃蛇中蛇对象和食物对象的width和height利用组合继承改造后发现,代码量变多了,变得更复杂了。我们可以总结,一般在做网页特效,我们不会使用继承。而继承更多被使用在**写框架**的时候。
# 五、函数进阶
### (1)函数的定义方式
- 1. 函数声明和函数表达式
```js
// 1 函数声明
function fn(){
console.log('test');
}
fn()
// 2 函数表达式
var fn = function(){
console.log('test');
}
```
函数声明和函数表达式的区别:
- 函数表达式的变量声明会自动提前:
```js
var fn // 声明提前
fn = function(){
console.log('test');
}
```
注意: 现代浏览器不会提升if语句中的函数声明,但在老的IE版本中,if语句中的函数声明也会提升。解决该问题的办法,就是不要使用函数声明,而使用函数表达式:
```js
var fn // 使用函数表达式
if (true){
fn = function(){
console.log('fn - true');
}
} else {
fn = function (){
console.log('fn - false');
}
}
fn()
```
- 2. new Function()
特点:
1.要写在字符串里
2.运行速度较前两种较慢,因此不推荐
```js
var fn = new Function('var name = "张三"; console.log(name) ')
fn()
console.dir(fn); // 但由此可知,函数也是一个对象
// 传递参数的写法
var fnn = new Function('a','b','console.log(a+b)')
fnn(1,2) // 3
```
### (2)函数的调用方式及this指向
1. 普通函数调用(this指向window)
```js
function fn(){
console.log(this) // window
}
fn()
```
2. 方法调用(this指向调用该方法的对象)
```js
var obj = {
fn: function(){}
}
obj.fn
```
3. 作为构造函数调用
(构造函数内部的this指向由该构造函数创建的实例对象)
4. 作为事件的处理函数
(this指向触发该事件的对象)
```js
btn.onclick = function(){
console.log(this) // btn
}
```
5. 作为定时器的参数
(this指向window)
```js
setInterval(function(){
console.log(this) // window
},1000)
```
### (3)函数内this指向的不同场景
终极总结:函数内部的this,不是书写的时候决定的,而是由函数调用的时候来确定其指向的。
```js
var obj = {
name:'zs',
fn:functon(){
console.log(this)
}
}
var fn = obj.fn
fn() // this -> window
obj.fn() // this -> obj
```
【注意】箭头函数的this定义: 箭头函数的this是在**定义函数时绑定的**,不是在执行过程中绑定的。
由于箭头函数不绑定this, 它会捕获**其所在(即定义的位置)上下文的this值**, 作为自己的this值,所以 call () / apply () / bind () 方法对于箭头函数来说只是传入参数,对它的 this 毫无影响。
### (4)call\apply\bind
- call
- 用法:call(this指向, 其他参数)
- 作用:改变this,并直接调用函数
- apply
- 用法:apply(this指向, [数组])
- 作用:改变this,并展开数组,将每一项一起传给函数并调用
- bind
- 用法:bind(this指向)
- 作用:改变this,但不调用函数
### (5)函数的其它成员
```js
function fn(x,y){
// 【需要掌握的】 函数内部的私有变量arguments
console.log(arguments);
// 伪数组 获取到的是函数的实参
console.log(fn.arguments);
// 函数的调用者,如果在全局范围内caller是null
console.log(fn.caller);
// 函数的名称 字符串类型
console.log(fn.name);
// 函数的形参个数
console.log(fn.length);
}
console.dir(fn);
function foo(){
fn(1,2,3)
}
foo()
```
- arguments
- 作用:当函数的参数个数不固定的时候,在函数内部可以通过arguments获取到实际传过来的参数
```js
function max(){
var max = arguments[0]
for (var i = 0 ; i < arguments.length; i++){
if(max < arguments[i]){
max = arguments[i]
}
}
return max
}
console.log(max(1,2,3,999)); // 999
```
### (6)严格模式
JavaScript除了提供正常模式外,还提供了**严格模式(strict mode)**。ES5的严格模式是采用具有限制性JavaScript变体的一种方式,即在严格的条件下运行JS代码。
严格模式在IE10以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript语义做了一些更改:
1. 消除了 Javascript语法的一些不合理、不严谨之处,了一些怪异行为
2. 消除代码运行的一些不安全之处,保证代码运行的安全。
3. 提高编译器效率,增加运行速度。
4. 禁用了在 ECMAScript的未来版本中可能会定义的一些语法,为未来新版本的 Javascript做好铺垫。比如一些保留字如: class,enum, export, extends,import,super不能做变量名。
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为**为脚本开启严格模式**和**为函数开启严格模式**两种情况
#### 为脚本开启严格榄式
有的script基本是严格模式,有的script脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中,这独立创建一个作用域而不影响其他 script脚本文件。
```html
```
#### 为函数开启严格榄式
要给某个函数开启严格模式,需要把 "use strict"; 声明放在该函数体所有语句之前。
```js
function fn(){
'use strict'
}
```
#### 严格模式中的变化
1. 变量规定
① 在正常模式中,如果一个变量没有声明就賦值,默认是全局变量。严格模式禁止这种用法,**变量都必须先用var命令声明,然后再使用**
② **严禁删除已经声明变量**。例如, delete x语法是错误的。
2. this指向
| | 正常模式 | 严格模式 |
| ------------------------------- | ---------- | ----------- |
| 全局作用域中的this | window对象 | undefined |
| 构造函数不加new,当普通函数调用 | window对象 | undefined |
| 构造函数加new | 实例对象 | 同左 |
| 定时器this | window | 同左 |
| 事件、对象 | 调用者 | 同左 |
3. 函数变化
① 函数不能有重名的参数
② 函数必须声明在顶层新版本的 JavaScript会引入“块级作用域”(ES6中已引入)。为了与新版本接轨,不允许在**非函数的代码块内**(如if、for语句中)声明函数。
### (7)高阶函数
高阶函数:
1. 函数作为参数
2. 函数作为返回值的时候
- sort排序:
```js
var arr = [35, 1, 6, 20, 100, 23, 34, 12, 45, 1, 50, 13]
arr.sort(function(a,b){
return a - b // 从小到大排列
// return b - a
})
console.log(arr);
```
- 高阶函数: 函数作为返回值的时候
```js
function getSum(n){
return function(m){
return n + m
}
}
var fn100 = getSum(100)
var fn1000 = getSum(1000)
console.log(fn100(7)); // 107
console.log(fn1000(7)); // 1007
```
### (8)函数闭包
- 闭包(MDN,2021年11月)
一个函数和对其周围状态 **(lexical environment,词法环境)**的引用捆绑在一起(或者说函数被引用包围),这样的组合就是**闭包(closure)**。
也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
- 闭包(黑马,2018年)
在一个作用域中可以访问另一个作用域的变量
// 闭包特点: 延展了函数的作用域范围
- 经典面试题:点击li分别输出其索引
```html
```
方式一:
```js
// 1 给li注册点击事件
var heroes = document.getElementById('heroes')
var list = heroes.children
for ( var i = 0; i < list.length; i ++){
var li = list[i]
li.index = i // 将每次的li保存起来
li.onclick = function(){
// 2 点击li时输出当前li对应的索引
console.log(this.index);
}
}
```
**方式二:闭包**
```js
// 方式二
var heroes = document.getElementById('heroes')
var list = heroes.children
for ( var i = 0; i < list.length; i ++){
var li = list[i]
;(function(i){
li.onclick = function(){
console.log(i); // i存储在外层函数中
// 形成闭包时,其实外层函数的作用域延展了,没有释放内存,性能降低了
}
})(i)
}
```
### (9)setTimeout的执行过程
- 执行过程示例
```js
console.log('start')
setTimeout(function(){
console.log('timeout')
},0)
console.log('over')
```
以上代码输出结果依次为start, over, timeout,因为定时器执行时会先把里面的函数放到**任务队列**里,等**执行栈**执行完毕再处理任务队列中的函数。
- 代码改造
```js
console.log('start')
for (var i = 0; i < 3; i++){
setTimeout(function(){
console.log(i) // 3 3 3
}, 0)
}
console.log('end')
```
利用闭包,使每次输出序号:
```js
console.log('start')
for (var i = 0; i < 3; i++){
;(function(i){
setTimeout(function(){
console.log(i) // 0 1 2
}, 0)
})(i)
}
console.log('end')
```
### (10)代码思考
### (11)递归函数
递归函数:函数内部自己调用自己,这个函数就是递归函数。
```js
var num = 1
function fn(){
console.log('我要打印',num)
if (num == 6){
return // 递归函数里必须加推出条件,否则会造成栈溢出。
}
num++
fn()
}
fn()
```
#### 利用递归求1-n的阶乘
```js
function fn(n){
if(n == 1){
return 1
}
return n * fn(n-1)
}
console.log(fn(3)); // 6
console.log(fn(4)); // 24
```
#### 利用递归求斐波那契数列
斐波那契数列(兔子数列),1、1、2、3、5、8、13、21、34... ...
用户输入一个数字 n 就可以求出这个数字对应的兔子序列值。
```js
function fn(n){
if(n == 1){
return 1
} else if (n <= 0){
return 0
}
return fn(n-1) + fn(n-2)
}
console.log(fn(1));
console.log(fn(2));
console.log(fn(3));
console.log(fn(4));
```
#### 浅拷贝(ES6)和深拷贝
浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用。
深拷贝拷贝多层,每一级别数据都会拷贝。
- 浅拷贝
```js
var obj = {
id: 1,
name:'Hengyu',
msg:{
info:100
}
}
var copy = {}
for (var k in obj){ // 利用for in遍历实现浅拷贝
copy[k] = obj[k]
}
console.log(copy) // 和obj一模一样,但msg只拷贝了地址
copy.msg.info = 10
console.log(obj) // msg也被改变了
```
ES6 提供了一个新方法用于浅拷贝**Object.assign()** :
```js
Object.assign(copy,obj)
```
- 深拷贝
```js
var obj = {
id: 1,
name:'hengyu', // 简单数据类型
msg:{ info:100 }, // 复杂数据类型
color:['navy','wheat'] // 复杂数据类型
}
var copy = {}
function deepCopy(newObj, oldObj){
for (var k in oldObj){
var item = oldObj[k]
if (item instanceof Array){ // 复杂数据类型
newObj[k] = []
deepCopy(newObj[k],item)
} else if (item instanceof Object){ // 复杂数据类型
newObj[k] = {}
deepCopy(newObj[k],item)
} else { // 简单数据类型
newObj[k] = item
}
}
}
deepCopy(copy,obj)
console.log(copy)
```
# 六、ES6中的类
ES6之前通过**构造函数+原型**实现面向对象编程
ES6通过**类**实现面向对象编程
### (1)基本语法
- 创建类
语法:
```js
class Name {
// class body
}
```
- 创建实例(必须使用new实例化对象)
```js
var xx = new name()
```
注意事项:
1. 类里面的函数不需要写function
2. 类中的成员不需要加逗号分隔
### (2)类constructor 构造函数
constructor()方法是类的构造函数(默认方法),**用于传递参数,返回实例对象**,通过new命令生成对象实例时,自动调用该方法。如果没有显示定义,类内部会自动给我们创建一个constructor()
```js
class Star{
job='star'
constructor(uname,age){
this.uname = uname
this.age = age
}
}
```
### (3)类的继承
利用extends和super实现类的继承
super关键字用于访问和调用对象父类上的函数,可以调用父类的构造函数,也可调用父类的普通函数。
注意:子类如果在构造函数中使用super,如果同时使用了this,必须先写super,再用this。
- 语法:
```js
class Father{
// 父类
hi()
}
class Son extends Father{ // extends :使子类继承父类的属性和方法
constructor(x,y){
super(x,y) // 将参数传给父类的constructor
this.x = x
}
hi(){
super.hi() // 调用父类的hi函数
}
}
```
继承中,如果实例化子类输出一个方法,先在子类中查找,如果没有,再查找父类。(就近原则)
### (4)类和对象的三个注意点:
1. 在ES6中类没有变量提提升,所以必须先定义类,才能通过类实例化对象
2. 类里面的共有的属性和方法一定要加this使用
3. 类里的this指向问题
```js
class Star{
constructor(){
console.log(this)
// constructor里的this指的是创建的实例对象
}
foo(){
console.log(this)
// 函数中的this指的是调用该函数的实例对象
}
}
var s1 = new Star()
```
### (5)class的set和get
```js
// get 和 set
class Phone{
get price(){ // 通常用来对对象的动态属性进行封装
console.log("价格属性被读取了")
return 'iloveyou'
}
set price(newVal){ // 通常用来对对象的动态属性进行封装
console.log("价格属性被修改了");
}
}
let s = new Phone()
console.log(s.price)
s.price = 'free'
```
### (6)类的本质
类的本质其实还是一个函数,我们也可以简单的认为,类就是构造函数的另外一种简单的写法
ES6之前通过**构造函数+原型**实现面向对象编程
1. 构造函数有原型对象prototype
2. 构造函数原型对象里有constructor指向构造函数本身
3. 构造函数可以通过原型对象添加共有方法
4. 构造函数创建的实例对象有__proto__,指向函数的原型对象
ES6通过**类**实现面向对象编程
ES6的类的绝大部分功能,ES5都能做到,新的class写法只是让原型对象的写法更清晰,更像面向对象编程的语法而已。
所以ES6的类其实就是**语法糖**。
# 七、ES6中的新增方法概述
### (1)数组新增方法
forEach map filter some every
#### 1.forEach
作用:迭代(遍历)数组
```js
var arr = [1,2,3]
var sum = 0
arr.forEach(function(value,index,array){
console.log('每个数组元素' + value)
console.log('每个数组元素的索引号' + index)
console.log('每个数组元素' + array)
sum += value
})
console.log(sum) // 6
```
#### 2.filter
作用:filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于**筛选数组**。
**注意:它直接返回一个新数组,因此需要一个变量来接**
```js
var arr = [12, 66, 4, 88, 3, 7]
var newArr = arr.filter(function(value, index,arr){
return value >= 20
})
console.log(newArr) // [66, 88]
```
#### 3.map
作用:map()方法创建一个新数组,数组中的元素是通过对原数组的每个元素分别执行函数得到的。
**注意:它直接返回一个新数组,因此需要一个变量来接**
#### 4.some
作用:some()方法用于检测数组中的元素是否满足指定条件,通俗来说即查找数组中是否有满足条件的元素。
注意:
1. **它的返回值是布尔值**,如果查找到满足条件的元素,即返回true,否则返回false。
2. 当查找到第一个满足条件的元素时,就立刻终止循环,不再继续查找
```js
var arr = [10,30,4]
var flag = arr.some(function(value){
return value < 3
})
console.log(flag) // false
```
- forEach 和 some 的区别:
forEach、filter、map中的return true不会终止迭代
**some中的return true会终止迭代,如果需要在数组中查找唯一的元素,some效率更高**
#### 5.every
与some类似,但用于判断是否数组中所有元素都满足指定条件,返回布尔值。
### (2)字符串新增方法
#### trim
trim()方法会从一个字符串的**两端**删除空白字符,且不影响字符串本身,返回的是一个新的字符串。
```js
var str = ' andy '
console.log(str)
var str1 = str.trim()
console.log(str1) // andy
```
### (3)对象新增方法
#### Object.defineProperty()
作用:定义对象中新属性或修改原有的属性
参数:
- obj:必需。目标对象
- prop:必需。需定义或修改的属性的名字
- descriptor:必须。目标属性所拥有的特性
第三个参数descriptor需要用对象形式来写:
- value:设置属性的值,默认为undefined
- writable:值是否可以重写, true|默认false
- enumerabke: 目标属性是否可以被枚举(遍历), true|默认false
- configurable: 目标属性是否可以被删除或是否可以再次修改特性 true|默认false
```js
var obj = {
id:1
name:'xiaomi'
price:1000
}
obj.defineProperty(obj,'price',{
value:9.9
writable:false
})
obj.defineProperty(obj,'address',{
value:'12121212121121221212'
enumerable:false // 这样可以把该属性隐藏起来,无法遍历出来
configurable:false
})
console.log(obj.keys(obj)) // 'id' 'name' 'price'
delete obj.address // 删不掉
```
# 八、正则表达式
正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象。
正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或下划线,昵称输入框中可以输入中文 **(匹配)**。此外,正则表达式还常用于过滤页面内容中的一些敏感词 **(替换)**,或从字符串中获取我们想要的特定部分 **(提取)**等。
### (1)正则表达式的特点
1. 灵活性、逻辑性和功能性非常的强
2. 可以迅速地用极简单的方式达到字符串的复杂控制。
3. 对于刚接触的人来说,比较嗨涩难懂。
4. 实际开发一般都是直接复制写好的正则表达式,但是要求会使用正则表达式,并且根据实际情况修改正则表达式.比如用户名:/^[a-z0-9_-](3,16)$/
### (2)正则表达式在JavaScript中的使用
#### 1.创建正则表达式
在JavaScript中,可以通过两种方式创建一个正则表达式
1. 通过调用RegExp对象的构造函数创建
`var 变量名 = new RegExp(/表达式/)`
2. 通过字面量创建
`var 变量名 = /表达式/`
#### 2.测试正则表达式
test()正则对象方法,用于检测字符串是否符合该规则,该对象会返回true或false,其参数是测试字符串。
`regexObj.test(str)`
1. regexObj 是写的正则表达式
2. str 我们要测试的文本
3. 就是检测str文本是否符合我们写的正则表达式规范
```js
var rg = /123/
console.log(rg.test(123)) // true
console.log(rg.test('abc')) // false
```
#### 3.特殊字符
1. 边界符
正则表达式中的边界符(位置符)用来**提示字符所处的位置**,主要有两个字符。
| 边界符 | 说明 |
| -------------- | --------------------------- |
| ^ | 表示匹配行首的文本(以谁开始) |
| $ | 表示匹配行尾的文本(以谁结束) |
如果 ^ 和 $ 在一起,表示必须是精确匹配
2. 字符类
(1) [] :`/^[abc]$/`表示有一系列字符可供选择,只要匹配其中一个就可以了。
(2) [-] 方括号内部的范围符 - [a-z] [0-9]
(3) 字符组合:`/^[a-zA-Z0-9_-]$/` 表示26个英文字符大小写、0-9、短横线、下横线都可
(4) [^]取反:`/^[^a-zA-Z0-9_-]$/` 方括号中的^表示**取反**
3. 量词符
量词符用来设定某个模式出现的次数。
| 量词 | 说明 |示例 |
| ----- | ---------------- | ---------------- |
| * | 重复零次或更多次 | /^a*$/ |
| + | 重复一次或更多次 | /^a+$/ |
| ? | 重复零次或1次 | /^a?$/ |
| {n} | 重复n次 | /^a{3}$/ |
| {n,} | 重复n次或更多次 | /^a{3,}$/ |
| {n,m} | 重复n到m次 | /^a{2,3}$/ |
```js
var reg = /^[a-zA-Z0-9_-]{6,16}$/
// 量词{6,16}中间不要有空格
```
注意:
`/^abc{3}$/` 表示让c重复三次 => abccc
`/^(abc){3}$/` 表示让abc重复三次 => abcabcabc
4. 预定义类
预定义类指的是**某些常见模式的简写方式**。
| 预定类 | 说明 |
| ------ | ------------------------------------------------------------ |
| \d | 匹配0-9之间任一数字,相当于 [0-9] |
| \D | 匹配所有0-9以外的字符,相当于 \[^0-9] |
| \w | 匹配任意的字母、数字和下划线,相当于[A-Za-z0-9] |
| \W | 匹配所有\w以外的字符,相当于 \[^A-Za-z0-9] |
| \s | 匹配空格(包括换行符、制表符、空格符等),相当于 \[\t\r\n\v\f] |
| \S | 匹配所有非空格字符,相当于 \[^\t\r\n\v\f] |
- 座机号码验证:全国座机号两种格式:010-12345678 或 0530-1234567
`var reg = /^\d{3}-\d{8}|\d{4}-\d{7}$/`
- 正则里面的或者符号为: |
#### 4.正则表达式中的替换
1. replace 替换
`stringObj.replace(regexp/substr,replacement)`
- 第一个参数:被替换的字符串/正则
- 第二个参数:替换为的字符串
- 返回值:一个替换完毕的新字符串
2. 正则表达式参数
`/表达式/[switch]`
switch(也称为修饰符),按照什么样的模式来匹配,有三种值:
- g :全局匹配 (默认为只匹配一次,全局反之)
- i : 忽略大小写
- gi : 全局匹配 + 忽略大小写
比如:
`text.replace(/激情|gay/g,'**')`