前端入门第八步:高级特性、ES6 新特性

514 阅读9分钟

这一节主要是讲思路

[TOC]

面向对象

对象创建方法

方法一:new Object()

let person1 = new Object();
person1.name = "Joe";
person1.age  = 20;
person1.getAge = function () {
    console.log(this.age);
}

方法二:字面量化

let person1 = {
    name   : "Joe" ,
    age    : 20 ,
    getAge : function () {
        console.log(this.age);
    }
}

方法三:工厂函数封装

// 实质上就是将上面两个方法封装起来
// 封装对象
function Person(name,age) {
    let person = new Object();
    person.name = name;
    person.age  = age;
    person.showName = function () {
        console.log(this.name);
    }
    return person;
}
let person1 = new Person("Joe",20);

// 封装字面量
function Car(name,color) {
    return {
        name : name,
        color  : color,
        showName : function () {
            console.log(this.name);
        }
    }
}
let car1 = new Car("BMW","red");

对象内部属性及方法

通俗点说,属性就是用于存放数据的,方法是执行函数

// 属性
person1.name;
// 方法
car1.showName();

原型对象

上述创建对象过程中,每个新对象都会在内部“写”一套共同方法,这些方法会一遍又一遍出现,非常消耗内存资源

为了解决节约内存,我们简单将对象中所有东西分为公共部分和私有部分

公共部分共用一套,私有部分自己一套,这样能有效降低内存消耗

prototype

// 创建私有属性
function Person(name,age) {
      this.name = name;
      this.age = age;      
}

//  创建公共属性、方法
Person.prototype= {
    constructor:Person, // 构造函数
    version:"1.1",		// 对象的静态变量
    showName:function () {
        console.log(this.name);
    }
}
// 注意:公共部分的属性称为 静态成员 ,意思是所有对象共同访问的属性
//		私有部分的属性称为 实例成员 ,意思是只有自身单独访问的属性

//  创建公共属性、方法还可以这么写
//  Person.prototype.showAge = function(){};
//  能在之前写的公共部分中添加新的属性、方法

拓展内置对象的公共部分

常用的内置对象有: Array、Function、Math、Object

可以设置一些新的方法来拓展原来内置对象的功能

Array.prototype.getSum = function () {
    let sum = 0;
    for (let i = 0 ; i < this.length ; i++ )
        sum+=this[i];
    return sum;
}
console.dir(Array.prototype);
let arr = [1,2,3,4,5,6,7,8,9,10];
console.log(arr.getSum());

// 注意:拓展不能这样写(如下)
Array.prototype = {
    getSum:function() {
        let sum = 0;
        for (let i = 0; i < this.length; i++)
            sum += this[i];
        return sum;
    }
}
/* 这样不会拓展成功 
 * 原因:这样写会将其他原有方法、属性全部覆盖
 * js为了防止内置对象公共部分被污染,会自己拒绝这样拓展方式
 */

继承、函数进阶使用

面向对象特点

封装、继承、多态(抽象)

继承

继承意思为将一个对象内的东西给另一个对象,前者称为父级,后者称为子级

实体对象继承

let parent = {
    house: "五大街645号",
    shop : "七大街663号",
    car  : "京AXXXXXXX"
}
let child1 = {};
let child2 = {};

// 方法一:for循环
for (let i in parent)
    if (child1[i])
        continue;
    else
        child1[i]=parent[i];
console.log(child1);

// 方法二:函数封装
function extend( parent , child ) {
    for (let i in parent)
        if (child[i])
            continue;
        else
            child[i]=parent[i];
}
extend(parent,child2);
console.log(child2);

原型对象继承

child.prototype = parent.prototype;
// 记得修改构造函数,不然会使用父级的构造函数
child.prototype.constructor = Child;

call方法

call方法调用时,有两个功能:

  1. 更改函数内部this的指向(第一个传参)
  2. 调用函数执行内部代码(之后的传参)
function fun() {
    console.log(this.age);
}
let person = {
    name:"小明",
    age:20
}
fun.call(person);

继承父级对象属性

合理利用call方法将实体化属性写入子级中

function Car( color , carNum ) {
    this.color = color;
    this.carNum= carNum;
}
function Geely( color , carNum , carSize ) {
    Car.call(this,color,carNum);
    this.carSize = carSize;
}
let car1 = new Geely("red","京AXXXXXXX","XMC-8");
console.log(car1);

继承父级对象方法

继承时要注意:父级自身的constructor不能继承

// 方法一:for循环一步一步继承
for (let i in Parent.prototype)
/*  若子级自带的方法(包括constructor),不继承  */
    if (Child.prototype[i])
        continue;
	else
        Child.prototype[i]=Parent.prototype[i];

// 方法二:原型继承
Child.prototype = Parent.prototype;
Child.prototype.constructor = Child;
// 注意:原型继承会将Parent的属性写入原型对象中

组合继承

继承父级对象的属性和方法,参考上面

函数进阶使用

注意: 函数fun不加括号为字符串输出,即使赋值也无法执行

函数声明

var fn = function(){};
// 调用必须在声明后面

function fun(){}
// 调用与声明无顺序关系

// 通过创建对象方式声明
var Fun = new Function('a','b','console.log(a+b)');
Fun(24,42);
// 最后一个传参为函数内部程序结构

调用函数和函数中的this

  1. 普通函数 通过给函数名或者变量名添加 () 方式调用,其中this默认指向window

  2. 构造函数 通过关键词new进行调用,内部的this指向创建的实例对象

  3. 对象方法 通过 对象.函数() 方法进行调用,内部的this指向对象自身

  4. 事件函数 通过触发事件进行调用,内部的this指向事件源

  5. 定时和延时器事件函数,自动执行,this默认指向window

调用方式非严格模式下备注
普通函数window严格模式下为undefined
构造函数实例对象原型方法中this也是实例对象
对象方法自身对象
事件绑定事件对象
延时器函数window
定时器函数window

call方法

call方法调用时,有两个功能:

  1. 更改函数内部this的指向(第一个传参)
  2. 调用函数执行内部代码(之后的传参)
fun.call(this,a,b,c ... );

apply方法

apply方法调用时,有两个功能:

  1. 更改函数内部this的指向(第一个传参)
  2. 调用函数执行内部代码(之后的传参)

用法与call差不多,但写法与call有区别

var fn = fun.apply(this,[a,b,c ... ]);

bind方法

bind方法调用时,有两个功能:

  1. 确定函数内部this的指向(第一个传参)
  2. 绑定函数,这样执行函数时会去访问被绑定函数
var fn = fun.bind(o,a,b,c ... );

函数对象

成员说明
arguments传入实参集合
arguments.callee()函数本身,arguments的一个属性
fn.caller函数的调用者,若是全局调用,返回null
fn.length形参个数
fn.name函数名称

高阶函数使用

  1. 函数作为另一个函数的传参
function fun(fn) {
    console.log("这是fun函数");
    fn();
}

function fn() {
    console.log("我是fn函数");
}

fun(fn);

  1. 函数作为返回值直接执行
function fun1( num ) {
    return function fun2(number) {
        console.log(num+number);
    }
}
let fun = fun1(100);
fun(20);

函数闭包

函数对其内部作用域内所有部分进行封闭,在函数外无法调用函数内部属性及方法

function outer() {
    var a = 10;
}
outer();
console.log(a);

正则表达式

作用: 1. 给定的字符串是否符合正则表达式的过滤逻辑(匹配)

​ 2. 可以通过正则表达式,从字符串中获取我们想要的特定部分(提取)

​ 3. 强大的字符串替换能力(替换)

注意:JavaScript中正则表达式也是个对象

正则表达式语法

创建正则表达式

// 方法一:创建 正则的字面量
var reg1 = /abc/;

// 方法二:通过 正则的构造函数 创建
var reg2 = new RegExp("abc");

字符串与正则表达式

字符串的方法

方法说明
split()根据匹配字符串切割父字符串
match()使用正则表达式与字符串相比较
返回一个包含匹配结果的数组
search()对正则表达式或指定字符串进行搜索
返回第一个出现的匹配项的下标
replace()用正则表达式和字符串直接比较
然后用新的子串来替换被匹配的子串
// split方法
let str1 = "aa bbb    c dd eeeeee";
console.log(str1.split(" "));
// (8) ["aa", "bbb", "", "", "", "c", "dd", "eeeeee"]
// 使用正则表达式就不会去空字符串
console.log(str.split(/\s+/));
// (5) ["aa", "bbb", "c", "dd", "eeeeee"]

// match方法
let str2 = "abcdefgh";
console.log(str2.match("dee"));
//null  
// 没有返回空
console.log(str2.match(/def/));
//["def", index: 3, input: "abcdefghabcdefgh", groups: undefined]
// 有就返回第一个匹配结果的数组
console.log(str2.match(/def/g));//  g表示全局变量,意思是搜索全部
// (3) ["def", "def", "def"]

// search方法
let str3 = "abcdefghabcdefgh";
console.log(str3.search("dee"));
//-1
// 没有结果返回-1
console.log(str3.search(/def/));
//3
// 有结果返回第一个匹配的序号

let str4 = "ajklsdajakdslfjmasdsakdadkasmlda";
console.log(str4.replace("a","A"));
//Ajklsdajakdslfjmasdsakdadkasmlda
// 只改变第一个匹配部分
console.log(str4.replace(/a/g,"A"));// 加上全局
//AjklsdAjAkdslfjmAsdsAkdAdkAsmldA
// 改变全部匹配部分 

正则表达式的方法

方法说明
exec()在目标字符串中执行一次正则匹配操作
test()测试当前正则是否能匹配目标字符串
匹配:true;未匹配:false
let reg = /abc/;
let str = "abcdefgh";
console.log(reg.test(str));
//true
console.log(/ac/.test(str));
//false

str = "abcdefghabcdefghabcdefgh";
console.log(reg.exec(str));
//["abc", index: 0, input: "abcdefghabcdefghabcdefgh", groups: undefined]
console.log(/abc/g.exec(str));// 即使设置全局变量,也只匹配第一个
//["abc", index: 0, input: "abcdefghabcdefghabcdefgh", groups: undefined]

正则表达式的组成

正则表达式分为 普通字符特殊字符(又称元字符)

普通字符:大小写字母、数字

特殊字符

特殊字符说明
\t制表符
\n回车符
\f换页符
\b空格

字符集

简单类:正则的多个字符对应一个字符,可以用 [] 括起来,让 [] 这个整体对应一个字符

​ o[abc]t ———— oat、obt、oct

范围类:类型又相同时,中间加个横线表示范围:[0-9]、[a-z]、[A-Z]

​ id[0-9] ————id0、id5

负向类:[]内部最前面加个原四分进行取反,表示匹配内容不为[]内部内容:[^0-9]

o[^0-9]t————oat、o?t、ott

组合类: 允许用中括号匹配不同类型的单个字符:[0-9a-z];

​ o[0-9a-z]t————o0t、oat、ozt

修饰符

修饰符说明例子
g执行全局匹配/[0-9]/g
i执行对大小写不敏感的匹配/abc/i
// 之前展示过全局匹配,这里只展示大小写不敏感
console.log(/abc/i.test("ABCD"));
// true

边界

边界说明例子
^开头,不能出现在左中括号后面/^123/
$结尾,只能写在后面/2345$/
// ^开头
console.log(/^hello/.test("hello world"));
//true
console.log(/^world/.test("hello world"));
//false

// $结尾
console.log(/hello$/.test("hello world"));
//false
console.log(/world$/.test("hello world"));
///true

// 结合
console.log(/^hello\s+world$/.test("hello world"));
//true

预定义类

预定义字符同义转换说明
.[^\n\r]除换行和回车外的任意字符
\d[0-9]数字字符
\D[^0-9]非数字字符
\s[ \t\n\x0B\f\r]空白字符
\S[^ \t\n\x0B\f\r]非空白字符
\w[a-zA-Z_0-9]单词字符(字母、数字、下划线)
\W[^a-zA-Z_0-9]非单词字符

量词

量词属性说明
{n}硬性量词对应出现零次或者n次
{n,m}软性量词至少出现n次但不超过m次(中间不能有空格)
{n,}软性量词至少出现n次
?软性量词出现零次或一次
*软性量词出现零次及以上
+软性量词至少出现一次

分组

通过()与量词组合,表示()内字符串重复次数

console.log(/(bye){2}/.test("byebye"));
//true
console.log(/(bye){2}/.test("byeBYE"));
//false

或操作符

|表示或者关系

console.log(/a|bcd/.test("aaa"));
//true
console.log(/a|bcd/.test("bbcd"));
//true

分组反向引用

第一种用法\N:匹配\N第N段字符串作为正则内容进行内部引用

console.log(/^([a-z]{3})\1$/.test("byebye"));
// true
// 引用第一段"bye"字符串作为正则内容匹配之后的内容
console.log(/^([a-z]{3})\1$/.test("byelie"));
//false

第二种用法$N:匹配\N第N段字符串作为正则内容进行外部引用

var str = "123*456".replace(/^(\d{3})\*(\d{3})$/,"$2*$1");
console.log(str);
//456*123

匹配中文字符

/^[a-z\u4e00-\u9fa5]+$/

ES6(ES2015)语法规则

ES5为09年规范的

ES2015及其之后的更新的版本都俗称为 ES6

作用域

在ES5时,作用域有两种:全局作用域、函数作用域

ES2015之后,作用域分三类:全局作用域、函数作用域、块级作用域

新增的 块级作用域

// 简单来说,块就是 {} 
if (true) {

}

声明方式

ES5:var

缺点:在window对象内声明的,无论在那里声明都是全局变量

ES6:const 、 let

let: 区域性声明,只能在作用域内使用作用域外无法访问

const: 常量/恒量,声明时必须赋值,赋值后无法修改,相当于只读状态下的let

建议:不用var,主用const,let协作

解构

数组的解构

数组解构的意思相当于获取数组某个位置上面的值

const arr  = [0,1,2,3,4,5,6,7];
// 常规方法
const num1 = arr[0];
const num2 = arr[1];

// 解构方法
const [n1,,]=arr;
// 若是取第几个,则在前面标注几个逗号,后面的逗号可以省略
console.log(n1);
//0
const [,,,n2]=arr;
console.log(n2);
//3
const [,,n3,n4,n5]=arr;
// 可以一次声明多个常量/变量

// 解构中的 ...
const [,N] = arr;
// ... 会自动从当前位置匹配到末端位置
console.log(N);
//(7) [1, 2, 3, 4, 5, 6, 7]

对象解构

对象解构的意思相当于获取对象某个成员上面的值

const obj = {
const obj = {
    name:"张三",
    age:20,
    place:"云南"
};
// 常规方法
const name = obj.name;
const age  = obj.age;

// 解构方法
const {name:name1,age:age1,place} = obj;
// 写法有两种:
// 	第一种是 属性名:变量/常量
//	第二种是 属性名与常量/变量名称一致
//  但是一般建议使用第一种
console.log(name1,age1,place);
//张三 20 云南

模板字面量

模板字面量与字符串

可以用模板字面量来代替字符串表达

const str = `this 
    is a \`string`
console.log(str)
//this 
//    is a `string

// 也可以将其他变量/常量直接写入,甚至可以运算以及方法函数调用
const name = "张三";
console.log(`${name}今年${10+12}岁,幸运数字为${parseInt(Math.random()*(0+300))}`);
//张三今年22岁,幸运数字为272

模板字面量与函数调用

可以用模板字面量来执行函数

const str = console.log`hello JavaScript`;
//["hello JavaScript", raw: Array(1)]
// 将模板内容作为执行时输入参数

function fun( string , name , age ) {
    return string[0] + name + string[1] + age + string[2] + (age>18? "It's an adult.":"It's a child");
}
let name = "Joe";
let age  = 8;
let nnn = fun`hello , ${name} is a ${age} years old boy.`;
console.log(nnn)
//hello , Joe is a 8 years old boy.It's a child

模板字面量与方法

方法说明
.startsWith( str )是否以 str 字符/字符串 开头
返回值为 布尔型
.endsWith( str )是否以 str 字符/字符串 结尾
返回值为 布尔型
.includes( str )是否存在 str 字符/字符串
返回值为 布尔型

函数新特性

参数默认值

function fun( string = "hello world" ) {
    console.log(string);
}

fun("1223");
//1223
fun();
//hello world

剩余参数

function fun(n,...args) {
  console.log(args)
}
fun(1,2,3,4)
//(3) [2, 3, 4]

箭头函数

const fun = (a,b,...arr) =>{
    console.log(a,b,arr);
}
fun(1,2,3,4,5,6,7);
//1 2 (5) [3, 4, 5, 6, 7]

// filter方法
const arr = [1,2,3,4,5,6,7,8];
console.log(arr.filter(i=>i%3));
//(6) [1, 2, 4, 5, 7, 8]

箭头函数与this

// 箭头函数没有prototype,函数自身没有this
// 定义this指向时,会继承自外层的第一个普通函数的this
const person = {
  name: "tom",
  sayHi: function () {
    setTimeout(() => {
      // this指向对象本身
      console.log(`hi,my name is ${this.name}`)
    },1000);
  }
}
person.sayHi()
//hi,my name is tom

对象新特性

字面量增强

const bar = "bar"
const age = "age"
const obj = {
  name: "tom",
  // 可以直接使用外部变量进行赋值,且变量名会变成对象的属性名
  bar,
  sayHi () {
    console.log('hi')
    console.log(this)
  },
  // 计算属性名
  [1+2]: 18
}
obj[age] = 18;
console.log(obj.bar);
//bar
obj.sayHi();
//hi
//{3: 18, name: "tom", bar: "bar", age: 18, sayHi: ƒ}
console.log(obj[3]);
//18

扩展方法

// Object.assign 方法
// 作用:将一个或者多个对象组合分配到第一个对象中
// Object.assign( target , ...source )
const source1 = {
  a: 123,
  b: 123
}
const source2 = {
  b: 678,
  d: 789
}
const target = {
  a:456,
  c:789
}
const result = Object.assign(target,source1,source2)
console.log(target)
//{a: 123, c: 789, b: 678, d: 789}
/* 组合原则:
 * 1.第一个对象中的属性/函数在组合中未能出现,该属性/方法不会变
 * 2.若在后续对象中出现相同属性/方法,靠后的会覆盖之前的
 * 3.返回值为第一个对象
 */
console.log(target === result)
//true

// 拓展思路
// 1.复制对象
const obj = target.assign({},target);
// 2.简化参数(构造函数为例)
function child( parent , others ) {
    this.assgin(this , parent , others);
}

判断

// Object.is 方法
console.log(NaN===NaN);
//false
console.log(Object.is(NaN,NaN));
//true
console.log(+0===-0);
//true
console.log(Object.is(+0,-0));
//false

class创建对象

传统构造对象时要创建要先写构造函数,再写原型对象

可以通过class 一次性创建到位

class Person {
  // 构造函数
  constructor (name, age) {
    this.name = name;
    this.age = age;
  }
  // 静态成员
  sayHi () {
    console.log(`hi,my name is ${this.name}`);
  }
}
let p1 = new Person("Joe",20);
console.log(p1);

静态方法

class Person {
  constructor (name, age) {
    this.name = name;
    this.age = age;
  }
  sayHi () {
    console.log(`hi,my name is ${this.name}`)
  } 
// 只能在class内使用
  static create (name,age) {
    console.log(this)
    return new Person(name,age)
  }     
}

类的继承

ES6中加入关键词 extends ,方便继承

继承的父类会储存在子类静态属性中的静态属性内

class Person {
    constructor (name, age) {
        this.name = name;
        this.age = age;
    }
    sayHi () {
        console.log(`hi,my name is ${this.name}`)
    }
}
class Student extends Person {
    constructor (name,age,number) {
        super(name,age)
        this.number = number
    }
    hello () {
        super.sayHi()
        console.log(`学号是 ${this.number}`)
    }
}
let s1 = new Student("Joe",20,13333);
console.log(s1);

内置对象

Set对象

属性/方法说明
size属性,返回Set中值的个数
add( value )方法,在Set末尾添加一个新元素
clear()方法,清空Set内所有元素
delete( value )方法,移除Set内值相同的元素,返回布尔型
true表示元素存在且被移除,false表示不存在
forEach( callbackFn[, thisArg] )方法,Set对象中的每一个值调用一次callBackFn(方法)
has( value )方法,判断Set对象中是否有该值
values()方法,返回一个新的Set对象,包含Set对象中所有元素
keys()方法,效果同上
let mySet = new Set();

mySet.add(1); // Set [ 1 ]
mySet.add(5); // Set [ 1, 5 ]
mySet.add(5); // Set [ 1, 5 ]
mySet.add("some text"); // Set [ 1, 5, "some text" ]
let o = {a: 1, b: 2};
mySet.add(o);

mySet.add({a: 1, b: 2}); // o 指向的是不同的对象,所以没问题

mySet.has(1); // true
mySet.has(3); // false
mySet.has(5);              // true
mySet.has(Math.sqrt(25));  // true
mySet.has("Some Text".toLowerCase()); // true
mySet.has(o); // true

mySet.size; // 5

mySet.delete(5);  // true,  从set中移除5
mySet.has(5);     // false, 5已经被移除

mySet.size; // 4, 刚刚移除一个值

console.log(mySet);
// logs Set(4) [ 1, "some text", {…}, {…} ] in Firefox
// logs Set(4) { 1, "some text", {…}, {…} } in Chrome

Map对象

属性/方法说明
size属性,返回对象键值对的数量
set( key, value )方法,设置对象中的键和键的值
其中键可以是函数,对象等
get( key )方法,查询特指的键的值,没有返回undefined
has( key )方法,返回布尔值,表示Map中是否含有对应的值
delete( key )方法,移除对应的键值对,返回true;若不存在返回false
clear()方法,移除Mao内所有键值对
forEach( callbackFn[,thisArg] )方法,按插入顺序,为 Map对象里的每一键值对调用一次callbackFn函数
values()方法,返回一个Iterator对象,按顺序插入Map对象的每个元素的值
let myMap = new Map();

let keyObj = {};
let keyFunc = function() {};
let keyString = 'a string';

// 添加键
myMap.set(keyString, "和键'a string'关联的值");
myMap.set(keyObj, "和键keyObj关联的值");
myMap.set(keyFunc, "和键keyFunc关联的值");

myMap.size; // 3

// 读取值
myMap.get(keyString);    // "和键'a string'关联的值"
myMap.get(keyObj);       // "和键keyObj关联的值"
myMap.get(keyFunc);      // "和键keyFunc关联的值"

myMap.get('a string');   // "和键'a string'关联的值"
                         // 因为keyString === 'a string'
myMap.get({});           // undefined, 因为keyObj !== {}
myMap.get(function() {}); // undefined, 因为keyFunc !== function () {}

新的数据类型

Symbol

作用:为对象添加一个独一无二的属性标识符

// 注意:Symbol没有构造函数,不能用new方法
let test = Symbol("123");
console.log(typeof test);
//symbol
console.log(Symbol("fff")==Symbol("fff"));
//false

// 使用for() 方法可以全局共享Symbol
console.log(Symbol.for(true)==Symbol.for("true"));
//true
// toStringTag

循环的新方法

for of

作用:访问(或者说是遍历)对象内部的值

const arr = [100,200,300,400,500];

for (const item of arr) {
    console.log(item);
}
//100
//200
//300
//400

ES2016

const arr = [1,true,NaN,23,'hello']
console.log(arr.indexOf(true))
//1
console.log(arr.indexOf(null))
//-1
console.log(arr.indexOf(NaN))
//-1
// NaN无法被识别

// includes 包括
// 作用:数值内是否有这个值,返回布尔型
console.log(arr.includes(NaN));

// 指数运算符 **
console.log(2**10);