2018最全前端面试题-JavaScript篇(整理)
闭包
是什么? 闭包是有权限访问其他函数作用域内的变量的一个函数。
详细的说: 由于在JS中,变量的作用域属于函数作用域,在函数执行后作用域就会被清理、内存也随之回收,但是由于闭包是建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数——也就是闭包,便拥有了访问上级作用域中的变量的权限,即使上级函数执行完后作用域内的值也不会被销毁。
解决了什么?
由于闭包可以缓存上级作用域,那么就使得函数外部打破了“函数作用域”的束缚,可以访问函数内部的变量。以平时使用的Ajax成功回调为例,这里其实就是个闭包,由于上述的特性,回调就拥有了整个上级作用域的访问和操作能力,提高了极大的便利。
应用场景?
闭包随处可见,一个Ajax请求的成功回调,一个事件绑定的回调方法,一个setTimeout的延时回调,或者一个函数内部返回另一个匿名函数,这些都是闭包。简而言之,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都有闭包的身影。
debounce 防抖、throttle 节流
在前端开发中会遇到一些频繁的事件触发,比如:window 的 resize、scroll、mousedown、mousemove、keyup、keydown。为了解决这个问题,一般有两种解决方案:debounce 防抖、throttle 节流
1、防抖:防抖的原理就是:你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行。
2、节流:如果你持续触发事件,每隔一段时间,只执行一次事件。
几种获得宽高的方式
dom.style.width/height:这种方式只能取到dom元素内联样式所设置的宽高,也就是说如果该节点的样式是在style标签中或外联的CSS文件中设置的话,通过这种方法是获取不到dom的宽高的。 dom.currentStyle.width/height:这种方式获取的是在页面渲染完成后的结果,就是说不管是哪种方式设置的样式,都能获取到。但这种方式只有IE浏览器支持。window.getComputedStyle(dom).width/height: 这种方式的原理和2是一样的,这个可以兼容更多的浏览器,通用性好一些。
dom.getBoundingClientRect().width/height: 这种方式是根据元素在视窗中的绝对位置来获取宽高的
dom.offsetWidth/offsetHeight: 这个就没什么好说的了,最常用的,也是兼容最好的。
拓展各种获得宽高的方式:
- 获取屏幕的高度和宽度(屏幕分辨率):window.screen.height/width
- 获取屏幕工作区域的高度和宽度(去掉状态栏):window.screen.availHeight/availWidth
- 网页全文的高度和宽度:document.body.scrollHeight/Width
- 滚动条卷上去的高度和向右卷的宽度:document.body.scrollTop/scrollLeft
- 网页可见区域的高度和宽度(不加边线):document.body.clientHeight/clientWidth
- 网页可见区域的高度和宽度(加边线):document.body.offsetHeight/offsetWidth
JS异步加载
- 动态生成script标签
- 添加h5的async defer属性,前者乱序不适合依赖性加载
- async 是“下载完就执行”, defer 是“渲染完再执行”
css与js动画差异
- css性能好
- css代码逻辑相对简单
- js动画控制好
- js兼容性好
- js可实现的动画多
- js可以添加事件
负载均衡
多台服务器共同协作,不让其中某一台或几台超额工作,发挥服务器的最大作用
1. http重定向负载均衡:调度者根据策略选择服务器以302响应请求,缺点只有第一次有效果,后续操作维持在该服务器
2. dns负载均衡:解析域名时,访问多个ip服务器中的一个(可监控性较弱)
3. 反向代理负载均衡:访问统一的服务器,由服务器进行调度访问实际的某个服务器,对统一的服务器要求大,性能受到 服务器群的数量
CDN
内容分发网络,基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。
内存泄漏
定义:程序中己动态分配的堆内存由于某种原因程序未释放或无法释放引发的各种问题
原因:
1. 全局变量
2. dom清空时,还存在引用
3. ie中使用闭包
4. 定时器未清理
5. 子元素存在引起的内存泄露
避免策略:
1. 减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;
2. 注意程序逻辑,避免“死循环”之类的 ;
3. 避免创建过多的对象原则:不用了的东西要及时归还;
4. 减少层级过多的引用。
如何编写高性能的JavaScript?
遵循严格模式:”use strict”;
将js脚本放在页面底部,加快渲染页面
将js脚本将脚本成组打包,减少请求
使用非阻塞方式下载js脚本
尽量使用局部变量来保存全局变量
尽量减少使用闭包
使用 window 对象属性方法时,省略 window
尽量减少对象成员嵌套
缓存 DOM 节点的访问
通过避免使用 eval() 和 Function() 构造器
给 setTimeout() 和 setInterval() 传递函数而不是字符串作为参数
尽量使用直接量创建对象和数组
最小化重绘(repaint)和回流(reflow)
描述浏览器的渲染过程,DOM树和渲染树的区别?
浏览器的渲染过程:
1. 解析HTML构建 DOM(DOM树),并行请求 css/image/js
2. CSS 文件下载完成,开始构建 CSSOM(CSS树)
3. CSSOM 构建结束后,和 DOM 一起生成 Render Tree(渲染树)
4. 布局(Layout):计算出每个节点在屏幕中的位置
5. 显示(Painting):通过显卡把页面画到屏幕上
DOM树 和 渲染树 的区别:
1. DOM树与HTML标签一一对应,包括head和隐藏元素
2. 渲染树不包括head和隐藏元素,大段文本的每一个行都是独立节点,每一个节点都有对应的css属性
重绘和回流(重排)的区别和关系?
重绘:当渲染树中的元素外观(如:颜色)发生改变,不影响布局时,产生重绘
回流:当渲染树中的元素的布局(如:尺寸、位置、隐藏/状态状态)发生改变时,产生重绘回流
注意:JS获取Layout属性值(如:offsetLeft、scrollTop、getComputedStyle等)也会引起回流,因为浏览器需要通过回流计算最新值。回流必将引起重绘,而重绘不一定会引起回流
如何最小化重绘(repaint)和回流(reflow)?
需要要对元素进行复杂的操作时,可以先隐藏(display:”none”),操作完成后再显示
需要创建多个DOM节点时,使用DocumentFragment创建完后一次性的加入document
缓存Layout属性值,如:var left = elem.offsetLeft; 这样,多次使用 left 只产生一次回流
尽量避免用table布局(table元素一旦触发回流就会导致table里所有的其它元素回流)
避免使用css表达式(expression),因为每次调用都会重新计算值(包括加载页面)
尽量使用 css 属性简写,如:用 border 代替 border-width, border-style, border-color
批量修改元素样式:elem.className 和 elem.style.cssText 代替 elem.style.xxx
script 的位置是否会影响首屏显示时间?
- 在解析 HTML 生成 DOM 过程中,js 文件的下载是并行的,不需要 DOM 处理到 script 节点。因此,script的位置不影响首屏显示的开始时间。
- 浏览器解析 HTML 是自上而下的线性过程,script作为 HTML 的一部分同样遵循这个原则
因此,script 会延迟 DomContentLoad,只显示其上部分首屏内容,从而影响首屏显示的完成时间。
W3C事件的 target 与 currentTarget 的区别?
target 只会出现在事件流的目标阶段,currentTarget 可能出现在事件流的任何阶段;当事件流处在目标阶段时,二者的指向相同,当事件流处于捕获或冒泡阶段时:currentTarget 指向当前事件活动的对象(一般为父级)。
new 操作符具体干了什么?
1、创建实例对象,this 变量引用该对象,同时还继承了构造函数的原型
2、属性和方法被加入到 this 引用的对象中
3、新创建的对象由 this 所引用,并且最后隐式的返回 this
Javascript作用链域?
全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节;
当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找
直至全局函数,这种组织形式就是作用域链。
介绍JavaScript的原型,原型链?有什么特点?
**原型:**JavaScript的所有对象中都包含了一个 [proto] 内部属性,这个属性所对应的就是该对象的原型;JavaScript的函数对象,除了原型 [proto] 之外,还预置了 prototype 属性;当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 [proto]。【函数的 prototype 属性指向调用该构造函数而创建的实例的原型】;每个原型都有一个 constructor 属性指向关联的构造函数。
原型链:
当一个对象调用的属性/方法自身不存在时,就会去自己 [proto] 关联的前辈 prototype 对象上去找;
如果没找到,就会去该 prototype 原型 [proto] 关联的前辈 prototype 去找。依次类推,直到找到属性/方法或 undefined 为止。从而形成了所谓的“原型链”
**原型特点:**JavaScript对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变。
列举一下JavaScript对象有哪些原生方法?
object.hasOwnProperty(prop);
object.propertyIsEnumerable(prop);
object.valueOf();
object.toString();
object.toLocaleString();
object1.isPrototypeOf(Object2);
JavaScript 对象生命周期的理解?
- 当创建一个对象时,JavaScript 会自动为该对象分配适当的内存
- 垃圾回收器定期扫描对象,并计算引用了该对象的其他对象的数量
- 如果被引用数量为 0,或惟一引用是循环的,那么该对象的内存即可回收
哪些操作会造成内存泄漏?
**定义:**JavaScript 内存泄露指对象在不需要使用它时仍然存在,导致占用的内存不能使用或回收。
1. 循环引用(两个对象相互引用)
2. 控制台日志(console.log)
3. 移除存在绑定事件的DOM元素(IE)
4. 未使用 var 声明的全局变量
5. 闭包函数
谈谈垃圾回收机制方式及内存管理
定义和用法:垃圾回收机制(GC:Garbage Collection),执行环境负责管理代码执行过程中使用的内存。
原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。
垃圾回收策略:标记清除(较为常用)和引用计数。
标记清除:定义和用法:当变量进入环境时,将变量标记”进入环境”,当变量离开环境时,标记为:”离开环境”。某一个时刻,垃圾回收器会过滤掉环境中的变量,以及被环境变量引用的变量,剩下的就是被视为准备回收的变量。
引用计数:引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间
ie 各版本和 chrome 可以并行下载多少个资源
- IE6 2 个并发
- iE7 升级之后的 6 个并发,之后版本也是 6 个
- Firefox,chrome 也是6个
什么是 “use strict”; ? 使用它的好处和坏处分别是什么?
“严格模式”(strict mode),使Javascript在更严格的条件下运行。
设立”严格模式”的目的,主要有以下几个:
1. 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
2. 消除代码运行的一些不安全之处,保证代码运行的安全;
3. 提高编译器效率,增加运行速度;
4. 为未来新版本的Javascript做好铺垫。
注:经过测试 IE6,7,8,9 均不支持严格模式。
哪些地方会出现css阻塞,哪些地方会出现js阻塞?
js 的阻塞特性:所有浏览器在下载 JS 的时候,会阻止一切其他活动,比如其他资源的下载,内容的呈现等等。直到 JS 下载、解析、执行完毕后才开始继续并行下载其他资源并呈现内容。为了提高用户体验,新一代浏览器都支持并行下载 JS,但是 JS 下载仍然会阻塞其它资源的下载(例如.图片,css文件等)。由于浏览器为了防止出现 JS 修改 DOM 树,需要重新构建 DOM 树的情况,所以就会阻塞其他的下载和呈现。嵌入 JS 会阻塞所有内容的呈现,而外部 JS 只会阻塞其后内容的显示,2 种方式都会阻塞其后资源的下载。也就是说外部样式不会阻塞外部脚本的加载,但会阻塞外部脚本的执行。
CSS 怎么会阻塞加载了?
当 CSS 后面跟着嵌入的 JS 的时候,该 CSS 就会出现阻塞后面资源下载的情况。而当把嵌入 JS 放到 CSS 前面,就不会出现阻塞的情况了。
根本原因:因为浏览器会维持 html 中 css 和 js 的顺序,样式表必须在嵌入的 JS 执行前先加载、解析完。而嵌入的 JS 会阻塞后面的资源加载,所以就会出现上面 CSS 阻塞下载的情况。
异步加载和延迟加载
- 异步加载的方案: 动态插入 script 标签
- 通过 ajax 去获取 js 代码,然后通过 eval 执行
- script 标签上添加 defer 或者 async 属性
- 创建并插入 iframe,让它异步执行 js
- 延迟加载:有些 js 代码并不是页面初始化的时候就立刻需要的,而稍后的某些情况才需要的
对于一个数字进行取整,你能说出多少种方法?
parseInt()、8.84|0、~~8.84、8.84>>0会对变量进行ToInt32的转换但是只能对于32位的数字进行转换,所以再加上一个符号位,那么他们所能处理的数字范围在2的正负31次幂之间。
当一个变量显式类型转换时(利用Number()方法),遵循的规则是什么?
对于布尔型:true的结果为1,false的结果为0;
对于undefined: 结果为NaN
对于null:结果为0
对于字符串类型:遵循数字常量的相关规则和语法。处理失败时会返回NaN。
对于复杂类型:会先调用该值得valueOf()方法,如果有并且返回基本类型之,就是用该值进行强制类型转换。如果没有就是使用toString()的返回来进行强制类型转换。
讲一讲parseInt()方法遵循的运算规则?
parseInt(string, radix);方法的接受两个参数:
* string:要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用 ToString 抽象操作)。字符串开头的空白符将会被忽略。
* radix:一个介于2和36之间的整数(数学系统的基础),表示上述字符串的基数。比如参数”10”表示使用我们通常使用的十进制数值系统。始终指定此参数可以消除阅读该代码时的困惑并且保证转换结果可预测。当未指定基数时,不同的实现会产生不同的结果,通常将值默认为10。
返回值:返回解析后的整数值。 如果被解析参数的第一个字符无法被转化成数值类型,则返回 NaN。如果 parseInt 遇到了不属于radix参数所指定的基数中的字符那么该字符和其后的字符都将被忽略,接着返回已经解析的整数部分。
前端性能优化的方法?
(1) 减少http请求次数:CSS Sprites, JS、CSS源码压缩、图片大小控制合适;网页Gzip,CDN托管,data缓存 ,图片服务器。
(2) 前端模板 JS+数据,减少由于HTML标签导致的带宽浪费,前端用变量保存AJAX请求结果,每次操作本地变量,不用请求,减少请求次数
(3) 用innerHTML代替DOM操作,减少DOM操作次数,优化javascript性能。
(4) 当需要设置的样式很多时设置className而不是直接操作style。
(5) 少用全局变量、缓存DOM节点查找的结果。减少IO读取操作。
(6) 避免使用CSS Expression(css表达式)又称Dynamic properties(动态属性)。
(7) 图片预加载,将样式表放在顶部,将脚本放在底部 加上时间戳。
(8) 避免在页面的主体布局中使用table,table要等其中的内容完全下载之后才会显示出来,显示比div+css布局慢。
对普通的网站有一个统一的思路,就是尽量向前端优化、减少数据库操作、减少磁盘IO。向前端优化指的是,在不影响功能和体验的情况下,能在浏览器执行的不要在服务端执行,能在缓存服务器上直接返回的不要到应用服务器,程序能直接取得的结果不要到外部取得,本机内能取得的数据不要到远程取,内存能取到的不要到磁盘取,缓存中有的不要去数据库查询。减少数据库操作指减少更新次数、缓存结果减少查询次数、将数据库执行的操作尽可能的让你的程序完成(例如join查询),减少磁盘IO指尽量不使用文件系统作为缓存、减少读写文件次数等。程序优化永远要优化慢的部分,换语言是无法“优化”的。
请说出三种减少页面加载时间的方法
- 优化图片
- 图像格式的选择(GIF:提供的颜色较少,可用在一些对颜色要求不高的地方)
- 优化CSS(压缩合并css,如 margin-top, margin-left…)
- 网址后加斜杠(如www.campr.com/目录,会判断这个目录是什么文件类型,或者是目录。)
- 标明高度和宽度(如果浏览器没有找到这两个参数,它需要一边下载图片一边计算大小,如果图片很多,浏览器需要不断地调整页面。这不但影响速度,也影响浏览体验。当浏览器知道了高度和宽度参数后,即使图片暂时无法显示,页面上也会腾出图片的空位,然后继续加载后面的内容。从而加载时间快了,浏览体验也更好了)
- 减少http请求(合并文件,合并图片)
SEO优化
- 合理的title、description、keywords:搜索对着三项的权重逐个减小,title值强调重点即可,重要关键词出现不要超过2次,而且要靠前,不同页面title要有所不同;description把页面内容高度概括,长度合适,不可过分堆砌关键词,不同页面description有所不同;keywords列举出重要关键词即可
- 语义化的HTML代码,符合W3C规范:语义化代码让搜索引擎容易理解网页
- 重要内容HTML代码放在最前:搜索引擎抓取HTML顺序是从上到下,有的搜索引擎对抓取长度有限制,保证重要内容一定会被抓取
- 重要内容不要用js输出:爬虫不会执行js获取内容
- 少用iframe:搜索引擎不会抓取iframe中的内容
- 非装饰性图片必须加alt
- 提高网站速度:网站速度是搜索引擎排序的一个重要指标
在前端开发中,客户端的缓存有多种,根据应用场景的不同可以分为:
永久性存储:如localStorage。
结构化存储:如indexedDB。
会话级存储:如sessionStorage。
所谓会话级别存储,就是说在关闭标签时(有时是浏览器关闭后)数据就会被清除掉,如果cookie存储的有效期(expires)没有设定的话,默认也是会话级,存在两种会话级别的存储——sessionStorage和session Cookie。
虽然都是会话级存储,但是二者还是有很多不同的。
最根本的区别就是作用域不同:
对于sessionStorage:在浏览器中每次打开一个标签就是建立一个独立的会话,所以每个标签页的sessionStorage是独立封闭的,不可以相互访问。
对于session Cookie:会话是建立在整个浏览器进程上,即浏览器进程关闭后才能消失,并且各个标签页是可以相互访问的。
除了跨标签访问的问题外,session Cookie还有另一个特点。
由于session Cookie的会话级是建立与整个浏览器进程的,而又由于现在的大部分浏览器即使在退出后进程仍然没有关闭,所以导致session Cookie的会话级存储被超预期的延长了。
浏览器本地存储
在较高版本的浏览器中,js提供了sessionStorage和globalStorage。在HTML5中提供了localStorage来取代globalStorage。html5中的Web Storage包括了两种存储方式:sessionStorage和localStorage
sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁,仅仅是会话级别的存储
而localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的
web storage和cookie的区别
存储时效来说:
* cookie可以手动设置失效期,默认为会话级
* sessionStorage的存储时长是会话级
* localStorage的存储时长是永久,除非用户手动利用浏览器的工具删除
访问的局限性:
* cookie可以设置路径path,所有他要比另外两个多了一层访问限制
* localStorage和sessionStorage的访问限制是文档源级别,即协议、主机名和端口
* 还要注意的是,cookie可以通过设置domain属性值,可以不同二级域名下共享cookie,而Storage不可以
存储大小限制:
* cookie适合存储少量数据,他的大小限制是个数进行限制,每个浏览器的限制数量不同
Storage的可以存储数据的量较大
操作方法:
* cookie是作为document的属性存在,并没有提供标准的方法来直接操作cookie
* Storage提供了setItem()和getItem()还有removeItem()方法,操作方便不易出错
其他:
* cookie在发送http请求时,会将本地的cookie作为http头部信息传递给服务器
cookie 和session 的区别:
- cookie数据存放在客户的浏览器上,session数据放在服务器上。
- cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗
- session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
- 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
–
sessionStorage和localStorage存储的数据类型是什么?
sessionStorage和localStorage只能存储字符串类型的数据,如果setItem()方法传入的数据不是字符串的话,会自动转换为字符串类型再进行存储。所以在存储之前应该使用JSON.stringfy()方法先进行一步安全转换字符串,取值时再用JSON.parse()方法再转换一次。
session级存储中,session cookie和sessionStorage有哪些区别?
sessionStorage的会话基于标签,即标签关闭则会话终止,而cookie基于浏览器进程。
sessionStorage的访问必须基于会话继承和延续,即只有在当前标签下或当前标签打开的标签下可以访问sessionStorage中的数据,而cookie是可以跨标签进行访问的。
GET和POST的区别,何时使用POST?
GET 代表获取指定服务器上资源,POST 代表向指定的资源提交要被处理的数据
GET参数通过url传递【不安全】,POST放在request body中。
GET请求可以被浏览器缓存,可以被添加到书签中,也可保存在浏览器历史记录中,POST不能
GET请求收到URL长度限制,所以数据长度也受限制,POST不会
GET请求只能传输ASCII字符,而POST不受此限制,还可以传输二进制数据
高级的答案:
GET产生一个TCP数据包;POST产生两个TCP数据包。
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)【并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次】。
在以下情况中,请使用 POST 请求:
无法使用缓存文件(更新服务器上的文件或数据库)
向服务器发送大量数据(POST 没有数据量限制)
发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠
websocket是否了解
**是什么:**WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,它和http一样,属于应用层协议。
在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
用途:它最重要的用途是实现了客户端与服务端之间的全双工通信,当服务端数据变化时,可以第一时间通知到客户端。
不同:除此之外,它与http协议不同的地方还有:
* http只能由客户端发起,而webSocket是双向的。
* webSocket传输的数据包相对于http而言很小,很适合移动端使用
* 没有同源限制,可以跨域共享资源
关于JSONP
JSONP是一种跨域共享资源的方法。JSONP是JSON with padding的缩写,即填充式JSON或参数式JSON,是被包含在函数调用中的JSON。
JSONP的实现方式:
1. 向当前页面中动态插入一个
XML和JSON的区别?
数据体积方面:JSON相对于XML来讲,数据的体积小,传递的速度更快些。
数据交互方面:JSON与JavaScript的交互更加方便,更容易解析处理,更好的数据交互
数据描述方面:JSON对数据的描述性比XML较差
传输速度方面:JSON的速度要远远快于XML
JSON 的了解?
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它是基于JavaScript的一个子集。数据格式简单, 易于读写, 占用带宽小
JSON字符串转换为JSON对象:
var obj =eval('('+ str +')');
var obj = str.parseJSON();
var obj = JSON.parse(str);
JSON对象转换为JSON字符串:
var last=obj.toJSONString();
var last=JSON.stringify(obj);
关于Ajax
AJAX 异步 JavaScript + XML ,在后台与服务器进行异步数据交换,不用重载整个网页,实现局部刷新。
创建 ajax 步骤:
1. 创建 XMLHttpRequest 对象:var xhr=new XMLHttpRequest()
2. 创建一个新的 HTTP 请求:xhr.open(“GET”,”ajax_info.txt”,true)
3. 设置响应 HTTP 请求状态变化的回调函数:onreadystatechange
4. 发送 HTTP 请求:xhr.send()
5. 获取异步调用返回的数据
6. 使用 JavaScript 和 DOM 实现局部刷新
Ajax 的最大的特点:
1. Ajax可以实现动态不刷新(局部刷新)
2. readyState 属性 状态 有5个可取值:
0: 请求未初始化,还没有调用send()方法
1: 服务器连接已建立,已调用send()方法,正在发送请求
2: 请求已接收 ,send()方法执行完成,已经接收到全部响应内容
3: 请求处理中
4: 请求已完成,且响应已就绪
Ajax 同步和异步的区别:
1. 同步:提交请求 -> 等待服务器处理 -> 处理完毕返回,这个期间客户端浏览器不能干任何事
2. 异步:请求通过事件触发 -> 服务器处理(这是浏览器仍然可以作其他事情)-> 处理完毕
ajax.open方法中,第3个参数是设同步或者异步。
Ajax 的缺点:
1. Ajax 不支持浏览器 back 按钮
2. 安全问题 Ajax 暴露了与服务器交互的细节
3. 对搜索引擎的支持比较弱
4. 破坏了程序的异常机制
5. 不容易调试
模块化规范
模块化的开发方式可以提高代码复用率,方便进行代码的管理。通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。
一、CommonJS
Node.js是commonJS规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,更合理的方案是使用异步加载。
// 定义模块math.js
var basicNum = 0;
function add(a, b) {
return a + b;
}
module.exports = { //在这里写上需要向外暴露的函数、变量
add: add,
basicNum: basicNum
}
// 引用自定义的模块时,参数包含路径,可省略.js
var math = require('./math');
math.add(2, 5);
// 引用核心模块时,不需要带路径
var http = require('http');
http.createService(...).listen(3000);
二、AMD和require.js
AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。这里介绍用require.js实现AMD规范的模块化。用require.config()指定引用路径等,用define()定义模块,用require()加载模块。
首先我们需要引入require.js文件和一个入口文件main.js。main.js中配置require.config()并规定项目中用到的基础模块。
/** 网页中引入require.js及main.js **/
<script src="js/require.js" data-main="js/main"></script>
/** main.js 入口文件/主模块 **/
// 首先用config()指定各模块路径和引用名
require.config({
baseUrl: "js/lib",
paths: {
"jquery": "jquery.min", //实际路径为js/lib/jquery.min.js
"underscore": "underscore.min",
}
});
// 执行基本操作
require(["jquery","underscore"],function($,_){
// some code here
});
引用模块的时候,我们将模块名放在[]中作为reqiure()的第一参数;如果我们定义的模块本身也依赖其他模块,那就需要将它们放在[]中作为define()的第一参数。
// 定义math.js模块
define(function () {
var basicNum = 0;
var add = function (x, y) {
return x + y;
};
return {
add: add,
basicNum :basicNum
};
});
// 定义一个依赖underscore.js的模块
define(['underscore'],function(_){
var classify = function(list){
_.countBy(list,function(num){
return num > 30 ? 'old' : 'young';
})
};
return {
classify :classify
};
})
// 引用模块,将模块放在[]内
require(['jquery', 'math'],function($, math){
var sum = math.add(10,20);
$("#sum").html(sum);
});
三、CMD和sea.js
require.js在申明依赖的模块时会在第一时间加载并执行模块内的代码:
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
// 等于在最前面声明并初始化了要用到的所有模块
if (false) {
// 即便没用到某个模块 b,但 b 还是提前执行了
b.foo()
}
});
CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。
/** AMD写法 **/
define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {
// 等于在最前面声明并初始化了要用到的所有模块
a.doSomething();
if (false) {
// 即便没用到某个模块 b,但 b 还是提前执行了
b.doSomething()
}
});
/** CMD写法 **/
define(function(require, exports, module) {
var a = require('./a'); //在需要时申明
a.doSomething();
if (false) {
var b = require('./b');
b.doSomething();
}
});
/** sea.js **/
// 定义模块 math.js
define(function(require, exports, module) {
var $ = require('jquery.js');
var add = function(a,b){
return a+b;
}
exports.add = add;
});
// 加载模块
seajs.use(['math.js'], function(math){
var sum = math.add(1+2);
});
四、ES6 Module
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
return a + b;
};
export { basicNum, add };
/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
ele.textContent = add(99 + basicNum);
}
如上例所示,使用import命令的时候,用户需要知道所要加载的变量名或函数名。其实ES6还提供了export default命令,为模块指定默认输出,对应的import语句不需要使用大括号。这也更趋近于ADM的引用写法。
/** export default **/
//定义输出
export default { basicNum, add };
//引入
import math from './math';
function test(ele) {
ele.textContent = math.add(99 + math.basicNum);
}
ES6的模块不是对象,import命令会被 JavaScript 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,所以无法实现条件加载。也正因为这个,使得静态分析成为可能。
ES6 模块与 CommonJS 模块的差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。 - CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。
编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。
CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
前端模块规范有三种:CommonJs,AMD和CMD。
CommonJs用在服务器端,AMD和CMD用在浏览器环境
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
AMD:提前执行(异步加载:依赖先执行)+依赖前置
CMD:延迟执行(运行到需加载,根据顺序执行)+依赖就近
CommonJS 中的 require/exports 和 ES6 中的 import/export 区别?
- ES6通过import和export来管理模块、node用require导入模块,exports和module.exports导出模块。NodeJS中每个js文件都有一个对象 module。module对象包含exports对象。当其require其他模块时NodeJS就是将module.exports对象输出,而exports对象是module.exports的引用。
- CommonJS 模块的重要特性是加载时执行,即脚本代码在 require 的时候,就会全部执行。一旦出现某个模块被”循环加载”,就只输出已经执行的部分,还未执行的部分不会输出。
- ES6 模块是动态引用,如果使用 import 从一个模块加载变量,那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
- import/export 最终都是编译为 require/exports 来执行的。
- CommonJS 规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的 exports 属性(即 module.exports )是对外的接口。加载某个模块,其实是加载该模块的 module.exports 属性。export 命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。