JavaScript 面向对象编程

面向对象编程的特性:封装性、继承性、多态性

面向过程编程的优点:性能比面向对象高,适合跟硬件联系的东西;缺点:没有面向对象易维护、易复用、易扩展

类和对象

1
2
3
4
5
6
7
8
9
10
11
// 创建类
class Star(){
// 构造函数,默认生成
constructor(){

}
// 声明其它函数
sum(){}
}
// 实例化对象
var first = new Star();
  1. 通过 class 关键字创建类,类名我们还是习惯性定义首字母大写
  2. 类里面有一个 constructor 函数,可以接受传递过来的参数,同时返回实例对象
  3. constructor 函数只要 new 生成实例时,就会自动调用这个函数,如果我们不写这个函数,类也会自动生成这个函数
  4. 生成实例 new 不能省略
  5. 最后注意语法规范,创建类 类名后面不要加小括号,生成实例 类名后面加小括号,构造函数不需要加function

this 指向问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 谁调用方法,this就指向谁
class Father{
constructor(x,y){
this.x = x
this.x = y
}
sum(){
console.log(this.x,this.y)
}
}
var fa1 = new Father();
fa1.sum()// 那么sum函数里面的this指向fa1,指向对象


class Father{
constructor(x,y){
this.x = x
this.y = y

// 获取按钮DOM节点
var this.btn = document.querySelector('button');
this.btn.onclick = this.sum() // 这样给按钮点击事件绑定 sum 函数,那么调用sum函数就不是对象,而是按钮
}
}
// 此时this = btn

// 如何获取对象的this => 定义全局 that 变量,保存 this
var that = this

类继承 extends 和 super 关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Father{
constructor(x,y){
this.x = x
this.x = y
}
sum(){
console.log(this.x,this.y)
}
}

// 写子类继承父类
class Son extends Father{
constructor(){
// 使用父类通过 supter关键词
super(x,y)//调用了父类的构思函数
}
}
var son1 = new Son();

总结

1
2
3
4
5
6
(1) 调用父类构造函数 super 关键字
super()
(2) 调用父类方法
son1.sum()
(3) 如果子类有该方法,父类也有该方法,会调用哪一个
就近原则,使用子类方法

重点

  1. 在 ES6 中类没有变量提示,所有必须先定义类,才能通过类实例化对象
  2. 类里面的共有属性和方法一定要加 this 使用
  3. 类里面的this指向问题
  4. constructor 里面的this指向实例对象,方法里面的this指向这个方法的调用者

类编程思想

1
2
// (1) 时刻需要注意类的this指向,谁调用了该方法,谁就是this指向的对象
// - 如果类的方法由按钮调用,那么对象就是 button,需要注意构造函数就是对象才能调用的

添加 标签

1
2
3
var newli =
'<li class="show"><span>新选项卡</span><span class="icon">&#10006;</span></li>';
that.lis[0].parentNode.insertAdjacentHTML("beforeend", newli);

insertAdjacentHTML() 方法将指定的文本解析为 Element 元素,并将结果节点插入到DOM树中的指定位置。它不会重新解析它正在使用的元素,因此它不会破坏元素内的现有元素。这避免了额外的序列化步骤,使其比直接使用innerHTML操作更快

语法:element.insertAdjacentHTML(position, text);

position:一个 DOMString,表示插入内容相对于元素的位置,并且必须是以下字符串之一:

  • 'beforebegin':元素自身的前面。
  • 'afterbegin':插入元素内部的第一个子节点之前。
  • 'beforeend':插入元素内部的最后一个子节点之后。
  • 'afterend':元素自身的后面。

构造函数和原型

ES6以前通过构造函数生成对象。构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与new一起使用。

创建对象可以通过三种方式:

  • 对象字面量
  • new Object()
  • 利用构造函数创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// (1) 利用 new Object() 创建
var obj1 = new Object();

// (2) 利用字面量创建
var obj2 = {}

// (1) 构造函数创建对象
function Star(uname,age){
this.uname = uname;
this.age = age;
this.sing = function(){

}
}

静态成员和实例成员

1
2
3
4
5
6
7
8
9
10
11
12
function Star(uname,age){
this.uname = uname;
this.age = age;
this.sing = function(){

}
}
// uname、age、sing 都是通过 this 添加的,是实例成员。只能通过实例化对象调用
this.uname、this.age、this.sing()

Star.username = 'simplelife'; // 直接在构造函数本身添加
// username 是静态成员,只能通过构造函数调用。对象调用是underfined

构造函数的问题

构造函数很好用,但是存在浪费内存的问题。当实例化很多对象,如果构造函数有函数,每一个实例化对象都需要开辟新的内存空间,去创建函数。

构造函数原型 prototype

原型是一个对象,我们也称 prototype 为原型对象

构造函数通过原型分配的函数是所有对象所共享的

1
2
3
4
// 不需要将函数写在构造函数里面,而是写在原型上去共享
Star.prototype.sing = function(){
console.log('Simplelife');
}

对象原型 __proto__

对象身上系统自动添加 __proto__ 对象,指向我构造函数原型对象 prototype

1
2
3
4
console.log(obj1.__proto__ === Star.prototype); //logs true 完全等价
// 查找方法规则
// 首先查找构造函数是否有该方法
// 其次去 __proto__ 构造原型查找该方法

修改原型对象

1
2
3
4
5
6
7
8
9
// 在给原型对象添加函数时,假如添加很多函数,可以如下写
Star.prototype = {
sing: function{
console.log('sing')
},
movice: function{
console.log('movice')
}
}

这样给原型添加函数方法会直接覆盖以前的原型,因为给原型赋值的是一个对象。如果这样我们必须手动的利用 constructor 指向原来的构造函数

1
2
3
Star.prototype = {
constructor: Star
}

构造函数、实例、原型对象三者之间的关系

Star构造函数有一个原型对象 prototype 指向了 原型对象 prototype

原型对象 prototype 的属性 constructor 又指向了 Star 构造函数,因此如果赋值prototype为对象,就没有 construtor指向 Start

实例化对象通过 __proto__ 指向 prototype 完全等价

原型链

Star 原型对象也有 __proto__ 指向的是 Object 原型对象的 prototype

Object 原型对象指向的是 null

PS:原型链对象成员查找规则(就近原则),当对象实例没有向上级 原型对象 prototype 找,直到最顶层

扩展内置对象

可以通过原型对象,对原来的内置对象进行扩展自定义的方法 。 比如给数组增加自定义求偶数和的功能

PS:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} , 只能是 Array.prototype.xxx = function(){} 的方式

1
2
3
4
5
6
7
8
9
10
11
12
Array.prototype.sum = function(){
var sum = 0;
for(var i=0; i<this.length; i++){
sum += this[i];
}
return sum;
}

// 使用自定义方法
var arr1 = [1,2,3] => var arr1 = new Array(1,2,3)
arr1.sum(); // logs 6

继承

ES6之前并没有给我们提供 extends 继承。我们通过构造函数+原型对象模拟实现继承,被称为组合继承

call()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 作用
改变函数 this 指向,可以呼叫函数

// 语法
call(thisArg, arg1, ... , argN)

// 示例
function sum(){
console.log(this) // 指向的是 window
}
var obj = {
name: 'simplelife'
}
sum.call() // 默认不传值等价于 sum(); 呼叫该函数
sum.call(obj) // 传入第一个值是 obj,表示 this 指向obj,改变了函数内部this指向
sum.call(obj,a,b) // 后面传入都是参数

借用父构造函数 继承属性

1
2
3
4
5
6
7
8
9
10
11
12
function Father(uname,age){
this.uname = uname;
this.age = age;
}
function Son(uname,age){
// 父构造函数有属性赋值操作,直接使用 call 去调用。因为父构造函数 this 是指向父构造函数的实例对象
// 因此需要使用 call 去更改为 Son
Father.call(this,uname,age);
}

var son = new Son('Simplelife',age);
console.log(son)// => 如下

继承方法

(1) 构造函数一般将方法写在原型上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Father(uname,age){
this.uname = uname;
this.age = age;
}

Father.prototype.money = function(){
console.log('赚钱'); // 父构造函数赚钱的方法
}
function Son(uname,age){
Father.call(this,uname,age);
}
// Son.prototype = Father.prototype
// 这样直接赋值可以将 父构造函数的函数方法赋值给子构造函数
// 如果再给 Son 添加原型方法,它会追加到 Father 上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Father(uname, age) {
this.uname = uname;
this.age = age;
}
Father.prototype.money = function () {
console.log('赚钱'); // 父构造函数赚钱的方法
}
function Son(uname, age) {
Father.call(this, uname, age);
}
Son.prototype = Father.prototype
Son.prototype.exam = function(){
console.log('需要考试')
}
console.log(Son.prototype);
console.log(Father.prototype);

PS:可以明显看到 Son 的原型 和 Father 原型一样,连给 Son 原型添加的方法,也添加到 Father 原型上,其实它们开辟的空间地址是同一个地址,因此修改会互相影响

使用父实例对象赋值

1
2
3
4
5
6
7
8
9
10
// 实例对象开辟的空间 和 构造函数原型开辟的空间是不一样的地址
// 实例对象 有个 __proto__ 等价于 构造函数原型 prototype 可以找到
Son.prototype = new Father();
// Son 原型被对象覆盖赋值,需要对构造函数重新指向
// 如上图 constructor 都是指向 Father
Son.prototype.constructor = Son; // 重新指向 Son
// 给 Son原型 添加函数方法
Son.prototype.exam = function(){
console.log('需要考试')
}

类的本质

类是本质就是函数function,ES6通过 类 实现面向对象编程。ES6 类 和 ES5 构造函数是一样的,只是另一种写法(语法糖)

类所有的方法都定义在 原型对象 上,和E6操作是一样的,只是 ES6 可以直接写在类里面

ES5 中新增的方法

数组方法

迭代(遍历)方法:forEach()、map()、filter()、some()、every()

1
2
3
4
5
6
7
// (1) forEach
var arr = [1,2,3,4]
arr.forEach((element,index,array) => {
// element 当前元素
// index 当前索引
// 数组本身
})
1
2
3
4
5
6
7
// (2) filter
// 作用:创建一个新的数组,将新数组中的元素是通过检查指定数组中符合条件的所有元素
// 主要用于筛选数组,注意返回的是一个新数组
arr.filter((currentValue,index,arr)=>{
return currentValue >= 20; // 返回条件,得到符合条件的新数组
})
var newArr = arr.filter…………
1
2
3
4
5
6
// (3) some
// 作用检查数组是否有某元素,有就终止循环,返回true。
// 返回值是布尔值 boolean, 传入的参数都是一样的
arr.some((currentValue,index,array)=>{
return currentValue === "simplelife";
})
1
2
3
4
5
6
7
8
// (4) map
// map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值(包括
// undefined)组合起来形成一个新数组。 callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用
// delete 删除的索引则不会被调用。
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// 返回是新数组
}[, thisArg])
// 传入 this 是给回调函数 callback 使用的
1
2
3
4
5
6
7
8
// (5) every() 
// 方法测试一个数组内的<所有>元素是否都能通过某个指定函数的测试。它返回一个布尔值。
const isBelowThreshold = (currentValue) => currentValue < 40;
const array1 = [1, 30, 39, 29, 10, 13];
console.log(array1.every(isBelowThreshold));
// 输入结果: true
// 如果存在某元素不满足条件,会返回false.
// 查找到不满足条件的直接退出循环,返回 false 和 方法 some 是一样的

forEach 和 som 的区别

1
2
3
4
5
6
7
8
9
10
// 当查询元素唯一标识选择 some 方法更高效
forEach 遍历数组,会一直执行下去
some 得到满足条件的,可以输入 return true; 终止迭代
arr.some(value=>{
if(value === 'simplelife'){
console.log(value);
// 终止迭代
return false
}
})

字符串方法

1.trim()

去除字符串两侧的空格。常用于 input 输入框去除空格

对象方法

1.Object.defineProperty(obj,prop,descriptor)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 定义对象中新属性或修改原有的属性
Object.defineProperty(obj,'prop',descriptor); // 三个参数必须传入
obj: 目标对象
prop: 需要定义或修改的属性名字(属性都需要用引号括起来)
descriptor: 目标属性所拥有的特性
{
// 里面可以写四个值
value: 设置属性值,
writable: 值是否可以重写. true | false 默认 false 不允许重写
enumerable: 目标属性是否可以被枚举
configurable: 目标属性是否可以被删除或者修改特性
}

var number = 18

// (1)defineProperty高级属性
Object.defineProperty(obj,'age',{
// 当有人修改对象的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get(){
return number
}
})
// 将number的值和对象obj的age建立联系,number修改后
// 如果有人在访问obj.age,就调用get方法,返回最新的number值

// (2) set
Object.defineProperty(obj,'age',{
// 当有人修改obj的age属性,set函数(setter)就会被调用,且会收到修改的具体值
set(value){
console.log('有人修改了age属性,且值是',value); // 最后结果是不会被修改,因为age值一直是 number 值
// 修改number的值,此时 age 值被修改了
number = value
}
})

2.Object.keys()

1
2
3
// 用于获取对象自身所有的属性
Object.keys(obj)
// 返回:一个由属性名组成的数组

函数进阶

函数的定义和调用

定义函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// (1) 函数定义方式

// 自定义函数 (命名函数)
function fn() {};
// 函数表达式(匿名函数)
var fn = function() {};
// 利用 new Function('参数1','参数2','函数体')
var fn = new Function('a','b','console.log(a+b)')
fn(1,2); // logs 3
// 传入的参数 a,b 和 函数体都必须使用 引号'' 括起来
// Function 里面的参数都必须是字符串格式

// 函数也属于对象 fn 也有 __proto__
console.log(fn instanceof Object); //logs true

调用六种函数的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// (2) 调用
1. 普通函数
function fn(){
console.log('simplelife')
}
fn(); | fn.call()
2. 对象的方法
var o = {
getName:function(){
console.log('simplelife')
}
}
o.getName();
3. 构造函数
function Star(){};
new Star();
4. 绑定事件函数
btn.onclick = function(){}; // 点击调用
5. 定时器函数
setInterval(function(){},1000) // 1s 调用一次
setTimeout(function(){},1000) // 1s后执行
6. 立即执行函数
(function(){
console.log('simplelife') // 不需要调用,立即执行
})()
(function(){}()) // 另一种写法

this

改变函数内部 this 指向

js专门提供了一些函数方法来帮我们更优雅处理函数内部 this 的指向问题,常用的 bind()、call()、apply()

  • call(thisArg) 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

注意:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组

1
语法: function.call(thisArg, arg1, arg2, ...)

call() 提供新的 this 值给当前调用的函数/方法。你可以使用 call 来实现继承:写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。

使用 call 方法调用父构造函数

在一个子构造函数中,你可以通过调用父构造函数的 call 方法来实现继承,类似于 Java 中的写法。下例中,使用 FoodToy 构造函数创建的对象实例都会拥有在 Product 构造函数中添加的 name 属性和 price 属性,但 category 属性是在各自的构造函数中定义的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Product(name, price) {
this.name = name;
this.price = price;
}

function Food(name, price) {
Product.call(this, name, price);
this.category = 'food';
}

function Toy(name, price) {
Product.call(this, name, price);
this.category = 'toy';
}

var cheese = new Food('feta', 5);
var fun = new Toy('robot', 40);
  1. apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
1
2
语法:func.apply(thisArg, [argsArray])
// apply 内部会将 数组解构

applycall() 非常相似,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量(array literal),如 fun.apply(this, ['eat', 'bananas']),或数组对象, 如 fun.apply(this, new Array('eat', 'bananas'))

  1. bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
1
2
3
4
5
// 语法
function.bind(thisArg[, arg1[, arg2[, ...]]])

// 返回值
// 返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。

配合 setTimeout

在默认情况下,使用 window.setTimeout() 时,this 关键字会指向 window (或 global)对象。当类的方法中需要 this 指向类的实例时,你可能需要显式地把 this 绑定到回调函数,就不会丢失该实例的引用。

1
2
3
4
5
6
7
setTimeout(function(){
console.log(this) // 没有做处理,打印的this 是window对象
},200)
// 绑定
setTimeout(function(){
console.log(this) // 此时 this 是传入的 this
}.bind(this),200)

总结

区别点:

  1. call 和 apply 会调用函数,并且改变函数内部this指向
  2. call 和 apply 传递的参数不一样,call 传递参数 aru1,aru2……,apply 必须数组形式[arag]
  3. bind 不会调用函数,可以改变函数内部 this 指向

主要应用场景:

  1. call 经常做继承,去使用父构造函数的成员
  2. apply 经常跟数组有关系。比如借助于数学对象实现数组最大值和最小值
  3. bind 不调用函数,但是还想改变 this 指向。比如改变定时器内部的 this 指向

严格模式

js 除了提供正常模式外,还提供了严格模式(strict mode)。ES5的严格模式是采用最有限制性js变体的一种方式,即在严格的条件下运行js代码

  1. 消除了js语法的一些不合理、不严谨之处、减少了一些怪异的行为
  2. 提高编译器效率,增加运行速度
  3. 禁用了在 ECAMScript 的未来版本中可能定义的语法,为未来新版本的js做好铺垫。比如不能使用 class、enum、export、super、为变量名

严格模式可以应用到整个脚本或个别函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// (1) 为脚本开启严格模式
// 在所有语句之前放一个特定语句
<script>
'use strict';
</script>
<script>
(function(){ // 立即执行函数
'use strict';
}())
</script>

// (2) 为个别函数添加严格模式
<script>
function fn(){
'use strict';
}
// 以下函数都是普通模式
function fn2(){

}
</script>

严格模式的变化

  1. 变量必须声明,不能是直接赋值num = 10 正常模式不会报错可以打印 num 值。但严格模式下必须 var num = 10 必须声明
  2. 严禁删除已经声明好了的变量
  3. 全局作用域中函数中的 this 是 undefined。正常模式是 window
  4. 构造函数不加new 调用,this会报错。this指向的是 undefined
  5. 定时器没有变化,还是指向 window 对象

函数变化

  1. 不允许有重名参数

  2. 严格模式禁止了不在脚本或者函数层面上的函数声明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    "use strict";
    if (true) {
    function f() { } // !!! 语法错误
    f();
    }

    for (var i = 0; i < 5; i++) {
    function f2() { } // !!! 语法错误
    f2();
    }

    function baz() { // 合法
    function eit() { } // 同样合法
    }

高阶函数

高阶函数是对其它函数进行操作的函数,它接收函数作为参数(回调函数)或将函数作为返回值输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 高阶函数运用案例
function fn(a,b,callback){
console.log(a+b);
callback && callback();
}
fn(1,2,function(){
console.log('我是回调函数');
})

// 返回函数
function fn(){
return function(){
console.log('返回的函数')
}
}
var f1 = fn(); // 接收
console.log(f1)

闭包

什么是闭包(closure):闭包指有权访问另外一个函数作用域中变量的函数

作用:延伸了变量的作用范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 闭包就是就是函数,在全局如何使用局部变量
function fn(){
var num = 10;
function fun(){
console.log(num)
}
}
// 正常情况全局无法得到局部的变量,现在让函数 fn 返回 fun函数
return fun

var f = fn(); // 得到了 fun函数
f(); // 使用了局部变量

// 直接 return function(){

}

思考题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// (1)
var name = 'The Window';
var object = {
name: 'My Object',
getNameFunc:function(){
return function(){
return this.name
}
}
}
console.log(object.getNameFunc()()) //logs what 'The Window'


// (2)
var name = 'The Window';
var object = {
name: 'My Object',
getNameFunc:function(){
var that = this;
return function(){
return that.name
}
}
}
console.log(object.getNameFunc()()) //logs what 'My Object'
1
2
3
4
5
6
7
8
9
object.getNameFunc()() => 等同于 立即执行函数
var f = object.getNameFunc()
f = function(){
return this.name
}
f() // 立即执行函数 this 指向 window

// (2)
形成了一个闭包,有使用局部变量 that

递归

如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。

递归里面必须加退出条件,不然死循环就会栈溢出

案例

1
2
3
4
5
6
7
8
9
// 利用递归函数求斐波那契数列(兔子序列)1、1、2、3、5、8、13、21
// 用户输入 序列 ,求出当前值
function fb(n){
if(n === 1 || n === 2){
return 1;
}
return fb(n-1) + fb(n-2)
}
console.log(fb(6)) //logs 8

浅拷贝 和 深拷贝

  • 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用
  • 深拷贝拷贝多层,每一级别的数据都会拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 浅拷贝
var obj = {
name: 'andy',
msg:{
age: 18
}
};
var o = {};
for(var k in obj){
// k 是属性名 obj[k] 属性值
o[k] = obj[k];
}
console.log(o)
// 浅拷贝无法深层拷贝
// 只是会拷贝深层地址 比如打印 o 对象,会有 age 属性
// 在 o 对象上更改 age 属性,同样也会更改打牌 obj 对象,因为是同一个地址

ES6 新增浅拷贝语法糖

1
2
3
Object.assign(obj,targetObject)
// targetObject:需要拷贝的对象
// obj: 拷贝到这个对象里面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 深拷贝 函数封装
var obj = {
id: 1,
name: 'andy',
msg:{
age: 18
},
color: ['pink','red']
};
var o = {};

function deepCopy(newobj,oldobj){
for(var k in oldobj){
// 判断我们的属性值属于哪种数据类型
// 1. 获取属性值 oldobj[k]
var item = oldobj[k];
// 2. 判断这个值是否是数组
if(item instanceof Array){
newobj[k] = [];
deepCopy(newobj[k],item);
} else if(item instanceof Object){
// 3. 判断这个值是否是对象
newobj[k] = {};
deepCopy(newobj[k],item);
} else {
// 4. 属于简单数据类型
newobj[k] = item;
}
}
}

// 之所以先判断是否是 数组,因为 数组也属于对象
var arr = []
console.log(arr instanceof Object) //logs true

正则表达式

正则表达式概述

正则表达式是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象

通常用来检索、替换那些符合某个模式的文本,例如表单验证、还用于过滤页面内容中的敏感词

特点:灵活性、逻辑性和功能性非常的强

​ 极为简单的控制复杂的字符串

正则表达式在 JavaScript中的使用

1
2
3
4
5
6
// 创建正则表达式
// (1) 通过调用 RegExp 对象的构造函数创建
var reg = new RegExp(/表达式/);

// (2) 通过字面量创建
var reg = /123/;

测试正则表达式 test

test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串

正则表达式的特殊字符

一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单和特殊字符的组成,比如/ab*c/。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^、$、+等

边界符 ^ $

^ 表示匹配行首的文本(以谁开始)

$ 表示匹配行尾的文本(以谁结束)

如果两者边界符出现,表示必须是精度匹配

1
2
3
4
var reg = /^simplelife$/;
console.log(reg.test('simplelife')); //logs true
// 表示必须只能是字符串 'simplelife'

字符类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//字符类:[] 表示有一些列字符可供选择,只要匹配其中一个就可以了
var rg = /[abc]/; // 三选一 只能是其中一个
console.log(rg.test('a')) //logs true
console.log(rg.test('c')) //logs true
console.log(rg.test('abc')) //只能匹配一个 abc 返回就是 false

// 方括号符 [-]
var reg = /^[a-z]$/ //小写a到z 任何一个字母都行 返回 true

// 字符组合
var reg = /^[a-zA-Z]$/ // 26个英文字母都行
var reg = /^[a-zA-Z0-9_-]$/ //可以使用数组 下划线 横杠-

// 取反字符 如果中括号中存在 ^ 表示取反
var reg = /^[^a-zA-Z0-9_-]$/ //表示不允许使用这些

量词符

量词符同于设定某个模式出现的次数

1
2
3
4
5
6
7
8
9
10
11
12
13
// * 号 相当于 >= 0 可以出现0次或则很多次
var reg = /^a*$/;
console.log(reg.test('')) //logs true
console.log(reg.test('a')) //logs true
console.log(reg.test('aa')) //logs true

// + 号 相当于 >= 1 可以出现 1 次或者很多次

// ? 号 相当于 1 || 0 可以出现 1 次或者 0 次

// {3} 号 重复三次
// {3,} >= 3 重复三次或以上
// {3,16} 大于等于3 并且 小于等于16

总结 中括号 大括号 小括号

1
2
3
4
5
6
7
8
9
var reg = /^abc{3}$/ // 它只是让c重复三次 abccc

// 小括号 表示优先级
var reg = /^(abc){3}$/ //表示重复 abc 3次 abcabcabc

1. 大括号 量词符 里面表示重复次数
2. 中括号 字符集合 匹配方括号中的任意字符
3. 小括号 表示优先级
4. 或者号 | 就一个这个符号 表示或者

预定义类

预定义类指的是某些常用模式的简写方式

预定义类 说明
\d 匹配0-9之间的任一数字,相当于[0-9]
\D 匹配所有0-9以外的字符,相当于 [^0-9]
\w 匹配任意的字母、数字、下划线,相当于 [^A-Za-z0-9_
\W \w 取反
\s 匹配空格(包括换行符、制表符、空格符等)
\S 匹配非空格字符

常用正则表达式(测试正则匹配):https://c.runoob.com/front-end/854/

正则表达式中的替换

1
2
3
4
5
6
7
// 替换 replace
var str = 'andy和red'
var newstr = str.replace('andy','baby') // 将 andy 替换 baby
console.log(newstr) // baby和red

// 加入正则表达式
var newstr = str.replace(/andy/,'bady') //结果一样

PS:上面这种替换方式之后替换一次

正则表达式参数

/表达式/[switch]

switch 按照什么样的模式来匹配,有三种值

  • g:全局匹配
  • i: 忽略大小写
  • gi:全局匹配 + 忽略大小写

ES6

1.箭头函数

箭头函数不绑定 this 关键字,箭头函数中的 this,指向的是 函数定义位置的上下文 this

1
2
3
4
5
6
7
8
9
10
var obj = {
name: '箭头函数',
age: 18,
say: ()=>{
console.log(this.age);
}
}

// 打印结果为 undefined 未定义
// 对象没有作用域,箭头函数this指向全局 在全局下没有 age

2.伪数组转化成真正的数组

1
2
3
var divs = document.querySelectorAll('div') // 获取所有的div 得到伪数组
// 使用扩展运算符 ... 将divs转化成 newarr 数组
var newarr = [...divs]
1
2
3
4
5
6
7
8
// 使用数组内置方法转化
var ary = Array.from(divs)
// 同样可以将为伪数组转化为数组

// Array.from 还有第二个参数是函数
var ary = Array.from(divs,(item)=>{
// 代码体 item是当前元素
})

3.Array的扩展方法

find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Array.prototype.find((item,index)=>{
// 符合条件的返回
return item == 'simplelife'
})

var ary = [{
id:1,
name: '张三'
},{
id:2,
name: '李四'
}]

let target = ary.find((item,index)=> item.id == index)

findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。

1
2
3
const array1 = [5, 12, 8, 130, 44];
const isLargeNumber = (element) => element > 13;
console.log(array1.findIndex(isLargeNumber));

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false

1
2
3
4
5
6
7
8
9
10
11
const array1 = [1, 2, 3];

console.log(array1.includes(2));
// true
const pets = ['cat', 'dog', 'bat'];

console.log(pets.includes('cat'));
// true

console.log(pets.includes('at'));
// false

4.模板字符串

1
2
3
4
5
6
7
8
9
// 模板字符串可以解析变量 
// 在模板字符串中可以调用函数,显示函数返回值
var str = 'siplelife';
var name = `你是谁:${str}`; //logs '你是谁:simplelife'

function fn(){
return 'simplelife'
}
var name = `你是谁:${fn()}`; // logs 同上

5.String 的扩展方法

1
2
// (1) startsWith() 表示参数字符串是否在原字符串的开头,返回布尔值
// (2) endsWith() 表示参数字符串是否在原字符串的结尾,返回布尔值
1
2
3
4
// repeat() 方法
// 表示将原字符串重复 n 次,返回一个新字符串
'x'.repeat(3) // 'xxx'
'hello'.repeat(1) // 'hellohello'

6.Set 数据结构

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Set 本身是一个构造函数,用来生成 Set 数据结构
const s = new Set();
console.log(s.size) //logs 0 表示是一个空的

const s1 = new Set(['a','b']);
console.log(s1.size) //logs 2

// 利用 Set 可以做数组去重,因为 Set 不能存储重复的值
const s1 = new Set(['a','b','b','a','c']);

var arr = [...s1] // 使用扩展运算符,转换成数组类型
console.log(arr)
// 打印 s1 的结果
Set(3) {'a', 'b', 'c'}
[[Entries]]
0: "a"
1: "b"
2: "c"
size: 3
[[Prototype]]: Set

实例方法

  • add(value):添加某一个值,返回 Set 结构本身
  • delete(value):删除个值,返回boolean值
  • has(value):返回一个布尔值,表示该值是否为Set成员
  • clear():清除所有成员,没有返回值
1
2
3
4
5
// 遍历
// Set结构的实例与数组一样,也拥有 forEach 方法,用于对每个成员执行某种操作,没有返回值

var s = new Set(['a','b','c']);
s.forEach(value => console.log(value))