模块化&数组&对象方法
密封
可以让对象密封,seal()不可新增/不可删除/可写
var obj = { a: 2 };//对象的密封console.log(Object.seal(obj));//判断对象是否密封//false没有密封/true已开启密封console.log(Object.isSealed(obj)); //true
freeze()冻结对象,不可写/不可新增/可枚举/可读
//密封对象级别最高的方式Object.freeze()var obj = { a: 2 };Object.freeze(obj);//false没有冻住/true冻住console.log(Object.isFrozen(obj));
//深度冻结function myFreeze(obj) {Object.freeze(obj);for (var key in obj) {if (typeof (obj[key]) === 'object' && obj[key] !== null) {myFreeze(obj[key]);}}}
相等
判断是否严格相等的API中的Object.is()
// ES5判断NaN === NaNconsole.log(NaN === NaN); //falseconsole.log(+0 === -0); //true//ES6新API判断严格相等 sameValueconsole.log(Object.is(NaN, NaN)); //trueconsole.log(Object.is(+0, -0)); //false
静态API
Array.of()
使用指定的参数作为定义数组里的数组元素
//Array()方法创建的数组没办法控制数组里的数组元素console.log(Array()); //[]console.log(Array(3)); //[empty x 3]
console.log(Array.of(1, 2, 3)); //[1, 2, 3]
Array.from()
将数组/类数组/部署迭代器接口的对象转化成真正的数组
//将类数组转化成真正的数组function test() {console.log(arguments);//Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]console.log(Array.from(arguments));//[1, 2, 3]}test(1, 2, 3);
//将部署迭代器接口的对象转化成真正的数组let obj = {start: [1, 3, 2, 4],end: [5, 7, 6],//中括号包裹字符串的方式[Symbol.iterator]() {//定义指针let index = 0,//组合新数组arr = [...this.start, ...this.end],//新数组长度len = arr.length;//将新数组进行迭代return {next() {if (index < len) {return {//累加的结果value: arr[index++],done: false}} else {return {value: undefined,done: true}}}}}}console.log(Array.from(obj));//[1, 3, 2, 4, 5, 7, 6]
原型API
Array.prototype.fill()
填充修改原数组里的数组元素
/*** fill(value, start, end);* @value 填充的值* @begin 从哪开始 包括* @end 从哪结束 不包括*/console.log([1, 2, 3, 4].fill(5)); //[5, 5, 5, 5]console.log([1, 2, 3, 4].fill(5, 1)); //[1, 5, 5, 5]console.log([1, 2, 3, 4].fill(5, 1, 2)); //[1, 5, 3, 4]
//对象也能用的情况console.log([].fill.call({length: 3}, 4));//{0: 4, 1: 4, 2: 4, length: 3}
Array.prototype.copyWithin()
指定区域复制到指定数组位置
/*** 复制序列替换到指定位置* 注意:会修改原数组* arr.copyWithin(target, start, end)* @target 当前替换到哪的位置下标* @start 选出替换内容开始位置* @end 选出替换内容结束位置*/console.log([1, 2, 3, 4, 5].copyWithin(2));//[1, 2, 1, 2, 3]console.log([1, 2, 3, 4, 5, 6, 7].copyWithin(-2));//[1, 2, 3, 4, 5, 1, 2]console.log([1, 2, 3, 4, 5, 6, 7].copyWithin(0, 3));//[4, 5, 6, 7, 5, 6, 7]console.log([1, 2, 3, 4, 5, 6, 7].copyWithin(0, 3, 4));//[4, 2, 3, 4, 5, 6, 7]console.log([1, 2, 3, 4, 5, 6, 7].copyWithin(-2, -3, -1));//[1, 2, 3, 4, 5, 5, 6]
//特殊写法//并不好用的copyWithinconsole.log([].copyWithin.call({length: 5,3: 1}, 0, 3));//{0: 1, 3: 1, length: 5}
Array.prototype.keys()
Array.prototype.values()
Array.prototype.entries()
遍历数组
//打印的皆为遍历器对象,说明具有迭代器接口let arr = ['a', 'b', 'c'];console.log(arr.keys()); //Array Iterator {}console.log(arr.values()); //Array Iterator {}console.log(arr.entries()); //Array Iterator {}
let arr = ['a', 'b', 'c'];var iter1 = arr.keys();console.log(iter1.next()); //{value: 0, done: false}console.log(iter1.next()); //{value: 1, done: false}console.log(iter1.next()); //{value: 2, done: false}console.log(iter1.next()); //{value: undefined, done: true}var iter2 = arr.values();console.log(iter2.next()); //{value: "a", done: false}console.log(iter2.next()); //{value: "b", done: false}console.log(iter2.next()); //{value: "c", done: false}console.log(iter2.next()); //{value: undefined, done: true}var iter3 = arr.entries();console.log(iter3.next()); //{value: [0, "a"], done: false}console.log(iter3.next()); //{value: [1, "b"], done: false}console.log(iter3.next()); //{value: [2, "c"], done: false}console.log(iter3.next()); //{value: undefined, done: true}
试着for of迭代
var iter1 = arr.keys();var iter2 = arr.values();var iter3 = arr.entries();for (let i of iter1) {console.log(i); //0 1 2}for (let i of iter2) {console.log(i); //a b c}for (let i of iter3) {console.log(i); //[0, "a"] [1, "b"] [2, "c"]
Array.prototype.find()
Array.prototype.findIndex()
/*** arr.find(function (elem,index,array) {})* @return 返回第一个符合指定规则的数组元素值*/var arr = [1, 2, 3, 4];console.log(arr.find(function (value) {return value > 2;})); //3
/*** arr.findIndex(function (elem,index,array) {})* @return 返回第一个符合指定规则的数组元素值的索引*/var arr2 = [1, 2, 2, 4];console.log(arr.findIndex(function (value) {return value > 2;})); //2
解决indexOf的不足
//找不到结果console.log([NaN].indexOf(NaN)); //-1//利用findIndex()解决//Object.is(NaN, val) 比对是否有NaNconsole.log([NaN].findIndex((val) => Object.is(NaN, val))); //0
Array.prototype.includes()
查看是否包含
//判断数组是否包含该值console.log([1, 2, 3].includes(1)); //trueconsole.log([1, 2, 3].includes(4)); //falseconsole.log([NaN].includes(NaN)); //true
模块化
JavaScript在开发中遇到的问题
- 依赖
- 引用无用的代码
- 变量覆盖重名污染全局
- 加载顺序
早期解决方案:
闭包 -> 立即执行函数 -> 模块独立作用域 -> 解决各个模块之间的变量共享问题
关于Commonjs:
Commonjs规范 -> 模块化规范 -> Nodejs
实际原理是引用的时候会创建新的模块实例,所有文件同步加载运行,对服务端友好,有缓存机制,只能在node上运行
关于AMD:
来源于Commonjs,针对客户端用,只是表面像Commonjs
Asynchronous Module Definition异步模块定义
- 定义模块 -
define(moduleName,[module],factory) - 引入模块 -
require([module, callback])
AMD语法
define(模块名称, [依赖模块], 回调函数)define(moduleName, [module], factory)
需要配置路径
require.config({path: {moduleA: 'js/module_a',moduleB: 'js/module_b',moduleC: 'js/module_c',}})
前置依赖: 这种写法需要前面的模块加载完毕才能执行后续程序
关于CMD阿里:
Common Module Definition 通用模块定义
//定义模块define(function(require, exports, module){});
//使用模块seajs.use([module路径], function(moduleA, moduleB, moduleC){});
为什么前端用es6而后端使用Commonjs?
总结:
require加载define定义exports导出module操作模块
跟CommonJS, AMD本质不同的是:
- 依赖就近
- 按需加载
关于ES6模块化:
Asynchronous Module Definition异步模块定义
import module from '路径'; //导入模块
export module; //导出模块
三者区别:
commonjs模块输出的是一个值的拷贝,es6输出的是值的引用commonjs模块是在运行时加载,es6模块是在编译时加载
补充:模块化多个函数的变量会挂载到全局,通过对象命名空间的方式存储各个函数的变量减少全局变量的数量
//模块化演变写法一:;var module = (function(){function m1(){};function m2(){};var obj = {m1: m1,m2: m2}return {obj: obj}})();
//模块化写法二之实例化构造函数//提供类的一部分功能//把相关性非常强的功能封装成一个类//用类的方式:主要是把相关性强的属性和方法封装在一起//通过立即执行函数的方式写出来的类,执行完可以利用垃圾回收销毁AO/GO,返回指定内容,保证变量的私有化
补充:宽放大模式
//宽放大模式实现模块的注入//多个模块会存在相互之间依赖的问题//注入参数会实现变量的私有化var module = (function(){})(window.module || {});var module = (function(){})(module1, module2, module3...);
补充:命名空间,一个对象
requireJS仍有缺点:
局限性大,指定模块,指定依赖,不能处理更多更复杂模块化的依赖关系
//模拟简化版AMD//实现方式:requireJS模块//calculator模块依赖math模块//参数m 依赖模块导出的参数/*** 执行namespace();* @参数name 模块名称* @参数deps 依赖模块* @参数fn 模块定义函数* @返回值 一个包含功能的方法的对象*///实现一个requireJS模块(具备功能依赖)var namespace = (function () {//模拟一个私有的对象包含指定的方法var cache = {};//创建模块return function createModule(name, deps, fn) {//递归的出口//情况为namespace(depsName);//返回模块化的名称if (arguments.length === 1) {return cache[name];}//情况2:有依赖的时候//遍历数组里所对应的依赖的方法['math','other',...]deps = deps.map(function (depsName) {//depsName为模块化之后拿到的当前的依赖return namespace(depsName);})//情况1:没有依赖的时候cache[name] = fn.apply(null, deps);//返回依赖对应的方法return cache[name];}})();//namespace(模块名称,依赖模块,回调函数)//定义一个模块var module1 = namespace('math', [], function () {function add(a, b) {return a + b;};function sub(a, b) {return a - b;};return {add,sub}});// console.log(module1.add(1, 2));var module2 = namespace('calculator', ['math'], function (m) {var action = 'add';function compute(a, b) {return m[action](a, b);}return {compute}})// console.log(module2.compute(3, 4));
插件化
插件化的开发方式:通过在全局对象上挂载属性,能够在外面通过<script>标签引入,在全局对象上new一个实例实现一部分功能的封装
插件/组件/模块化 区别:
- 插件是一种开发的方式(技巧),一系列功能封装的构造函数,实例化后能够达到不同的对象,在不同的地方去使用,可复用
- 组件是
react(函数组件,类组件),vue(单文件组件),组件基于特定功能的UI,根据视图进行划分,拆分逻辑结构样式,包含入口和出口js文件 - 模块化是特定的函数实现一个功能,多个函数累加在一起,功能集合,完成一系列功能的方法集合
导入导出
import/export是关键字,在ES6(es2015)标准下里的ES模块化(ES MODULE)
示例一://utils.jsexport function test(){...}//main.jsimport {test} from './utils.js';test();此时浏览器控制台报错:Uncaught SyntaxError: Cannot use import statement outside a module不能够在一个不是module的文件里使用import关键字然后给<script>加上type="module"此时浏览器控制台再次报错:Access to script at 'file:///C:/frontEnd/es-module-exercise/main.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.跨域报错,只能在http/https服务器环境下才能支持模块加载尝试通过vite启动服务器npm i -D vite@2.5.8启动服务器此时正常识别加载的模块并没有报错
import静态导入:
加载时编译的导入方式,初始化时加载模块
import xxx from './xxx';//依赖`type="module"`
import动态导入:
// 按需加载模块//不依赖`type="module"`import('./xxx');
注: 这里的
import()并不是一个方法,而是import关键字 + ()关于:
nomodule="true":运行在不支持ES2015标准的浏览器上,忽略type=module脚本
type="module": 运行在支持ES2015标准的浏览器上,忽略nomodule脚本
//写法一://utils.jsexport function plus(a, b) {return a + b;}export function minus(a, b) {return a - b;}//写法二:function plus(a, b) {return a + b;}function minus(a, b) {return a - b;}//注意:这里export导出的不是一个对象,而是一个模块类型!export {plus,minus}//写法一和写法二是同一个意思:都是导出导入一个模块而不是一个对象//main.js//导入时解构该模块:从一个模块类型里解构两个函数import {plus,minus} from './utils.js';console.log(plus(1, 2));console.log(minus(3, 4));
导入模块的几种写法:
//可以通过*模块并命名为utilsimport * as utils from './utils';/**console.log(utils);* Module {* minus: ƒ minus(a, b),* plus: ƒ plus(a, b)* }*/console.log(utils.plus(1, 2));console.log(utils.minus(3, 4));//也可以解构这个模块类型(像对象但不是一个对象)import { plus, minus } from './utils';console.log(plus(1, 2));console.log(minus(3, 4));//也可以别名import { plus as computePlus, minus } from './utils';console.log(computePlus(1, 2));console.log(minus(3, 4));//说明模块的每一个方法对应一个接口
导出模块的注意事项:
//默认导出一个对象/命名空间//default表示导出的不是一个模块而是一个对象//将一个对象作为一个模块导出export default {plus,minus}//上面导出的方式影响导入时不能解构且报错import { plus, minus } from './utils';Uncaught SyntaxError: The requested module '/utils.js' does not provide an export named 'minus'//只能这样导入import utils from './utils';//console.log(utils); {plus: ƒ, minus: ƒ}//此时的utils是一个对象
副作用导入模块(模块内部有马上要执行的代码):
//utils.jsexport function plus(a, b) {return a + b;}export function minus(a, b) {return a - b;}console.log('This is a UtilsModule');//main.js//模块一导入就会执行打印代码import './utils';
export default 和 export能否一起使用?
//可以共存//utils.jsexport default {c: 3,d: 4}export function plus(a, b) {return a + b;}export function minus(a, b) {return a - b;}//main.js//注意:default默认必须先声明且放第一位import utils, {plus,minus} from './utils';//console.log(utils); {c: 3, d: 4}是一个对象console.log(plus(1, 2));console.log(minus(3, 4));
补:动态导入属于实验阶段
什么时候要使用动态import?
- 静态导入太多的情况下,有一些不需要马上加载的模块可以使用动态导入
- 异步获取加载模块的方式
- 副作用加载
import('./utils'); - 不能滥用动态导入
//import('./utils')返回的是一个Promise对象,所以也是支持then的使用import('./utils').then(module => {// console.log(module);// Module {default: Object, minus: ƒ , plus: ƒ }});
