设计模式

说到设计模式,大家想到的就是六大原则,23种模式。这么多模式,并非都要记住,但作为前端开发,对于前端出现率高的设计模式还是有必要了解并掌握的,浅浅掌握9种模式后,整理了这份文章。 那么,我们先了解六大原则 六大原则: 依赖倒置原则(Dependence Inversion Principle):高层(业务层)不应该直接调用底层(基础层)模块 开闭原则(Open Close Principle):单模块对拓展开放、对修改关闭 单一原则(Single Responsibility Principle):单模块负责的职责必须是单一的 迪米特法则(Law of Demeter):对外暴露接口应该简单 接口隔离原则(Interface Segregation Principle):单个接口(类)都应该按业务隔离开 里氏替换原则(Liskov Substitution Principle):子类可以替换父类 六大原则也可以用六个字替换:高内聚低耦合。 高层不直接依赖底层:依赖倒置原则 内部修改关闭,外部开放扩展:开闭原则 聚合单一功能:单一原则 低知识接口,对外接口简单:迪米特法则 耦合多个接口,不如隔离拆分:接口隔离原则 合并复用,子类可以替换父类:里氏替换原则 我们采用模式编写时,要尽可能遵守这六大原则 23 种设计模式分为“创建型”、“行为型”和“结构型” 前端九种设计模式 一、创建型 创建型从功能上来说就是创建元素,目标是规范元素创建步骤 1.构造器模式:抽象了对象实例的变与不变(变的是属性值,不变的是属性名) // 需求:给公司员工创建线上基本信息 // 单个员工创建,可以直接使用创建 const obj = { name:'张三', age:'20', department:'人力资源部门' } // 可员工的数量过于多的时候,一个个创建不可行,那么就可以使用构造器模式 class Person { constructor(obj){ this.name = obj.name this.age = obj.age this.department = obj.department } } const person1 = new Person(obj) 2. 工厂模式:为创建一组相关或相互依赖的对象提供一个接口,且无须指定它们的具体类 即隐藏创建过程、暴露共同接口。 // 需求:公司员工创建完信息后需要为每一个员工创建一个信息名片 class setPerson { constructor(obj) { this.pesonObj = obj } creatCard() { //创建信息名片 } otherFynction(){ } } class Person { constructor(obj) { return new setPerson(obj) } } const person = new Person() const card = person.creatCard({ name:'张三', age:'20', department:'人力资源部门' }) 3. 单例模式:全局只有一个实例,避免重复创建对象,优化性能 // 需求:判断一款应用的开闭状态,根据不同状态给出不同提示 class applicationStation { constructor() { this.state = 'off' } play() { if (this.state === 'on') { console.log('已打开') return } this.state = 'on' } shutdown() { if (this.state === 'off') { console.log('已关闭') return } this.state = 'off' } } window.applicationStation = new applicationStation() // applicationStation.instance = undefined // applicationStation.getInstance = function() { // return function() { // if (!applicationStation.instance) { // 如果全局没有实例再创建 // applicationStation.instance = new applicationStation() // } // return applicationStation.instance // }() // } // application1和application2拥有同一个applicationStation对象 const application1 = window.applicationStation const application2 = window.applicationStation 二、结构型 结构型从功能上来说就是给元素添加行为的,目标是优化结构的实现方式 ...

October 1, 2024

WebWorker

前言 先来聊聊单线程的Javascript 众所周知,js最初设计是运行在浏览器中的,为了防止多个线程同时操作DOM,带来渲染冲突问题,所以js执行器被设计成单线程。但随着前端技术的发展,js能力远不止如此,当我们遇到需要大量计算的场景时(比如图像处理、视频解码等),js线程往往会被长时间阻塞,甚至造成页面卡顿,影响用户体验。为了解决单线程带来的这一弊端,Web Worker 应运而生。 1. Web Worker 1.1 Web Worker 是什么 Web Worker 是 HTML5 标准的一部分,这一规范定义了一套 API,允许我们在 js 主线程之外开辟新的 Worker 线程,并将一段 js 脚本运行其中,它赋予了开发者利用 js 操作多线程的能力。 因为是独立的线程,Worker 线程与 js 主线程能够同时运行,互不阻塞。所以,在我们有大量运算任务时,可以把运算任务交给 Worker 线程去处理,当 Worker 线程计算完成,再把结果返回给 js 主线程。这样,js 主线程只用专注处理业务逻辑,不用耗费过多时间去处理大量复杂计算,从而减少了阻塞时间,也提高了运行效率,页面流畅度和用户体验自然而然也提高了。 1.2 Web Worker 能干些什么 虽然 Worker 线程是在浏览器环境中被唤起,但是它与当前页面窗口运行在不同的全局上下文中,我们常用的顶层对象 window,以及 parent 对象在 Worker 线程上下文中是不可用的。另外,在 Worker 线程上下文中,操作 DOM 的行为也是不可行的,document对象也不存在。但是,location和navigator对象可以以可读方式访问。除此之外,绝大多数 Window 对象上的方法和属性,都被共享到 Worker 上下文全局对象 WorkerGlobalScope 中。同样,Worker 线程上下文也存在一个顶级对象 self。 详细信息请参考:Functions and classes available to Web Workers 2. Web Worker 使用 2.1 创建 worker 创建 worker 只需要通过 new 调用 Worker() 构造函数即可,它接收两个参数 ...

October 1, 2024

前端存储

什么是前端存储 开门见山。前端存储就是通过前端技术来存储一段信息,然后在同源下的不同页面中都可以获取到已存储信息的一种策略。 前端存储的作用 方便网页的加载,避免了在发送请求收到响应前页面的空白期 可以在非强制性要求实时更新时减少向服务端的请求,加快渲染速度 在网络不佳或无网时仍有离线数据可以查看 有哪些前端存储方案 如图,一共有5种前端存储方案。大致可以分为3类: Cookie WebStorage:LocalStorage、SessionStorage 数据库存储:IndexedDB、WebSQL 先做一个简单对比: 下面我们详细介绍。 Cookie Cookie 的工作流程: Cookie 的构成: 域、路径、失效时间和安全性都是服务器给浏览器的指示,它们不会随着请求发送给服务器,发送给服务器的只有名称与值的键值对。 Cookie 的生命周期: 如果设定了 Cookie 的过期时间,那么 Cookie 会在到期时自动失效 如果没有设定过期时间,那么 Cookie 就是 session 级别的,即浏览器关闭时 Cookie 自动消失 Cookie 的优缺点: 优点: 可以控制过期时间,不会永久有效,有一定的安全保障 可进行扩展,可跨域共享 通过加密与安全传输技术,可以减少 Cookie 被破解的可能性 有较高的兼容性 缺点: 存储大小最多4KB 存储数量根据浏览器或浏览器版本的不同而不同,并且每个域最多20条 请求头上的数据容易被拦截攻击 存储的数据只能是字符串类型 操作 Cookie: // 设置 Cookie // Cookie 值必须是字符串类型,并且不支持分号、逗号以及空格, // 所以有时需要先使用 encodeURIComponent() 进行编码,或者使用 JSON.stringify() 进行序列化 document.cookie = '键1=值1;键2=值2;键n=值n'; // 读取 Cookie // 有时需要使用 decodeURIComponent() 或者 JSON.parse() document.cookie // 修改 Cookie // 如果键不存在,就新增;否则就修改 document.cookie = '已经存储过的键=新值'; // 删除 Cookie document.cookie = '要删除的键=任意值;max-age=0'; WebStorage WebStorage 是 HTML5 新加的,WebStorage(Web 存储) 分为 LocalStorage(本地存储) 和 SessionStorage(会话存储)。 ...

October 1, 2024

跨域

一、什么是跨域? 在前端领域中,跨域是指浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。 什么是同源策略? 同源策略是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。 同源策略限制以下几种行为: Cookie、LocalStorage 和 IndexDB 无法读取 DOM和JS对象无法获得 AJAX 请求不能发送 二、常见的跨域场景 URL 说明 是否允许通信 www.domain.com/a.js www.domain.com/b.js www.domain.com/lab/c.js 同一域名,不同文件或路径 允许 www.domain.com:8000/a.js www.domain.com/b.js 同一域名,不同端口 不允许 http://www.domain.com/a.js https://www.domain.com/b.js 同一域名,不同协议 不允许 http://www.domain.com/a.js http://192.168.4.12/b.js 域名和域名对应相同ip 不允许 http://www.domain.com/a.js http://x.domain.com/b.js http://domain.com/c.js 主域相同,子域不同 不允许 http://www.domain1.com/a.js http://www.domain2.com/b.js 不同域名 不允许 三、9种跨域解决方案 1、JSONP跨域 jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。 1)原生JS实现: <script> var script = document.createElement('script'); script.type = 'text/javascript'; // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数 script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback'; document.head.appendChild(script); // 回调执行函数 function handleCallback(res) { alert(JSON.stringify(res)); } </script> 服务端返回如下(返回时即执行全局函数): ...

October 1, 2024

正则表达式

前言 正则表达式使用单个[字符串]来描述、匹配一系列匹配某个[句法规则]的字符串,通常被用来检索、替换那些符合某个模式(规则)的文本。 创建 1.构造函数创建 new RegExp(‘正则表达式’,‘修饰符’) var reg = new RegExp('hello','igm'); 2.直接直面量创建 /正则表达式/修饰符 var reg = /hello/igm; 字符分类 1.普通字符 字母、数字、下划线、汉字、没有特殊含义的符号(,;!@等) 实际上不是特殊字符的字符都是普通字符 2.特殊字符 \:将特殊字符转义成普通字符 3.模式修饰符 i:ignoreCase,匹配时忽视大小写 m:multiline,多行匹配 g:global,全局匹配 字面量创建正则时,模式修饰符写在一对反斜线后 实例方法 1.exec() 用来匹配字符串中符合正则表达式的字符串 如果匹配到,返回值是一个result数组: [匹配的内容,index: 在str中匹配的起始位置,input: 参数字符串,groups: undefined] 否则返回null var str = 'Hello world javascript hello'; var reg = new RegExp('hello','igm'); var res = reg.exec(str); console.log(res); 输出结果为: [ 'Hello', index: 0, input: 'hello world javascript hello', groups: undefined ] 2.test() 用来测试待检测的字符串中是否有可以匹配到正则表达式的字符串 如果有返回true,否则返回false var reg = /hello/; var str = 'hello world'; console.log(reg.test(str)); //true 3.toSting() toLocaleString() 把正则表达式的内容转化成字面量形式字符串/有本地特色的字符串 var reg = /hello/; var str = 'hello world'; console.log(reg.toString(),typeof reg.toString()); // /hello/ string console.log(reg.toLocaleString(),typeof reg.toLocaleString()); // /hello/ string 4.valueOf() 返回正则表达式本身 ...

October 1, 2024

call、apply和bind

apply 和 call简介 在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部 this 的指向。 JavaScript 的一大特点是,函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。 比如 A 对象有一个方法,而 B 对象因为某种原因,也需要用到同样的方法,那么这时候不用单独为 B 对象扩展一个方法,可以直接借用 A 对象的方法。这样既完成了需求,又减少了内存的占用。 直接来看个例子: function fruits() {} fruits.prototype = { color: "red", say: function() { console.log("My color is " + this.color); }} var apple = new fruits();apple.say(); //My color is red 但是如果我们有一个对象 banana= {color : "yellow"} ,我们不想对它重新定义 say 方法,那么我们可以通过 call 或 apply 用 apple 的 say 方法: banana = { color: "yellow"}apple.say.call(banana); //My color is yellowapple.say.apply(banana); //My color is yellow apply、call 的区别 对于 apply、call 二者而言,作用完全一样,只是接受参数的方式不太一样。例如,有一个函数定义如下: ...

October 1, 2024

事件冒泡、事件捕获、事件委托

DOM事件流(event flow )存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。 Dom标准事件流的触发的先后顺序为:先捕获再冒泡。即当触发dom事件时,会先进行事件捕获,捕获到事件源之后通过事件传播进行事件冒泡。 addEventListener的第三个参数 在我们平常用的addEventListener方法中,一般只会用到两个参数,一个是需要绑定的事件,另一个是触发事件后要执行的函数,然而addEventListener还可以传入第三个参数: element.addEventListener(event, function, useCapture); 第三个参数默认值是false,表示在事件冒泡阶段调用事件处理函数;如果参数为true,则表示在事件捕获阶段调用处理函数。如果不写第三个参数则默认在事件冒泡阶段调用事件处理函数。 下面先介绍事件冒泡: 1. 事件冒泡 事件冒泡(dubbed bubbling):当一个元素接收到事件的时候,会把他接收到的事件传给自己的父级,一直到 window (注意这里传递的仅仅是事件,例如click、focus等等这些事件, 并不传递所绑定的事件函数。) 事件源 =>根节点(由内到外)进行事件传播。 举例说明: 给三个盒子依次绑定点击事件,当点击盒子的时候,会依次触发父级元素的点击事件。 click small box click center box click big box 如果父元素没有绑定点击事件则只会触发点击盒子的事件。 click small box 如果子元素(small)的点击事件去掉,当我们点击small的时候会把当前操作的点击事件传递给父元素(因为父元素绑定了点击函数) click small box 有些时候我们不希望产生事件冒泡,所以可以 在子事件中加入e.stopPropagation() 取消冒泡 click small box 2. 事件捕获 事件捕获(event capturing): 当鼠标点击或者触发dom事件时(被触发dom事件的这个元素被叫作事件源),浏览器会从根节点 =>事件源(由外到内)进行事件传播。 事件捕获与事件冒泡是比较类似的,最大的不同在于事件传播的方向。 还是举上面的例子: click small box 3. 事件委托 事件委托也称为事件代理。就是利用事件冒泡,把子元素的事件都绑定到父元素上。如果子元素阻止了事件冒泡,那么委托就无法实现。 原理实现: 不是每个子节点单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置每个子节点。 应用场景:1000个button需要注册点击事件 如果循环给每个按钮添加点击事件,那么会增加内存损耗,影响性能 此时可以给button的父元素添加点击事件 这时相当于每个按钮都绑定了点击事件 优点: 替代循环绑定事件的操作,减少内存消耗,提高性能。比如: 在table上代理所有td的click事件。 在ul上代理所有li的click事件。 简化了dom节点更新时,相应事件的更新。比如: 不用在新添加的li上绑定click事件。 当删除某个li时,不用移解绑上面的click事件。 缺点: ...

October 1, 2024

DOM操作

1、DOM基本概念 1.1、DOM、DOM节点与DOM树 DOM:(Document Object Model, 文档对象模型)是文档内容(HTML或XML)在编程语言上的抽象模型,它建模了文档的内容和结构,并提供给编程语言一套完整的操纵文档的API。 本文面向HTML,不讨论XML等文档,下文中“文档===HTML”。 DOM节点:简称节点(Node),是DOM模型的组成单元。HTML的基本单元是标签,节点常常与标签对应,但连续的文本内容也是一个文本标签。 DOM树:DOM树是DOM结构的表示形式,DOM把文档的每个节点根据父子关系连接,形成DOM树。 1.2、节点、节点类型和节点类 节点:前面提过,节点是DOM树的组成单元。在JS看来,一个节点就是JS对象。下面用node表示任意的节点。 节点类型:并非所有的节点都是一样的,DOM规定文档中有12种节点类型,分别用常量1 ~ 12(有与之对应的常量名称Node.XXX_NODE)表示,可以通过node.nodeType属性获取节点的类型常量。 现在,有些类型的节点已经弃用了,常见到的只有几种类型的节点,包括: 元素节点: 类型常量为Node.ELEMENT_NODE或1。最常见的一类节点,对应文档中的元素。大部分DOM操作都是在元素节点层次的。 文本节点:类型常量为Node.TEXT_NODE或2。对应文档中的文本,任何文档内容都有对应的文本节点,即使空格和换行符。 空格和换行不会对页面内容产生影响,但它们确实以文本节点的形式存在于DOM树中。 Document节点:类型常量为Node.DOCUMENT_NODE或9。它不对应文档的内容,而是作为文档的入口节点,每个文档都有且仅有一个入口,因为这种独特性,赋予一个特殊的变量名称document。 注释节点:类型常量为Node.COMMENT_NODE或8。它对应文档中的注释标签,文档的注释内容也是可读取和修改的。 节点类:DOM内置许多节点类,类之间存在继承关系,形成一套节点类框架。每个节点对象都属于节点类,拥有该类和其父类的方法与属性,这使得操作节点十分简单。节点类框架的一部分大概如图: 这些节点上有丰富的属性和方法,是继承的结果,可以看到,一个HTML标签元素至少有四层的继承关系。 以<a>标签为例,它属于HTMLAnchorElement类,获得了a.target,a.download等属性,接着继承了HTMLElement类上的title, hidden等属性和click()等方法,又从Element类继承了tagName, className等属性和getAttribute(), setAttribute()等方法,再从Node类继承了nodeType(前面说过的节点类型), appenChild(), removeChild()等方法,最后从EventTarget类中继承了事件相关的属性和方法。 不要混淆节点类型和节点类这两个概念。前者是一个生活中的类别,后者是编程意义上的类。节点对象的nodeType属性表示了它的类型,而节点类是该节点的从属的类。因为Dode是一个抽象类,所以,如果知道了某个节点从属的类,我们就知道它的节点类型。 区分节点与元素节点。我们经常关心元素节点(简称元素),因为这是一类最常使用的节点,但是并非所有节点都是元素。 1.3、探索DOM结构 前面的说法太抽象了,让我们用实际的例子看看文档、DOM树与节点的关系。 Live DOM Viewer是一个可以根据HTML文档实时查看DOM树的网站。你把下面的例子复制过去,或者自己去探索。 一个简单的HTML文档: <!DOCTYPE HTML> <html> <body> A simple text. <ol title="this is a title"> <li>czpcalm</li> <!-- comment --> </ol> </body> </html> 它对应的DOM图(颜色区分了节点类型): 留意这个图,你要注意几点: 总共有4种类型的节点,分别是标签节点(红色),文本节点(灰色),注释节点(黄色)和DOCTYPE节点(紫色)。 文档没有<head>标签却出现了HEAD节点。这是因为HTML必然存在<html>,<head>,<body>标签,不存在时会自动补上。顺便一提,当出现<table>标签时,也一定会有<tbody>标签。 文档中的文本都会形成文本节点的内容,包括空格␣和换行↵。第一,单独的空格和换行都会形成对应的文本节点;第二,有内容的文本节点的值包含前导和后继的空白。 不是说HTML中的空白字符都被忽略吗?怎么这里又说全都是有效的字符? 在从文档解析生成DOM树的过程中,HTML中的任何字符都是有效的;不过,在接下去的页面渲染的过程中,空白内容被忽略。所以从文档到页面的整过过程中,空白确实被忽略了。 理解DOM树中的父子关系对应文档中的包含(嵌套)关系。一个极佳的类比是文件树,把元素看做文件夹,文本看做文件,文件夹中可以存放文件和新的文件夹,然后一层层深入下去,DOM树也是如此。 几个原则可以帮我们快速理解这个DOM树的构建: ...

October 1, 2024

垃圾回收

写在前面 我们知道垃圾回收机制是引擎来做的,JS引擎有很多种(各个浏览器都不同),其垃圾回收机制在一些细节及优化上略有不同,本文我们以一些通用的回收算法作为切入,再由 V8 引擎发展至今对该机制的优化为例(为什么以 V8 为例?因为它市场占有率大 😄 ),一步一步深入来助我们了解垃圾回收机制,因为只有真正了解垃圾回收机制,后面才能理解内存泄漏的问题以及手动预防和优化 JavaScript 是门魅力无限的语言,关于它的 GC(垃圾回收)方面,你了解多少呢?想来大部分人是因为面试才去看一些面试题从而了解的垃圾回收,那在正式开始之前,给大家列几个小问题,大家可以先想一下答案,带着问题及答案再去看文章,最后读完此文如果你的答案可以优化,即有收获 什么是垃圾回收机制? 垃圾是怎样产生的? 为什么要进行垃圾回收? 垃圾回收是怎样进行的? V8 引擎对垃圾回收进行了哪些优化? 当然,我们可不仅仅是为了面试,其目的是一次性彻底搞懂 GC! GC是什么 GC 即 Garbage Collection ,程序工作过程中会产生很多 垃圾,这些垃圾是程序不用的内存或者是之前用过了,以后不会再用的内存空间,而 GC 就是负责回收垃圾的,因为他工作在引擎内部,所以对于我们前端来说,GC 过程是相对比较无感的,这一套引擎执行而对我们又相对无感的操作也就是常说的 垃圾回收机制 了 当然也不是所有语言都有 GC,一般的高级语言里面会自带 GC,比如 Java、Python、JavaScript 等,也有无 GC 的语言,比如 C、C++ 等,那这种就需要我们程序员手动管理内存了,相对比较麻烦 垃圾产生&为何回收 我们知道写代码时创建一个基本类型、对象、函数……都是需要占用内存的,但是我们并不关注这些,因为这是引擎为我们分配的,我们不需要显式手动的去分配内存 但是,你有没有想过,当我们不再需要某个东西时会发生什么?JavaScript 引擎又是如何发现并清理它的呢? 我们举个简单的例子 let test = { name: "isboyjc" }; test = [1,2,3,4,5] 如上所示,我们假设它是一个完整的程序代码 我们知道 JavaScript 的引用数据类型是保存在堆内存中的,然后在栈内存中保存一个对堆内存中实际对象的引用,所以,JavaScript 中对引用数据类型的操作都是操作对象的引用而不是实际的对象。可以简单理解为,栈内存中保存了一个地址,这个地址和堆内存中的实际值是相关的 那上面代码首先我们声明了一个变量 test,它引用了对象 {name: 'isboyjc'},接着我们把这个变量重新赋值了一个数组对象,也就变成了该变量引用了一个数组,那么之前的对象引用关系就没有了,如下图 没有了引用关系,也就是无用的对象,这个时候假如任由它搁置,一个两个还好,多了的话内存也会受不了,所以就需要被清理(回收) 用官方一点的话说,程序的运行需要内存,只要程序提出要求,操作系统或者运行时就必须提供内存,那么对于持续运行的服务进程,必须要及时释放内存,否则,内存占用越来越高,轻则影响系统性能,重则就会导致进程崩溃 垃圾回收策略 在 JavaScript 内存管理中有一个概念叫做 可达性,就是那些以某种方式可访问或者说可用的值,它们被保证存储在内存中,反之不可访问则需回收 至于如何回收,其实就是怎样发现这些不可达的对象(垃圾)它并给予清理的问题, JavaScript 垃圾回收机制的原理说白了也就是定期找出那些不再用到的内存(变量),然后释放其内存 ...

October 1, 2024

模块化

前言 在JavaScript发展初期就是为了实现简单的页面交互逻辑,寥寥数语即可;如今CPU、浏览器性能得到了极大的提升,很多页面逻辑迁移到了客户端(表单验证等),随着web2.0时代的到来,Ajax技术得到广泛应用,jQuery等前端库层出不穷,前端代码日益膨胀,此时在JS方面就会考虑使用模块化规范去管理。 本文内容主要有理解模块化,为什么要模块化,模块化的优缺点以及模块化规范,并且介绍下开发中最流行的CommonJS, AMD, ES6、CMD规范。 本文试图站在小白的角度,用通俗易懂的笔调介绍这些枯燥无味的概念,希望诸君阅读后,对模块化编程有个全新的认识和理解! 一、模块化的理解 1.什么是模块? 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信 2.模块化的进化过程 全局function模式 : 将不同的功能封装成不同的全局函数 编码: 将不同的功能封装成不同的全局函数 问题: 污染全局命名空间, 容易引起命名冲突或数据不安全,而且模块成员之间看不出直接关系 function m1(){ //... } function m2(){ //... } namespace模式 : 简单对象封装 作用: 减少了全局变量,解决命名冲突 问题: 数据不安全(外部可以直接修改模块内部的数据) let myModule = { data: 'www.baidu.com', foo() { console.log(`foo() ${this.data}`) }, bar() { console.log(`bar() ${this.data}`) } } myModule.data = 'other data' //能直接修改模块内部的数据 myModule.foo() // foo() other data 这样的写法会暴露所有模块成员,内部状态可以被外部改写。 IIFE模式:匿名函数自调用(闭包) 作用: 数据是私有的, 外部只能通过暴露的方法操作 编码: 将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口 问题: 如果当前这个模块依赖另一个模块怎么办? // index.html文件 <script type="text/javascript" src="module.js"></script> <script type="text/javascript"> myModule.foo() myModule.bar() console.log(myModule.data) //undefined 不能访问模块内部数据 myModule.data = 'xxxx' //不是修改的模块内部的data myModule.foo() //没有改变 </script> // module.js文件 (function(window) { let data = 'www.baidu.com' //操作数据的函数 function foo() { //用于暴露有函数 console.log(`foo() ${data}`) } function bar() { //用于暴露有函数 console.log(`bar() ${data}`) otherFun() //内部调用 } function otherFun() { //内部私有的函数 console.log('otherFun()') } //暴露行为 window.myModule = { foo, bar } //ES6写法 })(window) 最后得到的结果: ...

October 1, 2024