typescript之ECMAScript5、ES6

发布时间 2023-06-24 12:49:55作者: 六只小猫

1、基础常识

1.1 简介

ECMAScript(/ekma/,简称ES)是javascript的标准化规范,其实javascript是ECMAScript的扩展语言。ES定义了一些标准的语法,JS对其进行了DOM、BOM扩展。

1.2 ES迭代史

ES6(又叫ES2015)是ECMA协会在2015年6月发行的一个版本,因为是ECMAScript的第6个版本,所以也称为ES6。以此类推,ES2019又叫ES10.

注:自从ES2015发行后,此后ES每次发行,不再使用版本号命名,而是使用发行年份。

运行环境:nodejs或chrome

不同的nodejs版本对ES2015的支持情况,https://node.green/

 我的是v18.16

1.3 知识点

1.3.1 自有属性与原型属性

对象有自身的属性以及原型上的属性(原型属性又分显式原型属性prototype隐式原型属性__proto__每个函数都有一个显示原型属性prototype,实例化为对象后都有一个隐式原型属性__proto__)自有属性原型属性的优先级高,原型属性不能重写原有属性的值。可以给对象增加与原型属性名相同的自有属性,后续会默认访问自有属性。

自有属性:构造函数本身的属性。通过对象的引用添加的属性。自有属性的优先级高于原型属性,原型属性不能重写自有属性
原型属性:通过原型所定义的属性。用于共享属性和方法。从原型对象中继承来的属性,一旦原型对象中属性值改变,所有继承自该原型的对象属性均改变。

  • 原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中去
  • 当我们访问对象的一个属性或者方法时,它会先在对象自身中寻找,如果找到要查找的属性或者方法就会直接使用,如果没有就会去原型对象中查找,找到了就会直接使用
  • 在我们创建构造函数时,可以将对象的共有属性或方法,统一添加到构造函数的原型对象中去。这样做的好处就是不用分别为每一个对象添加这些共有的属性或方法,也不会影响到全局作用域,同时每个对象也都具有这些属性和方法。可以说给原型对象添加属性或者方法,函数的所有实例对象都会自动拥有原型中的属性和方法。

实例1:

演示自有属性和原型属性的区别

function Dog(name) { //定义构造函数
   this.name = name; //构造函数本身的属性 --自有属性
   this.sayName = function (){
      console.log("name is " + this.name);
   }
}
//通过构造函数的显示原型prototype属性新增属性或方法
Dog.prototype.color = 'black';
let dog = new Dog('huahua');
console.log(dog);
console.log("原型color属性:", dog.color);  //black
console.log("自有name属性:",dog.name);  //huahua
Dog.prototype.name = '小丽';
console.log("使用原型对name属性赋值后的自有name属性:", dog.name);
console.log("对象可有自有name属性:", dog.hasOwnProperty("name")) //false name属性只存在于原型对象中
delete dog.name; //删除对象的自有属性,无法删除原型属性
console.log("删除自有name属性后可访问到原型name属性:",dog.name);
console.log("对象可有自有name属性:", dog.hasOwnProperty("name")) //false name属性只存在于原型对象中

 从结果可看出原型属性不能重写自有属性,自有属性被删除后才显式原型属性,这说明访问一个属性时先在自有属性中查找,找不到再到原型属性中查找

实例2:

原型隐式属性和显式属性的区别

var fnObj = new Function();
console.log(fnObj)
console.log(fnObj.prototype) //默认指向一个Object空实例对象,没有我们的属性
console.log(fnObj.prototype.constructor === fnObj) //true
function Foo(){} //相当于var Foo = new Function(),所以Foo是个对象,对象都有隐式原型__proto__
console.log(Foo) 
console.log("显式原型对象:",Foo.prototype) 
console.log("原型对象中都有一个constructor属性指向函数对象", Foo.prototype.constructor === Foo) //true
//为原型中添加test()方法
Foo.prototype.test = function(){
    console.log('这里是为原型中添加的方法')
}
var fObj = new Foo() //创建实例对象时,内部产生语句: this.__proto__ = F.prototype
console.log("实例化后:", fObj) 
fObj.test();
console.log("实例对象的隐式原型的值和其构造函数显式原型的值对应", fObj.__proto__ === Foo.prototype) // true

1.4 低版本浏览器兼容处理

ES6~10的新语法,一般浏览器都没有问题,但低版本的浏览器无法解析该语句,所以要进行js转换,一般的vue2项目我们会使用babel,但vue+vite里可引入另一个插件legacy(或引入esbuild),他可以把指定文件转译成目标文件,如ts->js。

1.4.1 vue2 + webpack

ES6+虽然语言支持,但浏览器不支持,需要进行编译,比如使用webpack进行编译
本地安装webpack

npm install --save-dev webpack
npm install --save-dev webpack-cli

 对于大多数项目,我们建议本地安装。这可以使我们在引入破坏式变更(breaking change)的依赖时,更容易分别升级项目。

通常,webpack 通过运行一个或多个 npm scripts,会在本地 node_modules 目录中查找安装的

"scripts": {
  "start": "webpack --config webpack.config.js"
}

当你在本地安装 webpack 后,你能够从 node_modules/.bin/webpack 访问它的 bin 版本。

全局安装webpack
cnpm install --global webpack
cnpm install -g webpack-cli

不推荐全局安装 webpack。这会将你项目中的 webpack 锁定到指定版本,并且在使用不同的 webpack 版本的项目中,可能会导致构建失败

webpack使用

在cli命令行直接指定需要编译的文件,和编译后输出的文件(从4.0开始,运行 webpack 时一定要加参数 --mode development [开发环境] 或者 --mode production [生产环境] )
webpack --mode development ./src/index.js -o bundle.js

通过webpack.config.js配置运行webpack,此时 运行webpack命令时会找到webpack.config.js,根据配置进行编译

//webpack配置文件,里面输出一个大json
const path = require('path'); //引入node里的报path,负责把路径解析为绝对路径
module.exports={
    mode: 'development', //编译模式,必须要设置webpack的模式,是生产还是开发('development' or 'production')
    entry: './src/index.js', //入口文件,需要编译的文件
    output: { //出口,至少包含2个项目,一个path,一个
        path:path.resolve(__dirname, 'build'), //编译处的文件放置的地方
        filename:'bundle.js', //习惯把打包后的文件叫bundle.js,bundle是捆,即打包的意思
    }
}

配制了webpack.config.js后可在命令行直接运行webpck,否则就必须指定环境等相关信息。

执行根据webpack.config.js配置,执行webpack对模块等进行编译让浏览器能够支持

 webpack
在package.json里配置
{
  "scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
  },
}

然后执行npm run dev或build运行webpack

在使用的地方引入编译后的js文件bundle,js即可在浏览器上运行
<script src="./build/bundle.js"></script>

1.4.2 vue3+vite+legacy

我们见证了诸如 webpack、RollupParcel 等打包工具的变迁,它们极大地改善了前端开发者的开发体验。
Vite(发音 /vit/)插件是Rollup插件接口的一种扩展,使用 Rollup 打包你的代码
当以命令行方式运行 vite 时,Vite 会自动解析 项目根目录 下名为 vite.config.js 的文件

[参考文档](https://cn.vitejs.dev/config/)

Vite 使用 dotenv 从 环境文件目录 中加载环境文件,默认情况下,环境文件目录为项目的根目录,即把环境文件放在项目根目录下。
vite.config.ts 中可以通过配置envDir属性指定环境文件目录为env:

...
export default defineConfig({
  ...
  envDir: path.resolve(__dirname, './env')
})

vite环境变量要求以 VITE_开头,如VITE_URL='HTTP://api.cxx.cn'

viteruntime是基于native ESM的,所以如果开发者需要打包代码在 传统浏览器 or 兼容性差的浏览器版本,就需要用到此插件.
polyfill是添加额外的代码来使得旧浏览器支持某些特性(polyfills是通过在低版本浏览器中模拟等效的代码,来实现高版本语法的行为),
legacyPolyfillmodernPolyfill的功能是一样的,他们的区别仅仅在于语法,就如上文所讲到的代码编译,开启了 renderLegacyChunks才会代码编译生成 legacy chunk
modernPolyfills是针对现代浏览器的polyfill,为新式构建生成单独的 polyfills 块,官方不推荐使用,modernPolyfills为数组的话,plugin-legacy会使用vite内部的build方法(vite.build),使用虚拟模块打包

renderLegacyChunks,是否编译传统代码,为true会编译一份额外的针对传统浏览器的代码在这些浏览器上面运行
additionalLegacyPolyfills,针对传统浏览器的额外polyfills

legacy({
  renderLegacyChunks: false,
  modernPolyfills: ['es.global-this'],
})

这样就只会打包出针对现代浏览器的polyfill,而不会生成传统的polyfill。

传统浏览器一般指不支持 native ESM 的浏览器,如chrome<60,Edge<15,Firefox<59 等等,如果使用vite打包而不做任何的处理的话,是无法在这些浏览器上面运行的,因为打包出来的代码是 很新的规范。

处理如下:

npm i @vitejs/plugin-legacy  --save

在vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from "path"
import legacy from "@vitejs/plugin-legacy"
export default defineConfig({
    plugins: [
        vue(),
        legacy({
            // polyfills: ["es.promise.finally", "es/map", "es/set"],
         //    targets: ["chrome 49"],
         //    // modernPolyfills: ["es.promise.finally"],
            additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
            // modernPolyfills: true,
            targets: ['chrome 49', 'edge < 15'],
            renderLegacyChunks: true,
        }),
    ],
    //定义短路经别名
    resolve: {
        alias: {
          // 如果报错__dirname找不到,需要安装node,执行npm install @types/node --save-dev
          "@": path.resolve(__dirname, "src"),
          "@assets": path.resolve(__dirname, "src/assets"),
          "@components": path.resolve(__dirname, "src/components"),
          "@images": path.resolve(__dirname, "src/assets/images"),
          "@views": path.resolve(__dirname, "src/views"),
          "@store": path.resolve(__dirname, "src/stores"),
        },
    },
    //跨域处理,重写路径,替换/api
    server: { 
        proxy: {
            '/api': {
                target: 'http://localhost:3000', //目标url
                changeOrigin: true, //支持跨域
                rewrite: (path) => path.replace(/^\/api/, "http://testwechat.ispeak.cn/"), 
            }
        }
    },
    // ...esbuild({
    //   //替换成你想要的谷歌内核版本
    //   target: 'chrome64',
    //   loaders: {
    //     '.vue': 'js',
    //     '.ts': 'js'
    //   }
    // }),
})

1.4.3 require和import区别

requireimport 对于webpack来说,只是一个特殊的关键字,这些关键词告知webpack,当前模块的依赖者。寻找模块之间的依赖关系,是webpack的工作重点。

如果用的 require 就得配合着用 module.exports =
如果是 import 就需要对应使用 export/export default
vue3中存在require is not defined,但vue2中不存在这个问题,那是因为webpack帮我们解决了.
* webpack
const modulesFiles = require.context('./modules', true, /\.js$/)
* vite
对于vue3.0的项目,由于打包工具的变化,我们无法使用webpack提供的require.context了,
Vite 支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块,可以参考
[官网文档](https://cn.vitejs.dev/guide/features.html#glob-import)

封装引入

getAssetsFile: (url) => {
    return new URL(`/src/assets/${url}`, import.meta.url).href
}

模板中使用

bgImg.src = this.utils.getAssetsFile('images/bg.jpg');

vite中 / 绝对路径 代表 src目录
webpack通过require引入的图片;不加require,图片会引入不成功;
const img = require('@/images/log.png');
在vite中不需要添加require,可以直接通过路径引入
const img = '/images/log.png';
这个插件就是将代码从 require 语法转换为 import。

2、ES5特性实战

2.1 描述

ECMAScript 5 也称为 ES5 和 ECMAScript 2009

语法更改:

  • 对字符串的属性访问 [ ]
  • 数组和对象字面量中的尾随逗号
  • 多行字符串字面量
  • 作为属性名称的保留字

"use strict" 指令:
“use strict” 定义 JavaScript 代码应该以“严格模式”执行(“use strict” 只是一个字符串表达式。旧浏览器如果不理解它们就不会抛出错误)。

使用严格模式,不能使用未声明的变量。您可以在所有程序中使用严格模式。它可以帮助您编写更清晰的代码,例如阻止您使用未声明的变量。

新的特性:

"use strict" 指令
String.trim()
Array.isArray()
Array.forEach()
Array.map()
Array.filter()
Array.reduce()
Array.reduceRight()
Array.every()
Array.some()
Array.indexOf()
Array.lastIndexOf()
JSON.parse()
JSON.stringify()
Date.now()
属性 Getter 和 Setter
新的对象属性和方法

2.2 实战

2.2.1 String.trim()

空字符串过滤

var str = "    Hello World!    ";
console.log("|" + str.trim() + '|'); 

返回

2.2.2 Array.isArray

判断变量是否是数组

var fruits = ["Banana", "Orange"];
console.log(Array.isArray(fruits)); //true

2.2.3   numbers.forEach

for是遍历,forEach是迭代

遍历:指的对数据结构的每一个成员进行有规律的且为一次访问的行为。
迭代:迭代是递归的一种特殊形式,是迭代器提供的一种方法,默认情况下是按照一定顺序逐个访问数据结构成员

注:使用for循环时可以使用break跳出循环,但forEach并不支持break操作

完整的forEach格式:

arr.forEach(function(self,  index,   arr) {

  ...

}, this);

  • self:数组当前遍历的元素,默认从左往右依次获取数组元素。
  • index:数组当前元素的索引,第一个元素索引为0,依次类推。
  • arr:当前遍历的数组。
  • this:回调函数中this指向

数据去重实例:

let arr1 = [1, 2, 1, 3, 1];
let arr2 = [];
arr1.forEach(function (num, index, arr) {
    arr.indexOf(num) === index ? arr2.push(num) : null;
});
console.log(arr2); //[1,2,3]

2.2.4   Array.map()

为数组中每个元素执行函数

var numbers1 = [1, 2, 3];
var numbers2 = numbers1.map(function(value) {
    return value * 2;
});
console.log(numbers1, numbers2);

2.2.5  Array.filter()

过滤数组

var numbers = [45, 4, 9, 16, 25];
var numbers2 = numbers.filter(function(num) {
    return num > 18;
});
console.log(numbers, numbers2);

2.2.6  Array.reduce()

遍历后返回一个单个返回值。多用于遍历和字符串拼接等

var arr = [1, 2, 3];
let res = arr.reduce(function(total, value) {
    console.log(total, value);
    return total + value;
}, 100)
console.log(arr, res);

 从结果可看出每次遍历的值会赋值到第一个变量total上,第一次遍历的total值是第二个参数传递进去的100

2.2.7  Array.reduceRight()

类似于 Array.reduce(),只是遍历对原有数组倒序遍历后返回一个返回值

2.2.7  Array.every()

对所有元素进行检测,只要有一个不符合要求就返回false,否则返回true

作用判断元素是否有空值,判断所有人是否都及格等。

以下是检测班级里所有人成绩是否都及格

var arr = [65, 62, 63, 26, 61];
let res = arr.every(function(value) {
   console.log(value);
return value > 60; }) console.log(arr, res);

 依次遍历每个元素,只要有一个不符合要求就返回false,并终止继续遍历其它元素。

2.2.8  Array.some()

和Array.every()相对,在不符合检测要求时,会依次遍历每个元素,只要有一个元素符合要求就返回true,并终止检测。

var arr = [65, 62, 80, 61];
let res = arr.some(function(value) {
    console.log(value);
    return value > 78;
})
console.log(arr, res);

 从结果可看出检测到一个符合要求的元素就不再继续检测下去

2.2.8  Array.indexOf()

返回某个指定的字符串值在数组中首次出现的位置

var fruits = ["d", "a", "b", "a", "c"];
var index = fruits.indexOf("a");
console.log(index); // 返回1
index = fruits.indexOf("12");
console.log(index); //不存在,返回-1

2.2.9 Array.lastIndexOf()

返回某个指定的字符串值在数组中最后一次出现的位置

var fruits = ["d", "a", "b", "a", "c"];
var index = fruits.lastIndexOf("a");
console.log(index); //3
index = fruits.lastIndexOf("12");
console.log(index); //-1

2.2.10  JSON.parse()

将JSON字符串解析成JSON对象,常用于处理从Web 服务器接收的数据。

var obj = JSON.parse('{ "name":"小镭", "age":10000}');

2.2.11 JSON.stringify()

和JSON.parse()正好相反,它是将对象转换为字符串

var str = JSON.stringify({ "name":"小镭", "age":10000});
console.log(typeof  str, str);

2.2.12  Date.now()

返回自1970年1月1日00:00:00 UTC以来经过的毫秒数

var time = Date.now();
console.log(time);
time = Date(Date.now()); 
console.log(time);
time =new Date().getTime()
console.log(time);

2.2.13  属性 Getter 和 Setter

所谓getter与setter其实是两个概念,并没有这样的属性。与之对应的是两个访问描述符(access descriptor):

  • get, 它是一个函数,访问该属性时会自动调用,函数的返回值即为该属性的value。默认为undefined。
  • set, 它是一个函数,为该属性赋值时会自动调用,并且新值会被当做参数传入。

为属性赋值的时候会自动执行一个函数,通过get、set监控到数据的变化,从而实现mvvm的双向绑定,其实vue数据监控用到的核心原理就是这个。

get、set监控数据变化

const obj2 = {
    _nickname: 2,
    age: 10,
    get nickname() {
        console.log('自动执行Getter对象属性了');
        return this._nickname;
    },
    set nickname(val) {
        console.log('自动执行Setter对象属性了');
        this._nickname = val
    }
}
console.log(obj2);
const res2 = Object.getOwnPropertyDescriptor(obj2, 'nickname')
console.log('res2的nickname属性的属性描述符:', res2)
console.log('nickname属性的get名称:', res2.get.name) // get nickname
console.log('nickname属性的set名称:', res2.set.name) // set nickname
obj2.nickname = 'cdf'; // 为属性赋值时,set函数监控到数据变化会自动执行
console.log(obj2.nickname); // 读取属性时,get函数监控到数据变化会自动执行

可以看到,输出对象属性时,没有nickname却可以把nickname当做对象的一个属性赋值和取值,还能监控到对应的get、set变化。

获取或设置属性的语法可用来来定义对象的方法。

var person = {
    name: "",
    get getName() {
        return this.name;
    },
    set setName(value) {
        this.name = value;
    }
};
// 使用setter设置对象属性,然后使用Getter获取对象属性
person.setName = "cdf";
console.log(person.getName); //输出cdf

2.2.14 Object.defineProperty() 和getOwnPropertyDescriptor

Object.defineProperty(object, property, descriptor) 是 ES5 中的新对象方法。它允许您定义对象属性和/或更改属性的值并修改属性描述符。

descriptor参数说明:

  • configurable,表示“可配置”,当它为true时,该属性的描述符可被修改,并且该属性可被delete删除;当它为false时,我们无法再次调用defineProperty去修改描述符,也不可通过delete删除。
  • enumerable, 表示“可枚举”,含义是:当它为true时,该属性可被迭代器枚举出来。比如使用for in或者是Object.keys。
  • value, 该属性的值,即通过obj.key访问时返回。任何js数据类型都可以使用(number,string,object,function等)。
  • writable,表示该属性是否可写。当它为false时,属性不可被任何赋值语句重写。然而,此时还可以调用defineProperty来修改value,当然前提是configurable为true啦。

Object.getOwnPropertyDescriptor(object, property)用于查看自有属性描述符,自有属性都会有属性描述符。

以下演示访问一个对象的属性和更改一个对象的属性:

var person = {name:'cdf', language : "NO"};
let desc = Object.getOwnPropertyDescriptor(person, 'language');
console.log(desc);
// 更改属性:
Object.defineProperty(person, "language", {
    value: "EN",
    writable : false,
    enumerable : true,
    configurable : true
});
console.log("更改language值为CN,并设置描述符不可写:",  person);
console.log("language可枚举:", Object.keys(person));
// person.language = 'CN'; //会报错,因为上一步在属性描述符中把它限制为不可重写
Object.defineProperty(person, "language", {
    value: "CN",
    writable : false,
    enumerable : false,
    configurable : false
});
console.log("language不可枚举后:", Object.keys(person));
console.log("更改language值为CN,并多设置描述符不可再被更改、不可配置:",  person);
// Object.defineProperty(person, "language", { //不可配置后,就不可修改enumerable和configurable为其它值了,如果上一步修改属性时设置writable为true的话,到这一步还可修改writable和value值
//     value: "koa",
//     writable : false,
//     enumerable : true,
//     configurable : false
// });

因为更改language属性时,把属性描述符writable设置为false,再改变language 属性值person.language = 'CN';会报错

可看到每个属性都会有属性描述符,

2.2.15 Object.keys()等其它方法

Object.defineProperties(object, descriptors) // 添加或更改多个对象属性
Object.getOwnPropertyNames(object) // 将所有属性作为数组返回
Object.keys(object) // 将可枚举属性作为数组返回
Object.getPrototypeOf(object) // 访问原型
Object.preventExtensions(object) // 防止向对象添加属性
Object.isExtensible(object) // 如果可以将属性添加到对象,则返回 true
Object.seal(object) // 防止更改对象属性(而不是值)
Object.isSealed(object) // 如果对象被密封,则返回 true
Object.freeze(object) // 防止对对象进行任何更改
Object.isFrozen(object) // 如果对象被冻结,则返回 true

3、ES2015(也称ES6)特性实战

3.1 ES6新特性

ES6的特性比较多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化。两个发布版本之间时间跨度很大,所以ES6中的特性比较多。在这里列举几个常用的:

  • 模块化
  • 箭头函数
  • 函数参数默认值
  • 模板字符串
  • 解构赋值
  • 延展操作符
  • 对象属性简写
  • Promise
  • Let与Const

3.2 实战演示

3.2.1 类

早期的面向对象(即ES5的面向对象)并不是一个真正的面向对象,它并没有专门的一个来声明类的东西,如用class来声明类,而是使用函数的形式来声明,看着很诡异。

而且写法不统一,类和方法可以分开写,也可写在一起。

function Person(name) {
  this.name =name;
}
Person.prototype.showName=function(){
  console.log('父类', this.name);
}
-------------------------------------------------------
上面的类和方法也可写为(写法不统一)
function Person(name) {
  this.name =name;
  this.showName=function(){
    console.log('父类', this.name);
  }
}
----------------------------------------------------------------
let p = new Person('cx');
p.showName(); //输出cx

子类如何继承父类呢?

function Worker(name, job) {
  Person.call(this, name); //父类属性继承,这里的this指的是Worker对象,把父类所有地方属性如name都加到this所指的子类worker对象上
  this.job = job; //再给子类加自己的属性
}
Worker.prototype = new Person();//父类方法的继承,把父类的实例赋值给子类的prototype,子类就有父类的一些东西
Worker.prototype.constructor = Worker; //继承父类会破坏子类的constructor属性,需要重新把子类赋值给constructor
Worker.prototype.showJob = function(){ //然后再声明子类的方法
   console.log('子类job:', this.job);
}
let obj = new Worker('cdf', 'it');
obj.showName();
obj.showJob();

 看着写法超级麻烦,针对ES5的问题,ES6使用了专门的关键字class来声明类,有专门的构造函数constructor,有专门的extends继承父类(但不能继承属性,需要另外使用super继承),有专门的关键字super用来继承父类属性

class Person {
  constructor(name) {
    this.name = name;
  }
  showName() {
    console.log('父类name:', this.name);
  }
}
class Worker extends Person { //使用专门关键字extends继承父类方法
  constructor(name, job) {
    super(name); //使用super继承父类属性
    this.job = job; //声明自己的属性
  }
  showJob() { //声明自己的方法
    console.log('子类job:', this.job);
  }
}
let obj = new Worker('cdf', 'it');
obj.showName();
obj.showJob();

3.2.2 模块化

模块系统演变史:

早期的没有模块 --> CMD(Common Module Definition,非官方,同步加载,所有模块加载完才能运行)--> AMD(Asynchronous Module Definition,异步加载,按需加载)-->ES6开始语言支持模块

模块基本使用

ES5不支持原生的模块化,在ES6中模块作为重要的组成部分被添加进来。模块的功能主要由 exportimport 组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过import来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。

模块里的变量必须export才能在引入的地方使用

export导出:

定义一个模块mod.js

export let height=172;
export let name='cdf';
// 一次性导一批变量
let age = 32;
let weight= '64kg';
export {age, weight};
let sqrt = Math.sqrt;
export default sqrt; //导出默认函数
export const sub = (a, b) => a + b;//导出函数

import引入:

//*就是全部,把模块里所有输出成员全部引进来,起一个名字叫mod1,./mod1即./mod1.js,'.js'可不写,默认都是.js
import * as mod from "../../../model/mol1.js"
import { name } from "../../../model/mol1.js" //按需引入name成员
import { weight, height as myHeight} from "../../../model/mol1.js" //按需引入weight, 按需引入height时起别名
import sqrt,  { sub } from "../../../model/mol1.js"  //引入默认成员和其它非默认成员

  console.log('一次性引入所有成员:', mod);
  console.log('只引入一个name成员:', name);
  console.log('引入weight、height多个成员:', weight, myHeight);
  console.log('引入默认函数sqrt:', sqrt(49));
  console.log('引入其它非默认函数:', sub(1, 3));

 注:使用export default导出的函数是默认函数,import导入时函数名不需要加{},其它非默认函数,如果不是使用*全部导入,都需要加{}

除此之外还有异步引入

let p= await import('./mod1.js'); //这是一个promise,等待引完了就能使用模块里的东西

import不仅能引入模块,还可以引入图片、css等,语法

import 'filename'

 3.2.3 箭头函数

箭头函数就是把匿名函数function(a, b) { ... }拆分为(a, b)=>{}用箭头表示的函数

有2种情况下,可对箭头函数进行简写

1.1 如果仅有return语句,块作用域{}可不写

如以下匿名函数

let arr = [19, 13, 15];
arr.sort(function(a, b){
  return a-b;
})

可写为箭头函数

arr.sort((a, b) => {
  return a-b;
})

如果方法体只有return,可进一步简写为

arr.sort((a, b) => a-b)

1.2 如果有且仅有一个参数,()可不写

function show(n, fn) {
  alert(fn(n)); //fn接收n参数
}

调用方式

show(2, function(n) {
  return n+2;
})

可简写为:

show(2, n=>n+2);

this所指对象的变化:

用箭头函数限制this为当前环境.普通的function() {}里的this是变的,跟着执行人走,如

let json={
  a:12,
  fn:function(){ //里面的this为当前执行对象,若改为箭头函数,则this为window对象
    console.log("this:", this); 
    console.log("this.a:",this.a);
  }
}
json.fn();  //输出12,this为当前json对象
let dateObj = new Date();
dateObj.fn=json.fn;
dateObj.fn(); //undifined,this为当前执行对象Date
let json2 = {
  a:12,
  fn:() => { //里面的this为当前环境
    console.log("箭头函数this:", this); 
    console.log("箭头函数this.a:",this.a);
  }
}
json2.fn();  
let dateObj2 = new Date();
dateObj2.fn = json2.fn;
dateObj2.fn(); 

 3.2.4 默认参数

默认参数的匹配规则是从前往后

const test = (a='a', b='b', c='c')=>{
    return a+b+c
}
console.log(test('A','B')) //ABc

 3.2.5 模板字符串

模板字符串是ES6中新增的语法结构,主要的作用是处理字符串拼接问题 和引号嵌套问题。

在ES5中 定义字符串有两种方式:单引号和双引号 ,但是单双引号嵌套的时候,比较麻烦。在ES6中 我们可以使用反引号`` 声明字符串,单引号在反引号中可以随意嵌套。

反单引号·,在数字按键1的左侧,与~共用一个键。

使用它以后,通过·${var}·可以向字符串动态传入变量,就像php里的“{$var}”一样

let name = 'cx';
const str = `Your name is ${name}`;
console.log(str) //Your name is cx

 3.2.6 解构赋值

两边的结构必须一致,对对象解构,两边都必须是对象,对数组解构,两边都必须是数组类型。

作用:从大的结构取数据

对右边的结构进行解构,对左边相同结构里的变量赋值。解构和赋值不能分开写。

如ajax请求后返回JSON格式返回值data\msg\status,可let {data,status,msg) = res;解构

JSON是JavaScript原生格式,它是一种严格的js对象的格式,JSON的属性名必须有双引号,如果值是字符串,也必须是双引号

如对象解构:

 
let {name, age} = {name:'cdf', age:20};
alert(name+'----'+age);

不能写为let {n, a} = {name:'cdf', age:20};  //解构的变量名称必须和对象属性名称一致

不能写为let {name, age} = {'cdf', 20}; //右边不是对象,对象必须有属性名,对象是一个无序的“名称/值”对集合,无属性名不叫对象

不能写为let [name, age] = {name:'cdf', age:20};  //两边结构不一致(左边数组、右边对象,不能解构)

如数组解构:

let  [name, age] = ['cdf', 20];
alert(name+'----'+age);

 3.2.7 延展操作符...

形参展开,收集剩余参数:

function show(a, ...b) { //第一个叫声明的参数,第二个叫剩余参数剩余参数只能放在最后一位
  console.log(b);    //[23, 46]
}
show(12, 23, 46); //a接收第一个参数,b接收剩余参数

实参展开:

let arr = [12, 34];
function show(a,b) {
  alert(a +'-'+b);
}
show(...arr); //类似于show(12, 34);

数组展开:

就像把数组里元素掏出来放在那一样

let arr1 = [12,34];
let arr2 =[ 1,4,6,7];
let arr = [...arr1, ...arr2]; 
alert(arr); //12,34,1,4,6,7

对象展开:

let json1 = {name:'cdf', age:32};
let json2 = {...json1, height:176};
console.log(json2); //{name: "cdf", age: 32, height: 176}

3.2.8 对象属性和方法简写

在ES6中新增了对象属性和方法的简写方式,他是ES6新语法,大大简化ES6的对象语法模式。

属性的简写:

let name = "admin";
let age = 18;
let obj = {
    name, //以前写为name : name,
    age
}

方法的简写:

let obj2 = {
    name,
    say(){  //以前写为say : function () {
        console.log(this);
    }
}

3.2.9 Promise

注:ajax需要服务器环境(配置域名访问),而不是直接打开html访问

使用promise对ajax异步操作进行封装

let p1 = new Promise(function(resolve, reject){    
  $.ajax({
    url:'./data/1.txt',
    type:'get',
    dataType:'json',
    success(arr){
      resolve(arr) //成功执行resolve函数
    },
    error(err){
      reject(err)
    }
  })
})
p1.then(function(data){ //上面的resolve就是这个匿名函数,data就是success里的arr,这里是对结果进行处理
  console.log(data);
},function(data){})

其实ajax里就有对promise的封装,也就是ajax本身就是promise

et p = $.ajax('./data/1.txt',{
  data:{},
  dataType:'json',//服务器返回json格式数据
  type:'get',//HTTP请求类型
  timeout:10000,//超时时间设置为10秒;
  success:function(data){
    console.log(data);
  },
  error:function(xhr,type,errorThrown){

  }
});
console.log(p); //可看到普通的ajax里有promise和then的属性,

可用来把一些异步请求封装为同步请求,方便顺序执行

getImgInfo(imgUrl) {
    return new Promise((resolve, reject) => {
        let img = new Image();
        img.src = imgUrl;
        img.onload = function () {
            resolve({
                width: img.width,
                height: img.height,
            });
        }
    })
}

Promise.all()

一次执行多个ajax,要么全部成功,要么全部失败,就像事件的原子性一样

Promise.all([
  $.ajax({url:'data/1.txt', dataType:'json'}),
  $.ajax({url:'data/2.txt', dataType:'json'}),
]).then(arr=>{
  console.log(arr); //成功后arr[0]是第一个ajax返回结果,arr[1]是第二个ajax返回结果
}, res => {
  console.log(arr);//失败
}); 

Promise.race()

race竞速,从几个ajax读数据,哪个先返回成功返回数据先使用哪个作为结果(如果有失败的返回结果,哪一个先成功使用哪个结果,都失败最终才返回失败)。可用在从CDN取数据,哪个快使用哪个。一般不用,浪费资源

3.2.10 let和const

以前使用var声明变量,可重复声明,属于函数级别的变量,在es6 使用letconst

 

let 不可重复声明,属于块级作用域(如{  let a=12})。let声明的是局部变量,let声明的变量名称,不能再用let声明一次。

const 声明常量,不可改变

<button id="a">事件a</button>
<button id="b">事件b</button>
let btnObj = document.getElementsByTagName('button');
for(var i=0;i<btnObj.length;i++) {
  btnObj[i].onclick=function(){
    alert(i);
  }
}

点击两个按钮都输出2,这是因为它们使用一个全局变量.可把它包在一个函数中,这样i就成为函数级变量,i值就能互相区分

let btnObj = document.getElementsByTagName('button');
for(var i = 0; i < btnObj.length; i++) {
  (function(i) {
     btnObj[i].onclick=function(){
            alert(i);
     }
  })(i);
}

上面解决问题的方法会对阅读造成影响,可使用ES6中提供的let声明变量,i会成为块级变量

for(let i=0;i<btnObj.length;i++) {
  btnObj[i].onclick=function(){
    alert(i);
  }
}

 

 

 

 

参考文档:

一文读懂原型链 prototype和__proto__详解(https://blog.csdn.net/weixin_44384728/article/details/125951909)

理解defineProperty以及getter、setter(https://www.cnblogs.com/lvdabao/p/7989238.html)

 

参考文档

https://blog.csdn.net/qq_34586870/article/details/89515336

https://www.jianshu.com/p/a64a6aa4cd95

 

参考视频:

https://www.bilibili.com/video/av41783773/?p=2

https://www.bilibili.com/video/BV1v7411G7Ht/?p=2