前端基本功-常见概念(一) 点这里
前端基本功-常见概念(二) 点这里
前端基本功-常见概念(三) 点这里javascript
AMD:AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。全部依赖这个模块的语句,都定义在一个回调函数中,等到加载完成以后,这个回调函数才会运行。css
AMD是requirejs 在推广过程当中对模块定义的规范化产出,提早执行,推崇依赖前置。用define()定义模块,用require()加载模块,require.config()指定引用路径等html
首先咱们须要引入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()
的第一参数。vue
// 定义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:seajs 在推广过程当中对模块定义的规范化产出,延迟执行,推崇依赖就近java
require.js在申明依赖的模块时会在第一之间加载并执行模块内的代码:node
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推广过程当中产生的。jquery
/** 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); });
CommonJs:Node.js是commonJS规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。git
// 定义模块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);
commonJS用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取很是快,因此这样作不会有问题。可是在浏览器端,限于网络缘由,更合理的方案是使用异步加载。github
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 引擎静态分析,在编译时就引入模块代码,而不是在代码运行时加载,因此没法实现条件加载。也正由于这个,使得静态分析成为可能。
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- 编译时加载: ES6 模块不是对象,而是经过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时能够指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
本节参考文章:前端模块化:CommonJS,AMD,CMD,ES6
ES5的继承时经过prototype或构造函数机制来实现。ES5的继承实质上是先建立子类的实例对象,而后再将父类的方法添加到this上(Parent.apply(this))。
ES6的继承机制彻底不一样,实质上是先建立父类的实例对象this(因此必须先调用父类的super()方法),而后再用子类的构造函数修改this。
具体的:ES6经过class关键字定义类,里面有构造方法,类之间经过extends关键字实现继承。子类必须在constructor方法中调用super方法,不然新建实例报错。由于子类没有本身的this对象,而是继承了父类的this对象,而后对其进行加工。若是不调用super方法,子类得不到this对象。
ps:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可以使用this关键字,不然报错。
区别:(以SubClass,SuperClass,instance为例)
ES5中继承的实质是:(那种经典寄生组合式继承法)经过prototype或构造函数机制来实现,先建立子类的实例对象,而后再将父类的方法添加到this上(Parent.apply(this))。
ES6中继承的实质是:先建立父类的实例对象this(因此必须先调用父类的super()方法),而后再用子类的构造函数修改this
静态方法继承实质上只须要更改下SubClass.__proto__到SuperClass便可
本节参考文章:连接
请求报文 | 响应报文 |
---|---|
请求行 请求头 空行 请求体 | 状态行 响应头 空行 响应体 |
HTTP request报文结构是怎样的
首行是Request-Line包括:请求方法,请求URI,协议版本,CRLF
首行以后是若干行请求头,包括general-header,request-header或者entity-header,每一个一行以CRLF结束
请求头和消息实体之间有一个CRLF分隔
根据实际请求须要可能包含一个消息实体 一个请求报文例子以下:
GET /Protocols/rfc2616/rfc2616-sec5.html HTTP/1.1 Host: www.w3.org Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36 Referer: https://www.google.com.hk/ Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8,en;q=0.6 Cookie: authorstyle=yes If-None-Match: "2cc8-3e3073913b100" If-Modified-Since: Wed, 01 Sep 2004 13:24:52 GMT name=qiu&age=25
请求报文
HTTP response报文结构是怎样的
首行是状态行包括:HTTP版本,状态码,状态描述,后面跟一个CRLF
首行以后是若干行响应头,包括:通用头部,响应头部,实体头部
响应头部和响应实体之间用一个CRLF空行分隔
最后是一个可能的消息实体 响应报文例子以下:
HTTP/1.1 200 OK Date: Tue, 08 Jul 2014 05:28:43 GMT Server: Apache/2 Last-Modified: Wed, 01 Sep 2004 13:24:52 GMT ETag: "40d7-3e3073913b100" Accept-Ranges: bytes Content-Length: 16599 Cache-Control: max-age=21600 Expires: Tue, 08 Jul 2014 11:28:43 GMT P3P: policyref="http://www.w3.org/2001/05/P3P/p3p.xml" Content-Type: text/html; charset=iso-8859-1 {"name": "qiu", "age": 25}
响应报文
工厂模式集中实例化了对象,避免实例化对象大量重复问题
//工厂模式 function createObject(a,b){ var obj = new Object(); //集中实例化 obj.a = a; obj.b = b; obj.c = function () { return this.a + this.b; }; return obj; //返回实例化对象 } var box = createObject('abc',10); var box1 = createObject('abcdef',20); alert(box.c()); //返回abc10 alert(box1.c()); //返回abcdef20
//构造函数 function Create(a,b) { this.a =a; this.b =b; this.c = function () { return this.a + this.b; }; } var box = new Create('abc',10); alert(box.run()); //返回abc10
构造函数相比工厂模式:
构造函数编写规范:
构造函数和普通函数的区别:
查看归属问题,要建立两个构造函数:
function Create(a,b) { this.a =a; this.b =b; this.c = function () { return this.a + this.b; }; } function DeskTop(a,b) { this.a =a; this.b =b; this.c = function () { return this.a + this.b; }; } var box = new Create('abc',10); var box1 = new DeskTop('def',20); alert(box instanceof Object); //这里要注意:全部的构造函数的对象都是Object. alert(box instanceof Create); //true alert(box1 instanceof Create); //false alert(box1 instanceof DeskTop); //true
Promise.resolve()
能够生成一个成功的Promise
Promise.resolve()语法糖
例1:Promise.resolve('成功')
等同于new Promise(function(resolve){resolve('成功')})
例2:
var resolved = Promise.resolve('foo'); resolved.then((str) => console.log(str);//foo )
至关于
var resolved = new Promise((resolve, reject) => { resolve('foo') }); resolved.then((str) => console.log(str);//foo )
Promise.resolve方法有下面三种形式:
这三种形式都会产生一个新的Promise。其中:
实际上第二种形式能够归在第三种形式中。
本节参考文章:ES6中的Promise.resolve()
推荐阅读:性感的Promise...
伪类 用于当
已有元素
处于的某个状态时,为其添加对应的样式,这个状态是根据用户行为而动态变化的。
当用户悬停在指定的元素时,咱们能够经过 :hover
来描述这个元素的状态。虽然它和普通的 CSS 类类似,能够为已有的元素添加样式,可是它只有处于 DOM 树没法描述的状态下才能为元素添加样式,因此将其称为伪类。
伪元素 用于建立一些
不在文档树中
的元素,并为其添加样式。
咱们能够经过 :before
来在一个元素前增长一些文本,并为这些文本添加样式。虽然用户能够看到这些文本,可是这些文本实际上不在文档树中。
本节参考文章:前端面试题-伪类和伪元素、总结伪类与伪元素
DOM文档加载的步骤为:
触发的时机不同,先触发DOMContentLoaded事件,后触发load事件。
原生js
// 不兼容老的浏览器,兼容写法见[jQuery中ready与load事件](http://www.imooc.com/code/3253),或用jQuery document.addEventListener("DOMContentLoaded", function() { // ...代码... }, false); window.addEventListener("load", function() { // ...代码... }, false);
jQuery
// DOMContentLoaded $(document).ready(function() { // ...代码... }); //load $(document).load(function() { // ...代码... });
head 中资源的加载
body 中资源的加载
注意:
页面中引用的js 代码若是有异步加载的 js、css、图片,是会影响 load 事件触发的。video、audio、flash 不会影响 load 事件触发。
推荐阅读:再谈 load 与 DOMContentLoaded
本节参考文章:DOMContentLoaded与load的区别、事件DOMContentLoaded和load的区别
由于浏览器生成Dom树的时候是一行一行读HTML代码的,script标签放在最后面就不会影响前面的页面的渲染。那么问题来了,既然Dom树彻底生成好后页面才能渲染出来,浏览器又必须读彻底部HTML才能生成完整的Dom树,script标签不放在body底部是否是也同样,由于dom树的生成须要整个文档解析完毕。
咱们再来看一下chrome在页面渲染过程当中的,绿色标志线是First Paint的时间。纳尼,为何会出现firstpaint,页面的paint不是在渲染树生成以后吗?其实现代浏览器为了更好的用户体验,渲染引擎将尝试尽快在屏幕上显示的内容。它不会等到全部HTML解析以前开始构建和布局渲染树。部分的内容将被解析并显示。也就是说浏览器可以渲染不完整的dom树和cssom,尽快的减小白屏的时间。假如咱们将js放在header,js将阻塞解析dom,dom的内容会影响到First Paint,致使First Paint延后。因此说咱们会 将js放在后面,以减小First Paint的时间,可是不会减小DOMContentLoaded被触发的时间。
本节参考文章:DOMContentLoaded与load的区别
clientheight:内容的可视区域,不包含border。clientheight=padding+height-横向滚动轴高度。
这里写图片描述
offsetheight,它包含padding、border、横向滚动轴高度。
offsetheight=padding+height+border+横向滚动轴高度
scrollheight,可滚动高度,就是将滚动框拉直,再也不滚动的高度,这个很好理解。 It includes the element’s padding, but not its border or margin.
本节参考文章:css clientheight、offsetheight、scrollheight详解
不容许重复的属性名称或参数值。当检测到对象中重复命名的属性,例如:
var object = {foo: "bar", foo: "baz"};)
或检测到函数中重复命名的参数时,例如:
function foo(val1, val2, val1){})
严格模式会抛出错误,所以捕捉几乎能够确定是代码中的bug能够避免浪费大量的跟踪时间。
本节参考文章:经典面试题(4)
JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象建立一个新变量。在浏览器中,全局对象是 window 。
function foo(arg) { bar = "this is a hidden global variable"; }
真相是: ``` function foo(arg) { window.bar = "this is an explicit global variable"; } ``` 函数 foo 内部忘记使用 var ,意外建立了一个全局变量。此例泄漏了一个简单的字符串,无伤大雅,可是有更糟的状况。 另外一种意外的全局变量可能由 this 建立: ``` function foo() { this.variable = "potential accidental global"; } // Foo 调用本身,this 指向了全局对象(window) // 而不是 undefined foo(); ``` 在 JavaScript 文件头部加上 'use strict',能够避免此类错误发生。启用严格模式解析 JavaScript ,避免意外的全局变量。
被遗忘的计时器或回调函数
在 JavaScript 中使用 setInterval 很是日常。一段常见的代码:
var someResource = getData(); setInterval(function() { var node = document.getElementById('Node'); if(node) { // 处理 node 和 someResource node.innerHTML = JSON.stringify(someResource)); } }, 1000);
此例说明了什么:与节点或数据关联的计时器再也不须要,node 对象能够删除,整个回调函数也不须要了。但是,计时器回调函数仍然没被回收(计时器中止才会被回收)。同时,someResource 若是存储了大量的数据,也是没法被回收的。
对于观察者的例子,一旦它们再也不须要(或者关联的对象变成不可达),明确地移除它们很是重要。老的 IE 6 是没法处理循环引用的。现在,即便没有明确移除它们,一旦观察者对象变成不可达,大部分浏览器是能够回收观察者处理函数的。
观察者代码示例:
var element = document.getElementById('button'); function onClick(event) { element.innerHTML = 'text'; } element.addEventListener('click', onClick);
对象观察者和循环引用注意事项
老版本的 IE 是没法检测 DOM 节点与 JavaScript 代码之间的循环引用,会致使内存泄漏。现在,现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法,已经能够正确检测和处理循环引用了。换言之,回收节点内存时,没必要非要调用 removeEventListener 了。
脱离 DOM 的引用
有时,保存 DOM 节点内部数据结构颇有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组颇有意义。此时,一样的 DOM 元素存在两个引用:一个在 DOM 树中,另外一个在字典中。未来你决定删除这些行时,须要把两个引用都清除。
var elements = { button: document.getElementById('button'), image: document.getElementById('image'), text: document.getElementById('text') }; function doStuff() { image.src = 'http://some.url/image'; button.click(); console.log(text.innerHTML); // 更多逻辑 } function removeButton() { // 按钮是 body 的后代元素 document.body.removeChild(document.getElementById('button')); // 此时,仍旧存在一个全局的 #button 的引用 // elements 字典。button 元素仍旧在内存中,不能被 GC 回收。 }
此外还要考虑 DOM 树内部或子节点的引用问题。假如你的 JavaScript 代码中保存了表格某一个 <td> 的引用。未来决定删除整个表格的时候,直觉认为 GC 会回收除了已保存的 <td> 之外的其它节点。实际状况并不是如此:此 <td> 是表格的子节点,子元素与父元素是引用关系。因为代码保留了 <td> 的引用,致使整个表格仍待在内存中。保存 DOM 元素引用的时候,要当心谨慎。
闭包是 JavaScript 开发的一个关键方面:匿名函数能够访问父级做用域的变量。
避免滥用
本节参考文章:4类 JavaScript 内存泄漏及如何避免
js垃圾回收有两种常见的算法:引用计数和标记清除。
垃圾收集器在运行时会给储存在内存中的全部变量加上标记,而后会去掉环境中的变量以及被环境中的变量引用的变量的标记,当执行完毕那些没有存在引用 没法访问的变量就被加上标记,最后垃圾收集器完成清除工做,释放掉那些打上标记的变量所占的内存。
function problem() { var A = {}; var B = {}; A.a = B; B.a = A; }
引用计数存在一个弊端就是循环引用问题(上边)
标记清除不存在循环引用的问题,是由于当函数执行完毕以后,对象A和B就已经离开了所在的做用域,此时两个变量被标记为“离开环境”,等待被垃圾收集器回收,最后释放其内存。
分析如下代码:
function createPerson(name){ var localPerson = new Object(); localPerson.name = name; return localPerson; } var globalPerson = createPerson("Junga"); globalPerson = null;//手动解除全局变量的引用
在这个🌰中,变量globalPerson取得了createPerson()函数的返回的值。在createPerson()的内部建立了一个局部变量localPerson并添加了一个name属性。因为localPerson在函数执行完毕以后就离开执行环境,所以会自动解除引用,而对于全局变量来讲则须要咱们手动设置null,解除引用。
不过,解除一个值的引用并不意味着自动回收该值所占用的内存,解除引用真正的做用是让值脱离执行环境,以便垃圾收集器下次运行时将其收回。
本节参考文章:JavaScript的内存问题
本节参考文章:2018前端面试总结...
一般 SPA 中前端路由有2种实现方式:
下面就来介绍下这两种方式具体怎么实现的
window.history 对象包含浏览器的历史,window.history 对象在编写时可不使用 window 这个前缀。history是实现SPA前端路由是一种主流方法,它有几个原始方法:
- history.back() - 与在浏览器点击后退按钮相同
在HTML5,history对象提出了 pushState() 方法和 replaceState() 方法,这两个方法能够用来向历史栈中添加数据,就好像 url 变化了同样(过去只有 url 变化历史栈才会变化),这样就能够很好的模拟浏览历史和前进后退了,如今的前端路由也是基于这个原理实现的。
pushState(stateObj, title, url) 方法向历史栈中写入数据,其第一个参数是要写入的数据对象(不大于640kB),第二个参数是页面的 title, 第三个参数是 url (相对路径)。
- stateObj :一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。若是不须要这个对象,此处能够填null。
关于pushState,有几个值得注意的地方:
- pushState方法不会触发页面刷新,只是致使history对象发生变化,地址栏会有反应,只有当触发前进后退等事件(back()和forward()等)时浏览器才会刷新
replaceState(stateObj, title, url) 和pushState的区别就在于它不是写入而是替换修改浏览历史中当前纪录,其他和 pushState如出一辙
- 定义:每当同一个文档的浏览历史(即history对象)出现变化时,就会触发popstate事件。
<a class="api a">a.html</a> <a class="api b">b.html</a>
// 注册路由 document.querySelectorAll('.api').forEach(item => { item.addEventListener('click', e => { e.preventDefault(); let link = item.textContent; if (!!(window.history && history.pushState)) { // 支持History API window.history.pushState({name: 'api'}, link, link); } else { // 不支持,可以使用一些Polyfill库来实现 } }, false) }); // 监听路由 window.addEventListener('popstate', e => { console.log({ location: location.href, state: e.state }) }, false)
popstate监听函数里打印的e.state即是history.pushState()里传入的第一个参数,在这里即为{name: 'api'}
url 中能够带有一个 hash http://localhost:9000/#/a.html
window 对象中有一个事件是 onhashchange,如下几种状况都会触发这个事件:
- 直接更改浏览器地址,在最后面增长或改变#hash;
// 注册路由 document.querySelectorAll('.api').forEach(item => { item.addEventListener('click', e => { e.preventDefault(); let link = item.textContent; location.hash = link; }, false) }); // 监听路由 window.addEventListener('hashchange', e => { console.log({ location: location.href, hash: location.hash }) }, false)
本节参考文章:vue 单页应用(spa)前端路由实现原理