let
和const
是ES6
新增的命令,用于声明变量,这两个命令跟ES5
的var
有许多不一样,而且let
和const
也有一些细微的不一样javascript
var
和let
/const
的区别css
const
命令两个注意点:html
ES5只有全局做用域和函数做用域,没有块级做用域。前端
这带来不少不合理的场景:vue
var tmp = new Date();
function f() {
console.log(tmp); // 想打印外层的时间做用域
if (false) {
var tmp = 'hello world'; // 这里声明的做用域为整个函数
}
}
f(); // undefined
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]); // i应该为这次for循环使用的变量
}
console.log(i); // 5 全局范围均可以读到
复制代码
function f1() {
let n = 5;
if (true) {
let n = 10;
console.log(n); // 10 内层的n
}
console.log(n); // 5 当前层的n
}
复制代码
{{{{
{let insane = 'Hello World'}
console.log(insane); // 报错 读不到子做用域的变量
}}}};
复制代码
{
let a = ...;
...
}
{
let a = ...;
...
}
复制代码
以上形式,能够用于测试一些想法,不用担忧变量重名,也不用担忧外界干扰java
块级做用域声明函数:node
在块级做用域声明函数,由于浏览器的要兼容老代码,会产生一些问题!react
在块级做用域声明函数,最好使用匿名函数的形式。css3
if(true){
let a = function () {}; // 做用域为块级 令声明的函数做用域范围更清晰
}
复制代码
ES6 的块级做用域容许声明函数的规则,只在使用大括号的状况下成立,若是没有使用大括号,就会报错。web
// 报错
'use strict';
if (true)
function f() {} // 咱们须要给if加个{}
复制代码
变量提高的现象:在同一做用域下,变量能够在声明以前使用,值为undefined
ES5 时使用var
声明变量,常常会出现变量提高的现象。
// var 的状况
console.log(foo); // 输出undefined
var foo = 2;
// let 的状况
console.log(bar); // 报错ReferenceError
let bar = 2;
复制代码
只要一进入当前做用域,所要使用的变量就已经存在了,可是不可获取,只有等到声明变量的那一行代码出现,才能够获取和使用该变量
var tmp = 123; // 声明
if (true) {
tmp = 'abc'; // 报错 由于本区域有tmp声明变量
let tmp; // 绑定if这个块级的做用域 不能出现tmp变量
}
复制代码
暂时性死区和不能变量提高的意义在于:
在测试时出现这种状况:
var a= '声明';const a '不报错'
,这种状况是由于babel
在转化的时候,作了一些处理,在浏览器的控制台中测试,就成功报错
let
、const
不容许在相同做用域内,重复声明同一个变量
function func(arg) {
let arg; // 报错
}
function func(arg) {
{
let arg; // 不报错
}
}
复制代码
var a = 1;
// 若是在 Node环境,能够写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
复制代码
let p; var p1; // 不报错
const p3 = '立刻赋值'
const p3; // 报错 没有赋值
复制代码
简单类型:不能改动
const p = '不能改变';
p = '报错'
复制代码
复杂类型:变量指针不能变
考虑以下状况:
const p = ['不能改动']
const p2 = {
name: 'OBKoro1'
}
p[0] = '不报错'
p2.name = '不报错'
p = ['报错']
p2 = {
name: '报错'
}
复制代码
const所说的一旦声明值就不能改变,实际上指的是:变量指向的那个内存地址所保存的数据不得改动
push
、shift
、splice
等方法也是容许的,你就是把值一个一个全都删光了都不会报错。复杂类型还有函数,正则等,这点也要注意一下。
优势:
缺点:
优势:
缺点:
总结:若是动画只是简单的状态切换,不须要中间过程控制,在这种状况下,css动画是优选方案。它可让你将动画逻辑放在样式文件里面,而不会让你的页面充斥 Javascript 库。然而若是你在设计很复杂的富客户端界面或者在开发一个有着复杂UI状态的 APP。那么你应该使用js动画,这样你的动画能够保持高效,而且你的工做流也更可控。因此,在实现一些小的交互动效的时候,就多考虑考虑CSS动画。对于一些复杂控制的动画,使用javascript比较可靠。
共同点:都是保存在浏览器端,且同源的
区别:
跨域概念解释:当前发起请求的域与该请求指向的资源所在的域不同。这里的域指的是这样的一个概念:咱们认为若协议 + 域名 + 端口号均相同,那么就是同域。 以下表
利用script
标签没有跨域限制的漏洞,网页能够获得从其余来源动态产生的 JSON 数据。JSONP请求必定须要对方的服务器作支持才能够
JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)
优势:
缺点:只支持get请求具备局限性,,不安全可能会遭受XSS攻击
// index.html
function jsonp({ url, params, callback }) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
window[callback] = function(data) {
resolve(data)
document.body.removeChild(script)
}
params = { ...params, callback } // wd=b&callback=show
let arrs = []
for (let key in params) {
arrs.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arrs.join('&')}`
document.body.appendChild(script)
})
}
jsonp({
url: 'http://localhost:3000/say',
params: { wd: 'Iloveyou' },
callback: 'show'
}).then(data => {
console.log(data)
})
复制代码
上面这段代码至关于向http://localhost:3000/say?wd=Iloveyou&callback=show
这个地址请求数据,而后后台返回show('我不爱你')
,最后会运行show()
这个函数,打印出'我不爱你'
// server.js
let express = require('express')
let app = express()
app.get('/say', function(req, res) {
let { wd, callback } = req.query
console.log(wd) // Iloveyou
console.log(callback) // show
res.end(`${callback}('我不爱你')`)
})
app.listen(3000)
复制代码
互联网有一项著名的8秒原则。用户在访问Web网页时,若是时间超过8秒就会感到不耐烦,若是加载须要太长时间,他们就会放弃访问。
主要包括这些方面:html压缩、css 压缩、js的压缩和混乱和文件合并。
资源压缩能够从文件中去掉多余的字符,好比回车、空格。你在编辑器中写代码的时候,会使用缩进和注释,这些方法无疑会让你的代码简洁并且易读,但它们也会在文档中添加多余的字节。
一、异步加载的方式 异步加载的三种方式——async
和defer
、动态脚本建立
① async方式
<script type="text/javascript" src="xxx.js" async="async"></script>
复制代码
② defer方式
<script type="text/javascript" src="xxx.js" defer></script>
复制代码
③动态建立script标签
在还没定义defer和async前,异步加载的方式是动态建立script,经过window.onload方法确保页面加载完毕再将script标签插入到DOM中,具体代码以下:
function addScriptTag(src){
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function(){
addScriptTag("js/index.js");
}
复制代码
二、异步加载的区别
其中蓝色线表明网络读取,红色线表明执行时间,这俩都是针对脚本的;绿色线表明 HTML 解析。
经过将静态资源(例如javascript,css,图片等等)缓存到离用户很近的相同网络运营商的CDN节点上,不但能提高用户的访问速度,还能节省服务器的带宽消耗,下降负载。
资源预加载是另外一个性能优化技术,咱们可使用该技术来预先告知浏览器某些资源可能在未来会被使用到。
经过 DNS 预解析来告诉浏览器将来咱们可能从某个特定的 URL 获取资源,当浏览器真正使用到该域中的某个资源时就能够尽快地完成 DNS 解析。例如,咱们未来可从 example.com 获取图片或音频资源,那么能够在文档顶部的 标签中加入如下内容:
<link rel="dns-prefetch" href="//example.com">
复制代码
当咱们从该 URL 请求一个资源时,就再也不须要等待 DNS 的解析过程。该技术对使用第三方资源特别有用。经过简单的一行代码就能够告知那些兼容的浏览器进行 DNS 预解析,这意味着当浏览器真正请求该域中的某个资源时,DNS 的解析就已经完成了,从而节省了宝贵的时间。 另外须要注意的是,浏览器会对a标签的href自动启用DNS Prefetching,因此a标签里包含的域名不须要在head中手动设置link。可是在HTTPS下不起做用,须要meta来强制开启功能。这个限制的缘由是防止窃听者根据DNS Prefetching推断显示在HTTPS页面中超连接的主机名。下面这句话做用是强制打开a标签域名解析
<meta http-equiv="x-dns-prefetch-control" content="on">
复制代码
内存泄漏的常见场景
文章前言部分就有说到,JS 开发者喜欢用对象的键值对来缓存函数的计算结果,可是缓存中存储的键越多,长期存活的对象也就越多,这将致使垃圾回收在进行扫描和整理时,对这些对象作无用功。
var leakArray = [];
exports.leak = function () {
leakArray.push("leak" + Math.random());
}
复制代码
以上代码,模块在编译执行后造成的做用域由于模块缓存的缘由,不被释放,每次调用 leak 方法,都会致使局部变量 leakArray 不停增长且不被释放。
闭包能够维持函数内部变量驻留内存,使其得不到释放。
声明过多的全局变量,会致使变量常驻内存,要直到进程结束才可以释放内存。
//dom still exist
function click(){
// 可是 button 变量的引用仍然在内存当中。
const button = document.getElementById('button');
button.click();
}
复制代码
// vue 的 mounted 或 react 的 componentDidMount
componentDidMount() {
setInterval(function () {
// ...do something
}, 1000)
}
复制代码
vue 或 react 的页面生命周期初始化时,定义了定时器,可是在离开页面后,未清除定时器,就会致使内存泄漏。
componentDidMount() {
window.addEventListener("scroll", function () {
// do something...
});
}
复制代码
在页面生命周期初始化时,绑定了事件监听器,但在离开页面后,未清除事件监听器,一样也会致使内存泄漏。
内存泄漏优化
确保占用最少的内存可让页面得到更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据再也不有用,最好经过将其值设置为 null 来释放其引用——这个作法叫作解除引用(dereferencing)
function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
}
var globalPerson = createPerson("Nicholas");
// 手动解除 globalPerson 的引用
globalPerson = null;
复制代码
解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正做用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。
var leakArray = [];
exports.clear = function () {
leakArray = [];
}
复制代码
首先,明确gzip
是一种压缩格式,须要浏览器支持才有效(不过通常如今浏览器都支持),并且gzip
压缩效率很好(高达70%左右)。而后gzip通常是由apache
、tomcat
等web服务器开启。
固然服务器除了gzip
外,也还会有其它压缩格式(如 deflate
,没有gzip高效,且不流行),因此通常只须要在服务器上开启了gzip压缩,而后以后的请求就都是基于gzip压缩格式的,很是方便
对于web应用来讲,缓存是提高页面性能同时减小服务器压力的利器。
缓存能够简单的划分红两种类型: 强缓存(200fromcache
)与 协商缓存(304
)
区别简述以下:
200fromcache
)时,浏览器若是判断本地缓存未过时,就直接使用,无需发起http请求304
)时,浏览器会向服务端发起http请求,而后服务端告诉浏览器文件未改变,让浏览器使用本地缓存对于协商缓存,使用 Ctrl+F5强制刷新可使得缓存无效。可是对于强缓存,在未过时时,必须更新资源路径才能发起新的请求(更改了路径至关因而另外一个资源了,这也是前端工程化中经常使用到的技巧)。
属于强缓存控制的:
(http1.1) Cache-Control(浏览器)/Max-Age(服务端)
(http1.0)Pragma(浏览器)/Expires(服务端)
复制代码
注意: Max-Age
不是一个头部,它是Cache-Control
头部的值。
属于协商缓存控制的:
(http1.1) If-None-Match(浏览器)/E-tag(服务端)
(http1.0) If-Modified-Since(浏览器)/Last-Modified(服务端)
复制代码
能够看到,上述有提到http1.1
和http1.0
,这些不一样的头部是属于不一样http时期的。
再提一点,其实HTML页面中也有一个meta标签能够控制缓存方案- Pragma。
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
复制代码
不过,这种方案仍是比较少用到,由于支持状况不佳,譬如缓存代理服务器确定不支持,因此不推荐。
头部的区别
首先明确,http的发展是从http1.0到http1.1,而在http1.1中,出了一些新内容,弥补了http1.0的不足。
http1.0中的缓存控制:
Pragma
:严格来讲,它不属于专门的缓存控制头部,可是它设置 no-cache
时可让本地强缓存失效(属于编译控制,来实现特定的指令,主要是由于兼容http1.0,因此之前又被大量应用)Expires
:服务端配置的,属于强缓存,用来控制在规定的时间以前,浏览器不会发出请求,而是直接使用本地缓存,注意,Expires通常对应服务器端时间,如Expires:Fri,30Oct 1998 14:19:41
If-Modified-Since/Last-Modified
:这两个是成对出现的,属于协商缓存的内容,其中浏览器的头部是 If-Modified-Since
,而服务端的是Last-Modified
,它的做用是,在发起请求时,若是If-Modified-Since
和 Last-Modified
匹配,那么表明服务器资源并未改变,所以服务端不会返回资源实体,而是只返回头部,通知浏览器可使用本地缓存。Last-Modified
,顾名思义,指的是文件最后的修改时间,并且只能精确到 1s之内http1.1中的缓存控制:
Cache-Control
:缓存控制头部,是浏览器的头部,有no-cache
、max-age
等多种取值Max-Age
:服务端配置的,用来控制强缓存,在规定的时间以内,浏览器无需发出请求,直接使用本地缓存,注意,Max-Age
是Cache-Control
头部的值,不是独立的头部,譬如 Cache-Control:max-age=3600
,并且它值得是绝对时间,由浏览器本身计算If-None-Match/E-tag
:这两个是成对出现的,属于协商缓存的内容,其中浏览器的头部是 If-None-Match
,而服务端的是E-tag
,一样,发出请求后,若是If-None-Match
和 E-tag
匹配,则表明内容未变,通知浏览器使用本地缓存,和Last-Modified
不一样,E-tag
更精确,它是相似于指纹同样的东西,基于FileEtagINodeMtimeSize
生成,也就是说,只要文件变,指纹就会变,并且没有1s精确度的限制。Max-Age相比Expires?
Expires
使用的是服务器端的时间,可是有时候会有这样一种状况-客户端时间和服务端不一样步。那这样,可能就会出问题了,形成了浏览器本地的缓存无用或者一直没法过时,因此通常http1.1后不推荐使用Expires
。而 Max-Age
使用的是客户端本地时间的计算,所以不会有这个问题,所以推荐使用 Max-Age
。
注意,若是同时启用了Cache-Control
与Expires
,Cache-Control
优先级高。
E-tag相比Last-Modified?
Last-Modified
:
而E-tag
:
若是同时带有E-tag
和Last-Modified
,服务端会优先检查E-tag
。
各大缓存头部的总体关系以下图:
// apply
let arr = [0,245,7,986];
let result = Math.max.apply(null,arr)
console.log(result) // 986
// call
let arr2 = [0,245,7,986];
let result2 = Math.max.call(null,[...arr])
console.log(result2) // 986
复制代码
// 第一种最方便
let arr = [1,2,3,4,5,1,2,1]
let result = Array.from(new Set(arr))
console.log(result) // [1,2,3,4,5]
// 第二种利用对象的属性不能相同的特色进行去重(兼容性好)
let arr = [1,2,3,4,5,1,2,1];
let array = [];
var obj = {};
for (var i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
obj[arr[i]] = true;
array.push(arr[i]);
}
}
console.log(array); //[1,2,3,4,5]
// 第三种双层循环 (兼容性好)
var arr = [1,2,3,4,5,1,2,1];
var array = [];
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < array.length; j++) {
if (arr[i] == array[j]) {
break;
}
}
//若是这两个数相等说明循环完了,没有相等的元素
if (j == array.length) {
array.push(arr[i]);
}
}
console.log(array); //[1,2,3,4,5]
// 第四种利用 indexOf()
var arr = [1,2,3,4,5,1,2,1];
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array.indexOf(arr[i]) == -1) {
array.push(arr[i]);
}
}
console.log(array); //[1,2,3,4,5]
// 第五种利用 forEach 和 indexOf()
var arr = [1,2,3,4,5,1,2,1];
var array = [];
arr.forEach(function(item, index) {
if (array.indexOf(item) == -1) {
array.push(item);
}
});
console.log(array); //[1,2,3,4,5]
// 第六种利用 filter()和 indexOf()
var arr = [1,2,3,4,5,1,2,1];
var array = arr.filter(function(item, index) {
return arr.indexOf(item) == index;
});
console.log(array); //[1,2,3,4,5]
// 第7种ES6 的 includes 实现去重
var arr = [1,2,3,4,5,1,2,1];
var array = [];
arr.forEach(function(item, index) {
if (!array.includes(item)) {
array.push(item);
}
});
console.log(array); //[1,2,3,4,5]
复制代码
createDocumentFragment() //建立一个DOM片断
createElement() //建立一个具体的元素
createTextNode() //建立一个文本节点
复制代码
appendChild() // 添加
removeChild() // 移除
replaceChild() // 替换
insertBefore() // 插入
复制代码
getElementsByTagName() //经过标签名称
getElementsByName() //经过元素的Name属性的值
getElementById() //经过元素Id,惟一性
querySelector() // 经过选择器获取一个元素
querySelectorAll() // 经过选择器获取一组元素
getElementsByClassName() // 经过类名
document.documentElement // 获取html的方法
document.body // 获取body的方法
复制代码
(1) function Foo () {
getName = function () { alert(1) };
return this;
}
(2) Foo.getName = function () { alert(2) };
(3) Foo.prototype.getName = function () { alert(3) };
(4) var getName = function () { alert(4) };
(5) function getName () { alert(5) };
//输出的值
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); 3
复制代码
此题涉及的知识点众多,包括变量定义提高、this指针指向、运算符优先级、原型、继承、全局变量污染、对象属性及原型属性优先级等等
此题包含7小问,分别说下。
第一问
先看此题的上半部分作了什么,首先定义了一个叫Foo的函数,以后为Foo建立了一个叫getName的静态属性存储了一个匿名函数,以后为Foo的原型对象新建立了一个叫getName的匿名函数。以后又经过函数变量表达式建立了一个getName的函数,最后再声明一个叫getName函数。
第一问的 Foo.getName 天然是访问Foo函数上存储的静态属性,天然是2,没什么可说的
第二问
直接调用 getName 函数。既然是直接调用那么就是访问当前上文做用域内的叫getName的函数,因此跟1 2 3都没什么关系。此题有无数面试者回答为5。此处有两个坑,一是变量声明提高,二是函数表达式。
变量声明提高
即全部声明变量或声明函数都会被提高到当前函数的顶部。
备注:
例以下代码:
console.log('x' in window); // true
var x;
x = 0;
复制代码
代码执行时js引擎会将声明语句提高至代码最上方,变为:
var x;
console.log('x' in window); // true
x = 0;
复制代码
函数表达式var getName
与 function getName
都是声明语句,区别在于 var getName
是函数表达式,而 function getName
是函数声明。
函数表达式最大的问题,在于js会将此代码拆分为两行代码分别执行。
例以下代码:
console.log(x); // 输出:function x(){} ?
function x(){}
var x=1;
function x(){}
复制代码
实际执行的代码为,先将 var x=1 拆分为 var x; 和 x = 1; 两行,再将 var x; 和 function x(){} 两行提高至最上方变成:
function x(){}
var x;
console.log(x); // function x(){}
x=1;
复制代码
因此最终函数声明的x覆盖了变量声明的x,log输出为x函数
。
同理,原题中代码最终执行时的是:
function Foo() {
getName = function () { alert (1); };
return this;
}
function getName() { alert (5);}//提高函数声明,覆盖var的声明
var getName;//只提高声明不提高初始化
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
getName = function () { alert (4);};//最终的赋值再次覆盖function getName声明
----------------------------------------------------------------------------
getName();//最终输出4
复制代码
第三问
第三问的 Foo().getName(); 先执行了Foo函数,而后调用Foo函数的返回值对象的getName属性函数。Foo函数的第一句 getName = function () { alert (1); };
是一句函数赋值语句,注意它没有var声明,因此先向当前Foo函数做用域内寻找getName变量,没有。再向当前函数做用域上层,即外层做用域内寻找是否含有getName变量,找到了,也就是第二问中的alert(4)函数,将此变量的值赋值为 function(){alert(1)}。
此处其实是将外层做用域内的getName函数修改了。
**注意:**此处若依然没有找到会一直向上查找到window对象,若window对象中也没有getName属性,就在window对象中建立一个getName变量。以后Foo函数的返回值是this,而JS的this问题各大文章中已经有很是多的文章介绍,这里再也不多说。
简单的讲,this的指向是由所在函数的调用方式决定的。而此处的直接调用方式,this指向window对象。
遂Foo函数返回的是window对象,至关于执行 window.getName() ,而window中的getName已经被修改成alert(1),因此最终会输出1
此处考察了两个知识点,一个是变量做用域问题,一个是this指向问题。
第四问
直接调用getName函数,至关于 window.getName() ,由于这个变量已经被Foo函数执行时修改了,遂结果与第三问相同,为1
第五问
第五问 new Foo.getName(); ,此处考察的是js的运算符优先级问题。
经过查文档能够得知点(.)的优先级高于new(无参数列表)操做,遂至关因而:
new (Foo.getName)();
复制代码
因此实际上将getName函数做为了构造函数来执行,遂弹出2。
第六问
第六问 new Foo().getName() ,首先看运算符优先级括号高于new,实际执行为
(new Foo()).getName()
复制代码
遂先执行Foo函数,而Foo此时做为构造函数却有返回值,因此这里须要说明下js中的构造函数返回值问题。
构造函数的返回值
在传统语言中,构造函数不该该有返回值,实际执行的返回值就是此构造函数的实例化对象。
而在js中构造函数能够有返回值也能够没有。
一、没有返回值则按照其余语言同样返回实例化对象。
二、如有返回值则检查其返回值是否为引用类型。若是是非引用类型,如基本类型
(string,number,boolean,null,undefined)则与无返回值相同,实际返回其实例化对象。
三、若返回值是引用类型,则实际返回值为这个引用类型。
原题中,返回的是this,而this在构造函数中原本就表明当前实例化对象,遂最终Foo函数返回实例化对象。
以后调用实例化对象的getName函数,由于在Foo构造函数中没有为实例化对象添加任何属性,遂到当前对象的原型对象(prototype)中寻找getName,找到了。
遂最终输出3。
第七问
第七问, new new Foo().getName(); 一样是运算符优先级问题。
最终实际执行为:
new ((new Foo()).getName)();
复制代码
先初始化Foo的实例化对象,而后将其原型上的getName函数做为构造函数再次new。
遂最终结果为3