- JavaScript面向对象
- 目标:
- 能够说出什么是面向对象
- 能够说出类和对象的关系
- 能够使用class创建自定义类
- 能够说出什么是继承
- 目标:
- 面向对象编程介绍:
- 两大编程思想:
- 面向过程
- 面向对象
- 两大编程思想:
- 面向过程编程POP(process-oriented programming)
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
- 简单理解:面向过程,就是按照我们分析好了的步骤,按照步骤解决问题。
- 例子:将大象装进冰箱,面向过程做法
- 打开冰箱门
- 大象装进去
- 关上冰箱门
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
- 面向对象编程OOP(Object Oriented Programming)
- 面向对象是把事务分解成一个个对象,然后由对象之间分工与合作。
- 例子:将大象装进冰箱,面向对象做法
- 先找出对象,并写出这些对象的功能:
- 大象对象:
- 进去
- 冰箱对象:
- 打开
- 关闭
- 使用大象和冰箱的功能
- 大象对象:
- 先找出对象,并写出这些对象的功能:
- 面向对象是以对象功能来划分问题,而不是步骤
- 在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
- 面向对象编程具有灵活性、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
- 面向对象的特性:
- 封装性
- 继承性
- 多态性
- 面向过程和面向对象的对比:
- 面向过程:
- 优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。
- 缺点:没有面向对象易维护、易复用、易扩展
- 面向对象:
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
- 缺点:性能比面向过程低
- 面向过程:
- ES6中的类和对象
- 面向对象
- 面向对象更贴近我们的实际生活,可以使用面向对象描述现实世界事物,但是事物分为具体的事物和抽象的事物
- 面向对象的思维特点:
- 抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)
- 对类进行实例化,获取类的对象
- 面向对象编程我们考虑的是有哪些对象,按照面向对象的思维特点,不断的创建对象,使用对象,指挥对象做事情。
- 对象:
- 现实生活中:万物皆对象,对象是一个具体的事物,看得见摸得着的事物。例如,一本书、一辆汽车、一个人可以是“对象“,一个数据库、一张网页、一个与远程服务器的连接也可以是“对象”。
- 在JavaScript中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
- 对象是由属性和方法组成的:
- 属性:事物的特征,在对象中用属性来表示(常用名词)
- 方法:事物的行为,在对象中用方法来表示(常用动词)
- 类class
- 在ES6中新增了类的概念,可以使用class关键字声明一个类,之后以这个类来实例化对象。
- 类抽象了对象的公共部分,它泛指某一大类(class)
- 对象特指某一个,通过类实例化一个具体的对象
- 面向对象
- 创建类:
- 语法:
- class 类名 {
- 语法:
constructor(自定义参数1名, 自定义参数2名, ...) {
this.自定义属性名1 = 自定义参数1名;
this.自定义属性名2 = 自定义参数2名;
}
方法名(自定义参数a1名, 自定义参数a2名, ...) {
}
}
- 通过class关键字创建类,类名我们还是习惯性定义首字母大写
- 类里面有个constructor函数,可以接收我们传递过来的参数,同时返回实例对象
- this.后面的自定义属性名为对象中的属性
- constructor函数只要new生成实例时,就会自动调用这个函数,如果不写这个函数,类也会自动生成这个函数
- 生成实例new不能省略
- 最后注意语法规范,创建类 类名后面不要加小括号,生成实例 类名后面加小括号,构造函数不需要加function
- 我们类里面所有的函数不需要写function
- 多个函数方法之间不需要添加逗号分隔
- 方法名前面没有static关键字,表示这是类创建实例后原型上面的方法,实例可以直接调用。如果方法名关键字前面有static关键字,这表示为静态方法,只能通过类名.方法名()的方式调用。
- 通过typeof检测类名得到的结果是function,因此class声明类实际上是创建了一个具有构造函数方法行为的函数。
- 与构造函数不同,类的prototype属性为只读属性,不可被赋予新值
- 创建实例:
- var 实例名 = new 类名('constructor内参数1值', 'constructor内参数2值', ...);
- 创建实例类名后面的constructor参数值是和constructor函数后面的参数是一一对应的。
- var 实例名 = new 类名('constructor内参数1值', 'constructor内参数2值', ...);
- 调用类中的方法:
- 实例名.方法名(参数a1值, 参数a2值, ...);
- 注意:类必须使用new实例化对象
- 实例对象内的属性为constructor函数体内this.后面的属性名,值为等号赋值后面传递过来的参数值
- 类constructor构造函数
- constructor()方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过new命令生成对象实例时,自动调用该方法,如果没有显示定义,类内部会自动给我们创建一个constructor()
- 里面写类的共有属性和方法,共有属性和方法前面一定要加this.
- 类的继承
- 继承:
- 现实中的继承:子承父业,比如我们都继承了父亲的姓
- 程序中的继承:子类可以继承父类的一些属性和方法。
- 语法:
- class 自定义父类名 {
- 继承:
constructor(父类自定义参数1名, 父类自定义参数2名, ...) {
this.自定义属性名1 = 父类自定义参数1名;
this.自定义属性名2 = 父类自定义参数2名;
…
this.方法名();
}
方法名1(自定义参数a1名, 自定义参数a2名, ...) {
}
方法名2(自定义参数a1名, 自定义参数a2名, ...) {
}
…
}
class 自定义子类名 extends 自定义父类名 {
}
var 自定义子类实例名 = new 自定义子类名();
- 调用子类继承父类中的方法:
- 自定义子类实例名.父类中的方法名();
- 调用子类继承父类中的方法:
- super关键字
- super关键字用于访问和调用对象父类上的函数,可以调用父类的构造函数,也可以调用父类的普通函数
- 子类在构造函数中使用super,必须放到this前面(必须先调用父类的构造方法,再使用子类构造方法)
- 语法:
- class 自定义子类名 extends 自定义父类名 {
constructor(父类自定义参数1, ...子类自定义参数1,...) {
super(父类自定义参数1, 父类自定义参数2, ...)
}
方法名(){
super.父类中的方法名()
}
}
- super(自定义参数1, 自定义参数2, ...)将子类中的参数传递到父类中去,调用父类中的构造函数
super(自定义参数1, 自定义参数2, ...)写在子类的constructor函数里面,就可以调用父类中有参数的方法
super后面的参数会传递给父类中的constructor后面的参数中
子类方法中写super.父类中的方法()可以调用父类中没有参数的方法
子类方法中写super.父类中的方法(自定义参数1, 自定义参数2, ...)可以调用父类中有参数的方法,但子类的constructor中必须写super(父类中自定义参数1, 父类中自定义参数2, ...)
- 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
- 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
- 子类调用父类中的方法:
- 子类中没有构造函数constructor:
- 子实例名.父类中的方法名
- 子类中有构造函数constructor:
- 在子类构造函数中写:super(父类构造函数constructor中的参数)
- 子类中没有构造函数constructor:
- 子类调用父类中的方法:
调用的时候直接写:子类实例名.父类中的方法名(父类方法中的参数)
必须写在子类this.的前面
- 在子类方法中写super.父类中的方法名(父类方法中的参数)
- 三个注意点:
- 在ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象。
- 类里面的共有属性和方法一定要加this使用
- 类里面的this指向问题
- 类中this指向的是我们创建出来的实例对象本身
- constructor里面的this指向实例对象,方法里面的this指向这个方法的调用者
- 将that声明为全局变量,在constructor中给that赋值给this
- this指向:
- this关键字是JS中最复杂的机制,它是一个很特别的关键字,被自动定义在所有函数的作用域中
- this指向的是一个对象,我们把this指向的对象叫做函数执行的上下文对象
- 当函数被调用的时候,this指向会发生改变,指向调用函数的对象
- 只看()前的写法
- 在函数预编译阶段,会生成AO对象,程序还会默认把this作为AO对象的一个属性名,默认属性值是window
- 函数AO对象里面默认有this属性和argument实参列表属性
- 通过new操作符调用函数,函数中的this指向空对象。
- this的指向规则:
- this是执行期上下文,那也就是说this到底指向哪个对象和函数调用有关。
- 默认绑定:
- 一个函数最普通的调用方式,就是通过函数名直接加()调用,这种调用方式,函数内的this就是默认绑定
- 普通函数中this默认绑定window
- 隐式绑定:
- 通过对象名.方法()调用,方法中的this绑定的就是这个对象名
- 显示绑定:
- Call,apply方法调用函数
- 优先级:
- 默认绑定优先级最低
- 显示绑定优先级高于隐式绑定
- new操作符的优先级高于隐式绑定
- new操作符的优先级高于显示绑定bind
- new操作符后面不能直接加显示绑定,需要将显示绑定赋值给一个变量,然后通过new操作符调用。
- 面向对象版tab栏切换:
- 功能需求:
- 点击tab栏,可以切换效果
- 点击+号,可以添加tab项和内容项
- 点击X号,可以删除当前的tab项和内容项
- 双击tab项文字或者内容项文字,可以修改里面的文字内容
- 抽象对象:tab对象
- 该对象具有切换功能
- 该对象具有添加功能
- 该对象具有删除功能
- 该对象具有修改功能
- 面向对象tab栏切换添加功能:
- 点击+可以实现添加新的选项卡和内容
- 第一步:创建新的选项卡li和新的内容section
- 第二步:把创建的两个元素追加到对应的父元素中
- 以前的做法:动态创建元素creatElement,但是元素里面内容较多,需要innerHTML赋值,再appendChild追加到父元素里面。
- 现在高级做法:利用insertAdjacentHTML()可以直接把字符串格式元素添加到父元素中
- 语法:
- Element.insertAdjacentHTML(‘位置’,’插入的字符串’)
- 语法:
- 功能需求:
位置必须是下面字符串之一:
beforebegin:元素自身的前面
afterbegin:插入元素内部的第一个子节点之前
beforeend:插入元素内部的最后一个子节点之后
afterend:元素自身的后面
- appendChild不支持追加字符串的子元素,insertAdjacentHTML支持追加字符串的元素
- 面向对象tab栏删除功能
- 点击x可以删除当前的li选项卡和当前的section
- X是没有索引号的,但是它的父亲li有索引号,这个索引号正是我们想要的索引号
- 所以核心思路是:点击X号可以删除这个索引号对应的li和section
- remove()方法可以直接删除指定的元素
- 面向对象版tab栏切换 编辑功能
- 双击选项卡li或者section里面的文字,可以实现修改功能
- 双击事件:ondblclick
- 如果双击文字,会默认选定文字,此时需要双击禁止选中文字:
- window.getSelection?window.getSelection().removeAllRanges():document.selection.empty();
- 核心思路:双击文字的时候,在里面生成一个文本框,当失去焦点或者按下回车然后把文本框输入的值给原先元素即可。
- 构造函数和原型
- 概述
- 在典型的OOP的语言中(如Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在ES6之前,JS中并没用引入类的概念。
- ES6,全称ECMAScript6.0,2015.06发版。但是目前浏览器的JavaScript是ES5版本,大多数高版本的浏览器也支持ES6,不过只实现了ES6的部分特性和功能。
- 在ES6之前,对象不是基于类创建的,而是用一种称为构造函数的特殊函数来定义对象和它们的特征
- 概述
- 创建对象可以通过一下三种方式:
- 对象字面量
- new Object()
- 自定义构造函数
- 构造函数
- 构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与new一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。
- 在JS中,使用构造函数时要注意以下两点:
- 构造函数用于创建某一类对象,其首字母要大写
- 构造函数要和new一起使用才有意义
- new在执行时会做四件事情
- 在内存中创建一个新的空对象
- 让this指向这个新的对象
- 执行构造函数里面的代码,给这个新对象添加属性和方法。
- 返回这个新对象(所以构造函数里面不需要return)
- JavaScript的构造函数中可以添加一些成员,可以在构造函数本身上添加(静态成员),也可以在构造函数内部的this上添加(实例成员)。通过这两种方式添加的成员,就分别称为静态成员和实例成员。
- 静态成员:在构造函数本身上添加的成员称为静态成员,只能由构造函数本身来访问
- 静态成员只能通过构造函数访问
- 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问
- 不可以通过构造函数访问实例成员
- 静态成员:在构造函数本身上添加的成员称为静态成员,只能由构造函数本身来访问
- 构造函数的问题
- 构造函数方法很好用,但是存在浪费内存的问题。
- 我们希望所有的对象使用同一个函数,这样就比较节省内存,那么我们要怎么做呢?
- 构造函数原型prototype
- 构造函数原型prototype
- 构造函数通过原型分配的函数是所有对象所共享的。
- JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象,注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有
- 我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
- 构造函数名.prototype.方法名=function(){}
- 好处:没有修改构造函数默认的原型对象
- 缺点:添加属性不方便
- 使用:
- 实例名.方法名();
- 构造函数名.prototype.方法名=function(){}
- 我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
- 原型是什么:一个对象,我们也称为prototype为原型对象。
- 原型是构造函数创建对象的原始模型。
- 原型自带constructor属性,constructor指定构造函数
- 构造函数名.prototype.constructor = 这个构造函数
- 构造函数创建出的对象会继承原型的属性和方法
- 每一个函数都具有一个属性,叫做prototype,这个属性值是一个对象,如果一个函数不是作为构造函数使用,这个属性没有任何意义。【只有函数有原型protoype】
- 原型可以直接赋值,但是会丢失constructor属性。丢失constructor属性后就不能指回构造函数本身了。此时需要重新给原型对象prototype设置constructor属性指回构造函数本身:原型.prototype.constructor=构造函数名
- 好处:添加属性方便
- 缺点:修改了构造函数的原型对象【需要添加constructor属性指回构造函数】
- 原型的作用:共享方法
- 一个构造函数A创建的实例如何调用另一个构造函数上B的方法【原型链开发】:
- 方法一【此方法只能调用另一个构造函数原型上的方法】:
- 第一步:将B构造函数的原型赋值给A构造函数的原型【A.protoType=B.protoType】
- 第二步:创建A构造函数实例对象a【此时a就可以调用B构造函数原型上面的方法和自己本来身上的属性和方法。】
- 此方法如果给B构造函数的原型加方法会修改A构造函数原型上加上该方法
- 方法二【此方法既可以调用另一个构造函数原型上的方法,也可以调用另一个构造函数内部的属性和方法】【原型链继承:父类的实例作为子类的原型】:
- 第一步:将B构造函数的实例对象赋值给A构造函数的原型【A.protoType = new B()】
- 第二步:将A构造函数的原型的constructor属性指回A【可以知道a实例是由A构造函数而来a.__proto__.constructor】
- 第三步:创建A构造函数实例对象a【此时a既可以调用自己的属性和方法,也可以调用B构造函数的属性和方法,还可以调用B构造函数原型上的属性和方法。实现了原型链的继承】
- 此方法给B构造函数原型上加方法不会修改A构造函数原型上面的方法。
- 方法三:
- 第一步:创建一个没有任何属性和方法的构造函数C
- 第二步:构造函数C的原型设置为B构造函数的原型
- 第三步:用构造函数C创建一个实例对象c
- 第四步:A构造函数的原型设置为c
- 第五步:创建A构造函数实例对象a【此时A构造函数的实例就可以调用B构造函数原型上面的属性和方法了】
- 方法一【此方法只能调用另一个构造函数原型上的方法】:
- 构造函数继承:
- 子类构造函数调用父类构造函数且父类绑定子类的this对象
- 通过调用父类构造函数,继承父类的属性并保留传参的优点
- 父类方法都在构造函数中定义,子类只能继承父类的实例属性和方法,不能继承父类原型上面的属性/方法,无法实现函数复用,每个子类都有父类实例函数的副本,影响性能。
- 案例:
function People(name) {
this.name = name;
}
People.prototype.sayHello = function () {
alert("你好我是" + this.name);
}
function Student(name, xuehao) {
// 核心语句
People.call(this, name);
// 这样就会在新parent对象上执行Person构造函数中定义的所有对象初始化代码
this.xuehao = xuehao;
}
Student.prototype.study = function () {
alert("好好学习,天天向上");
}
var xiaohong = new Student("小红", 1001);
console.log(xiaohong.sayHello)
- 组合继承:
- 就是将原型链继承和构造函数继承组合再一起继承。通过构造函数,继承父类的属性并保留传参的优点,然后再通过将父类实例作为子类原型,实现函数复用。
- 一般情况下,我们的公共属性定义到构造函数里面,公共的方法我们放到原型对象身上
- 对象原型__proto__
- 对象都会有一个属性__proto__指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在。
- __proto__对象原型和原型对象prototype是等价的
- 实例名.__proto__===构造函数名.prototype
- 方法查找规则:
- 首先看实例对象上是否有方法名,如果有就执行这个对象上的方法。
- 如果没有这个方法,因为有__proto__的存在,就去构造函数原型对象prototype身上去查找这个方法
- constructor构造函数
- 对象原型(__proto__)和构造函数(prototype)原型对象里面都有一个属性constructor属性,constructor我们称为构造函数,因为它指回构造函数本身。
- constructor主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
- 很多情况下,我们需要手动的利用constructor这个属性指回 原来的构造函数
- 构造函数的prototype直接赋值为对象,对象里面写方法,此时,当前的对象就会覆盖以前构造函数的对象,原先的constructor就会被当前的对象覆盖掉而没有,此时需要手动利用constructor这个属性指回。
- 代码实现:
- 构造函数的prototype直接赋值为对象,对象里面写方法,此时,当前的对象就会覆盖以前构造函数的对象,原先的constructor就会被当前的对象覆盖掉而没有,此时需要手动利用constructor这个属性指回。
- 很多情况下,我们需要手动的利用constructor这个属性指回 原来的构造函数
构造函数名.prototype = {
constructor: 构造函数名,
方法名:function() { }
}
- 构造函数、实例、原型对象三者之间的关系
- 构造函数名.prototype 指向 原型对象prototype
- 构造函数名.prototype.consturctor 指向 构造函数
- new 构造函数名()创建对象实例
- 实例名.__proto__ 指向 原型对象
- 原型对象里面的__proto__原型指向的是 Object.prototype
- 以上关系构成原型链:
- 原型链的本质:
- 主要是对象,一定有原型对象【构造函数的原型对象表达方式为:构造函数名.protoype。实例对象的原型对象为:实例名.__proto__】,就是说只要这个东西是个对象,那么一定有__proto__属性。
- Object.prototype是所有对象的原型链的终点,所以我们直接给Object.prototype增加一个方法,那么世界上所有的对象都能调用这个方法。
- 原型链的本质:
- JavaScript的成员查找机制(规则)
- 当访问一个对象属性(包括方法)时,首先查找这个对象自身有没有该属性。
- 如果没有就查找它的原型(也就是__proto__指向的prototype原型对象)。
- 如果还没有就查找原型对象的原型(Object的原型对象)。
- 依次类推一直找到Object为空(null)
- __proto__对象原型的意义在于为对象成员查找机制提供一个方向,或者说一条路线。
- 原型对象this指向问题
- 构造函数中,里面this指向的是实例对象
- 原型对象函数里面的this指向的是实例对象
- 扩展内置对象
- 可以通过原型对象,对原来的内置对象进行扩展自定义的方法,比如给数组增加自定义求偶数和的功能
- 代码实现:
Array.prototype.sum = function () {
var sum = 0;
//原型对象函数里面的this指向的是实例对象
for (var i = 0; i < this.length; i++) {
sum += this[i]
}
return sum;
}
var arr = [1, 2, 3]
console.log(arr.sum())
- 注意:数组和字符串内置对象不能给原型对象。覆盖操作Array.prototype={},只能是Array.prototype.xxx=function(){}的方式
- 继承:
- ES6之前并没有给我们提供extends继承,我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。
- call()
- 调用这个函数,并且修改函数运行时的this指向
- 语法:函数名.call(thisArg, arg1,arg2,…)
- thisArg:当前调用函数this的指向对象名
- arg1,arg2:传递的其他参数
- 借用构造函数继承父类型属性
- 核心原理:通过call()把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性。
- 共有的属性写在构造函数里面,共有的方法写在原型对象里面
- Son.prototype = Father.prototype;
- 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
- 解决方案:
- Son.prototype = new Father()
- 孩子继承父元素的属性和方法,但this会指向Father构造函数
- Son.prototype.constructor = Son;
- 孩子的constructor指回自己这个构造函数
- Son.prototype.方法名 = function(){}
- 添加孩子的专有方法
- Son.prototype = new Father()
- ES5中的新增方法
- ES5中给我们新增了一些方法,可以很方便的操作数组或者字符串,这些方法主要包括:
- 数组方法
- 字符串方法
- 对象方法
- ES5中给我们新增了一些方法,可以很方便的操作数组或者字符串,这些方法主要包括:
- 数组方法:
- 迭代(遍历)方法:
- forEach()、map()、filter()、some()、every();
- 以上这些方法接收的参数一模一样,接受两个参数:
- 第一个参数:就是循环的回调函数
- 第二个参数:修改第一个参数中的this指向【第一个参数为普通函数时】
- 以上这些方法接收的参数一模一样,接受两个参数:
- forEach()、map()、filter()、some()、every();
- 迭代(遍历)方法:
- forEach()
- 数组普通for循环的变种
- 语法:
- array.forEach(function(currentValue,index,arr){},this指向)
- 第一个参数:循环的回调函数【回调函数函数体打印arguments对象可知回调函数内部形参分别表示什么】
- array:遍历数组的数组名
- currentValue:数组遍历的当前项的值【回调函数第一个形参】
- index:数组当前项的索引【回调函数第二个形参】
- arr:数组对象本身【回调函数第三个形参】
- 第二个参数:改变第一个参数回调函数函数体内的this指向
- 返回值:
- 不管第一个参数内是不是有return,整个forEach返回undefined
- 第一个参数:循环的回调函数【回调函数函数体打印arguments对象可知回调函数内部形参分别表示什么】
- forEach里面的return不会终止迭代(只会终止本次循环体,继续遍历数组元素后面部分)
- array.forEach(function(currentValue,index,arr){},this指向)
- 使用forEach目的就是为了拿到数组的每一项及索引,然后操作。
- map映射
- 映射的本质就是将原数组映射成为一个新数组,新数组和原数组数据个数保持一致
- 语法:array.map(function(){
return 数组项返回值
})
- array:原数组名
- return 后面的值,是每次循环新数组对应的值。
- 不会修改原数组
- 返回值:原数组映射出的新数组。
- map必须有return
- 如果没有return默认return undefined,此时新数组的每一项都为undefined
- 返回值:返回一个和原数组一一对应的新数组【不会修改原数组】
- 应用场景:
- 新数组和原数组成一一对应关系,当原数组中每一项都为对象时,新数组需要和原数组一样,需要用到map第一个函数参数中的第一个形参。但如果新数组的每一项中对象的属性和原数组每一项的属性不一样时,这时就需要在函数return语句对象后面设置新数组的对象名和对象值,对象值就取原数组的数据。
- 案例:
var arr = [
{
name: 'xiaoming',
age: 18
},
{
name: 'xiaohong',
age: 20
}
]
let newarr = arr.map(function (currentvalue, index, a) {
return {
uname: currentvalue.name,
uage: currentvalue.age
}
})
console.log(newarr)
- filter()
- 语法:
- array.filter(function(currentValue,index,arr){
- 语法:
return 新数组中数据满足的条件表达式
})
- array:遍历数组的数组名
- currentValue:数组遍历的当前项的值
- index:数组当前项的索引
- arr:数组对象本身
- 查找满足条件的元素,返回一个数组,而且是把所有满足条件的元素返回回来【新数组中数据满足的条件表达式 条件表达式结果为true返回出来,结果为false,就过滤掉】
- filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组
- 返回值:返回一个新数组【不会修改原数组】
- filter里面的return不会终止迭代(只会终止本次循环体,继续遍历数组元素后面部分)
- filter过滤,只有当回调函数return true这一项才会保留,但是不会修改值。
- 案例:
- var newarr = arr.filter(function (value, index, array) {
return value > 3
})
console.log(newarr)
- 筛选数组arr中满足值大于3的数据,并保存到新数组newarr中
- 应用场景:
- 有些数组中存的是对象,但对象中有的属性却不存在,这时候用filter方法,就可以实现 没有该属性的这一个数组元素对象不会遍历出来,而return后有该属性的数组对象项就会遍历出来,实现了筛选数组。【过滤掉不需要的数据】
- 数组去重:
- let nerArr = arr.filter((val,index,arr) =>{
return arr.indexOf(val) == index
})
- some()
- 语法:
- array.some(function(currentValue,index,arr){
- 语法:
return 新数组中数据满足的条件表达式
})
- some()方法用于检测数组中的元素是否满足指定条件,通俗点 查找数组中是否有满足条件的元素
- some里面的return会终止迭代,不再遍历数组元素,效率会更高
- 注意它返回值是布尔值,如果查找到这个元素,就返回true,如果查找不到就返回false
- 如果找到第一个满足条件的元素,则终止循环,不再继续查找
- currentValue:数组遍历的当前项的值
- index:数组当前项的索引
- arr:数组对象本身
- 查找满足条件的元素是否存在,返回的是一个布尔值如果查找到第一个满足条件的元素就终止循环。
- 如果查询数组中唯一的元素,用some方法更合适,因为它找到这个元素就不再进行循环,效率更高
- 查询商品案例
- 数据渲染
- 根据价格查询商品:
- 当我们点击了按钮,就可以根据我们的商品价格去筛选数组里面的对象
- 代码实现:
search_price.addEventListener('click', function() {
// alert(11);
var newDate = data.filter(function(value) {
return value.price >= start.value && value.price <= end.value;
});
console.log(newDate);
// 把筛选完之后的对象渲染到页面中
setDate(newDate);
});
- 根据名称查询商品
- 当我们输入商品内容,点击查询,就可以搜索唯一商品信息
- 代码实现:
- 根据名称查询商品
search_pro.addEventListener('click', function() {
var arr = [];
data.some(function(value) {
if (value.pname === product.value) {
arr.push(value);
return true; // return 后面必须写true,终止迭代
}
});
// 把拿到的数据渲染到页面中
setDate(arr);
})
- reduce():
- 语法:array.reduce(function(pre,val,index,arr){})
- array:目标数组
- pre:上一次迭代目标数组array的返回值【迭代的初始值:从哪个数组元素开始迭代,默认是第一个元素】
- 当第一个形参函数的函数体内 return后面的值不为形参函数的形参时:
- 第一次迭代的返回值为目标数组第一项数组元素,后面的迭代返回值都为undefined。
- 当第一个形参函数的函数体内 return后面的值为形参函数的形参时:
- return后面的值为形参函数的第二个形参val:
- 当第一个形参函数的函数体内 return后面的值不为形参函数的形参时:
- 语法:array.reduce(function(pre,val,index,arr){})
第一次迭代的返回值为目标数组第一项数组元素,后面每次迭代的返回值中,第一个函数形参的第一个形参pre对应的值是上一次迭代索引号对应的数组元素
- return后面的值为形参函数的第三个形参index:
第一次迭代的返回值为目标数组第一项数组元素,后面每次迭代的返回值中,第一个函数形参的第一个形参pre对应的值是上一次迭代数组元素对应的索引
- return后面的值为形参函数的第四个形参arr:
第一次迭代的返回值为目标数组第一项数组元素,后面每次迭代的返回值中,第一个函数形参的第一个形参pre对应的值是 要迭代数组array
- val:数组遍历的当前项
- index:数组当前项的索引
- arr:数组对象本身
- 返回值:undefined
- 该方法从左往右迭代
- 当第一个函数形参后面有第二个形参时,就是将第一个形参函数形参的第一个形参pre初始化为该形参值
- 应用场景:
- 数组内所有元素值求和
- 案例:
- 数组内所有元素值求和
var arr = [1, 2, 3, 4, 5]
var newarr = arr.reduce(function (pre, value, index, arrs) {
return pre += value
})
console.log(newarr)
- reduceRight():
- 该方法从右往左迭代,和reduce()方法迭代方向相反
- ES6新增循环for of:
- ES6引入的作为遍历所有数组结构的统一的方法。
- 一个数据结构只要部署了Symbol.iterator属性【迭代器】,就被视为具有iterator接口,就可以用for…of遍历它的成员,也就是说,for…of循环内部调用的是数据结构的Symbol.iterator方法。
- 语法:
- for( let val of arr){
Console.log(val)
}
- 当arr为数组的时候,val在循环体内部为每次遍历数组的的当前元素值,arr在循环体内部为遍历的这个数组
- 当arr改为arr.keys()方法时,val在循环体内部 为每次遍历数组的当前元素的索引
- arr.keys()相当于索引组成的数组
- 当arr改为arr.values()方法时,val在循环体内部 为每次遍历数组的当前元素值
- arr.values()相当于数组元素组成的数组
- 当arr改为arr.entries()方法时,val在循环体内部 为每次遍历数组的当前元素的索引值和元素组组成的数组
- arr.entries()相当于数组元素索引号和数组元素值组成的二维数组
- 注意:values,keys,entries这三个方法是挂载到构造函数Array的原型对象上的【所以实例对象数组也可以调用该方法[每个数组都可以调用该方法]】
- 总结:
- 如果只需要迭代数组的值,直接使用for(let val of arr){}
- 如果既想要获取数组的值,又想要获取索引那就迭代entries【for (let val of arr.entries()){}】
- 数组解构和迭代器的综合使用:
- 语法:for (let [index val] of arr.entries()){}
- 这样循环体中index变量为当前迭代的数组索引,val为当前迭代的数组元素值。
- 语法:for (let [index val] of arr.entries()){}
- 字符串方法:
- trim()方法会从一个字符串的两端删除空白字符
- str.trim()
- trim()方法并不影响原字符串本身,它返回的是一个新的字符串。
- 使用场景:表单输入内容为空或空字符串时,提示用户输入内容
- if(input.value.trim()==’’){alert(‘请输入内容’)}
- trim()方法会从一个字符串的两端删除空白字符
- 对象方法:
- Object.keys()用于获取对象自身所有的属性
- 语法:Object.keys(obj)
- 效果类似于for…in
- 返回一个由属性名组成的数组
- obj:对象名
- 语法:Object.keys(obj)
- Object.defineProperty()定义对象中新属性或修改原有的属性。
- 语法:Object.defineProperty(obj,prop,descriptor)
- obj:必须。目标对象,需要操作的对象名
- prop:必须。需定义或修改的属性的名字
- descriptor:必需。目标属性所拥有的特性
- 以对象形式{}书写
- 语法:Object.defineProperty(obj,prop,descriptor)
- Object.keys()用于获取对象自身所有的属性
value:设置属性的值 默认为undefined
writable:值是否可以重写。true | false 默认为false
true:允许修改
false:不允许修改【obj.属性修改目标属性属性值无效】
enumerable:目标属性是否可以被枚举。true | false 默认为false
false:不会遍历出来
true:可以遍历出来
用Object.defineProperty()添加的属性不能用Object.keys()遍历出来【enumerable默认为false】
为什么js自带的属性不能通过for in遍历,就是因为默认的enumerable属性这个描述是false。
configurable:目标属性是否可以被删除或是否可以再次修改特性true|false 默认为false
false:不允许删除这个属性,也不可以修改这个属性
true:可以删除这个属性
- 返回值为obj这个修改后的对象
- 通过这个方法添加的属性,不能直接操作对象修改该属性值【除非原对象也有该属性或在该方法的第三个参数对象里设置了writable属性的属性值为true】
- 定义多个属性:
- 语法:
Object.defineProperties(obj,{
prop:descriptor,
prop:descriptor
…
})
- obj:目标对象
- prop:目标对象的属性
- descriptor:目标对象属性的描述对象
- 删除对象里面的某个属性:
- delete 对象名.属性名
- 函数的定义方式:
- 函数声明方式function关键字(命名函数)
- 函数表达式(匿名函数)
- new Function(‘参数1’,’参数2’,’函数体’)
- new Function(‘参数1’,’参数2’,’函数体’)
- Function里面参数都必须是字符串格式
- 这种方式执行效率低,也不方便书写,因此较少使用
- 所有函数都是Function的实例(对象)
- 函数也属于对象
- 函数的调用方式:
- 普通函数
- 函数名()
- 函数名.call()
- 对象的方法
- 对象名.方法名()
- 构造函数
- new 构造函数名()
- 绑定事件函数
- btn.onclick=function(){} //点击了按钮就可以调用这个函数
- 定时器函数
- setInterval(function()(),间隔的毫秒数) //这个是定时器自动多少毫秒调用一次
- 立即执行函数
- (function(){})() //立即执行,自动调用
- 普通函数
- 函数内this的指向
- 这些this的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this的指向不同,一般指向我们的调用者
调用方式 | this指向 |
普通函数调用 | Window |
构造函数调用 | 实例对象 原型对象里面的方法也指向实例对象 |
对象方法调用 | 该方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器 | Window |
立即执行函数 | Window |
- 改变函数内部this指向
- JavaScript为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部this的指向问题,常用的由bind()、call()、apply()
- call方法
- call()方法会调用一个函数,简单理解为调用函数的方式,但是它可以改变函数的this指向
- 函数名.call(thisArg, arg1,arg2,…)
- thisArg:函数的this指向
- 因为this指向一个对象,如果传入字符串,数字,布尔值基本数据类型,那么this将会指向他们的包装类
- 如果call方法传入第一个参数是没有包装类的undefined和null,那么this不会改变,依然指向默认的window
- arg1,arg2:参数
- 参数是散列值
- thisArg:函数的this指向
- call的主要作用可以实现继承
- 子构造函数(Son)中写
- Father.call(this)
- 子构造函数(Son)中写
- apply方法
- apply()方法会调用一个函数,简单理解为调用函数的方式,但是它可以改变函数的this指向
- 语法:目标函数名.apply(thisArg, [argsArray])
- thisArg:在目标函数名这个函数运行时指定的this值
- 当我们不想改变this指向时,第一个参数写undefined或null
- argsArray:传递的值,必须包含在数组里面【实参列表,会拆解为散列的值,依次赋值给形参】
- 返回值就是函数的返回值,因为它就是调用函数
- thisArg:在目标函数名这个函数运行时指定的this值
- 应用场景:
- 求数组的最大值
- 求数组中的最小值
- 案例:
var arr = [1, 2, 3, 9, 7, 6, 4]
console.log(Math.max.apply(Math, arr))
- bind方法【硬绑定】
- bind()方法不会调用函数。但是能改变函数内部this指向
- 返回的是原函数改变this之后产生的新函数
- 语法:目标函数名.bind(thisArg,arg1,arg2,…)
- thisArg:在目标函数运行时指定的this值
- arg1.arg2:传递的其他参数
- 返回由指定的this值和初始化参数改造的原函数拷贝
- 应用场景:
- 如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向,此时用bind方法
- 案例:
- bind方法【硬绑定】
<script>
var btns = document.querySelectorAll('button')
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
this.disabled = true;
setTimeout(function () {
this.disabled = false
}.bind(this), 2000)
}
}
</script>
- 定时器函数里面的this指向为window,在定时器里面的函数后加.bind(this)可以改变定时器里面的this指向,而bind(this)括号内的this在定时器函数外面,在事件btns[i]上,所有此时的this指向为当前点击的btn[i]这个按钮
- 将定时器函数体内部的this指向改为父作用域this的方法:
- 方法一:
- 父作用域定义一个接收this的变量that
- 定时器函数体内将that赋值给this【此时定时器函数体内this指向就是父作用域的this指向了】
- 方法二:
- 给定时器第一个函数参数后加.bind(this) 来绑定父作用域的this
- 方法三:
- 给定时器第一个函数参数设置形参this,给定时器第三个参数设置实参this的指向。
- 方法四:
- 定时器第一个函数参数改为箭头函数,此时函数体内的this就会指向父作用域的this【箭头函数没有this属性,就会直接在父作用域去找】
- 方法一:
- call、apply、bind总结:
- 相同点:
- 都可以改变函数内部的this指向
- 区别点:
- call和apply会调用函数,并且改变函数内部this指向【函数执行时改变this指向,函数执行完毕,函数的this指向会复原,即下一次调用该函数,他的this指向是以前的this指向】
- call和apply传递的参数不一样,call传递参数aru1,aru2…形式,apply必须数组形式[arg]
- bind不会调用函数,可以改变函数内部this指向。
- 主要应用场景:
- call经常做继承
- apply经常跟数组有关,比如借助于数学对象实现数组最大值最小值
- bind不调用函数,但是还想改变this指向,比如改变定时器内部的this指向。
- 相同点:
- 严格模式:
- JavaScript除了提供正常模式外,还提供了严格模式(strict mode)。ES5的严格模式是采用具有限制性JavaScript变体的一种方式,即在严格的条件下运行JS代码。
- 严格模式在IE10以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
- 严格模式对正常的JavaScript语义做了一些更改:
- 消除了JavaScript语法的一些不合理,不严谨之外,减少了一些怪异行为。
- 比如:变量不声明不能使用
- 消除代码运行的一些不安全之处,保证代码运行的安全。
- 提高编译器效率,增加运行速度。
- 禁用了在ESMAScript的未来版本中可能会定义的一些语法,为未来新版本的JavaScript做好铺垫。比如一些保留字如:class,enum,export,import,super不能做变量名。
- 消除了JavaScript语法的一些不合理,不严谨之外,减少了一些怪异行为。
- 开启严格模式
- 严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为脚本开启严格模式和为函数开启严格模式两种情况。
- 为脚本开启严格模式:
- 有的script基本是严格模式,有的script脚本是正常模式,这样不利于文件合并,所有可以将整个脚本文件放在一个立即执行的匿名函数之中,这样独立创建一个作用域而不影响其他script脚本文件。
- <script>
- 有的script基本是严格模式,有的script脚本是正常模式,这样不利于文件合并,所有可以将整个脚本文件放在一个立即执行的匿名函数之中,这样独立创建一个作用域而不影响其他script脚本文件。
(function () {
“use strict”;
})
- </script>
- 为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句‘use strict’;(或”use strict”;);
- 下面的js代码就会按照严格模式执行代码
- 为函数开启严格模式
- 要给某个函数开启严格模式,需要把“use strict”;(或‘use strict’;)声明放在函数体所有语句之前。
- 严格模式中的变化
- 严格模式对JavaScript的语法和行为,都做了一些改变。
- 变量规定
- 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用
- 我们不能随意(严禁)删除已经声明变量。例如,delete x;语法是错误的。
- 严格模式下this指向问题
- 以前在全局作用域函数中的this指向window对象
- 严格模式下全局作用域中的this是undefined
- 以前构造函数时不加new也可以调用,当普通函数,this指向全局对象
- 严格模式下,如果 构造函数不加new调用,this指向的是undefined,如果给他赋值,会报错
- New实例化的构造函数指向创建的对象实例
- 定时器this还是指向window
- 事件,对象还是指向调用者
- 函数变化
- 函数不能有重名的参数。
- 函数必须声明在顶层。新版本的JavaScript会引入“块级作用域”(ES6已引入),为了与新版本接轨,不允许在非函数的代码块内声明函数
- 高阶函数:
- 高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出
- 函数作为参数:
- 原函数中的形参参数为自定义函数名
- 原函数的函数体中直接调用该形参参数即这个自定义函数体,则会执行对应的实参函数。
- 原函数调用的实参参数为一个函数
- 原函数中的形参参数为自定义函数名
- 函数作为返回值:
- 原函数的返回值为另外一个函数
- 函数作为参数:
- 函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。
- 高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出
- 闭包:
- 变量作用域:
- 变量根据作用域的不同分为两种:全局变量和局部变量。
- 函数内部可以使用全局变量
- 函数外部不可以使用局部变量。
- 当函数执行完毕,本作用域内的局部变量会销毁。
- 变量根据作用域的不同分为两种:全局变量和局部变量。
- 什么是闭包:
- 闭包(closure)指有权访问另一个函数作用域中的变量的函数。 ---JavaScript高级程序设计【被访问变量所在的函数也称为闭包函数】
- 方法里返回一个方法
- 一个A函数内部定义了一个B函数,这个B函数被移到A函数作用域以外的其他作用域中重新定义,导致这个B函数一直,那么这个B函数定义时记录的父作用域(即A函数作用域)不会销毁,从而形成了闭包。
- 简单理解:一个作用域可以访问另外一个函数内部的局部变量。
- 外部变量使用内部函数的值,导致内部局部作用域不会被销毁
- 闭包(closure)指有权访问另一个函数作用域中的变量的函数。 ---JavaScript高级程序设计【被访问变量所在的函数也称为闭包函数】
- 闭包的作用:
- 延申了变量的作用范围
- 变量共享
- 缓存数据
- 延长变量的生命周期
- 创建私有环境
- 闭包的问题:
- 闭包的作用域不会被销毁,导致内存泄漏
- 闭包会导致原有作用域链不释放,造成内存泄漏
- 闭包的作用域不会被销毁,导致内存泄漏
- 闭包的特性:
- 因为闭包的作用域不会被销毁,可以利用闭包做缓存数据【也是全局作用域的特性】。
- 闭包案例:
- 循环注册点击事件
- 以前做法:
- 循环注册点击事件
- 变量作用域:
for (var i = 0; i < lis.length; i++) {
lis[i].index = i
lis[i].onclick = function () {
console.log(this.index)
}
}
- 闭包做法:
for (var i = 0; i < lis.length; i++) {
(function (i) {
lis[i].onclick = function () {
console.log(i)
}
})(i)
}
- 立即执行函数也称为小闭包,因为立即执行函数里面的任何一个函数都可以使用它的i这个变量
- 循环中的setTimeout().
for (var i = 0; i < lis.length; i++) {
(function (i) {
setTimeout(function () {
console.log(i)
}, 3000)
})(i)
}
- 计算打车价格
var car = (function () {
var start = 13;
var total = 0;
return {
//正常的总价
price: function (n) {
if (n <= 3) {
total = start;
} else {
total = start + (n - 3) * 5
}
return total;
},
//拥堵之后的费用
yd: function (flag) {
return flag ? total + 10 : total;
}
}
})();
car.price(5)
console.log(car.yd(false))
- 递归:
- 什么是递归:
- 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
- 简单理解:函数内部自己调用自己,这个函数就是递归函数
- 递归函数的作用和循环效果一样
- 由于递归很容易发生“栈溢出”错误,所以必须要加退出条件return。
- 计数器写在函数外面,操作表达式写在函数内部,return写在条件内,满足条件,直接退出函数【条件写在递归函数前面】
- 计数器写在函数参数里面操作表达式写在递归函数里面,return写在条件内,满足条件,直接退出函数
- 案例:
- 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
- 什么是递归:
var sum = 1
function getsum() {
console.log('打印6句话')
if (sum == 6) {
return
}
sum++;
getsum()
}
getsum()
- 利用递归求数学题
- 1*2*3*4…
- 利用递归求数学题
function getsum(sum) {
if (sum == 1) {
return 1;
}
return sum * getsum(sum - 1)
}
console.log(getsum(10))
- 用户输入一个数字n,就可以求出这个数学对应的兔子序列值【斐波那契数列】
function fb(n) {
if (n === 1 || n === 2) {
return 1;
}
return fb(n - 1) + fb(n - 2);
}
console.log(fb(3))
- 利用递归求:根据id返回对应的数据对象
function getID(json, id) {
var o = {};
json.forEach(function (currentValue, index, arr) {
if (currentValue.id == id) {
o = currentValue;
} else if (currentValue.friend && currentValue.friend.
length > 0) {
o = getID(currentValue.friend, id)
}
});
return o
}
console.log(getID(data, 9))
- 浅拷贝和深拷贝
- 浅拷贝只是拷贝一层,更深层对象级别的只拷贝引用。
- 浅拷贝对象里面的对象拷贝的是地址,当修改浅拷贝的数据时,会改变原来的数据
- 案例:
- 深拷贝拷贝多层,每一级别的数据都会拷贝。
- Object.assign(target,sources) es6新增方法可以浅拷贝
- target:拷贝赋值的对象
- source:拷贝的对象
- 深拷贝案例:
- 浅拷贝只是拷贝一层,更深层对象级别的只拷贝引用。
- 浅拷贝和深拷贝
var data = {
name: 'xiaoming',
age: 18,
friends: {
name: 'xiaohong',
age: 20
}
}
var o = {}
function deepCopy(newobj, oldobj) {
for (var k in oldobj) {
//1.获取属性值
var item = oldobj[k]
//2.判断这个属性值是否是数组
if (item instanceof Array) {
newobj[k] = []
deepCopy(newobj, item)
} else if (item instanceof Object) {
newobj[k] = {}
deepCopy(newobj, item)
} else {
newobj[k] = item;
}
}
}
deepCopy(o, data)
o.friends.age = 29
console.log(o)
console.log(data)
</script>
- 正则表达式概述:
- 什么是正则表达式:
- 正则表达式(Regular Expression)是用于匹配字符串中字符组合的模式,在JavaScript中,正则表达式也是对象。
- 正则表达式是字符串的一种匹配模式,专门为简化字符串操作而生。简单点说就是为了检索字符串中特定字符的规则。正则并不是单纯的字符串,而是一种逻辑公式。
- 正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线,昵称输入框中可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等
- 其他语言也会使用正则表达式,本阶段我们主要利用JavaScript正则表达式完成表单验证。
- 正则表达式的特点:
- 灵活性、逻辑性和功能性非常的强
- 可以迅速地用极简单的方式达到字符串的复杂控制。
- 对于刚接触的人来说,比较难懂
- 实际开发,一般都是直接复制写好的正则表达式,但是要求会使用正则表达式并且根据实际情况修改正则表达式。比如用户名:/^[a-z0-9_-]{3,6}$/
- 可以使用正则为参数的字符串方法:
- search获取字符在字符串中的位置【只能获取第一个字符或字符串在目标字符串中的第一个位置,不能从某个位置往后继续查获取后面的位置】
- split字符串转换为数组
- 例子:字符串变量名.split(正则表达式变量名)
- replace字符串替换
- match
- 原字符串变量名.match(正则变量名)
- 返回值为一个数组【当正则没有子项时】
- 原字符串变量名.match(正则变量名)
- 什么是正则表达式:
正则为i/m不区分大小写匹配时
该数组第一项为正则变量保存的表达式
该数组第二项为index:该表达式在字符串中位置的索引号
该数组第三项为input:原字符串变量保存的字符串值
该数组第四项为groups:
正则为g全局匹配时:
该数组每一项都为该正则,字符串中有几次该目标正则表达式,数组中就有几个该目标正则表达式。
- 正则表达的组成:
- 一个正则表达式可以由简单的字符构成,比如/abc/,也可以是简单和特殊字符的组合,比如/ab*c/。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号。比如^、$、+、()、? 、\、|、等。
- 普通字符我们可以直接拿来用,但是,特殊字符是一定要转义。因为特殊字符在正则中有特殊的意义
- 正则表达式里面不需要加引号,不管是数字型还是字符串型
- 特殊字符非常多,可以参考:
- 一个正则表达式可以由简单的字符构成,比如/abc/,也可以是简单和特殊字符的组合,比如/ab*c/。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号。比如^、$、+、()、? 、\、|、等。
- 正则表达式在JavaScript中的使用
- 创建正则表达式:
- 在JavaScript中,可以通过两种方式创建一个正则表达式:
- 通过调用RegExp对象的构造函数创建:
- 语法一:var 变量名=new RegExp(/表达式/);
- 语法二:
- 通过调用RegExp对象的构造函数创建:
- 在JavaScript中,可以通过两种方式创建一个正则表达式:
- 创建正则表达式:
var 变量名= new RegExp(参数1,参数2)
参数1为正则的规则【参数为字符串】
参数2为修饰符【参数为字符串】
“i”:表示不区分大小写ignoreCase
“g”:表示全局匹配global
“m”:表示换行匹配multiline
- 利用字面量创建【字面量//中间必须有表达试[否则会当注释符解析就会报语法错误]】:
- var 变量名=/表达式/修饰符;
- 利用字面量创建【字面量//中间必须有表达试[否则会当注释符解析就会报语法错误]】:
表达式为正则的规则
修饰符为字母:
i:表示不区分大小写ignoreCase
g:表示全局匹配global
m:表示换行匹配multiline
- 测试正则表达式test
- test()正则对象方法,用于检测字符串是否符合该规则,该对象会返回true或false,其参数是测试字符串。
- 语法:regexObj.test(str)
- regexObj是写的正则表达式
- str我们要测试的文本
- str写在引号里面,只要有这个字符串就返回true,可以加限定符来约束测试文本
- str对应的正则表达式写在[]里面,只有这个字符串里面有方括号内其中一个字符就返回true,可以加限定符来约束测试文本
- 测试正则表达式test
字符类表示有一系列可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内
[-]方括号内范围符-
var rg = /^[a-z]$///字母a到z中任意一个小写字母
方括号内写^表示取反,没有的意思,千万别和边界符^混淆了。
- 就是检测str文本是否符合我们写的正则表达式规范。
- 正则表达式的特殊字符:
- 边界符:
- 正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
- 边界符:
边界符 | 说明 |
^ | 表示匹配行首的文本(以谁开始) |
$ | 表示匹配行尾的文本(以谁结束) |
匹配字符(?=目标字符) | 匹配到 匹配字符 后面紧跟着 目标字符 的 匹配字符 |
- 如果^和$在一起,表示必须是精确匹配【字符串就是这个正则表达式边界符中间的字符串】。
- 量词符
- 量词符用来设定某个模式出现的次数。
量词 | 说明 |
* | 重复零次或更多次,等价于{0,} |
+ | 重复一次或更多次,等价于{1,} |
? | 重复零次或一次,等价于{0,1} |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
- 量词写在单个符号的后面,用来规定该符号在正则表达式中出现的次数
- 例子:var reg = /a{3}/
- 量词写在模式的后面,用来规定该模式允许出现的次数
- 例子:var reg = /^[a-zA-Z0-9_-]{6,16}$/
- 无论字符集还是元字符,都只能匹配到一个字符,无法匹配多个字符,所以引入量词的概念,用来设置匹配到字符的个数
- 匹配的原则符合贪婪匹配原则,即尽可能多的匹配,可以在量词后面加上?,能够取消贪婪匹配。重复书写某个规则时可以用量词代替,由/\d\d\d\d\d\d/改为/\d{6}/
- 贪婪与惰性:
- 量词+默认贪婪匹配,就是说尽量往指定范围类最大的匹配,在量词后面加上?符号,变为惰性匹配,也就是尽量少的去匹配。
- 量词写在单个符号的后面,用来规定该符号在正则表达式中出现的次数
- 预定义类
- 预定义类指的是某些常见模式的简写方式。
预定类 | 说明 |
\d | 匹配0-9之间的任一数字,相当于[0,9]。数字字符 |
\D | 匹配所有0-9以外的字符,相当于[^0,9]。非数字字符 |
\w | 匹配任意的字母、数字和下划线,相当于[a-zA-Z0-9_]。单词字符(所有字母) |
\W | 除所有字母、数字和下划线以外的字符,相当于[^a-zA-Z0-9_]。非单词字符 |
\s | 匹配空格(包括换行符、制表符、空格符等),相等于[\t\r\n\v\f] |
\S | 匹配非空格的字符,相当于[^\t\r\n\v\f] |
. | [^\n\r]除了换行和回车之外的任意字符 |
- 预定义的特殊字符:
预定类 | 说明 |
\t | 制表符 |
\n | 回车符 |
\f | 换页符 |
\b | 与回退字符 |
- ^
- 正则表达式方括号外表示以什么开始的文本【是边界符】
- 正则表达式方括号内表示取反,没有的意思
- 括号总结:
- 中括号 字符集合。匹配方括号中的任意字符
- 大括号 量词符,里面表示重复次数
- 小括号 表示优先级
- 正则表达式在线测试匹配:
- 其他工具在线测试:
- ^
- 字符集:
- JavaScript的正则表达式中有四类字符集:
- 普通字符集【简单类】:
- 它是有一一对应的字符组成的集合,通过[]包裹住,来表示这几个字母组成的一个集合
- 如:[ab45]表示由ab45四个字符组成的一个集合,简单理解:匹配ab45四个字符中的任意一个
- 它是有一一对应的字符组成的集合,通过[]包裹住,来表示这几个字母组成的一个集合
- 范围字符集:
- 通过首位字母以及-组成的一个范围集合
- 如:[a-z]表示小写字母集合;[A-Z]表示大写字母集合;[0-9]表示数字集合;[i-f]表示小写字母i到y的集合
- 通过首位字母以及-组成的一个范围集合
- 负向类:
- 通过在[]内部最前面添加^表示不包含该集合的字符集
- 如:[^abc]表示不包含abc的任意字符集合
- 通过在[]内部最前面添加^表示不包含该集合的字符集
- 组合类:
- 通过[]将几个集合拼接在一起表示一个组合的集合。
- 如:[a-zA-Z]表示大小写字母的集合
- 通过[]将几个集合拼接在一起表示一个组合的集合。
- 普通字符集【简单类】:
- 注意:
- 中括号的字符集里面无论你写多少个字符只会匹配其中一个【简单理解为任意一个】
- 特殊的中文字符集:
- [\u4e00-\u9fa5]表示中文集
- JavaScript的正则表达式中有四类字符集:
- 正则中匹配字符后面跟的目标字符:
- 匹配字符(?=目标字符)
- 匹配到 匹配字符 后面紧跟着 目标字符 的 匹配字符
- (?!n)
- 匹配到 匹配字符 后面没有紧跟 目标字符 的 匹配字符
- 匹配字符(?=目标字符)
- 正则的两个方法:
- text方法:
- 目标正则表达式.text(目标字符串)
- 只要目标字符串里面有目标正则表达式,就返回true【目标正则表达式在目标字符串中匹配成功就返回true,否则返回false】
- 目标正则表达式.text(目标字符串)
- exec方法:
- 目标正则表达式.exec(目标字符串):
- 提取匹配成功的字符串
- 和字符串的match方法很像,返回值为一个数组:
- 该数组第一项为正则变量保存的表达式
- 该数组第二项为index:该表达式在字符串中位置的索引号
- 该数组第三项为input:原字符串变量保存的字符串值
- 该数组第四项为groups
- 如果正则没有全局匹配,那么exec方法和字符串的match方法结果一模一样,如果有子项,也会匹配到子项。如果有全局匹配,那么exec方法不会发生变化,但是match方法会将所有匹配成功的项【该正则表达式】组成数组,此时会忽略子项匹配。
- 目标正则表达式.exec(目标字符串):
- text方法:
- 正则的子项:
- 定义正则时,可以用()将部分规则包裹起来,这样在使用match或者exec做匹配的时候,能得到的结果里面拿到该部分匹配的内容
- 注意一点加全局匹配g,match方法就不包含子项了
- 子项的反引用【也叫捕获组】:
- 当正则有子项时,如果希望子项后面的匹配结果与子项完全一样,则可以通过\n的方式来引用子项匹配结果【n为子项的序号,是第几个子项,n的序号就为多少,这样在子项字面量()后面加上\n就可以实现子项的反引用。多次反引用就可以多加几次\n,这样可以反复匹配子项。】
- 定义正则时,可以用()将部分规则包裹起来,这样在使用match或者exec做匹配的时候,能得到的结果里面拿到该部分匹配的内容
- 正则的或|:
- [目标1字符目标2字符]
- 匹配目标1字符或目标2字符
- 目标1字符|目标2字符
- 匹配目标1字符或目标2字符
- [目标1字符目标2字符]
- 用户名验证:
- 功能需求:
- 如果用户名输入合法,则后面提示信息为:用户名合法,并且颜色为绿色
- 如果用户名输入不合法,则后面提示信息为:用户名不符合规范,并且颜色为红色
- 用户名验证分析:
- 用户名只能为英文字母,数字,下划线或者短横线组成,并且用户名长度为6~16位
- 首先准备好这种正则表达式模式/^[a-zA-Z0-9-]{6,16}$/
- 当表单失去焦点就开始验证
- 如果符合正则规范,则让后面的span标签添加right类
- 如果不符合正则规范,则让后面span标签添加wrong类
- 代码实现:
- 功能需求:
<input type="text" class="uname"><span></span>
<script>
var ipt = document.querySelector('.uname')
var span = document.querySelector('span')
var reg = /^[a-zA-Z0-9-_]{6,16}$/
ipt.onblur = function () {
if (reg.test(this.value)) {
console.log('正确的')
span.innerHTML = '用户名输入正确'
} else {
console.log('错误的')
span.innerHTML = '用户名输入错误'
}
}
</script>
- 座机号码验证:全国座机号码有两种格式:
- 010-12345678
- 0530-1234567
- 正则表示:
- var reg = /^\d{3}-\d{8}|\d{4}-\d{7}$/;
- 昵称是中文的正则表达式:
- /^[\u4e00-\u9fa5]{2,8}$/
- 正则表达式中的替换:
- replace()替换:
- replace()方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。
- 语法:stringObject.replace(regexp / substr, replacement)
- stringObject:原字符串
- 第一个参数:被替换的字符串或者正则表达式
- 正则表达式参数:
- replace()替换:
/表达式/[switch]
switch(也称修饰符)按照什么样的模式来匹配,有三种值:
/6g:全局匹配
i:忽略大小写
gi:全局匹配+忽略大小写
- 第二个参数:替换为的字符串
- 返回值是一个替换完毕的新字符串,只会替换第一个【不会修改原字符串】
- 什么是ES6?
- ES的全称ECMAScript,它是由ECMA国际标准化组织,制定的一项脚本语言的标准化规范。
年份 | 版本 |
2015年6月 | ES2015 |
2016年6月 | ES2016 |
2017年6月 | ES2017 |
2018年6月 | ES2018 |
…… | … |
- ES6实际上是一个泛指,泛指ES2015及后续的版本。
- 为什么使用ES6?
- 每一次标准的诞生都意味着语言的完善,功能的加强。JavaScript语言本身也是有一些令人不满意的地方。
- 变量提升特性增加了程序运行时的不可预测性
- 语法过于松散,实现相同的功能,不同的人可能会写出不同的代码。
- 每一次标准的诞生都意味着语言的完善,功能的加强。JavaScript语言本身也是有一些令人不满意的地方。
- ES6中新增语法
- let:
- ES6中新增的用于声明变量的关键字。
- let声明的变量只在所处于的块级有效【块级作用域:{}里面的代码块】
- 同一个代码块里面不允许let同一个变量多次【语法报错:SyntaxError】
- 子代码块可以let父代码块中的变量名
- 注意:使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性。
- 防止循环变量变成全局变量
- 每次循环都会产生一个块级作用域,每个块级作用域中的变量都是不同的,函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值
- 不存在变量提升【必须先声明后使用,否则程序会报错】
- 使用包括:输出变量值和赋值变量值。
- 暂时性死区:
- Let声明的变量在块级作用域内会对 这个块级作用域整体进行绑定,所以这个变量在声明之前使用就会报错,不会再全局作用域去查找。【在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”[tepporal dead zone,简称TDZ]】
- let声明的变量在该代码块内,前面不能出现该变量名。后面可以出现和修改该变量,但在该变量名前面不能出现var 和let声明该变量的关键字
- for循环里比较特殊,可以将()中的循环变量理解为父级作用域中的变量,循环体中的变量理解为子作用域中的变量,所以他们let同一个变量不会报错
- let声明的变量不是window的属性
- 常量const
- 作用:声明常量,常量就是值(内存地址)不能变化的量。
- 具有块级作用域
- 声明常量时必须赋初始值【否则控制台会报语法错吴:SyntaxError】
- const PI = 3.14;
- 常量赋值后,值不能修改。
- 对于基本数据类型来讲:
- 一旦赋值,值不可更改
- 对于复杂数据类型来讲:
- 不能重新赋值,但可以更改数据结构内的值。【比如数组里面数据的修改】【并没有改变值对应的内存地址】
- 对于基本数据类型来讲:
- let:
const arr = [1,2,3,4]
arr[0] = 0
冻结对象,让其内部的数据也不可以修改:
Object.freeze(目标对象名)
目标对象可以时数组,对象
返回值为原对像
原对像的数据将不可被修改
- 修改常量的值,控制台会报错【基本数据类型不能修改值,引用数据类型不能修改内存地址】
- 常量命名规范:
- 全大写【普通函数小驼峰命名,构造函数大驼峰命名,常量全大写】
- 使用let、const、var的区别:
- 使用var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象。
- 全局的变量声明会作为window环境的属性
- 使用let声明的变量,其作用域为该语句所在的代码块内,不存在变量提升。
- 全局的变量声明不会作为window环境的属性。
- 使用const声明的是常量,在后面出现的代码中不能再修改该常量的值。
- 使用var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象。
var | let | const |
函数级作用域 | 块级作用域 | 块级作用域 |
变量提升 | 不存在变量提升 | 不存在变量提升 |
值可更改 | 值可更改 | 值不可更改 |
- 对象的简写:
- 对象属性的简写:
- 当对象里面属性名和属性值【属性值为变量名】一样的时候,可以简写【只写属性名】。对象打印输出的时候,对象属性不变,对象的属性值为该属性变量存储的值。
- 应用场景:
- 先对象解构,再赋值给对象名【对象已知,可以得到对象里面变量的值】
- 先数组解构变量,再对象简写【变量值已知,可以得到对象的值】
- 对象方法的简写:
- 语法【既能简化function关键字的使用,又不改变内部的this指向】:
- 方法名(){
- 语法【既能简化function关键字的使用,又不改变内部的this指向】:
- 对象属性的简写:
//方法体
}
- 等价于方法名:function(){}
- 也等价于【但后面这个会改变方法内部的this指向】:方法名:()=>{}
- 调用方法:对象名.方法名()
- ES6以后,对象字面量内部的属性也可以使用表达式【该属性表达式必须在中括号[]里面】
- 解构赋值
- ES6中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构。
- 按照一定模式,从数组或对象中提取值,将提取出来的值赋值给另外的变量。
- 数组解构:
- 数组解构允许我们按照一一对应的关系从数组中提取值,然后将值赋值给变量
- 例子:let [a, b, c] = [1, 2, 3]
- 相当于1赋值给a,2赋值给b,3赋值给c.
- 例子:let [a, b, c] = [1, 2, 3]
- let/const/var后面跟上一对用中括号[]包裹的变量列表,变量的值为对应位置上的数组的值。变量列表中多的变量打印时为undefined,数组列表中多的数据不起作用。假如我们不想数组前面坐标的数据,而是想要中间位置,或者最后位置的,那么前面位置上的每一个元素留空,用逗号相隔就行了。
- 数组解构的变量列表既可以在数组解构语法前面声明,也可以在解构时声明
- 如果解构不成功,变量的值为undefined
- 模式匹配:只要等号两边的模式相同,左边的变量就会被赋予对应的值【如果模式不匹配,控制台就会报错】
- 不成功的解构:
- 变量比值多,说明有一些变量得不到值,那就是undefined
- 不完全解构:
- 变量比值少,说明有些值没有变量来接收
- 应用场景:
- 有时候,后台的数据为数组,我们可以用数组解构的方式来给变量赋值,当然有时候后台没有数据,我们需要将返回值null数据返回的结果改为为空数组,此时可用如下代码:let[a,b] = 调用函数 || []
- 调用函数有数组结构时就按数组解构,当调用函数返回结果为null时,右边就使用空数组进行结构【不成功的解构】
- 有时候,后台的数据为数组,我们可以用数组解构的方式来给变量赋值,当然有时候后台没有数据,我们需要将返回值null数据返回的结果改为为空数组,此时可用如下代码:let[a,b] = 调用函数 || []
- 解构的默认值:
- 在变量列表中直接给变量赋值的值就是解构中的变量的默认值
- 什么时候启用默认值:只有当值严格等于undefined的时候开启默认值
- 解构中的默认值是惰性的:当解构中的的一项的默认值为函数调用时,只要该项对应的 解构的数值列表 的数值不为undefined,该函数就不会被调用。
- 默认值可以引用解构赋值的其他变量
- 前提时该变量必须是已经声明的【即变量列表中后面的目标变量的默认值可以是 变量列表中 该目标变量前面的变量。但是不能是变量列表中该目标变量后面的变量(会报错【用let声明解构数组时】【如果解构数组前面就已经声明了该变量不会报错】)】。【即前面变量可以作为后面变量的默认值,但后面变量不能作为前面变量的默认值。】
- 数组解构中,数值可以是前面已经声明的变量,但是前面用let声明的最后一个变量必须加英文分号结尾。
- 数组解构允许我们按照一一对应的关系从数组中提取值,然后将值赋值给变量
- 对象解构:
- 对象解构允许我们使用变量的名字匹配对象的属性 匹配成功将对象属性的值赋值给变量
- 对象解构中,属性匹配可以匹配多次
- 对象解构语法中,let 后面必须紧跟对象解构语法【否则无法进行对象解构】
- let/const/var后面跟上用一对{}包裹的变量列表,变量名与对象属性名相同,则就会取对象属性对应的值初始化给变量。
- 例子1[简写]:let { name, age } = { name: 'xiaoming', age: 18 }
- 打印输出时直接写入变量名即可打印输出变量名匹配的属性名对应的属性值
- 为什么可以简写:
- 例子1[简写]:let { name, age } = { name: 'xiaoming', age: 18 }
ES6中,当对象的属性名和属性值变量相同时,可以直接写属性名
- 例子2【完整写法】:
- let { name: myname, age: myage } = { name: 'xiaoming', age: 18 }
- 例子2【完整写法】:
等号左边是属性属性名: 该属性名对应的变量名
等号左边的对象内,冒号左边的用于属性匹配,冒号右边的才是属性名对应变量的变量值。
打印输出时,需要些等号左边该属性名对应的变量名即可打印输出该属性对应的属性值
- 启用默认值案例:
- let {x=6} = obj || {}
- 数组也可以按照对象的解构方式解构【索引对象=数组】
- 案例【{}在表达式的上下文是对象,在语句的上下文是语句】:
- 启用默认值案例:
var arr = [1, 2, 3]
let obj = {
'0': x,
"1": y,
"2": z
} = arr
console.log(y)
console.log(x)
- 字符串解构:
- 按照或则转为数组和对象解构:
- 案例:
- 按照数组结构:let[one,two,three]=’abc’
- 按照对象结构:let{ 0:one,1:two,2:three}=’abc’
- 案例:
- 按照或则转为数组和对象解构:
- 函数参数的解构:
- 形参可以给实参对象解构,也可以数组解构
- 函数参数解构的默认值:
- 在解构形参后赋值给空数组或空对象【模式一定要匹配】,这样当没有传递实参的时候,形参列表将解构空数组或空对象,就不会报模式不匹配的错误。当传递了参数的时候,就按实参解构,实参列表项没有值的按形参列表项的默认值解构,没有默认值就返回值为undefined
- 字符串解构:
- 解构中关于圆括号的影响:
- 如果模式中出现圆括号怎么处理。ES6的规则是,只要有,可能导致解构的歧义,也可能导致语句歧义,无法确定是声明语句还是赋值语句
- 不能使用圆括号的情况:
- 变量声明语句【用let/var声明的解构模式下的属性名和变量名以及他们整体都不能用圆括号包裹】
- 函数的参数【函数的参数也属于变量声明,所以不能加圆括号】
- 赋值语句的模式【解构模式的外侧不能加圆括号】
- 可以使用圆括号的情况:
- 没有用let/var等声明的解构 内的只有变量的解构项可以加圆括号
- 函数的默认值:
- ES6之前设置默认参数:
- 在函数体内写:形参=形参 || ‘默认值’
- 案例:
- ES6之前设置默认参数:
function fn(a, b) {
a = a || '1'
b = b || '2'
console.log(a, b)
}
fn(12)
- ES6中函数的形参新增了可以直接赋值的操作【即函数形参默认值】
- 函数调用的时候,英文逗号前面必须要有实参,否则会报错
- 函数的参数默认已经定义了,函数体内部不能再用let/ const定义该形参变量名了。
- 默认参数对函数length的影响:
- 返回函数形参的个数:函数名.length
- 指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数【从第一个参数开始到刚开始设置默认值参数结束】。也就是说,指定了默认值后,length属性将失真
- 返回函数形参的个数:函数名.length
- 默认值与作用域:
- 一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域,函数体内会形成一个参数作用域的子作用域。这种语法行为在不设置参数默认值时,是不会出现的
- ES6中函数的形参新增了可以直接赋值的操作【即函数形参默认值】
- rest参数【也叫剩余参数,三点运算符,扩展运算符,reset重置运算符】
- ES6引入rest参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。reset参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
- 扩展功能【扩展运算符【函数体或语句部分】】:
- 将数组或对象扩展为散列的值:扩展运算符后面紧跟数组或对象,可以将数组或对象的每一项变为散列的值,并用逗号分隔每一项【需在{}或[]内才有效】
- 案例:console.log(…[1,2,3,4])
- 新对象或新数组里面用扩展运算符添加扩展运算符后面对象或数组中的数据项
- 应用场景:
- 替代apply方法:
- 以前传递数组中的参数:fn.apply(null,args)
- 现在:fn(…args)
- 替代apply方法:
- 将数组或对象扩展为散列的值:扩展运算符后面紧跟数组或对象,可以将数组或对象的每一项变为散列的值,并用逗号分隔每一项【需在{}或[]内才有效】
- 重置运算符:
- 实参列表为数组散列的值,形参只有…数组形参名,此时数组形参名接收的就是散列的值重置为数组后的值
- 案例:
function fun(...arr) {
console.log(arr)
}
fun(10, 20, 30)
- 剩余运算符【函数形参中 数组解构的变量列表中 对象解构的变量列表中】:
- 形参列表中,没有展示出来的尾部的实参列表项用…变量名表示,此时该变量名接收的就是剩余参数的数组列表【…变量名只能在形参列表最后一项】。
- 重置运算符可以归类到剩余运算符中
- 剩余运算符【函数形参中 数组解构的变量列表中 对象解构的变量列表中】:
- Rest参数总结:
- …变量名
- 该语句之前 该变量名没有初始化时,当剩余参数处理
- 该语句之前 该变量名已经初始化了,当扩展运算符处理
- …变量名
- 三目和扩展运算符的运用:
- […(num ? arr1 : arr2)]
- 如果num表达式的结果为真,返回arr1这个数组,再拆解为散列的值,最后重新合并为数组返回arr1值结果
- 如果num表达式的结果为假,返回arr2这个数组,再拆解为散列的值,最后重新合并为数组返回arr2值结果
- […(num ? arr1 : arr2)]
- 箭头函数:
- ES6中新增的定义函数的方式
- 语法:let fun = () => { }
- fun是箭头函数的函数名【箭头函数只能是表达式定义,不能作为语句出现】
- 箭头函数是用来简化函数定义语法的。
- 小括号里面放参数,大括号里面放函数体
- 在箭头函数中 如果函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号【如果你的箭头函数内只有return一行语句,那么可以省略大括号和return关键字【箭头函数返回值的简写】】
- 例子: const sum = (num1, num2) => num1 + num2;
- 注意事项:当箭头函数函数体只有return一行语句,但return后面的返回值是对象时,箭头函数简写返回一个对象,该对象必须用小括号括起来。【{}表达式上下文是对象,语句上下文是语句。】
- 如果形参只有一个,可以省略小括号
- 箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this。
- 对象内,方法用箭头函数,则箭头函数内的this指向window【此时箭头函数this定义在对象里面,而对象不能产生作用域,所以此时箭头函数定义在全局作用域下,即this是在全局作用域下指向的是window】
- 因为箭头函数this指向的固定化,箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正因为它没有this,所以也就不能用作构造函数使用【new调用时控制台会报错】。
- 其他例子:
- const fn = () => {
console.log('jdb')
}
fn()
- 箭头函数中没有arguments实参列表【即函数体内没有arguments对象】
- 可以用剩余参数…arr来接收实参列表
- 箭头函数中没有arguments实参列表【即函数体内没有arguments对象】
- 尾逗号:
- 形参列表和实参列表最后一个参数后面加一个逗号
- 好处:方便复制和移动形参实参位置。
- 剩余参数:
- 剩余参数语法允许我们将一个不定数量的参数表示一个数组。
- 当函数实参个数大于形参个数时,我们可以将剩余的参数放在数组中
- 形参前面加三个点(…)表示剩余参数放在形参这个数组中。
- 剩余运算符…【永远放在最后一个形参位置上】
- 剩余运算符对函数的length属性也有影响
- 剩余参数和解构配合使用:
- 案例:
- let [s1, ...s2] = ['wangwu', 'zhangsan', 'lisi']
- 案例:
- 将类数组转换为正真的数组:
- 方法一:var 数组名=[…伪数组名]
- 方法二:var 数组名 =Array.prototype.slice.call(伪数组名)
- 方法三:var 数组名 =Array.prototype.slice.apply(arguments)
- 方法四:var 数组名 = Array.from(伪数组名)
- Array的扩展方法
- 扩展运算符(展开语法)
- 扩展运算符可以将数组或者对象转为用逗号分隔的参数序列。
- 在常量或变量名前面加三个点(…),可以将这个变量存储的数组或变量的值转为用逗号分隔的参数序列。
- 扩展运算符的应用:
- 扩展运算符可以应用于合并数组。
- 案例方法一:
- 扩展运算符可以应用于合并数组。
- 扩展运算符(展开语法)
let ary1 = [1, 2, 3]
let ary2 = [4, 5, 6]
let ary3 = [...ary1, ...ary2]
- 案例方法2:
- let ary1 = [1, 2, 3]
- 案例方法2:
let ary2 = [4, 5, 6]
let ary3 = ary1.push(...ary2)
ary1会改变为追加数组元素后的新数组
push()方法返回的是新数组的长度,所以ary3为6
- 将类数组【伪数组】或可遍历对象转换为真正的数组
- 语法:
- 将伪数组转换为真正的数组:
- 语法:
- 将类数组【伪数组】或可遍历对象转换为真正的数组
var 接收数组变量名 = […伪数组名]
不会修改原伪数组
- 将可遍历对象【属性为索引号,且有length属性】转换为正真的数组
var 接收数组变量名 = Array.from(对象名)
不会修改原对象
对象中必须有length属性名和值【值为数组长度】
对象中属性为字符串的索引号
没有的索引号默认值为undefined
接收数组变量名中保存的数据的个数为对象中length属性的属性值。
案例:
var arrayLike = {
"0": "张三",
"1": "李四",
"2": "王五",
"length": 3
}
var arr = Array.from(arrayLike)
console.log(arrayLike)
console.log(arr)
var 接收数组变量名 = Array.from(对象名)
- 案例:
var oDiv = document.getElementsByTagName('div')
var arr = [...oDiv]
- 转换为正真的数组就可以使用数组中的方法了
正在数组里面的数据元素,对象不会发生变化,可以进行相应操作
- 不会修改原来的伪数组
- 构造函数方法:Array.from()
- 作用:把类数组对象转成正真的数组
- 当参数为undefined,null会报错
- 当参数为空对象,空数组,返回值为空数组
- 方法还可以接受第二个函数参数,作用类似于数组的map方法,用来对每个元素进行处理【函数体内的return 返回值】,将处理后的值放入返回的数组。
- 该方法第一个参数是需要转换的伪数组或对象
- 该方法第二个参数是函数【可有可无】:
- 该方法第一个参数为数组时:
- 函数第一个参数在函数体内为数组的数组元素项
- 函数第二个参数在函数体内为数组的数组元素项对应的索引号
- 该方法第一个参数为对象【该对象属性为索引,且有length属性】时:
- 函数第一个参数在函数体内为对象中的属性值
- 函数第二个参数在函数体内为对象中的属性名【即索引号】
- 该方法第一个参数为数组时:
- 案例:
let arrayLike = {
"0": "5",
"1": "2",
"2": "3",
"length": 3
}
let arr = Array.from(arrayLike, item => item * 2)
console.log(arr)
- 该方法还可以接收第三个参数:
- 改变第二个参数函数体内this指向。
- 应用场景:
- 将伪数组转为正真的数组:
- 语法:Array.from(伪数组名)
- 将对象【属性为索引号,且有length属性】转为数组:
- 语法:Array.from(对象名)
- 将字符串转为数组:
- 语法:Array.from(字符串名)
- 返回值为数组,数组每一个元素项为每个字符
- 语法:Array.from(字符串名)
- 将伪数组转为正真的数组:
- 该方法还可以接收第三个参数:
- Array.of()
- 把一组散列的值转为数组【把参数转为数组】
- 整体用法和new Array()很相似,它的创建是为了弥补,new Array在创建数组时,参数只有一个且为数字的情况。
- 实例方法:find()
- 找出第一个符合条件的数组成员,如果没有找到返回undefined
- 参数为函数,函数参数接收三个参数:数组当前元素值,当前元素值对应的索引号,原数组
- 语法:
- var 满足条件的元素变量名 = 查找数组名.find((item,index)=>{ return 条件})
- item:在函数体内表示当前数组元素
- index:在函数体内表示当前数组元素索引号
- 整体返回值【满足条件的元素变量名】为return 后面的值为true时,终止迭代,将该次迭代的数组元素item值给整体。
- var 满足条件的元素变量名 = 查找数组名.find((item,index)=>{ return 条件})
- 返回值:
- 为原数组中数组元素项条件成立的数组元素值
- 实例方法:findIndex()
- 用于找出第一个符合条件的数组成员的位置【索引】,如果没有找到返回-1.
- 语法:
- var 满足条件的元素变量名 = 查找数组名.findIndex((item,index)=>{ return 条件})
- item:当前数组元素
- index:当前数组元素索引号
- var 满足条件的元素变量名 = 查找数组名.findIndex((item,index)=>{ return 条件})
- 返回值:
- 为原数组中数组元素项条件成立的数组元素索引
- arr.fill():
- 语法:arr.fill(需要填充的值,填充开始位置,填充结束位置)
- 会修改原数组
- 只有两个参数时:
- 第二个参数为填充的开始位置
- 填充区域:[开始位置索引,数组最后一项]
- 有三个参数时:
- 第二个参数为填充的开始位置索引
- 第三个参数为填充的结束位置索引
- 填充区域:[开始位置索引,结束位置索引)
- 当没有开始位置索引和结束位置索引时:
- 默认数组所有项修改为该默认值【需要填充的值】
- 数组的空位:
- 数组的空位指,数组的某一个位置没有任何值。
- 注意,空位不是undefined,一个位置的值等于undefined,依然是有值的,空位是没有任何值,in运算符可以说明这一点。
- ES6对空位的处理,已经很不一致了,大多数情况下会忽略空位。
- forEach(),filter(),reduce(),every()和some()都会跳过空位。
- 迭代函数不会执行
- map()会跳过空位,但会保留这个值
- 应用场景:创建有多个空对象元素的数组
- 案例:
- forEach(),filter(),reduce(),every()和some()都会跳过空位。
var arr = new Array(3).fill('').map(function () {
return {}
})
console.log(arr)
- join和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
- ES6明确将空位转为undefined
- Array.from()方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。
- 扩展运算符也将空位转为undefined
- entries(),keys(),values(),find()和findIndex()会将空位处理成undefined
- 由于空位的处理规则非常不统一,所以建议避免出现空位
- 实例方法:includes()
- 表示某个数组是否包含给定的值,返回布尔值。有就返回true,没有就返回false
- 没有该方法之前,我们通常使用indexOf判断,结果为-1就是不包含
- indexOf方法有两个缺点:一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格运算符(===)进行判断,这会导致对NaN的误判【NaN这个元素无法用indexOf查到】。
- 该方法内部的使用非常相似于Object.is(目标值,对应值)
- 如果目标值和对应值相等返回true否则返回false(NaN也可以用)
- 案例:
- var arr = [1, 2, 3, 4, 5]
- 表示某个数组是否包含给定的值,返回布尔值。有就返回true,没有就返回false
console.log(arr.includes(2))
- 应用场景:
- 数组去重
- 应用场景:
- 模板字符串
- ES6新增的创建字符串的方式,使用反引号【键盘上数字1左边那个键】定义。
- 模板字符串中可以解析变量。
- 模板字符串里面的变量用${变量名},即可解析变量
- 变量可以进行计算【加减乘除,都会自动计算】
- 案例:
- let name = 'zhangsan'
- 模板字符串里面的变量用${变量名},即可解析变量
let sayHello = `hello,my name is ${name}`
console.log(sayHello)
- 模板字符串中可以换行。
- 案例:
- let result = {
- name: '张三',
- age: 18
- }
- let html = `
- <div>
- <span>${result.name}</span>
- <span>${result.age}</span>
- </div>
- `
- console.log(html)
- 案例:
- 模板字符串可以调用函数。
- 模板字符串里面的函数调用写在${函数名()}
- 字符串模板注意:
- 如果在模板字符串中需要使用反引号,则单个反引号前面要用反斜杠\转义
- 如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中
- 模板字符串中嵌入变量,需要将变量名写在${}之中
- 大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性
- 模板字符串中还可以调用函数。
- 模板字符串中可以换行。
- 实例方法:startWith()和endsWith()
- startsWith():表示参数字符串是否在原字符的头部,返回布尔值
- 语法:原字符串.startWith(‘头部字符串’,[开始查询的索引号])
- 头部字符串在原字符串的开始查询索引号位置,在该位置返回true,反之则返回false
- 开始查询索引号没写,默认重索引号为0开始查【从头到尾】
- 语法:原字符串.startWith(‘头部字符串’,[开始查询的索引号])
- endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
- 语法:原字符串.endsWith(‘尾部字符串’,[结束位置索引])
- 尾部字符串在原字符串的 参数结束位置索引号-1 索引号的位置 ,则返回true,反之则返回false
- 开始索引号没写,默认为原字符串.length.[结束索引号字符取不到]
- 语法:原字符串.endsWith(‘尾部字符串’,[结束位置索引])
- 应用场景:
- 检测文件协议是不是以http开头,或文件是不是以com结尾等
- startsWith():表示参数字符串是否在原字符的头部,返回布尔值
- 实例方法:includes()
- includes():表示参数字符串是否在原字符串中纯在,返回布尔值
- 语法:原字符串.includes(‘目标字符串’,[开始查询的索引])
- 目标字符串在原字符串中存在返回true,反之则返回false。
- 开始查询索引号没写,默认重索引号为0开始查【从头到尾】
- 语法:原字符串.includes(‘目标字符串’,[开始查询的索引])
- includes():表示参数字符串是否在原字符串中纯在,返回布尔值
- 实例方法:repeat()
- repeat方法表示将原字符串重复n次,返回一个新字符串。【原字符串依然纯在】
- 语法:原字符串.repeat(n)
- n:重复次数
- -1和1之间的数【取值范围:(-1,1]】:返回空字符串
- 参数NaN等同于0
- 1:原返回字符串本身
- 小数:会先向下取整该小数,然后重复取整后的次数重复字符串
- 负数【小于-1的负数】或则Infinity:报错
- 参数如果是字符串,会先转换为数字
- 转不了就是NaN等同于0,即返回空字符串
- 转得了,就重复转好的次数
- n:重复次数
- 填充字符串padStart,padEnd:
- ES2017引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全
- 开始位置填充padStart:
- 语法:原字符串.padStart(num,[pstr])
- num:填充后整个字符串的长度
- 如果该长度比原字符串的长度还小,则不会填充也不会截取原字符串。
- pstr:填充字符或字符串【当填充的字符串长度过长时,最后一次填充只填充该字符串前面的字符】
- 当第二个参数不传的时候,默认用空格字符填充
- num:填充后整个字符串的长度
- 语法:原字符串.padStart(num,[pstr])
- 结束位置填充padEnd
- 语法:原字符串.padEnd(num,[pstr])
- num:填充后整个字符串的长度
- 如果该长度比原字符串的长度还小,则不会填充也不会截取原字符串。
- pstr:填充字符或字符串【当填充的字符串长度过长时,最后一次填充只填充该字符串前面的字符】
- 当第二个参数不传的时候,默认用空格字符填充
- num:填充后整个字符串的长度
- 语法:原字符串.padEnd(num,[pstr])
- ES6对象扩展;
- 对象新增:
- Object.is()用来比较两个值是否相等【0和-0是两个不一样的值】
- ES5比较两个值是否相等,只有两个运算符:相等运算符(==)和严格运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0.
- JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
- ES6提出同值相等算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。只有在特定对象比较时,两种比较方法才会不一样
- +0和-0【三等比较true,Object.is比较false】
- NaN和NaN【三等比较false,Object.is比较true】
- 注意:引用数据类型比较返回结果都为false【全等,比较运算符,Object.is()方法比较引用数据类型结果都为false】
- Object.assign()用来合并对象的
- 语法:let 新对象= Object.assign(目标对象,需要合并的对象)
- 会修改原对象,新增需要合并的对象里面的属性和方法
- 语法:let 新对象= Object.assign(目标对象,需要合并的对象)
- Object.is()用来比较两个值是否相等【0和-0是两个不一样的值】
- 对象新增:
当合并对象里面有数组时,属性会是索引号,属性值会是数组项。
当需要合并的对象中,后面的对象里面的属性在前面对象中已经存在,则在合并时,后面对象的属性会覆盖前面对象的属性。
- 参数:
第一个参数为原对象
原对象不能是undefined,null【否则会报错】
第二个参数及后面的参数都为需要合并的对象
- 返回值:修改后的原对象,也就是目标对象【新对象和目标对象是同一个对象,目标对象也是原对象,只不过是修改后的原对象】
- 应用场景:
- 浅拷贝对象【拷贝的是值,内存地址没有拷贝,也叫对象合并重整数据,原对象是没有被修改的】:
let 浅拷贝对象名 = Object.assign({},原对象)
- 深拷贝对象【拷贝的是内存地址和值】:
let 深拷贝对象名 = Object.assign(原对象,{})
- 原型方法优化:
Object.assign(构造函数名.protype,{
方法1名(){
//方法1体
}
方法2名(){
//方法2体
}
})
- 对象合并重整数据【浅拷贝原对象】:
- 法一:var 浅拷贝后的对象名 = Object.assign({新增对象属性及方法},原对象)
- 法二【利用扩展运算符重整数据】:
- let 浅拷贝后的对象名 = {
…原对象,
新增属性和方法
}
- 法二更常用,可以代替法一
- 对象的所有属性名作为数组的每一项【该对象所有属性名构成的数组】:
- Object.keys(对象名)
- 对象的所有属性值作为数组的每一项【该对象所有属性值构成的数组】:
- Object.values(对象名)
- 对象所有属性和属性值作为数组的每一项【该对象所有属性和对应属性值构成的二维数组】:
- Object.entries(对象名)
- 对象是没有迭代器的,因此对象不能使用for of循环
- 对象使用for of循环步骤:
- 第一步[给对象设置迭代器]:
- 对象名[Symbol.iterator] = Array.prototype[Symbol.iterator]
- 第二步【使用for of循环】:
- for(let val of Object.values(对象名)){}
- for(let key of Object.keys(对象名)){}
- for(let item of Object.entries(对象名)){}
- 第一步[给对象设置迭代器]:
- 上面方法写在觉得累,新方法如下:
- 第一步【对象解构】:
- let {values,keys,entries} = Object;
- values在Object对象中就有这个属性,而这个属性保存的是一个方法,所以以后使用该方法时就可以直接使用values(对象名)调用,不用写Object.values(对象名)调用,简化了代码。
- keys在Object对象中就有这个属性,而这个属性保存的是一个方法,所以以后使用该方法时就可以直接使用keys(对象名)调用,不用写Object.keys(对象名)调用,简化了代码。
- entries在Object对象中就有这个属性,而这个属性保存的是一个方法,所以以后使用该方法时就可以直接使用entries(对象名)调用,不用写Object.entries(对象名)调用,简化了代码。
- let {values,keys,entries} = Object;
- 第二步【使用for of循环】:
- for(let val of values(对象名)){}
- for(let key of keys(对象名)){}
- for(let item of entries(对象名)){}
- 第一步【对象解构】:
- 对象使用for of循环步骤:
- 数据类型Symbol
- 为什么用Symbol:
- ES5里面对象的属性名都是字符串,如果你需要使用一个别人提供的对象,你对这个对象有哪些属性也不是很清楚,但又想为这个对象新增一些属性,那么你新增的属性名就很可能和原来的属性名发生冲突,显然我们时不希望这种情况发生的,所以,我们需要确保每个属性名都是独一无二的,这样就可以防止属性名的冲突了。因此ES6里就引入了Symbol,用它来产生一个独一无二的值。
- Symbol是什么:
- Symbol是ES6引入的一种原始数据类型【基本数据类型】
- Symbol的使用:
- Js提供了一个函数Symbol用于创建symbol数据,但是请记住,它不是构造函数,不能使用new调用。
- Symbol基本使用语法:let 变量名=Symbol()
- Symbol的参数:
- Symbol参数是字符串,你可以理解是一个标记,用来标识当前的Symbol
- Symbol参数不是字符串时【会将参数转为字符串】:
- 参数为数字时:会转为字符标识
- 参数为对象时:会调用对象的toString方法
- 参数为数组时:会去掉数组字面量后转为字符串标识
- Symbol参数不是字符串时【会将参数转为字符串】:
- Symbol参数是字符串,你可以理解是一个标记,用来标识当前的Symbol
- Symbol值不可以进行运算
- Symbol值是不能进行运算的,不仅不能和Symbol值进行运算,也不能和其他类型的值进行运算,否则会报错。
- Symbol就是为了对象的属性名而生,Symbol作为属性名的方法:
- 方法一:
- 对象名[Symbol变量名] = 对象属性值
- 方法二:
- 对象名 = {[Symbol变量名]:对象属性值}
- 方法三:
- Object.defineProperty(obj,Symbol变量名,{value:对象属性值})
- 方法一:
- Symbol总结:
- Symbol函数前不能用new【Symbol函数不是一个构造函数,前面不能用new操作符,所以Symbol类型的值也不是一个对象,不能添加任何属性,它只是一个类似于字符型的数据类型。如果强行在Symbol函数前加上new操作符,会报错。】
- Symbol()返回的是一个唯一值,通常使用最为唯一的key使用
- Symbol是一种单独的数据类型,就叫symbol,是基本数据类型
- 如果symbol作为了key,那么用for…in.,,循环【symbol创建的变量在对象中用作对象属性】,是循环不出来symbol的key的,但可以遍历出来其他的属性和值
- 变量用作对象的属性必须用中括号[]将变量包裹起来,这样才会解析变量保存的值,否则会将变量名直接当对象的属性。
- 在对象内部,使用Symbol值定义属性时。Symbol变量【或则变量值】必须放在方括号之中,否则只是一个字符串。
- Symbol值作为属性名的遍历:
- 使用for in和for of都无法遍历到Symbol值的属性,Symbol值作为对象的属性名,也无法通过Object.keys(),Object.getOwnPropertyNames()来获取了
- Object.getOwnPropertyNames(对象名):获取对象中所有的属性构成一个数组。
- 但是不用担心,我们可以使用Object.getOwnPropertySymbols()方法获取一个对象上的Symbol属性名。【只会遍历Symbol创建的对象属性】
- 使用for in和for of都无法遍历到Symbol值的属性,Symbol值作为对象的属性名,也无法通过Object.keys(),Object.getOwnPropertyNames()来获取了
- 为什么用Symbol:
- ES6数值扩展:
- 数字进制新增:
- 定义二进制【Binary】字面量:0b开头【数字0和小写字母b开头】+进制数
- 进制数只能是0和1.
- 如果其后面不符合二进制规范就会报错
- 定义二进制【Binary】字面量:0b开头【数字0和小写字母b开头】+进制数
- 数值方法的变动:
- Number新增方法:
- parseInt()和parseFloat()
- 以前的这两个方法都是挂载在window身上的,我们会觉得很奇怪,明明是处理数字的方法,为什么不定义在Number身上,而要定义全局,ES6改变了,现在挂载在Number自己身上
- 语法:
- parseInt()和parseFloat()
- Number新增方法:
- 数字进制新增:
Number.parseInt(取整数数值)
Number.parseFloat(取浮点数数值)
- 这两个方法是玩玩全全移植过来的,没有任何变化。这样做的目的:逐步减少全局性方法,使得语言逐步模块化
- Number.isNaN()
- 语法:
Number.isNaN(判断是否为非数字的数值)
参数值为非数字,返回值为true
参数值为数字,返回值为false
- 和全局函数isNaN()方法相比,该方法不会强制将参数转换成数字,只有在参数是正真的数字类型,且值为NaN的时候,才会返回true
案例:
console.log(Number.isNaN('你好')) //false
console.log(window.isNaN('你好')) //true
- Number.isfinite()
- 语法:
- Number.isfinite()
Number.isFinite(num)
当num为有限的数字时,返回值为true
当num为Infinity,-Infinity,字符串,布尔值时,返回值为false【参数不存在自动转换为数字型】
- Numbet.isInteger()
- 语法:
- Numbet.isInteger()
Number.isInteger(目标数值)
判断目标数值是否为整数
是整数返回true【当目标数值小数点后面没有数或则后面的数全是0时,返回值为true[在JavaScript中整数和浮点数是同样的存储方法[js所有数字都是浮点数字],所以3.0和3被视为同一个值]】【同样目标数值不存在强制数据类型转换】
不是整数返回false
- ES6Math新增:
- ES6在Math对象上新增与数学相关的方法,所有这些方法都是静态方法,只能在Math对象上调用
- 截取,保留整数部分:
- 语法:Math.trunc(目标值)
- 当目标值为字符串类型时,会先进行强制数据类型转换【将字符串转换为数字类型,转不了就返回NaN】
- 目标值转换为数字类型后,只截取保留整数部分。目标值转不了的,返回值也为NaN
- 语法:Math.trunc(目标值)
- Math.sign()
- Math.sign()方法用来判断一个数到底是正数,负数,还是0.对于非数值,会先将其转换为数值
- 语法:
- Math.sign(目标值)
- 他的返回值有5中值:
- 参数为正数,返回值为1
- 参数为负数,返回值为-1
- 参数为0,返回值为0
- 参数为-0,返回值为-0
- 其他值,返回值为NaN
- Math.sign(目标值)
- Math.cbrt():
- Math.cbrt()方法用于计算一个数的立方根
- 对于非数值,Math.cbrt方法内部先使用Number方法将其转为数值
- 案例:
- Math.cbrt(-1)
- Math.hypot()
- Math.hypot()方法返回所有参数的平方和的平方根。
- 指数晕眩符【**】:
- 语法:底数 ** 指数
- 扩展运算符:**=:
- a = a ** b 等价于 a **=b【将a的b次方得到的值赋值给a】
- 改变背景颜色样式:
- 方法一【用16进制表示颜色,将数字类型的数字转换为16进制的字符串,然后重索引号为2的位置开始截取长度为6的字符串[开始索引号截取得到]】: list.style.backgroundColor = `#` + Math.random().toString(16).substr(2, 6)
- 方法二【用rgb表示颜色,用模板字符串解析变量】:
let a = Math.floor(Math.random() * 256)
let b = Math.floor(Math.random() * 256)
let c = Math.floor(Math.random() * 256)
console.log(a, b, c)
list.style.backgroundColor = `rgb(${a},${b},${c})`
- 创建数组的两种方式:
- 字面量
- 构造函数
- Set数据结构
- ES6提供了新的数据结构Set。它类似于数组【但不能像数组那样通过索引号来获取对应数据】,但是成员的值都是唯一的,没有重复的值。
- Set本身是一个构造函数,用来生成Set数据结构。
- new Set();
- Set参数:必须是有迭代器的数据,否则就报错
- 可以没有参数:
- 空set对象,所有的数据靠后天加入,很想let arr = new Array()
- 参数除了可以是数组,还可以是类数组
- 返回值为一个对象,属性为索引号,属性值为对应的数组项,size属性为类数组长度【相当于数组的length属性】
- 参数可以是字符串
- 返回值为一个对象,属性为索引号,属性值为对应的字符串字符,size属性为类数组长度【相当于数组的length属性】
- 可以没有参数:
- Set函数可以接收一个数组作为参数,用来初始化。
- size属性用来输出数组中不同值得数量
- 案例:
- size属性用来输出数组中不同值得数量
const s = new Set([1, 2, 3, 45, 6, 1, 2, 3]);
console.log(s.size)
- 可用于数组去重
- 案例:
- 可用于数组去重
const s = new Set([1, 2, 3, 45, 6, 1, 2, 3]);
console.log([...s])
- Set的属性和实例方法:
- add(value):添加某个值,返回Set结构本身【会修改原Set数据结构,会添加新数据到数据结构中,一次只能添加一个值,添加多个值时,使用链式添加:set变量名.add(值).add(值)…】
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功【会修改原Set数据结构,会删除指定值,一次只能删除一个值,删除多个值时,使用链式删除:set变量名.delete(值).delete(值)…】
- has(value):返回一个布尔值,表示该值value是否为Set的成员【有这个值返回true,反之返回false】【set变量名.has(值)】
- clear():清除所有成员,没有返回值。【会修改Set数据结构,里面为空】
- set的迭代:
- 通过打印set我们可以发现set原型上有迭代器,因此set可以使用for of 迭代
- 语法:for(let key of set变量名){}
- 每次迭代,key为每次迭代的当前数据
- 语法:for(let key of set变量名){}
- 通过打印set我们可以发现set原型上有迭代器,因此set可以使用for of 迭代
- Set的作用:set最大的作用就是去重
- 数组去重:
- 第一步:将原数组转为set类型【let set变量=new Set(原数组名)】
- 第二步:将set类型转为正真的数组【let 去重后的新数组名=[…set变量]】
- 字符串去重:
- let newStr=[…new Set(str)].join(‘’)
- newStr:去重后的字符串
- str:需要去重的字符串
- let newStr=[…new Set(str)].join(‘’)
- 数组去重:
- 将set数据结构转为数组的方法:
- 方法一:[…set变量名]
- 方法二:Array.from(set变量名)
- 注意:
- 向Set加入值得时候,不会发生类型转换,所以s和‘s’是两个不同的值
- Set内部判断两个值是否不同,使用的算法叫做‘Same-value equality’,它类似于精确相等运算符(===),主要区别是NaN等于自身,而精确相等运算符认为NaN不等于自身。
- 遍历:
- Set结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
- Set数据结构的中,索引和属性值是一样的【索引是属性值】
- 案例:
- const s = new Set([1, 2, 3, 45, 6, 1, 2, 3]);
- Set结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
- Set的属性和实例方法:
s.forEach((item) => { console.log(item) })
- weakSet的使用:
- weackSet为了处理对象弱引用问题
- let weakSet变量=new WeakSet()
- WeakSet和Set的不同之处:
- 在WeakSet中add方法传入非对象参数会导致报错,has,delete放啊传入非对象则会返回false
- 在WeakSet集合不可迭代,不能使用for of,forEach循环
- 不支持size属性
- WeakSet不暴露任何迭代器,不能使用keys(),values()方法
- Map数据结构:
- 类似于对象,但是对象的键key只能是字符串。Map的key可以是任意类型
- 为了解决这个对象属性只能是字符串的问题,ES6提供了Map数据结构。它类似于对象,也是键值对的集合,但是‘键’的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了‘字符串-值’的对应,Map结构提供了‘值-值’的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。
- 语法:
- 第一步创建map实例对象:
- let map实例对象变量名 = new Map()
- 第二步设置map实例对象属性和属性值:
- Map实例对象变量名.set(map对象属性名,map对象属性名对应的属性值)
- set方法的第一个参数是属性key【可以为任意数据类型】
- set方法的第二个参数是属性key对应的属性值
- Map实例对象变量名.set(map对象属性名,map对象属性名对应的属性值)
- 第一步创建map实例对象:
- 获取实例对象map对象的目标属性:
- let目标属性值变量=map变量.get(目标属性名)
- map变量存储的map对象中获取目标属性名对应的目标属性值
- let目标属性值变量=map变量.get(目标属性名)
- 删除map对象的map属性:
- map实例对象变量名.delete(map对象属性名)
- Map实例对象变量存储的值会删除参数对应的属性名
- 整体返回值为布尔值,删除成功返回true,否则返回false
- map实例对象变量名.delete(map对象属性名)
- has判断其中是否有某个属性值
- map实例对象变量.has(属性名)
- 实例对象中有该属性名,返回true,没有就返回false
- map实例对象变量.has(属性名)
- clear方法清空
- map实例对象变量.clear()
- 清空map实例对象变量里面的所有属性
- map实例对象变量.clear()
- Map实例对象的size属性:
- 类似于数组中的length属性,获取的是实例对象的属性个数
- 迭代:
- for(let key of map实例对象变量){}
- 等价于for(let key of map实例对象变量.entries()){},遍历出来的是属性和属性值构成的二维数组。
- key:循环体中 实例对象的属性和属性值构成的二维数组
- for(let key of map实例对象变量.key()){}
- key:循环体中 实例对象的每次循环的属性
- for(let key of map实例对象变量.values()){}
- key:循环体中 实例对象的每次循环的属性值
- for(let key of map实例对象变量){}
- weakMap:
- weakMap是弱引用Map集合,weakMap集合中键名必须是一个对象
- 总结Symbol与Set,Map:
- Set构成函数实参是数组,不重复,没有set和get方法(add方法)
- Map对json功能增强,key可以是任意类型的值(set,get)
- Iterator:
- 迭代器的理解:
- 迭代器是一种接口、是一种机制。
- 为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
- Iterator的作用有三个:
- 为各种数据结构,提供一个统一的、简便的访问接口
- 使得数据结构的成员额能够按某种次序排列
- 主要供for of消费。
- 以前的循环:
- for循环数组或字符串的本质:跟踪索引
- 迭代器本质:方法
- 迭代器执行以后,创建一个迭代对象,迭代对象上有next方法,返回一个结果,这个结果对象中包含value属性,为数据,和done属性判断迭代是否结束【迭代对象每调用一次next()方法,就返回当前迭代项 该数据value值和done属性值判断迭代是否结束的布尔值 的对象】
- 案例:
- 迭代器执行以后,创建一个迭代对象,迭代对象上有next方法,返回一个结果,这个结果对象中包含value属性,为数据,和done属性判断迭代是否结束【迭代对象每调用一次next()方法,就返回当前迭代项 该数据value值和done属性值判断迭代是否结束的布尔值 的对象】
- 迭代器的理解:
var arr = ['一', '二', '三']
//迭代器执行创建数组的迭代器对象obj
obj = arr[Symbol.iterator]()
console.log(obj.next())
console.log(obj.next())
console.log(obj.next())
console.log(obj.next())
- 自定义一个迭代器:
- 方法一【借用数组的迭代器】:
- 如果希望自定义对象也能使用for of迭代,首先属性要按照类数组定义【必须有length属性】
- 案例:
- 方法一【借用数组的迭代器】:
- 自定义一个迭代器:
var obj = {
0: 10,
1: 20,
2: 30,
[Symbol.iterator]: Array.prototype[Symbol.iterator],
length: 3,
}
for (let key of obj) {
console.log(key)
}
- 方法二【利用闭包,自定义迭代器方法】:
var obj = {
0: 10,
1: 20,
2: 30,
length: 3,
[Symbol.iterator]: function () {
let This = this
let i = 0
return {
next: function () {
let value = obj[i]
let done = i >= obj.length;
i++
return {
value,
done,
}
}
}
}
}
var aa = obj[Symbol.iterator]()
console.log(aa.next())
console.log(aa.next())
- 普通函数实现Iterator:
function myIter(obj) {
let i = 0
return {
next() {
let done = (i >= obj.length);
let value = !done ? obj[i++] : undefined
return {
value,
done,
}
}
}
}
- 原生具备Iterator接口的数据结构如下:
- Array(数组)
- Map(map实例对象)
- Set(set实例对象)
- String(字符串)
- 函数的arguments
- NodeList对象(原生节点列表)
- Generator生成器:
- 基本概念:
- Generator生成器是ES6一个重要的特性
- 执行Generator函数会返回一个迭代器对象,也就是说,Generator函数还是一个迭代器对象生成函数。返回的迭代器对象,可以依次遍历Generator函数内部的每一个状态【next()方法】。
- 生成器也是一个函数
- 跟普通函数的区别:
- function关键字与函数名之间有一个星号(*)
- 函数体内部使用yield表达式,定义不同的内部状态【yield只能写在生成器函数里面,不能写在其他函数里面,否则报错】
- Generator函数不能跟new一起使用
- 箭头函数不能创建生成器
- Generator的基本使用:
- 定义generator函数:
- 在function关键字和函数名中间加一个*号就表示这是一个generator函数【ES6没有规定,function关键字与函数名之间的星号,写在哪个位置。这导致星号两边有没有空格无所谓。】
- 添加单个状态值语法:
- 定义generator函数:
- 基本概念:
function * 函数名(){
yield 状态值;
yield 状态值
…
}
- 调用该函数会生成一个迭代器对象,函数体内有多少个状态值就可以调用多少次next方法。第一个状态值为迭代器对象的初始化的next方法的返回值【第一次调用next方法,next方法的参数无效】
yield就像一个断点,每调用一次next方法,函数就执行到下一个yield前面的代码。【第一次调用next方法,next方法的参数无效,第一个yield前面的代码都会执行。第二次调用next方法,next方法的参数值会赋值给第一个yield左边赋值号左边的变量[然后执行改变量赋值后的下面的代码,下面变量可以重新赋值修改],第二个yield前面的代码都会执行。…】
控制台上调用该迭代器对象的next方法会返回一个value值和一个done值,value对应yield后面的状态值,done值对应一个布尔值,迭代完了,done值对应的布尔值为true
- 状态值是常数,调用next方法返回值就是常数本身,状态值是变量,返回值就是变量保存的值,状态值是变量和表达式,返回值就是变量和表达式操作的结果
- 调用该函数生成的迭代器对象可以使用数组解构的方式解构状态值给对应的变量。
- 批量添加状态值语法:
function * 函数名(arr){
for(let i;i<arr.length;i++){
yield arr[i]
}
}
- arr为数组
- generator不同的调用:
- 循环:for(let val of generator迭代对象变量名){}
- 解构:let [a,b]=generator函数名()
- 扩展运算符:console.log(…show())
- 回调函数的本质:将我们的函数作为形参传递给另外一个函数。
- Promise承诺,许诺:
- 概念:
- Promise是异步编程的一种解决方案,比传统的解决方案—回调函数和事件—更合理和更强大。
- 所以Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
- 特点:
- 对象的状态不受外界影响。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
- 状态:
- Promise对象代表一个异步操作,有三种状态:
- pending(进行中) 此时操作尚未完成
- fulfilled(resolve)(已成功) 异步操作成功
- rejected(reject)(已失败) 异步操作失败
- 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- Promise对象代表一个异步操作,有三种状态:
- 缺点:
- 无法取消promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
- Promise基本使用:
- var promise实例对象变量= new Promise(function(resolve,reject){
- 概念:
If(条件表达式){
resolve(成功的状态值)
}else{
reject(失败的状态值)
}
})
- promise实例对象变量保存的对象状态和状态值以及报错信息
- 控制台对象状态:
- promise实例对象变量保存的对象状态和状态值以及报错信息
<pending> 表示进行中
<fulfilled> 表示已成功【resolve(成功的状态值)已执行】
返回值为resolve参数中 成功的状态值
<rejected> 表示已失败【reject(失败的状态值)已执行】
返回值为reject参数中 失败的状态值和报错信息
报错信息会在控制台的最后一行打印
- Promise对象变量保存的对象状态和状态值只有一种。
当调用构造函数的函数形参它的参数时,如果调用是在同步语句里面,则状态值为成功状态或则失败状态,如果调用是在异步,如定时器里面,则状态值为pending【看Promise构造函数里面的函数形参 它的形参resolve和reject的调用位置 如果是在同步语句中,则状态为成功或失败状态,如果在异步语句中,则为进行中状态】
- Promise构造函数:
- 该构造函数的参数为函数,这个函数有两个形参:
- 第一个形参为成功状态函数名,该函数名调用后 该构造函数创建的实例对象会得到该函数参数的状态值
- 第二个形参为失败状态函数名,该函数名调用后 该构造函数创建的实例对象会得到该函数参数的状态值
- 该构造函数的参数为函数,这个函数有两个形参:
- Promise实例对象的then方法【调用该方法,它的状态会是由Promise构造函数中的形参函数 它的形参第一个resolve或reject回调函数执行后的状态确定执行then方法的哪一个形参函数】【同步异步都处理后的状态,由这个状态确定调用then方法的哪个形参函数】:
- 传递两个参数:
- 第一个参数是函数,用于promise实例对象 接收成功执行的代码块
- 传递两个参数:
- Promise构造函数:
函数第一个形参在函数体内表示成功的状态值
- 第二个参数是函数,用于接收失败执行的代码块【报错信息将不会在控制台出现】
- 控制台打印该实例对象出现的结果顺序:
- 第一个:<状态>:状态值
- 第二个:主程序
- 第三个:成功或失败的信息
- Then方法
- 如果一个对象实现了then方法,这个对象就被称为thenable对象,所有的promise对象都是thenable对象,但是并非所有的thenable对象都是promise
- Promise的then()方法返回值还是一个promise对象,并且默认是成功状态,值为return后面的值。没有return返回值时默认成功状态值为undefined。
- Promise实例对象的then方法的参数:
- 第一个参数为成功状态的函数,这个函数的第一个形参在函数体内为调用then方法的这个promise实例对象的成功状态值【成功执行形参第一个函数体内代码】
- 第二个参数为失败状态的函数,该函数的第一个形参在函数体内为调用then方法的这个promise实例对象的失败状态值【失败执行形参第二个函数体内代码】
- 上面两个函数参数只执行一个或都不执行【promise实例对象的状态为成功,即Promise构造函数创建的实例对象执行了该构造函数的第一个参数函数,则实例对象执行then方法的第一个参数函数,否则执行then方法的第二个参数函数。】
- 只有有状态值的promise实例对象才可以调用then方法【构造函数Promise创建的实例对象需要执行构造函数的函数参数[这样才有状态值]才有then方法,实例对象promise调用then方法返回的promise对象,可以连续调用then方法,因为默认状态值为成功状态,值为undefined】
- Ajax请求的回调地狱:
- Ajax请求里面嵌套Ajax请求:
$.ajax({
url: `https://jsonplaceholder.typicode.com/todos/10`,
type: 'GET',
success:function(data){
console.log(data)
$.ajax({
url: `https://jsonplaceholder.typicode.com/todos/1`,
type: 'GET',
success:function(data){
console.log(data)
resolve(data)
},
error:function(err){
console.log(err)
}
})
},
error:function(err){
console.log(err)
reject(err)
}
})
- 使用promise处理后台请求:
- 创建promise实例对象,获取数据成功的状态值为请求数据
let promise=new Promise((resolve,reject) =>{
$.ajax({
//请求方式
type:’GET’,
/* 请求成功,函数的第一个形参为请求的数据,第二个形参为请求成功的方法名success */
success:function(data){
resolve(data)
},
/* 请求失败,函数的第一个形参为,第二个形参为请求失败的方法名error */
error:function(err){
reject(err)
}
})
})
- Promise实例对象调用then方法:
- 获取成功状态 的状态值【参数函数的第一个形参】,即请求数据,
- 获取失败状态 的状态值【参数函数的第二个形参】,即错误状态值
- Promise实例对象的成功与失败状态 决定then方法执行then方法的哪一个参数函数。如果promise实例对象没有状态值,则不会执行then方法里面的函数。
- ajax对象的success方法中,第一个参数是请求地址的所有数据,第二个参数是方法名,请求数据成功就会执行该方法中的代码
- ajax对象的error方法中,当数据请求失败就会执行该方法中的代码
- 代码优化:
- Promise实例对象调用then方法:
let promise = new Promise(function (resolve, reject) {
$.ajax({
url: `https://jsonplaceholder.typicode.com/todos/1`,
type: 'GET',
success: resolve,
error: reject
})
})
- 使用promise拉平Ajax请求【避免Ajax里面嵌套Ajax[ajax请求的回调地狱]】
- 创建promise实例对象:
let promise = new Promise(function (resolve, reject) {
$.ajax({
url: `https://jsonplaceholder.typicode.com/todos/1`,
type: 'GET',
success: resolve,
error: reject
})
})
- Promise实例对象调用then方法返回请求地址数据和设置新ajax请求地址:
promise.then(function (data) {
console.log(data)
return new Promise(function (resolve, reject) {
$.ajax({
url: `https://jsonplaceholder.typicode.com/todos/2`,
type: 'GET',
success: function (data) {
reject(data)
},
error: function (err) {
console.log(err)
}
})
})
}, function (err) {
console.log(err)
})
.then(function (data) {
console.log(data)
return new Promise(function (resolve, reject) {
$.ajax({
url: `https://jsonplaceholder.typicode.com/todos/3`,
type: 'GET',
success: function (data) {
reject(data)
},
error: function (err) {
console.log(err)
}
})
})
}, function (err) {
console.log(err)
})
…
- catch方法:
- 捕获错误,将以前then方法的第二个函数参数写在.catch方法的参数里面
- 以前promise对象调用then方法,then方法的第一个函数参数为成功状态执行,then方法的第二个参数为失败状态执行。有了catch方法之后,then方法的第二个函数参数可以作为catch方法的参数:表示成功状态执行then方法中的函数,失败状态执行catch方法中的函数。
- catch方法中的第一个参数为调用该方法的promise对象是失败状态时的状态值
- 捕获错误,将以前then方法的第二个函数参数写在.catch方法的参数里面
- Promise.resolve()方法的参数【作用:将现有对象转为Promise对象】:
- 参数是promise对象:
- 返回值和参数promise对象是同一个对象
- 参数是一个thenable对象【可以改变Promise对象的状态】:
- Thenable对象指的是具有then方法的对象【手动设置】【then方法中有两个函数参数,第一个参数调用返回成功状态的状态值,第二个函数参数调用返回失败状态的状态值】
- Thenable对象中,then方法调用的第一个参数函数,则静态创建了一个成功状态的promise对象,状态值为第一个参数函数调用的参数。Then方法调用的第二个参数函数,则静态创建了一个失败状态的promise对象,状态值为第二个参数函数调用的参数。
- Promise.resolve()方法会将这个参数对象转为Promise对象,然后立即执行thenable对象的then方法。【then方法中的函数参数调用,决定创建的promise对象的状态是成功状态还是失败状态】
- Thenable对象指的是具有then方法的对象【手动设置】【then方法中有两个函数参数,第一个参数调用返回成功状态的状态值,第二个函数参数调用返回失败状态的状态值】
- 参数是不具有then的数据【字符串,不具有then方法的对象】
- 状态为成功
- 状态值为该数据
- 不带任何参数:
- Promise.resolve()方法允许调用时不带参数,直接返回一个fulfilled【已成功】状态的Promise对象。状态值为undefined
- 所以,如果你希望得到一个Promise对象,比较方便的方法就是直接调用Promise.resolve()方法
- 参数是promise对象:
- Promise.reject()方法的参数【失败状态】:
- Promise.reject()方法也返回一个新的Promise实例,该实例的状态为rejected
- Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数,这一点与Promise.resolve方法不一致
- Promise.all()方法:
- all方法的参数是数组
- 返回值都是promise对象:
- 当数组元素里面的所有元素没有失败状态的promise对象时,返回值为成功状态,且成功状态值就为这个数组。
- 当数组元素里面有失败状态的Promise对象时,返回值为失败状态,且状态值为数组元素失败状态的promise对象对应的状态值
- Promise.all()方法中的数组参数元素都为成功状态的promise对象时,调用then方法:
- 返回值状态:<fulfilled>成功
- then方法函数的第一个参数为所有成功状态promise的状态值数组
- Promsie.all()方法中的数组参数元素中有失败状态的promise对象时,调用then方法:
- 返回值状态:<rejected>失败
- then方法函数的第一个参数为数组元素中第一个失败状态的promise对象 对应的状态值
- Promise.race()方法:
- 这个参数和all用法一样,唯一不同之处就是race方法返回值为 数组promise对象中第一个返回状态值的promise对象【promise构造对象函数中第一个函数参数resolve或reject调用的promise对象】
- then方法函数的第一个参数在方法体内为 数组元素中的promise实例对象中状态参数函数最先调用 的promise对象 对应的状态值
- 应用场景:
- 超时不作处理
- 对象的迭代器方法:
- 案例:
var obj = {
*[Symbol.iterator]() {
yield 10;
yield 20;
yield 30;
}
}
for (let val of obj) {
console.log(val)
}
- 处理本地异步程序:
- function asyncFun(num) {
return function (cb) {
setTimeout(function () {
cb(num)
}, 3000)
}
}
let fn = asyncFun(10)
fn(function (val) {
console.log(val)
})
- 封装一个generator处理本地异步的函数:
- Generator的状态值为数值
- function run(generator) {
- Generator的状态值为数值
let obj = generator()
let result = obj.next()
console.log(result)
//采用递归循环
function step() {
if (!result.done) {
console.log(result.value)
result = obj.next()
step()
}
}
step()
}
run(
function* () {
yield 10;
yield 20;
yield 30
}
)
- Generator的状态值为函数,函数回调返回数据
- function asyncFun(num) {
- Generator的状态值为函数,函数回调返回数据
return function (cb) {
setTimeout(function () {
cb(num)
}, 3000)
}
}
function run(generator) {
let obj = generator()
let result = obj.next()
console.log(result)
//采用递归循环
function step() {
if (typeof result.value == 'function') {
result.value(function (val) {
console.log(val)
result = obj.next(val)
step()
})
}
}
step()
}
run(
function* () {
yield asyncFun(10);
yield asyncFun(20);
yield asyncFun(30)
}
)
- generator的状态值是函数和数值的混合:
- function asyncFun(num) {
- generator的状态值是函数和数值的混合:
return function (cb) {
setTimeout(function () {
cb(num)
}, 1000)
}
}
function run(generator) {
let obj = generator()
console.log(obj)
let result = obj.next()
console.log(result)
function step() {
/* console.log(result.done) */
if (!result.done) {
if (typeof result.value === 'function') {
result.value(function (val) {
console.log(result)
console.log(val)
result = obj.next(val)
step()
})
} else {
console.log(result)
console.log(result.value)
result = obj.next(result)
step()
}
}
}
step()
}
run(function* () {
yield asyncFun(10)
yield asyncFun(20)
yield 30
yield 40
yield asyncFun(50)
})
- 使用Ajax获取数据:
- function asyncFun(url) {
return function (cb) {
$.ajax({
url,
success(data) {
cb({
iserr: 0,
data,
})
//在ajax成功函数内部直接打印获取得到的数据
console.log(data)
}, error(err) {
cb({
iserr: 1,
data: err,
})
}
})
}
}
let fn = asyncFun('https://jsonplaceholder.typicode.com/todos/1')
fn(
function (result) {
//通过回调函数 打印获取回调函数在原函数体内调用时的实参数据
if(!result.iserr){
console.log(‘请求成功’)
console.log(result.data)
}else{
console.log(‘请求失败’)
}
console.log(result)
})
- generator处理Ajax异步程序:
- 语法糖:
- 意指那些没有给计算机语言添加新功能,而只是对人类来说更“甜蜜”的语法。语法糖往往给程序员提供了更实用的编码方式,有益于更好的编码风格,更易读。不过并没有给语言添加什么新东西
- 语法盐:
- 主要目的是通过反人类的语法,让你更痛苦的写代码,虽然同样能达到避免代码书写错误的效果,但是编程效率很低,毕竟提高了语法学习门槛.
- async函数&await
- async含义:
- es2017标准引入了async函数,使得异步操作变得更加方便
- async函数是generator函数的语法糖
- async的使用:
- async函数使用时就是将generator函数的星号(*)替换成async,将yield替换成await,仅此而以
- 语法:
- async function 异步函数名() {
- async含义:
let 变量=await '12'
await '13'
return 成功状态值
}
let p1 = show()
- async的特点:
- await只能放到async函数中使用
- 相比generator语义化更强
- await后面可以是promise对象,也可以是数字,字符串,布尔值
- await后面一般是promise对象,会自动调用该promise对象的then方法,将成功状态的状态值赋值给await左边的变量
- await后面不是promise对象,也会将后面的值先变为promise对象,成功状态值就为await后面的这个值。再将这个成功状态的状态值给我们await左边的这个变量,因为没什么意义,这种情况一般直接用赋值语句来替换。
- await后面的语句出错,函数后面将中断执行。
- 错误在成功之后,错误和成功都会执行
- async函数返回的是一个promise对象
- 只要await语句后面的promise的状态变成reject,那么整个async函数会中断执行
- async的特点:
- async函数对Generator函数的区别:
- 内置执行器:
- Generator函数的执行必须靠执行器(next方法),而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
- 更好的语义:
- async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
- 正常情况下,await命令后面是一个Promise对象。如果不是,会被转成一个立即resolve的promise对象。
- 返回值是promise:
- Async函数的返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便多了,可以用then方法指定下一步的操作
- 进一步说,async函数完全可以看作多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。
- 内置执行器:
- 在控制台上抛出错误:
- 语法:
- throw new Error(‘错误信息’)
- 可以使用模板字符串,能够解析变量。
- throw new Error(‘错误信息’)
- 语法:
- Proxy代理:
- Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种‘元编程’,即对编程语言进行编程。
- 讲通俗一点就是扩展(增强)了对象,方法(函数)的一些功能
- ES6原生提供Proxy构造函数,用来生成Proxy实例
- Proxy其实是设计模式的一种,代理模式
- 基本使用:
- let 代理对象名 = new Proxy(原对象名, {
//拦截函数
})
- 如果拦截函数是空的(没有拦截函数),访问代理对象名等同于访问原对象
- 当执行代理对象名.属性名时【获取属性,就会执行get拦截函数】:
- 会先看是否有拦截函数,没有拦截函数就相当于访问原对象
- 有get获取拦截,在get函数内部,第一个参数为原对象,第二个参数为 代理对象操作的属性,第三个参数为代理对象【这三个参数不能和字符串拼接,只能单独使用】。之后会将get函数return的返回值 赋值给给我们的 代理对象名.属性名【代理对象名.属性名 就是我们代理对象操作的属性,这个属性就是get函数的第二个参数在get函数内部的表达含义】
如果想让代理对象和我们原对象一样【即代理对象可以调用原对象里面的属性】:
没有get函数
get获取拦截函数体中,return返回值后面的值为get函数第一个形参参数[get函数第二个形参参数]
get获取拦截函数体中,return返回值后面的值为Reflect.get(…arguments)【通过Reflect反射到js默认的对象获取属性的行为上】
- 当执行代理对象名.属性名=属性值时【赋值操作,设置值,就会执行代理对象中的set拦截函数】
- 当执行‘属性名‘ in 代理对象名 时【判断代理对象中是否有指定属性名这个字符串,就会执行has拦截函数】
- 当has拦截函数没有return返回值时,执行‘属性名‘ in 代理对象名默认返回值为false,有了return返回值后,会将return后面的返回值给‘属性名‘ in 代理对象名
- 当执行delete 代理对象名[代理对象属性]时【删除代理对象的某个属性,会执行deleteProperty拦截函数】
- 拦截函数:
- 获取拦截:
- get(target,prop,receiver){}
- 获取拦截:
target:代理的目标对象【原对象】
prop:代理对象操作的属性
receiver:代理对象
- 设置拦截:
- set(target,prop,value,receiver){}
- 设置拦截:
target:代理的目标对象【原对象】
prop:操作的属性
value:被写入的属性值
receiver:代理对象
- 如果代理对象没有任何拦截(陷阱)行为,那么代理对象的操作 都是用js原生的行为
- 拦截行为:
- 获取:原对象名.属性名【获取该属性名对应的属性值】
- 修改:原对象名.属性名=‘修改后的属性值’【将原来该属性对应的属性值修改为新值】
- 删除:delete 原对象名.属性名
- 判断对象是否有xx属性:xx in 原对象名
- 拦截行为:
- 如果代理对象没有任何拦截(陷阱)行为,那么代理对象的操作 都是用js原生的行为
- 代理对象的拦截函数之间可以相互调用,所以:
- 获取拦截函数里面,代表代理对象的形参不能进行获取操作【获取操作还包括判断】
- 设置拦截函数里面,代表代理对象的形参不能进行赋值操作
- 拦截函数代理对象不能进行某个操作,是为了避免反复调用自己,递归导致程序报错
- 两个拦截函数之间不能相互调用,否则也会递归导致程序报错
- 当一个拦截函数里面有对代理对象的操作时,会先看该操作是否会触发另一个拦截函数的执行,如果该操作会触发另一个拦截函数的执行,则不会改变目标对象【即原对象不会改变,代理对象的值当然也不会改变】。如果该操作不会触发另一个拦截函数,则该操作就会生效【原对象就会跟着代理对象的改变而改变】
- Proxy支持的拦截操作【set拦截函数内部不能修改[这里修改就是赋值的意思]代理对象[代理对象会随着原对象的变化而跟着变化],修改代理对象就会报错】【因为拦截函数可以相互调用,在拦截函数的某个拦截函数写上代理对象.属性名,就会跳到获取拦截这个函数,获取拦截调用完了再回来继续执行下面的代码,同理可知,某一个拦截函数内部不能写触发该函数的语句,否则就会出现递归调用,导致程序报错】
【拦截函数参数设置:
- target:代理的目标对象
- propkey:代理对象操作的属性 保存的变量名【操作包括获取,设置,判断,删除】
- 所以需要对目标对象操作时写为target[propkey]
- receiver:代理对象
- value:给代理对象属性设置的属性值
- thisArg:代理对象的目标对象为函数时,代理对象调用绑定的this指向,没有绑定this指向,默认为undefined
- arg:代理对象为函数时,调用传递的参数会转为数组保存在该变量中】:
- get(target,propkey,receiver):拦截对象属性的读取【执行代理对象名.属性名的时候执行该拦截函数,执行代理对象名.属性名的返回值为get函数return后面的返回值,get函数没有return语句默认返回undefined,如果没有get拦截函数,就相当于操作原对象】
- set(target,propkey,value,receiver):拦截对象属性的设置【执行代理对象名.属性名=属性值时执行该拦截函数。执行代理对象名.属性名=属性值的返回值为设置的这个属性值,在条件判断语句里面会转变为布尔值】
- has(target,propkey):拦截属性名 in 代理对象名【执行该语句时执行has拦截】
- deleteProperty(target,propkey):拦截delete 代理对象[‘属性名’] 或者 delete 代理对象名.‘属性名‘【执行delete 代理对象[‘属性名’] 时执行该拦截函数,返回值为布尔值】
- ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
- getOwnPropertyDescriptor(target,propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
- defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
- preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
- getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
- isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
- setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
- apply(target, thisArg, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
- construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
- 访问代理对象上不具有的属性就报错:
- proxy = new Proxy(obj, {
get(target, prop, receiver) {
if (!(prop in receiver)) {
throw new ReferenceError(`属性${prop}不存在`)
}
return target[prop]
}
})
- 函数代理:
- 语法:
- function 函数1名(){
- 语法:
//函数体
}
- let 代理对象函数1名= new Proxy(函数1名,{
apply(target,thisArg,arg)
- })
- Apply的形参:
- Target:目标函数(原函数)
- thisArg:代理对象的this指向
- arg:实参列表
- Apply的形参:
- })
- 取消代理:
- Proxy.revocable方法返回一个撤销代理实例,参数与Proxy构造函数一致
- 语法一:
- let { proxy,revoke}=Proxy.revocable(原对象,{拦截函数})
- 解构出来,变量proxy为代理对象
- revoke为取消代理对象的函数方法,当执行revoke()时,代理对象就会清空属性和方法,再次使用代理对象名.属性就会报错。
- let { proxy,revoke}=Proxy.revocable(原对象,{拦截函数})
- 语法二:
- let 目标变量名=Proxy.revocable(原对象,{拦截函数})
- 目标变量名.proxy:即为代理对象
- 代理对象操作时就会执行拦截函数
- 当执行目标变量名.revoke(),代理对象就会被取消【目标变量名.proxy就为变为空对象,且不能调用任何属性和方法】,此时获取代理对象中的属性就会报错
- 目标变量名.proxy:即为代理对象
- let 目标变量名=Proxy.revocable(原对象,{拦截函数})
- Reflect【用在拦截函数的函数体里面】
- Reflect.get(…arguments)
- 将获取代理对象操作的属性值取过来【返回值为获取拦截函数中获取的属性值】
- Reflect.set(…arguments)
- 将设置代理对象的操作 是否完成的结果取过来【返回值为true和false】
- Reflect.apply(…arguments)
- 将原对象return后面的返回值获取过来【返回值为原对象的返回值,可以进行算术操作】
- Reflect.get(…arguments)
- Reflect.apply(调用的函数名,this指向,数组实参列表)
- 数组实参列表为数组
- 等价于:调用的函数名.apply(this指向,数组实参列表)
- 模块化:
- 模块化只能在服务器环境【有端口号,有ip地址,有协议】下运行
- 模块化Module:【<script type=’module’>模块化代码</script>】
- Javascript采用“共享一切”的方式加载代码,也就是在ES6之前,JavaScript中定义的一切都共享同一个全局作用域,这样随着程序复杂度提升的同时,也会带来命名冲突等诸多问题。因此ES6的一个目标就是解决作用域问题,也为了使JavaScript应用程序显得有序,于是引进了模块
- 模块功能主要由两个命令构成:export和import
- export命令用于规定模块的对外接口【导出[也叫暴露],写在js文件内部导出变量,函数,…】
- import命令用于输入其他模块提供的功能。【导入,嵌套在<script type=’module’></script>标签内部导入】
- 一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。【一个js文件就是一个模块,模块内部使用export关键字导出里面的变量,函数,…;export关键字后面跟变量初始化,或函数初始化,或对象内使用初始化好后的变量名,变量名与变量名之间用逗号分隔】
- export【该关键字后面跟初始化代码[变量初始化,函数初始化,…]或者跟对象字面量包裹的变量名列表和函数名列表,变量名和函数名之间用逗号分隔】
- export 【该关键字写在js文件内,一个js文件就是一个模块】:
- 单个变量导出语法:
- export let 变量名 = 变量值
- 导出的变量名会成为模块对象的属性,变量值会成为模块对象的属性值。【模块对象里面的属性不能随意修改属性值】
- export let 变量名 = 变量值
- 一次性导出多个变量语法:
- 先初始化变量【声明和赋值】:
- let 变量1名 = 变量1值
- 先初始化变量【声明和赋值】:
- 单个变量导出语法:
- export 【该关键字写在js文件内,一个js文件就是一个模块】:
let 变量2名 = 变量2值
let 变量3名 = 变量3值
…
- 再export {变量1名,变量2名,变量3名…}:
- export {变量1名,变量2名,变量3名…}
- 再export {变量1名,变量2名,变量3名…}:
- 单个函数导出语法:
- export function 函数名(参数列表){
//函数体
return 返回值;
}
- 一次性导出多个函数或变量语法:
- 先初始化变量或函数:
- let 变量1名 = 变量1值
- 先初始化变量或函数:
- 一次性导出多个函数或变量语法:
let 变量2名 = 变量2值
let 变量3名 = 变量3值
…
function 函数1名(){}
function 函数2名(){}
function 函数3名(){}
…
- 再export {变量1名,变量2名,变量3名…,函数1名,函数2名,函数3名,…}:
- export {变量1名,变量2名,变量3名…,函数1名,函数2名,函数3名…}
- 再export {变量1名,变量2名,变量3名…,函数1名,函数2名,函数3名,…}:
- export导出多个变量时,可以给这些变量重命名:
- export {变量1名 as 变量1别名,变量2名 as 变量2别名,…}
- 该js文件【模块内部】内部打印该变量值需要使用原变量
- 其他html文件使用:用import【嵌套在html文件的<script type=’module’></script>标签内部】导入模块时,是用import后面的原变量名去匹配export导出模块后面变量的别名【在模块中打印变量值需要使用原变量名。在html文件中,打印变量值,需要使用原变量名的别名】
- export {变量1名 as 变量1别名,变量2名 as 变量2别名,…}
- 注意:
- export命令只能在模块顶层使用,位置不限
- 不允许出现在if语句中,不能有条件导出,函数体内都不允许有export关键字。
- 如果想条件导出某个变量,可以先条件判断来改变变量的值,最后导出变量:
- if(true){
目标变量 = 1值
}else{
目标变量 = 2值
}
export{ 目标变量}
- import:
- 语法一【也叫无绑定导入】:
- import ‘目标文件路径’
- 将目标文件执行一遍【就相当于引入一个js文件】
- import ‘目标文件路径’
- 语法二:
- import * from ‘目标文件路径’
- 将目标文件执行一遍
- import * from ‘目标文件路径’
- 语法三:
- import * as 模块对象名 form ‘目标文件路径’
- 会将目标文件执行一遍
- 会将目标文件内export后面导出的变量作为模块对象名的属性,变量保存的值作为模块对象的属性对应的属性值
- import * as 模块对象名 form ‘目标文件路径’
- 语法四:
- import {目标文件内的变量1名,目标文件内的变量2名.…} from ‘目标文件路径’
- import关键字后面的变量名会一一在from关键字后面的目标文件模块中去匹配export关键字后面的变量,从而拿到该变量的值。【当export关键字后面的变量用来as关键字重命名时,import关键字后面的变量需要使用as关键字重命名的变量名】
- import {目标文件内的变量1名,目标文件内的变量2名.…} from ‘目标文件路径’
- 语法一【也叫无绑定导入】:
- 只有整体导入导出才可以通过as关键字修改名称:
- 导入import关键字后面的原变量名 去匹配 导出export关键字后面原变量名的别名;import关键字写在html文件中,且在该文件中只能使用别名打印出变量值【写原变量可能会报错未定义或拿不到值】;export关键字写在js文件中,且在该文件中只能使用原变量名打印输出变量值。
- 简单理解:export关键字后面的变量名如果有别名,在模块内部还是用 原变量名。import关键字后面的变量名如果有别名,在html文件内部是用别名。Export关键字和import关键字匹配时,是用import关键字后面变量的原变量名去匹配export关键字后面变量的别名。
- 导入import关键字后面的原变量名 去匹配 导出export关键字后面原变量名的别名;import关键字写在html文件中,且在该文件中只能使用别名打印出变量值【写原变量可能会报错未定义或拿不到值】;export关键字写在js文件中,且在该文件中只能使用原变量名打印输出变量值。
- 默认导入导出:
- export default 导出值
- 导出值可以是变量【变量需是声明了的】、函数、数值、对象
- import 自定义模块对象名 from “文件路径”
- 此时导入的自定义模块对象名保存的就是文件路径下export default 后面的导出值。
- 一个模块只能有一个默认导出【一个模块有多个默认导出会直接报错】
- export default 导出值
- import提升:
- import会将该语法提到程序的最前面,首先执行。虽然程序会将import关键字语法导入提升到页面数据最前,但是尽量还是自己将导入放到当前模块最前面
- import按条件导入不同模块的同名变量,需要给其中一个变量取别名:
- import {目标变量名} from ‘模块1路径’
import {目标变量名 as 目标变量别名} from ‘模块2路径’
- import特点:
- import多次导入同一个模块,该模块只会执行一次【模块具有缓存机制】
- import的导入会进行提升,提升到代码最顶部,首先执行
- import()动态引入【也叫按需导入,返回值是一个promise对象】:
- 默认的import语法不能写在if之类的判断里面的,因为是静态引入,先引入再使用。
- import()动态导入返回值是一个成功状态的promise对象,状态值为导出的模块对象。
- 动态导入可以写在if条件语句中:
- let flag=true
if(flag){
import(‘模块1路径’)
.then(data => {
console.log(data)
})
}else{
import(‘模块2路径’)
.then(res=> {
console.log(res)
})
}
- 代码优化:
let url = flag ? “模块1路径” :“模块2路径”
import(url).then(res=>{console.log(res)})
- Class类的了解:
- ES6类:
- 大多数面向对象的编程语言都支持类和类继承的特性,JavaScript却不支持,因此只能通过其他方法并关联多个相似的对象来模拟类功能。而在ES6中引入了类的特性 但是要注意ES6类和其他语言中的类 还是不太一样,其实本质就是以前构造函数的语法糖。
- 类只能用new来调用,除此之外其他方式调用类会导致程序报错
- 类声明和let声明类似,不会提升,存在暂时性死区,同时不能重复声明
- 在类的方法中不能修改类名,但是在类的外部可以修改类名
- 创建类:
- class 类名{
- ES6类:
constructor(自定义参数1名,自定义参数2名,…){
//实例上的属性和方法
this.自定义属性1名 = 自定义参数1名;
this.自定义属性2名 = 自定义参数2名;
}
//实例上的属性和方法
自定义属性名 = 自定义属性值
//构造函数原型上的方法
实例原型上的方法名(){}
//类上的静态方法
static 类名上面的方法名(){}
}
- 关键字static上面的方法是静态方法,只能通过类名调用。
- 类的表达式:
- let 类名= class {
constructor(自定义参数1名,自定义参数2名,…){
this.自定义属性1名 = 自定义参数1名;
this.自定义属性2名 = 自定义参数2名;
}
实例原型上的方法名(){}
static 类名上面的方法名(){}
}
- ES6类内部的方法:
- 类的表达式:
- 类和函数都有两种存在的形式:声明形式和表达式形式
- 声明形式:
- class 类名{
- 声明形式:
- 类和函数都有两种存在的形式:声明形式和表达式形式
- 类的表达式:
constructor(自定义参数1名,自定义参数2名,…){
this.自定义属性1名 = 自定义参数1名;
this.自定义属性2名 = 自定义参数2名;
}
构造函数原型上的方法名(){}
static 类名上面的方法名(){}
}
- 关键字static上面的方法是静态方法,只能通过类名调用
- 表达式形式:
- let 类名 = class {
constructor(属性值形参1变量名, 属性值形参2变量名,…){
this.自定义属性1名 = 属性值形参1变量名;
this.自定义属性2名 = 属性值形参2变量名;
…
}
构造函数原型上的方法1名(){}
构造函数原型上的方法2名(){}
…
static 类名上面的方法1名
static 类名上面的方法2名
…
get 属性名(){}
set 属性名(val){ return this.属性名 = 属性值}
}
- constructor 构造函数方法:
- 如果不明确指定这个方法,那么在使用new调用类时,会触发内部的constructor
- 一般实例化的对象没有属性,不需要在new调用传参时,可以不明确指定constructor
- constructor方法是类的默认方法,通过new命令生成实例对象时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加
- 如果constructor方法显示return返回一个引用类型【数组和对象】时,此时通过new关键字创建的实例的构造函数就会发生变化。
- 如果不明确指定这个方法,那么在使用new调用类时,会触发内部的constructor
- 可计算成员名称:
- 类和对象字面量还有很多相似之处,类方法和访问器属性也支持使用可计算名称,使用方式和对象字面量一样,用方括号包裹一个表达式即可使用计算名称。
- 例子:[‘say’+’hello’](){}
- 类和对象字面量还有很多相似之处,类方法和访问器属性也支持使用可计算名称,使用方式和对象字面量一样,用方括号包裹一个表达式即可使用计算名称。
- 访问器属性:
- 类除了在构造函数中创建实例的属性,同时也支持在原型上定义访问器属性。通过关键字get,set定义访问器属性的设置与获取。
- new关键字调用时会先执行构造函数方法,生成一个实例对象,当这个实例对象调用 实例对象名.set后面的属性名=属性值 时,就会执行set后面的方法,return后面的返回值如果是构造函数内部的语句,则会覆盖原属性名对应的属性值。然后会看set后面的属性名在get后面的属性名中是否存在,如果存在,以后调用实例对象名.set后面的属性名就相当于调用实例对象名.get后面的属性名,此时就会将get后面的方法中的return的返回值,就会返回给这个属性当属性值。如果不存在,以后调用实例对象名.set后面的属性名返回值就为undefined【并不会将我们手动设置的属性值作为这个属性的属性值,get获取这个方法的返回值才是这个属性的属性值。】【和事件代理有点相似】
- 类除了在构造函数中创建实例的属性,同时也支持在原型上定义访问器属性。通过关键字get,set定义访问器属性的设置与获取。
- 生成器方法:
- * createInte(){
- constructor 构造函数方法:
yield 状态1值
yield 状态2值
…
}
- 迭代器方法【只做了解】:
- *[Symbol.iterator](){
- 迭代器方法【只做了解】:
yield 状态1值;
yield 状态2值;
…
}
- 这样就可以用for of循环遍历出 类创建对象的状态值了【状态值需要为类创建对象的属性或属性值,就需要使用for in循环】。
- for of循环遍历出实例对象的属性值:
- 这样就可以用for of循环遍历出 类创建对象的状态值了【状态值需要为类创建对象的属性或属性值,就需要使用for in循环】。
*[Symbol.iterator]() {
for (let key in this) {
yield this[key]
}
}
- for of循环遍历出实例对象的属性:
*[Symbol.iterator]() {
for (let key in this) {
yield key
}
}
- 静态方法:
- 在ES5及早期版本中,直接将方法添加到构造函数中来模拟静态方法,但在ES6类语法简化了创建静态方法的过程,在方法前使用关键字static【也叫静态注释】但要注意的是在类中构造函数constructor是唯一限制不能使用static的方法,静态方法不能通过类的实例来访问,只能通过类名来访问静态方法。【ES5中的静态方法是通过构造函数来访问的】
- ES6明确规定,class内部只有静态方法,没有静态属性。
- 静态方法:
- 类的继承:
- ES6类的继承:
- 通过关键字extends来继承类【也叫扩展类或派生类】【父类叫基类,子类叫派生类】
- ES6类的出现让我们可以轻松地实现继承功能,使用extends关键字就可以指定类继承函数,但这种继承没法添加自己扩展的属性和方法,添加就报错
- 如果派生类中指定了构造函数则必须要调用super(),如果不这样就会报错。
- 通过关键字extends来继承类【也叫扩展类或派生类】【父类叫基类,子类叫派生类】
- super:
- super只可在派生类【子类】的构造函数中使用,如果尝试在非派生类【未使用extends的类】或函数中使用则会导致程序报错
- 在构造函数中访问this之前一定要调用super(),因为super()负责初始化this,如果在super()之前访问this会导致报错【不能在super之前使用this,所以一般情况下,super会在派生类构造函数的第一行代码执行】
- 如果不想调用super(),则唯一的方法就是让类的构造函数显示的返回一个对象【即在派生类的构造函数最后一行代码写return{父类中的自定义属性列表,子类中的自定义属性列表}】
- 类方法的遮蔽:
- 子类创建的实例,当子类中的方法名在父类中也存在,子类的方法会覆盖掉父类中的方法。【派生类中的方法总会覆盖基类中的同名方法,如果你想调用基类中的方法,则可以通过super在派生类同名方法中手动调用基类方法:super.基类中的同名方法()这样就会执行基类中的方法,然后执行派生类后面的代码。】
- ES6类的继承:
- 拖拽元素:
- ES5
- ES6
- 深浅拷贝:
- 浅拷贝:只拷贝一层数据,更深层对象级别的只拷贝引用地址
- 浅拷贝对象里面的对象拷贝的是地址,当修改浅拷贝对象里面的对象数据时,会改变原来的数据
- 深拷贝:拷贝多层数据,每一级别的数据都会拷贝
- 方案一:
- 方案二:
- 浅拷贝:只拷贝一层数据,更深层对象级别的只拷贝引用地址
function clone(origin, target = {}) {
for (let key in target) {
if (target[key] != null && typeof target[key] == 'object') {
if (Object.prototype.toString.call(target[key]) === '[object Array]') {
origin[key] = []
} else {
origin[key] = {}
}
clone(origin[key], target[key])
} else {
origin[key] = target[key]
}
}
return origin
}
let origin = clone({}, obj)
- DOM树解析:
- 什么是DOM树==>DOM节点按照树型解构排列
- DOM树生成的原则:深度优先【对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次】
- 浏览器渲染页面的步骤:
- 解析DOM节点,生成DOM树
- 继续解析css代码生成CSS树
- DOM树和CSS树合并在一块生成Render树
- 浏览器开始渲染绘制页面
- JS加载是同步的
- 同步加载js是为了防止js操作DOM元素造成问题
- Js下载会阻止后面得标签执行,js还学会阻止网页中所有得下载行为
- 如果需要异步加载JS【不会操作DOM节点】:
- 比如要加载一些工具类的script文件,这些js文件并不会去操作DOM,也就不会对DOM结构和CSS产生影响
- 那么现在同步加载就会影响性能,因为如果没有加载完成,会阻塞后面的DOM结构的解析文件和css文件的加载,过多的工具类script文件,会让网页的加载时间过长,不利于用户体验
- 个别工具类script文件是按需加载,使用时才加载,不使用的时候就不加载
- 但有的时候我们是希望JS的加载是异步的
- 有些JS并不会操作页面,只是初始化一个数据,还有就是引入工具包,工具包就是一个一个function,你不调用,压根不会执行,这样的js我们是不希望同步吧,因为如果在加载过程中有1k的内容没有加载过了,你整个程序就中断了,js会阻塞后面的内容
- 后来研究,就加载那些工具按需加载,用到再加载,不用不加载。
- Js操作DOM和CSS造成的重构和重绘:
- DOM树生成完成了再等着,等着CSS树生成
- rander树一旦生成页面就绘制了【这种方式叫原生DOM渲染】
- reflow重构:
- 如果在页面也就渲染完成了,你此时通过js修改了DOM树,就会生成新的DOM树,然后生成新的rander树,造成性能的浪费,所以即便你要修改DOM也请一次修改完,别修改一次 等一会 你再一次修改DOM树
- 重构效率是最低的,哪几种情况会触发重构呢:
- DOM节点的删除 添加
- DOM节点的宽高变换,位置变换none-block
- Repaint重绘:
- 如就是改变背景原色,触发CSS树变换的叫重绘,这个重绘的只是一部分,并不会导致整个页面重构。
- 比如改个文字颜色啊,改个背景颜色啊,影响比较小,但是文字大小改变就会触发重构
- 重构你可以理解为结构发生了变换,导致整体发生变化,重绘只是样式发生了变换,不会影响整体。
- reflow重构:
- 异步加载js文件的三种方案:
- defer异步加载:
- 在DOM文档解析完成之后才执行js文件,IE独有
- 语法:在引入的script标签内部加defer属性。
- 这样在遇到js文件引入时,会先下载,但要在所有DOM文档解析完成之后才会执行这个js代码
- 例子:<script src=’路径’ defer></script>
- async:
- 在加载完js文件之后就执行,只能加载外部js文件,不能把js代码写在js文件里
- Js文件加载完成后立即执行,不会影响其他代码加载执行
- 注意:
- 内嵌js不能用
- 非IE用的
- 这是W3C的方法,IE9以上都可以用,IE9及其以下不能用
- 在加载完js文件之后就执行,只能加载外部js文件,不能把js代码写在js文件里
- 创建script标签,插入在DOM中,在js文件加载完毕之后callBack回调能够实现按需加载,封装兼容性loadScript
- defer异步加载:
- Js的时间线:
- Js出生时浏览器执行的事,记录浏览器执行的顺序。
- 创建document对象,解析HTML元素和他们的文本内容后添加Element对象和Text节点到文档中。这个阶段document.readyState=’loading’
- 如果遇到link外部css文件,浏览器会创建新线程加载,同时继续解析文档
- 如果遇到外部js文件,并且没有设置async、defer,浏览器会加载js文件,阻塞主线程,等待js文件加载完成并执行该文件,然后继续解析文档。
- 如果遇到外部js文件,并且设置有async、defer,浏览器会创建新线程加载js文件,同时继续解析文档。对于async属性的js文件,会在js文件加载完成后立即执行
- 如果遇到img,iframe等,浏览器在解析DOM结构时,会异步加载src,同时解析文档
- 当DOM文档解析完成,document.readyState=’interactive’
- 文档解析完成后,所有设置有defer的脚本会按照顺序执行。
- document对象触发DOMContentLoaded事件,这也标志着程序执行从同步脚本阶段转化为事件驱动阶段
- 当所有async的脚本加载完成并执行后,img等加载完成后,document.readyState=’complete’,window对象触发load事件
- 从此,以异步响应方式处理用户输入,网络事件等。
- 简化三个步骤:
- 创建document对象
- 文档解析完成
- 文档加载完成
- 状态变化:每次状态变化都会触发document.onreadystatechange事件
- document.onreadystatechange = function(){
- Js出生时浏览器执行的事,记录浏览器执行的顺序。
console.log(document.readyState)
}
- Ajax:
- 什么是Ajax:
- Asynchronous JavaScript and xml(异步JavaScript和XML)
- 主要作用是用来处理前后端数据传输
- 2005年由美国人Jesse James Garrett推广并取名
- 提交方式:
- GET请求:
- 通过url地址来提交数据
- 优点:
- Get请求有天生优势:便于分享网址。
- 缺点:
- 暴露隐私,所有的表单的字段的值都是通过URL来传输的,明码传输,能够看见浏览器的历史记录,就能够看见某一次的表单值;
- 数据内容不能太大
- Post请求:
- Post请求也可以让用户的数据传输到服务器上,但是不是利用URL,二十利用HTTP request报文头
- 优点:
- 安全,不会通过网址来暴露我们的表单;
- 内容不限量,post请求是可以无限的,表单域填多少都没有问题
- 缺点:
- 地址不可以分享,很明显post请求不影响URL
- GET请求:
- 什么是Ajax:
- Ajax的使用:
- 第一步:
- 创建Ajax对象:
- 主流浏览器创建Ajax对象:
- var ajax对象 = new XMLHttpRequest();
- iE浏览器:
- var ajax对象 = new ActiveXObjext(‘Msxml2.XMLHTTP.6.0’)
- 兼容性写法:
- if(typeof XMLHttpRequest != ‘undefined’) { //主流浏览器
- 主流浏览器创建Ajax对象:
- 创建Ajax对象:
- 第一步:
var ajax对象 = new XMLHttpRequest();
}else{
var ajax对象 = new ActiveXObject(‘Msxml2.XMLHTTP.6.0’)
}
- 第二步:
- 服务器的请求配置:
- Get请求:
- ajax对象.open(‘get’,路径+参数,是否使用异步)
- Post请求:
- ajax对象.open(‘post’,路径,是否使用异步)
- Get请求:
- 补充:
- 配置对服务器请求的配置需要使用Ajax对象上的open方法,open字面意思是打开,就是打开一个请求。Open之后并没有真正的发送请求
- Open方法接收三个参数:
- Ajax对象.open(要发生的请求类型,路径,是否使用异步)
- 服务器的请求配置:
- 第二步:
第一个参数,请求方式,get请求还是post请求
第二个参数,请求的服务器路径
第三个参数,表示是否是异步处理,true为异步。
默认就是异步,所以一般不用第三个参数
- 第三步:
- 发送请求:
- Get请求:
- ajax对象.send(null)
- Post请求:
- ajax对象.send(参数)
- Get请求:
- 发送请求:
- 第四步:
- 接收服务器的返回信息【因为前面三步都是同步语句,接收服务器的返回信息是需要时间的,所以这里就需要用到readyState属性,表示ajax的状态,从而达到】:
- ajax对象.onreadychange=function(){
- 接收服务器的返回信息【因为前面三步都是同步语句,接收服务器的返回信息是需要时间的,所以这里就需要用到readyState属性,表示ajax的状态,从而达到】:
- 第三步:
if(ajax对象.readyState==4){
if(ajax对象.status >= 200 && ajax对象.status <300 || ajax对象.satus == 304){
console.log(ajax对象.responseText)
}
}
- }
- 补充:
- Ajax对象有五种状态【ajax对象.readyState】:
- 创建ajax对象完毕:ajax对象.readyState =0
- 调用了open()方法:ajax对象.readyState=1
- 调用了send()方法:ajax对象.readyState=2
- 服务器只返回了一部分数据:ajax对象.readyState=3
- 服务器数据全部返回,Ajax请求完成:ajax对象.readyState=4
- Ajax对象有五种状态【ajax对象.readyState】:
表示前后台交互通了,后台返回数据了。但不能保证后台返回的是正确的数据,也可能后台服务器出错了,给的是报错信息
- Onreadystatechange事件,该事件会感知ajax状态readyState的变化,只要状态一变化,事件就会执行
- http状态【ajax对象.status】:
- 2:成功
- 3:重定向
301:永久移动
302:临时移动
- 4:大部分是客户端错误,前端的错误
- 5:服务器错误
- 封装ajax:
- function ajax(json){
//确定请求方式
var method=json.method.toUpperCase() || ‘GET’
//向后台传输数据【有就传过来,没有将空对象赋值过来】
var data = json.data || {};
//创建ajax对象
var xhr;
if(typeof XMLHttpRequest != undefind){
xhr=new XMLHttpRequest();
}else{
xhr=new ActiveXObject(‘Msxml2.XMLHTTP.6.0’)
}
//配置发送请求
switch(method){
case ‘GET’:
xhr.open(method , json.url+’?’+jsonUrl(json.data),true)
xhr.send(null);
break;
case ‘POST’:
xhr.open(method , json.url,true)
xhr.sendRequestHeader(‘content-type’,application/x-www-form-urlencoded);
xhr.send(jsonUrl(json.data));
break;
}
//处理数据
function jsonUrl(data){
var arr = []
for(var key in data){
arr.push(key + ’=’ + data[key]);
}
return arr.join(‘&’)
}
//获取数据
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status >=200 && xhr <300 || xhr.status == 304){
json.success(xhr.responseText)
}else{
json.error(xhr.responseText)
}
}
}
}
//调用函数
ajax({
url:数据网址路径
method: ‘get’,
data: {},
success(){
console.log(‘res’,res)
},
error(){
console.log(err)
}
})
- ajax对象的成员
- 属性:
- responseText以字符串形式接受后台返回的信息
- 不能直接获取,ajax请求是异步的,同时后台返回的数据是以数据流的形式返回的,所以要通过状态监测数据是不是返回完了
- readyState表示ajax的状态
- ajax一共有五种状态:
- ajax对象.readyState = 0:创建ajax对象完毕
- ajax对象.readyState = 1:调用了open()方法
- ajax对象.readyState = 2:调用了send()方法
- ajax对象.readyState = 3:服务器端只返回了一部分数据
- ajax对象.readyState = 4:服务器端数据全部返回,ajax请求完成
- ajax一共有五种状态:
- onreadystatechange事件,该事件回感知ajax状态readyState的变化,只要状态一变化,事件就会执行
- responseText以字符串形式接受后台返回的信息
- 属性:
- 跨域:
- 解决方案:
- Cros解决跨域问题:
- 是一种解决跨域的技术,在后台中设置响应头【后端代码添加允许跨域的响应头】
- res.header(‘Access-Control-Allow-Origin’,’*’)
- 是一种解决跨域的技术,在后台中设置响应头【后端代码添加允许跨域的响应头】
- Jsonp解决跨域:
- JSON(JavaScript Object Notation)和JSONP(JSON with Padding)虽然表面上只有一个字母差别,实际上是两种不同的东西。JSON是一种数据交换格式,而JSONP是一种被开发人员创造出来的一种非官方的跨域数据交互格式。
- Cros解决跨域问题:
- 补充:
- 同源策略:
- 同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响
- 可以说web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现
- 同源策略,它是由Netscape提出的一个著名的安全策略,现在所有支持JavaScript的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。
- 同源策略:
- 解决方案:
- jQuery中的Jsonp使用:
- 黑恶黑1