1 申明
1-1「js 函数申明三种形式:」
(1) Function()结构器
var f =new Function()
(2) 函数申明
function f (){console.log(2);
}
(3) 函数表达式
var f = function() {console.log(1);
}
1-2「js 变量申明:」
- var 申明的变量会挂载在 window 上,而 let 和 const 申明的变量不会
- var 申明变量存在变量晋升,let 和 const 不存在变量晋升(严格来说,let 也存在)
- let 和 const 申明造成块作用域
- let 存在暂存死区
- const 申明必须赋值
(1) var 申明的变量会挂载在 window 上,而 let 和 const 申明的变量不会:
var a = 100;
console.log(a,window.a); // 100 100
let b = 10;
console.log(b,window.b); // 10 undefined
const c = 1;
console.log(c,window.c); // 1 undefined
(2) var 申明变量存在变量晋升,let 和 const 不存在变量晋升
console.log(a); // undefined ===> a 已申明还没赋值,默认失去 undefined 值
var a = 100;
console.log(b); // 报错:b is not defined ===> 找不到 b 这个变量
let b = 10;
console.log(c); // 报错:c is not defined ===> 找不到 c 这个变量
const c = 10;
(3) let 和 const 申明造成块作用域
if(1){
var a = 100;
let b = 10;
}
console.log(a); // 100
console.log(b) // 报错:b is not defined ===> 找不到 b 这个变量
//
if(1){
var a = 100;
const c = 1;
}
console.log(a); // 100
console.log(c) // 报错:c is not defined ===> 找不到 c 这个变量
(4) 同一作用域下 let 和 const 不能反复申明,而 var 能够
var a = 100;
console.log(a); // 100
var a = 10;
console.log(a); // 10
let a = 100;
let a = 10;
// 控制台报错:Identifier 'a' has already been declared ===> 标识符 a 曾经被申明了。
(5) 暂存死区
var a = 100;
if(1){
a = 10;
let a = 1;
// 在以后块作用域中存在 a 应用 let/const 申明的状况下,给 a 赋值 10 时,只会在以后作用域查找变量 a,// 而这时,还未到申明时候,所以控制台 Error:a is not defined
// 即 let 和 const 不会申明提前
}
(6) const
一旦申明必须赋值, 不能应用 null 占位。申明后不能再批改
如果申明的是援用类型数据,能够批改其属性
const a = 100;
const list = [];
list[0] = 10;
console.log(list); // [10]
const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj); // {a:10000,name:'apple'}
2 数据类型的分类:
2-1 根本类型:
- string(字符串)– 原始类型
- boolean(布尔值)– 原始类型
- number(数字)– 原始类型
- symbol(符号)– 原始类型
- null(空值)
- undefined(未定义)
- BigInt(BigInt 数据类型的目标是比 Number 数据类型反对的范畴更大的整数值,精度在 (2^53-1) 范畴内,BigInt(10) 值为:10n)ES2020 新出的
其中 Symbol
和 BigInt
是ES6
中新增的数据类型:
- Symbol 代表创立后举世无双且不可变的数据类型,它次要是为了解决可能呈现的全局变量抵触的问题。
- BigInt 是一种数字类型的数据,它能够示意任意精度格局的整数,应用 BigInt 能够平安地存储和操作大整数,即便这个数曾经超出了 Number 可能示意的平安整数范畴。
留神:NaN 不是数据类型
typeof NaN === 'number' //true
NaN==NaN //false
2-2 对象类型(援用类型),有以下 3 种:
A. 内置对象 / 原生对象
String、Number、Boolean、Array、Date、RegExp、Math、Error、Object、Function、Global
B. 宿主对象
- (1)BOM 对象:Window、Navigator、Screen、History、Location
- (2)DOM 对象:Document、Body、Button、Canvas 等
C. 自定义对象 –(指由用户创立的对象,兼容性问题须要由编写者留神)
创立自定义对象几种形式:
(1)对象间接量:
var obj1 = {};var obj2 = {x:0,y:0};var obj3 = {name:‘Mary’,age:18}
(2)工厂模式 – 用函数来封装以特定接口创建对象的细节:
function createPerson(name,age,job){var o = new Object();
o.name = name;
o.age = age;
o.job = job;
return o;
}
var person1 = createPerson('zhang',30,'java');
(3)构造函数模式:
function Person(name,age,job){
this.name= name;
this.age = age;
this.job = job;
}
var person1 = new Person('zhang',30,'java');
(4)原型模式:
function Person(){}
Person.prototype.name = 'zhang';
Person.prototype.age = '22';
Person.prototype.job = 'html5';
var person1 = new Person();
2-3 数据类型的判断:
2-3-1: typeof
个别通过 typeof 操作符来判断一个值属于哪种
根本类型
。
毛病 :无奈分辨 对象类型
typeof 'seymoe' // 'string'
typeof true // 'boolean'
typeof 10 // 'number'
typeof Symbol() // 'symbol'
typeof null // 'object' 无奈断定是否为 null
typeof undefined // 'undefined'
typeof {} // 'object'
typeof [] // 'object'
typeof(() => {}) // 'function'
为什么 typeof null 为 object:
js 在底层存储变量的时候,会在变量的机器码的低位 1 - 3 位存储其类型信息
- 000:对象
- 010:浮点数
- 100:字符串
- 110: 布尔值
- 1:整数
然而, 对于 undefined 和 null 来说,这两个值的信息存储是有点非凡的。
- null:所有机器码均为 0
- undefined:用 −2^30 整数来示意
所以,typeof 在判断 null 的时候就呈现问题了,因为 null 的所有机器码均为 0,因而间接被当做了对象来对待。
2-3-2:instanceof
判断对象类型:测试构造函数的 prototype 是否呈现在被检测对象的原型链上。
毛病:无奈判断一个值到底属于数组还是一般对象
[] instanceof Array // true
({}) instanceof Object // true
(()=>{}) instanceof Function // true
let arr = []
let obj = {}
arr instanceof Array // true
arr instanceof Object // true
obj instanceof Object // true
在这个例子中,arr 数组相当于 new Array() 出的一个实例,所以 arr.__proto__ === Array.prototype,又因为 Array 属于 Object 子类型,即 Array.prototype.__proto__ === Object.prototype,所以 Object 构造函数在 arr 的原型链上
判断不了原始类型
console.log(true instanceof Boolean);// false
console.log(undefined instanceof Object); // false
console.log(arr instanceof Array); // true
console.log(null instanceof Object); // false
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function);// true
2-3-3: Object.prototype.toString.call()
全能型,简直都能判断
Object.prototype.toString.call({})// '[object Object]'
Object.prototype.toString.call([])// '[object Array]'
Object.prototype.toString.call(() => {})// '[object Function]'
Object.prototype.toString.call('abc')// '[object String]'
传入原始类型却可能断定出后果是因为对值进行了包装。
「那么,什么是包装对象:」
所谓“包装对象”,指的是与数值、字符串、布尔值别离绝对应的 Number、String、Boolean 三个原生对象。这三个原生对象能够把原始类型的值变成(包装成)对象。
具体点击这里
3 js 运算符:
3-1 delete 运算符
delete 运算符用来删除 对象属性
或者 数组元素
,如果删除 胜利
或所删除的指标 不存在
,delete 将返回 true
。
然而,并不是所有的属性都可删除:
- 一些内置外围和客户端属性是不能删除的
- 通过 var 语句申明的变量不能删除
- 通过 function 语句定义的函数也是不能删除的。
例如:
var o = {x: 1, y: 2}; // 定义一个对象
console.log(delete o.x); // true,删除一个属性
console.log(delete o.x); // true,什么都没做,x 在已上一步被删除
console.log("x" in o); // false,这个属性在对象中不再存在
console.log(delete o.toString); // true,什么也没做,toString 是继承来的
console.log(delete 1); // true,无意义
var a = [1,2,3]; // 定义一个数组
console.log(delete a[2]); // true,删除最初一个数组元素
console.log(2 in a); // false,元素 2 在数组中不再存在
console.log(a.length); // 3,数组长度并不会因 delete 而扭转
console.log(a[2]); // undefined,元素 2 所在的地位被空了进去
console.log(delete a); // false,通过 var 语句申明的变量不能删除
function f(args){} // 定义一个函数
console.log(delete f); // false,通过 function 语句申明的函数不能删除
3-2 void 运算符
void 运算符能够利用于任何表类型的表达式,表达式会被执行,但计算 后果
会被疏忽并 返回 undefined
。
例如:
void 0;
void "you are useless?";
void false;
void [];
void /(useless)/ig;
void function(){ console.log("you are so useless?"); }
void alert(1)
// always return undefined
3-3 ++ — 运算符
++ — 递增递加运算符借鉴自 C 语言,它们分前置型和后置型,作用是扭转一个变量的值。
例如:
var a = 5;
console.log(a++); // 5 后加表不加
console.log(a); // 6
console.log(++a); // 7 先加,都有加
console.log(a) // 7
console.log(a--); // 7
console.log(a) // 6
console.log(--a); // 5
console.log(a) // 5
奇淫技巧 : 先家都有家,后家表不家
(加号在后面,自身和表达式都加 1; 加号在前面,表达式不加 1,自身加 1),减法同理。
3-4 valueOf
var a = '你好', b = 1, c = [], d = {}, e = function (){}
a.valueOf() // '好'
b.valueOf() // 1
c.valueOf() //[]
d.valueOf() // {}
e.valueOf() //ƒ (){}
3-5 + 和 -
“+” 操作符,如果有一个为字符串,那么都转化到字符串而后执行字符串拼接
“-” 操作符,转换为数字,相减 (-a, a * 1 a/1) 都能进行隐式强制类型转换
[] + {} // "[object Object]"
{} + [] // 0
1 + true //2
1 + false //1
4 内存
4-1 执行上下文
当代码运行时,会产生一个对应的执行环境,在这个环境中,所有变量会被当时提出来(变量晋升),有的间接赋值,有的为默认值 undefined,代码从上往下开始执行,就叫做执行上下文。
「执行环境有三种」:
- 1. 全局环境:代码首先进入的环境
- 2. 函数环境:函数被调用时执行的环境
- 3.eval 函数
「执行上下文特点:」
- 1. 单线程,在主过程上运行
- 2. 同步执行,从上往下按程序执行
- 3. 全局上下文只有一个,浏览器敞开时会被弹出栈
- 4. 函数的执行上下文没有数目限度
- 5. 函数每被调用一次,都会产生一个新的执行上下文环境
「执行 3 个阶段:」
1. 创立阶段
- (1). 生成变量对象
- (2). 建设作用域链
- (3). 确定 this 指向
2. 执行阶段
- (1). 变量赋值
- (2). 函数援用
- (3). 执行其余代码
3. 销毁阶段
- (1). 执行结束出栈,期待回收被销毁
具体点击这里
4-2 堆栈
「概念:」
- 栈: 栈会
主动调配
内存空间,它由零碎主动开释
;寄存根本
类型,简略的数据段,占据固定
大小的空间 - 堆:
动态分配
的内存,大小不定也不会主动开释
。寄存援用
类型,那些可能由多个值形成的对象
,保留在堆内存中
具体点击这里
5 垃圾回收机制
MDN 上有说: 从 2012 年起,所有古代浏览器都应用了
标记 - 革除垃圾
回收算法。所有对于 js 垃圾回收算法的改良都是基于标记 - 革除算法的改良
「什么是垃圾:」 一般来说,没有
被援用
的对象就是垃圾,就是要才革除的。但有个例外,如果几个对象互相援用造成一个环,但根拜访不到他们,他们也是垃圾(援用计数法,无奈革除他们)
「垃圾回收的几种算法:」
5- 1 援用计数法
概念: 记录有多少“程序”在援用本人,当援用的数值为 0 时,就开始革除它。
劣势:
- 可
马上
回收垃圾,当被援用数值为0
时,对象马上会把本人作为闲暇空间
连到闲暇链表
上,也就是说。在变成垃圾的时候就立即
被回收。 - 因为是即时回收, 那么‘程序’不会暂停去独自应用很长一段时间的 GC,那么
最大暂停
工夫很短
。 - 不必去遍历堆外面的所有流动对象和非流动对象
劣势:
- 计数器须要
占很大
的地位
,因为不能预估被援用的下限,打个比方,可能呈现 32 位即 2 的 32 次方个对象同时援用一个对象,那么计数器就须要 32 位。 - 最大的劣势是
无奈解决循环援用
无奈回收的问题 这就是前文中 IE9 之前呈现的问题
5-2 标记革除法
次要将 GC 的垃圾回收过程分为两个阶段
标记阶段 :把所有 流动
对象做上 标记
。
革除阶段 :把 没有
标记(也就是非流动对象)销毁
。
劣势:
- 实现简略,打标记也就是打或者不打两种可能,所以就一位二进制位就能够示意
- 解决了循环援用问题
毛病
- 造成碎片化(有点相似磁盘的碎片化)
- 再调配时遍次数多,如果始终没有找到适合的内存块大小,那么会遍历闲暇链表(保留堆中所有闲暇地址空间的地址造成的链表)始终遍历到尾端
5-3 复制算法
- 将一个内存空间分为两局部,一部分是
From
空间,另一部分是To
空间 - 将
From 空间
外面的流动
对象复制
到To 空间
- 开释掉整个
From
空间 - 再将 From 空间和 To 空间的身份
调换
,那么就实现了一次 GC。
具体点击这里 1
具体点击这里 2
6 内存透露
「概念:」 申请的 内存
没有 及时回收
掉,造成零碎 内存
的节约
,导致程序 运行
速度 减慢
甚至零碎解体等重大 结果
「内存透露产生的场景:」
(1) 意外的全局变量
function leaks(){leak = 'xxxxxx';//leak 成为一个全局变量,不会被回收}
(2) 忘记的定时器
setTimeout 和 setInterval 是由浏览器 专门线程
来保护
它的 生命周期
,如果在某个页面应用了定时器,当销毁页面时,没有手动去开释清理这些定时器的话,那么这些定时器还是存活着的
(3) 使用不当的闭包
var leaks = (function(){
var leak = 'xxxxxx';// 被闭包所援用,不会被回收
return function(){console.log(leak);
}
})()
(4) 脱漏的 DOM 元素
<div id="container">
</div>
$('#container').bind('click', function(){console.log('click');
}).remove();//dom 移除了,然而 js 还持有对它的援用
解决:
$('#container').bind('click', function(){console.log('click');
}).off('click').remove();
// 把事件革除了,即可从内存中移除
(5) 网络回调
「如何监控内存透露」
- 应用控制台
- 具体点击这里
7 作用域
扩大:
JavaScript 是门动静语言,跟 Java 不一样,JavaScript 能够随便定义全局变量和局部变量,每一个函数都是一个作用域,当函数执行时会优先查找以后作用域,而后逐级向上。
JavaScript 是动态作用域,在对变量进行查问时,变量值由函数定义时的地位决定,和执行时的所处的作用域无关。
ES6 曾经有块级作用域了,而且用 let 和 const 定义的变量不会晋升。
概念:
作用域:变量或者函数的无效作用范畴
作用域链:咱们须要查找某个变量值,会先在以后作用域查找,如果找不到会往上一级查,如果找到的话,就返回进行查找,返回查找的值,这种向上查找的链条关系,叫作用域
7-1 相干案例
(1)变量晋升 / 变量由函数定义时的地位决定
var a = 1;
function fn() {console.log('1:' + a);
var a = 2;
bar()
console.log('2:' + a)
}
function bar() {console.log('3:' + a)
}
fn()
别离打印:1:undefined 3:1 2:2
「解析:」
第一个 a 打印的值是 1:undefined 而不是 1。因为咱们在 fn() 中定义了变量 a,用 var 定义的变量会晋升到以后作用域的顶部(即以后函数作用域顶部),然而赋值还在原地,所以是 undefined。
第二个 a 打印的值是 3:1 而不是 2。因为函数 bar 是定义在全局作用域中的,所以作用域链是 bar -> global,bar 外面没有定义 a,所以就会顺着作用域链向上找,而后在 global 中找到了 a。留神 : 查找是在其 定义
的执行上下文环境中查找。
第三个 a 打印的值是 2:2。这句话所在的作用域链是 fn -> global,执行 console.log(‘2:’ + a) 会首先在 fn 作用域里查找 a,找到有 a,并且曾经赋值为 2,所以后果就是 2。
(2)变量赋值
var a = 1;
function fn() {console.log('1:' + a);
a = 2
}
a = 3;
function bar() {console.log('2:' + a);
}
fn();
bar();
别离打印:1:3 2:2
「解析:」
第一个 打印的值是 1:3。首先,fn 中的 a = 2 是给变量 a 赋值,并不是申明变量。而后,执行函数 fn,此时 a 曾经赋值为 3 了,留神,fn()是在 a = 3 前面执行。
第二个 打印的值是 2:2。函数 bar 所能拜访的作用域链为 bar->global,在执行函数 bar 时,因为在 bar 前执行了 fn()将 a 批改为 2 了,所以这个时候拿到的 a 为 2。
(3)全局变量申明提前
if(!(a in window)){var a = 10;}
console.log(a);
打印:undefined
「解析:」
相当于:
var a;
if(!(a in window)){a = 10;}
console.log(a);
用 var 定义的变量会晋升到以后作用域的顶部(即以后全局作用域),所以 a 会申明提前到 window 中,但值还是在原地,即为 undefined。所以 if 失去是 a in window 是 ture 故不走外面赋值 console.log(a) == undefined
上一个例子的变种:
(function(){var x = c = b = {a:1}
})()
console.log(c,b) // {a: 1} {a: 1}
console.log(x.a); // error , x is not defined
留神: x 是在函数中申明的,是局部变量,c 和 b 未声明,间接赋值,所以是全局变量。赋值过程是从右往左的,即 b ={a:1},c=b,x=c
(4)变量晋升 / 运算符程序
(function(){var a = b = 3;})()
console.log(typeof a === "undefined"); // true
console.log(typeof b === "undefined"); // false
console.log(typeof b === "number" && b ===3); // true
// 这里波及的就是立刻执行和闭包的问题, 还有变量晋升, 运算符执行方向(= 号自右向左)
// 那个函数能够拆成这样
(function()
var a; /* 局部变量, 内部没法拜访 */
b = 3; /* 全局变量,so . window.b === 3 , 内部能够拜访到 */
a = b;
})()
(5)变量晋升 / 运算符程序
var x = 1;
if (function f(){console.log(2)}) {x += typeof f;}
console.log(x); // 1undefined
// 因为函数体在 () 中会以表达式去运行,fn 函数不起作用,函数不会执行。
// 最初表达式转换为 true,f 未声明(下面的函数没起作用),值为 undefined
「知识点:」
(1) 在 JavaScript 中,通过 let 和 const 定义的变量具备块级作用域的个性。
(2) 通过 var 定义的变量会在它本身的作用域中进行晋升,而 let 和 const 定义的变量不会。
(3) 每个 JavaScript 程序都具备一个全局作用域,每创立一个函数都会创立一个作用域。
(4) 在创立函数时,将这些函数进行嵌套,它们的作用域也会嵌套,造成作用域链,子作用域能够拜访父作用域,然而父作用域不能拜访子作用域。
(5) 在执行一个函数时,如果咱们须要查找某个变量值,那么会去这个函数被 定义 时所在的作用域链中查找,一旦找到须要的变量,就会进行向上查找。
(6) “变量的值由函数定义时的地位决定”
这句话有歧义,精确说是查找变量时,是去 定义这个函数时
所在的作用域链查找。
8 闭包
「概念:」
闭包就是一个函数,这个函数可能拜访 其余函数
的作用域
中的 变量
「利用场景:」
- 函数防抖
- 封装公有变量
JavaScript 代码的整个执行过程,分为两个阶段,代码编译阶段
与代码执行阶段
。编译阶段由编译器实现,将代码翻译成可执行代码,这个阶段 作用域规定
会确定。执行阶段由引擎实现,次要工作是 执行可执行代码
,执行上下文在这个阶段创立。
9 this
9-1 this 的指向:
ES5 中:
this 永远指向 最初调用
它的那个对象
ES6 箭头函数:
箭头函数的 this 始终指向 函数定义时
的 this,而 非执行时
。
9-2 怎么扭转 this 的指向:
- 应用 ES6 的箭头函数
- 在函数外部应用 _this = this
- 应用 apply、call、bind
-
new 实例化一个对象
案例 1:var name = "windowsName"; var a = { name : "Cherry", func1: function () {console.log(this.name) }, func2: function () {setTimeout( function () {this.func1() },100); } }; a.func2() // this.func1 is not a function
在不应用箭头函数的状况下,是会报错的,因为最初调用 setTimeout 的对象是
window,然而在 window 中并没有 func1 函数。能够看做 window.setTimeout
案例 2:
var webName="long";
let func=()=>{console.log(this.webName);
}
func();//long
// 箭头函数在全局作用域申明,所以它捕捉全局作用域中的 this,this 指向 window 对象
案例 3:
var webName = "long";
function wrap(){let func=() => {console.log(this.webName);
}
func();}
wrap();//long
//wrap 函数执行时,箭头函数 func 定义在 wrap 中,func 会找到它最近一层非箭头函数的 this
// 也就是 wrap 的 this, 而 wrap 函数作用域中的 this 指向 window 对象。
9-3 箭头函数:
「Tips:」 家喻户晓,ES6 的箭头函数是能够防止 ES5 中应用 this 的坑的。箭头函数
的 this 始终指向函数 定义时
的 this,而 非执行时
。
箭头函数须要记着这句话:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值(箭头函数自身没有 this,然而在它申明时能够捕捉他人的 this 供本人应用。),如果箭头函数被非箭头函数蕴含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”。
特点:
- 没有 this
- 没有 arguments
- 不能通过 new 关键字调用
- 没有 new.target
- 没有原型
- 没有 super
具体点击这里
10 原型和原型链
10-1 背景:
一个函数能够看成一个类,原型是所有类都有的一个属性,原型的作用就是给这个类的每一个对象都增加一个对立的办法
10- 2 基本概念
「prototype:」 每个 函数
都会 有
这个 属性
,这里强调,是函数, 一般对象
是没有
这个属性的(这里为什么说一般对象呢,因为 JS 外面,所有皆为对象,所以这里的一般对象不包含函数对象)。它是构造函数的原型对象;
「「proto」:」 每个 对象
都有
这个 属性
,这里强调,是对象,同样,因为函数也是对象,所以函数也有这个属性。它指向构造函数的原型对象;
「constructor:」 这是原型对象上的一个指向构造函数的属性。
var webName = "long";
// Pig 的构造函数
function Pig(name, age) {
this.name = name;
this.age = age;
}
// 创立一个 Pig 的实例,小猪佩奇
var Peppa = new Pig('Peppa', 5);
Peppa.__proto__ === Pig.prototype。//true
Pig.__proto__ === Function.prototype //true
Pig.prototype.constructor === Pig //true
奇淫技巧: 韩信对饮(函数的显示原型 = 对象的隐士原型)
具体点击这里
10-3 什么是原型继承
一个对象能够应用 另外
一个对象的 属性
或者 办法
,就称之为 继承
。
具体是通过将这个对象的原型 设置为
另外一个 对象
,这样依据原型链的规定,如果查找一个对象属性且在本身不存在时,就会查找另外一个对象,相当于一个对象能够应用另外一个对象的属性和办法了。
11 深浅拷贝
11-1 浅克隆
function shallowClone(obj) {let cloneObj = {};
for (let i in obj) {cloneObj[i] = obj[i];
}
return cloneObj;
}
11-2 深克隆
深克隆:
- 思考根底类型
- 援用类型
- RegExp、Date、函数 不是 JSON 平安的
- 会失落 constructor,所有的构造函数都指向 Object
-
破解循环援用
function deepCopy(obj) {if (typeof obj === 'object') {var result = obj.constructor === Array ? [] : {}; for (var i in obj) {result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i]; } } else {var result = obj;} return result; }
具体点击这里
12 数组
12- 1 数组去重
1. es6 的 new Set
let arr=[1,2,2,3]
let arrNew=[...new Set(arr)]
console.log(arrNew)//[1,2,3]
2. 遍历旧数组往新数组中增加惟一的元素
function unique(arr) {var newArr = []
for (var i = 0; i < arr.length; i++) {f (newArr.indexOf(arr[i])===-1) {// 形式 1
newArr.push(arr[i])
}
// if (!newArr.includes(arr[i])) {// 形式 2
// newArr.push(arr[i])
//}
}
return newArr
}
console.log(unique(arr))//[1,2,3]
3. 利用 Map 数据结构去重
function unique(){let map =new Map()
let arr=new Array()
for(let i=0;i<arr.length;i++){if(map.has(arr[i])){// 如果有 key 值,它是把元素值作为 key
map.set(arr[i],true)
}else{map.set(arr[i],false)// 如果没有该 key 值
array.push(arr[i])
}
}
return array
}
console.log(unique([1,2,2,3]))
解析:
把每一个元素作为 key 值存到 Map 中,因为 Map 不会呈现雷同的 key,所以最终失去去重后的后果。
12- 2 数组开展
1. flat 办法
let arr = [1,2,[3,4],[5[6,7]]]
let arr1 = arr.flat(Infinity)//[1,2,3,4,5,6,7]
2.join,split
let arr = [1,2,[3,4],[5[6,7]]]
let arr1 = arr.join().split(",") //["1", "2", "3", "4", ""]
3.toString,split
let arr = [1,2,[3,4],[5[6,7]]]
let arr1 = arr.toString().split(",") //["1", "2", "3", "4", ""]
12- 3 数组合并
1. es6 开展合并
let arr1 = [1,2]
let arr2 = [3,4]
let arr = [...arr1,...arr2]//[1,2,3,4]
2. concat
let arr = arr1.concat(arr2)
12- 4 判断数组
instanceof
console.log(arr instanceof Array)
constructor
console.log(arr.constructor === Array)
Array.isArray
console.log(Array.isArray(arr))
toString
console.log(Object.prototype.toString.call(arr) === "[object Array]")
判断是否有数组的 push 等办法
console.log(!!arr.push && !!arr.concat)
13 对象
13-1 如何判断一个对象是不是空对象
1. 将 json 对象转化为 json 字符串,再判断该字符串是否为 ”{}”
var data = {};
var b = (JSON.stringify(data) == "{}"); //true
2.for in 循环判断
var obj = {};
var b = function() {for(var key in obj) {return false;}
return true;
}
b();//true
3.jquery 的 isEmptyObject 办法
此办法是 jquery 将 2 办法 (for in) 进行封装,应用时须要依赖 jquery
var data = {};
var b = $.isEmptyObject(data);
alert(b);//true
4.Object.getOwnPropertyNames()办法
此办法是应用 Object 对象的 getOwnPropertyNames 办法,获取到对象中的属性名,存到一个数组中,返回数组对象,咱们能够通过判断数组的 length 来判断此对象是否为空
留神:此办法不兼容 ie8,其余浏览器没有测试
var data = {};
var arr = Object.getOwnPropertyNames(data);
alert(arr.length == 0);//true
5. 应用 ES6 的 Object.keys()办法
与 4 办法相似,是 ES6 的新办法, 返回值也是对象中属性名组成的数组
var data = {};
var arr = Object.keys(data);
alert(arr.length == 0);//true
13 v8 引擎
13-1 v8 垃圾回收
「背景:」
V8 的垃圾回收策略基于 分代回收
机制,该机制又基于 世代假说
。
该假说有两个特点:
- 大部分
新生
对象偏向于早死
; 不死
的对象,会活
得更久
。
基于这个实践,古代垃圾回收算法依据对象的存活工夫将内存进行了分代,并对不同分代的内存采纳不同的高效算法进行垃圾回收。
「V8 的内存分代」
在 V8 中,将内存分为了 新生代
(new space)和 老生代
(old space)。
它们特点如下:
新生代 :对象的 存活
工夫 较短
。新生对象或只通过 一次垃圾回收
的对象。
老生代 :对象 存活
工夫 较长
。经验过 一次或屡次
垃圾回收的对象。
「V8 堆的空间」
V8 堆的空间等于新生代空间加上老生代空间。咱们能够通过 –max-old-space-size 命令设置老生代空间的最大值,–max-new-space-size 命令设置新生代空间的最大值。老生代与新生代的空间大小在 程序初始化时
设置,一旦失效则不能动静扭转。
- node –max-old-space-size=1700 test.js // 单位为 MB
- node –max-new-space-size=1024 test.js // 单位为 KB
默认设置下,64 位零碎的老生代大小为 1400M,32 位零碎为 700M。
对于新生代,它由两个 reserved_semispace_size 组成。每个 reserved_semispace_size 的大小在不同位数的机器上大小不同。默认设置下,在 64 位与 32 位的零碎下别离为 16MB 和 8MB。咱们将新生代、老生代、reserved_semispace_size 空间大小总结如下表。
类型 \ 零碎位数 | 64 位 | 32 位 |
---|---|---|
老生代 | 1400MB | 700MB |
reserved_semispace_size | 16MB | 8MB |
新生代 | 32MB | 16MB |
具体点击这里
14 event loop
14-1 什么是事件循环
具体点击这里 1
具体点击这里 2
15 严格模式的优缺点
15-1 概念
ECMAScript 5 中引入的一种将更好的
谬误查看
引入代码中的办法, 当初曾经被大多浏览器实现. 这种模式使得 Javascript 在更严格
的条件
下运行
15-2 长处
- 无奈再意外创立全局变量。
- 会使引起静默失败(silently fail,即:不报错也没有任何成果)的赋值操抛出异样。
- 试图删除不可删除的属性时会抛出异样(之前这种操作不会产生任何成果)。
- 要求函数的参数名惟一。
- 全局作用域下,this 的值为 undefined。
- 捕捉了一些常见的编码谬误,并抛出异样。
- 禁用令人困惑或欠佳的性能。
15-3 毛病
- 缺失许多开发人员曾经习惯的性能。
- 无法访问 function.caller 和 function.arguments。
- 以不同严格模式编写的脚本合并后可能导致问题。
16 ES6
16-1 背景
EC 版本 | 公布工夫 | 新增个性 |
---|---|---|
2009(ES5) | 2009 年 11 月 | 扩大了 Object、Array、Function 的性能等新增个性 |
2015(ES6) | 2015 年 6 月 | 类,模块化,箭头函数,函数参数默认值等 |
2016(ES7) | 2016 年 3 月 | includes,指数操作符 |
2017(ES8) | 2017 年 6 月 | sync/await,Object.values(),Object.entries(),String padding 等 |
16-2 罕用个性
- 类
- 模块化
- 箭头函数
- 函数参数默认值
- 模板字符串
- 解构赋值
- 延展操作符
- 对象属性简写
- Promise
- Let 与 Const
「(1) 类(class)」
传统的 javascript 中只有对象,没有类的概念。
它是基于原型的面向对象语言。原型对象特点就是将本身的属性共享给新对象。
这样的写法绝对于其它传统面向对象语言来讲,很有一种自成一家的感脚!非常容易让人困惑!
如果要生成一个对象实例,须要先定义一个构造函数,而后通过 new 操作符来实现。
上面用一个例子演示构造函数到 class 的演变:
构造函数 –
function Person(name,age) {
this.name = name;
this.age=age;
}
Person.prototype.say = function(){return "我的名字叫" + this.name+"往年"+this.age+"岁了";}
var obj=new Person("laotie",88);
// 通过构造函数创建对象,必须应用 new 运算符
console.log(obj.say());// 我的名字叫 laotie 往年 88 岁了
ES6 引入了 Class(类)这个概念,通过 class 关键字能够定义类。
该关键字的呈现使得其在对象写法上更加清晰,更像是一种面向对象的语言。
如果将之前的代码改为 ES6 的写法就会是这个样子:
class–
class Person{// 定义了一个名字为 Person 的类
constructor(name,age){//constructor 是一个构造方法,用来接管参数
this.name = name;//this 代表的是实例对象
this.age=age;
}
say(){// 这是一个类的办法,留神千万不要加上 function 和逗号
return "我的名字叫" + this.name+"往年"+this.age+"岁了";
}
}
var obj=new Person("laotie",88);
console.log(obj.say());// 我的名字叫 laotie 往年 88 岁了
console.log(typeof Person);//function--类本质上就是一个函数
console.log(Person===Person.prototype.constructor);//true
// 类本身指向的就是构造函数。所以能够认为 ES6 中的类其实就是构造函数的另外一种写法!
留神项:
- 在类中申明办法的时候,千万不要给该办法加上 function 关键字
- 办法之间不要用逗号分隔,否则会报错
-
class 不存在变量晋升,所以须要先定义再应用。因为 ES6 不会把类的申明晋升到代码头部,然而 ES5 就不一样,ES5 存在变量晋升, 能够先应用,而后再定义。
//ES5 能够先应用再定义, 存在变量晋升 new A(); function A(){} //ES6 不能先应用再定义, 不存在变量晋升 会报错 new B();//B is not defined class B{}
「(2) 模块化(Module)」
背景
在之前的 javascript 中是没有模块化概念的。如果要进行模块化操作,须要引入第三方的类库。随着技术的倒退,前后端拆散,前端的业务变的越来越复杂化。直至 ES6 带来了模块化,才让 javascript 第一次反对了 module。ES6 的模块化分为 导出(export)
与 导入(import)
两个模块。
export 的用法
在 ES6 中每一个 模
块即是一个 文件
,在文件中定义的变量,函数,对象在 内部
是无奈获取
的。如果你心愿内部能够读取模块当中的内容,就必须应用 export
来对其进行 裸露
(输入)。
先来看个例子,来对一个变量进行模块化。
// 咱们先来创立一个 test.js 文件,来对这一个变量进行输入:export let myName="laowang";
// 而后能够创立一个 index.js 文件,以 import 的模式将这个变量进行引入:
import {myName} from "./test.js";
console.log(myName);//laowang
如果要输入 多个变量
能够将这些变量包装成 对象
进行模块化输入:
let myName="laowang";
let myAge=90;
let myfn=function(){return "我是"+myName+"!往年"+myAge+"岁了"}
export {
myName,
myAge,
myfn
}
****************************** 接管的代码调整为 *********************
import {myfn,myAge,myName} from "./test.js";
console.log(myfn());// 我是 laowang!往年 90 岁了
console.log(myAge);//90
console.log(myName);//laowang
如果你不想裸露模块当中的变量名字,能够通过 as 来进行操作:
let myName="laowang";
let myAge=90;
let myfn=function(){return "我是"+myName+"!往年"+myAge+"岁了"}
export {
myName as name,
myAge as age,
myfn as fn
}
/****************************** 接管的代码调整为 **********************/
import {fn,age,name} from "./test.js";
console.log(fn());// 我是 laowang!往年 90 岁了
console.log(age);//90
console.log(name);//laowang
也能够间接导入整个模块,将下面的接管代码批改为:
import * as info from "./test.js";// 通过 * 来批量接管,as 来指定接管的名字
console.log(info.fn());// 我是 laowang!往年 90 岁了
console.log(info.age);//90
console.log(info.name);//laowang
默认导出(default export)一个模块只能有一个默认导出,对于默认导出,导入的名称能够和导出的名称不统一。
/****************************** 导出 **********************/
export default function(){return "默认导出一个办法"}
/****************************** 引入 **********************/
import myFn from "./test.js";// 留神这里默认导出不须要用{}。console.log(myFn());// 默认导出一个办法
能够将所有须要导出的变量放入一个对象中,而后通过 default export 进行导出
/****************************** 导出 **********************/
export default {myFn(){return "默认导出一个办法"},
myName:"laowang"
}
/****************************** 引入 **********************/
import myObj from "./test.js";
console.log(myObj.myFn(),myObj.myName);// 默认导出一个办法 laowang
同样也反对混合导出
/****************************** 导出 **********************/
export default function(){return "默认导出一个办法"}
export var myName="laowang";
/****************************** 引入 **********************/
import myFn,{myName} from "./test.js";
console.log(myFn(),myName);// 默认导出一个办法 laowang
重命名 export 和 import 如果导入的多个文件中,变量名字雷同,即会产生命名抵触的问题,为了解决该问题,ES6 为提供了重命名的办法,当你在导入名称时能够这样做:
/******************************test1.js**********************/
export let myName="我来自 test1.js";
/******************************test2.js**********************/
export let myName="我来自 test2.js";
/******************************index.js**********************/
import {myName as name1} from "./test1.js";
import {myName as name2} from "./test2.js";
console.log(name1);// 我来自 test1.js
console.log(name2);// 我来自 test1.js
「(3) 箭头(Arrow)函数」
查看 1 -5-3 箭头函数大节:
箭头函数的 this 始终指向函数定义时的 this,而非执行时。箭头函数须要记着这句话:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数蕴含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”。
「(4) 函数参数默认值」
ES6 反对在定义函数的时候为其设置默认值:
function foo(height = 50, color = 'red')
{// ...}
不应用默认值:
function foo(height, color)
{
var height = height || 50;
var color = color || 'red';
//...
}
这样写个别没问题,但当参数的布尔值为 false 时,就会有问题了。
比方,咱们这样调用 foo 函数:foo(0, "")
因为 0 的布尔值为 false,这样 height 的取值将是 50。同理 color 的取值为‘red’。所以说,函数参数默认值不仅能是代码变得更加简洁而且能躲避一些问题。
「(5) 模板字符串」
ES6 反对在定义函数 ES6 反对模板字符串,使得字符串的拼接更加的简洁、直观。
不应用模板字符串:
var name = 'Your name is' + first + '' + last +'.'
应用模板字符串:
var name = `Your name is ${first} ${last}.`
在 ES6 中通过 ${}
就能够实现字符串的拼接,只须要将 变量
放在 大括号之
中。
「(6) 解构赋值」
解构赋值语法是 JavaScript 的一种表达式,能够不便的从 数组
或者 对象
中疾速 提取值赋
给定义的 变量
。
获取数组中的值
// 从数组中获取值并赋值到变量中,变量的程序与数组中对象程序对应。var foo = ["one", "two", "three", "four"];
var [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
// 如果你要疏忽某些值,你能够依照上面的写法获取你想要的值
var [first, , , last] = foo;
console.log(first); // "one"
console.log(last); // "four"
// 你也能够这样写
var a, b; // 先申明变量
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2
// 如果没有从数组中的获取到值,你能够为变量设置一个默认值。var a, b;
[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7
// 通过解构赋值能够不便的替换两个变量的值。var a = 1;
var b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
获取对象中的值
const student = {
name:'Ming',
age:'18',
city:'Shanghai'
};
const {name,age,city} = student;
console.log(name); // "Ming"
console.log(age); // "18"
console.log(city); // "Shanghai"
「(7) 延展操作符」
在 ECMAScript 2018 中延展操作符减少了对对象的反对,用于对像和数组的拆解
var obj1 = {foo: 'bar', x: 42};
var obj2 = {foo: 'baz', y: 13};
var clonedObj = {...obj1};
// 克隆后的对象: {foo: "bar", x: 42}
var mergedObj = {...obj1, ...obj2};
// 合并后的对象: {foo: "baz", x: 42, y: 13} 雷同属性会进行笼罩
// 咱们能够这样合并数组:var arr1=['a','b','c'];
var arr2=[...arr1,'d','e']; //['a','b','c','d','e']
// 开展运算符也能够用在 push 函数中,能够不必再用 apply()函数来合并两个数组:var arr1=['a','b','c'];
var arr2=['d','e'];
arr1.push(...arr2); //['a','b','c','d','e']
// 用于解构赋值
let [arg1,arg2,...arg3] = [1, 2, 3, 4];
arg1 //1
arg2 //2
arg3 //['3','4']
// 开展运算符既然能合并数组,天然也能解构数组,不过要留神,解构赋值中【开展运算符】只能用在【最初】:let [arg1,...arg2,arg3] = [1, 2, 3, 4]; // 报错
「(8) 对象属性简写」
在 ES6 中容许咱们在设置一个对象的属性的时候不指定属性名。
不应用 ES6
const name='Ming',age='18',city='Shanghai';
const student = {
name:name,
age:age,
city:city
};
console.log(student);//{name: "Ming", age: "18", city: "Shanghai"}
// 对象中必须蕴含属性和值,显得十分冗余。
应用 ES6
const name='Ming',age='18',city='Shanghai';
const student = {
name,
age,
city
};
console.log(student);//{name: "Ming", age: "18", city: "Shanghai"}
// 对象中间接写变量,十分简洁。
「(9) Promise」什么是 Promise
Promise 是 异步编程
的一种 解决方案
,比传统的异步解决方案 回调函数
和事件
更正当、更弱小。现已被 ES6 纳入进标准中。
上面通过几个案例来加深 promise 的理解:
(1) Promise 构造函数是同步执行的.then 是异步的
const promise = new Promise((resolve, reject) => {console.log(1)
resolve()
console.log(2)
})
promise.then(() => {console.log(3)
})
console.log(4)
// 运行后果:1 2 4 3
解析:Promise
构造函数是 同步
执行的,promise.then
中的函数是 异步
执行的。
(2) promise 状态一旦扭转则不能再变
const promise = new Promise((resolve, reject) => {resolve('success1')
reject('error')
resolve('success2')
})
promise
.then((res) => {console.log('then:', res)
})
.catch((err) => {console.log('catch:', err)
})
// 运行后果:then: success1
解析:构造函数中的 resolve 或 reject 只有 第一次执行无效
, 屡次
调用 没有
任何 作用
,promise 状态一旦扭转则不能再变。
(3) .then 或者 .catch 都会返回一个新的 promise
Promise.resolve(1)
.then((res) => {console.log(res)
return 2
})
.catch((err) => {return 3})
.then((res) => {console.log(res)
})
// 运行后果:1 2
解析:promise 能够链式调用。提起链式调用咱们通常会想到通过 return this 实现,不过 Promise 并不是这样实现的。promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用。
(4) Promise 构造函数只执行一次
const promise = new Promise((resolve, reject) => {setTimeout(() => {console.log('once')
resolve('success')
}, 1000)
})
const start = Date.now()
promise.then((res) => {console.log(res, Date.now() - start)
})
promise.then((res) => {console.log(res, Date.now() - start)
})
// 运行后果:once
success 1005
success 1007
解析:promise 的 .then 或者 .catch 能够被调用屡次,但这里 Promise 构造函数只执行一次。或者说 promise 外部状态一经扭转(第一次调用.then 就扭转了),并且有了一个值,那么后续每次调用 .then 或者 .catch 都会间接拿到该值。
(5) .then 或者 .catch 都会返回一个新的 promise
Promise.resolve()
.then(() => {return new Error('error!!!')
})
.then((res) => {console.log('then:', res)
})
.catch((err) => {console.log('catch:', err)
})
// 运行后果:then: Error: error!!!
at Promise.resolve.then (...)
at ...
解析:.then 或者 .catch 中 return 一个 error 对象并不会抛出谬误,所以不会被后续的 .catch 捕捉,须要改成其中一种:
- return Promise.reject(new Error(‘error!!!’))
- throw new Error(‘error!!!’)
因为返回任意一个非 promise 的值都会被包裹成 promise 对象,即 return new Error(‘error!!!’) 等价于 return Promise.resolve(new Error(‘error!!!’))。
(6) .then 或 .catch 返回的值不能是 promise 自身
const promise = Promise.resolve()
.then(() => {return promise})
promise.catch(console.error)
// 运行后果:TypeError: Chaining cycle detected for promise #<Promise>
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
at Function.Module.runMain (module.js:667:11)
at startup (bootstrap_node.js:187:16)
at bootstrap_node.js:607:3
解析:.then 或 .catch 返回的值不能是 promise 自身,否则会造成死循环。
(7) .then 函数返回值类型与参数传递
Promise.resolve(2) // resolve(2) 函数返回一个 Promise<number> 对象
.then(x=>{console.log( x); // 输入 2,示意 x 类型为 number 且值为 2,也就是下面 resolve 参数值
return "hello world"; // 回调函数返回字符串类型,then 函数返回 Promise<string>
}) // then 函数返回 Promise<string> 类型
.then(x=>{console.log( x); // 输入 hello world,也就是上一个 then 回调函数返回值,表明上一个 then 的返回值就是下一个 then 的参数
}) // then 函数回调函数中没有返回值,则为 Promise<void>
.then(x=>{ // 后面的 then 的回调函数没有返回值所以这个 x 是 undefined
console.log(x); // undefined
}) // Promise<void>
.then(()=>{ // 后面没有返回值,这里回调函数能够不加返回值
return Promise.resolve("hello world"); // 返回一个 Promise<string> 类型
}) // 这里 then 的返回值是 Promise<string>
.then(x=>{ // 尽管下面的 then 中回调函数返回的是 Promise<string> 类型然而这里 x 并不是 Promise<string> 类型而是 string 类型
console.log(x); // hello world
return Promise.resolve(2); // 返回一个 Promise<number> 类型对象
}) // 返回 Promise<number> 类型
(8) .then 或者 .catch 的参数冀望是函数,传入非函数则会产生值穿透。
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log) //1
解析:.then 或者 .catch 的参数冀望是函数,传入非函数则会产生值穿透。
(9) .catch 相当于.then 的简写(省略了.then 的第二个参数)
Promise.resolve()
.then(function success (res) {throw new Error('error')
}, function fail1 (e) {console.error('fail1:', e)
})
.catch(function fail2 (e) {console.error('fail2:', e)
})
// 运行后果:fail2: Error: error
at success (...)
at ...
解析:.then 能够接管两个参数,第一个是解决胜利的函数,第二个是处理错误的函数。.catch 也相当于是一个.then,只不过把.then 的第二个参数省略了,然而它们用法上有一点须要留神:.then 的第二个处理错误的函数捕捉不了第一个解决胜利的函数抛出的谬误,而后续的 .catch 能够捕捉之前的谬误。
(10) 微工作宏工作执行程序
process.nextTick(() => {console.log('nextTick')
})
Promise.resolve()
.then(() => {console.log('then')
})
setImmediate(() => {console.log('setImmediate')
})
console.log('end')
// 运行后果:end
nextTick
then
setImmediate
解析:process.nextTick 和 promise.then 都属于 microtask
,而 setImmediate 属于 macrotask
,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,事件循环的开始会先执行一次 microtask。
「(10) let 和 const」
查看 1 -1js 变量申明
17 事件流
事件流是网页元素接管事件的程序,”DOM2 级事件 ” 规定的事件流包含三个阶段:事件捕捉阶段
、 处于指标阶段
、 事件冒泡阶段
。
- 首先产生的事件捕捉,为截获事件提供机会。
- 而后是理论的指标承受事件。
- 最初一个阶段是工夫冒泡阶段,能够在这个阶段对事件做出响应。
尽管捕捉阶段在标准中规定不容许响应事件,然而实际上还是会执行,所以有两次机会获取到指标对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> 事件冒泡 </title>
</head>
<body>
<div>
<p id="parEle"> 我是父元素
<span id="sonEle"> 我是子元素 </span>
</p>
</div>
</body>
</html>
<script type="text/javascript">
var sonEle = document.getElementById('sonEle');
var parEle = document.getElementById('parEle');
parEle.addEventListener('click', function () {alert('父级 冒泡');
}, false);
parEle.addEventListener('click', function () {alert('父级 捕捉');
}, true);
sonEle.addEventListener('click', function () {alert('子级冒泡');
}, false);
sonEle.addEventListener('click', function () {alert('子级捕捉');
}, true);
</script>
当容器元素及嵌套元素,即在捕捉阶段又在冒泡阶段调用事件处理程序时:事件按 DOM 事件流的程序执行事件处理程序:
父级捕捉 =》子级捕捉 =》子级冒泡 =》父级冒泡
- 点击【我是父元素】,顺次弹出(’ 父级 捕捉 ’=》’ 父级 冒泡 ’)
- 点击【我是父元素】,顺次弹出(’ 父级 捕捉 ’=》’ 子级 捕捉 ’=》’ 子级 冒泡 ’=》’ 父级 冒泡 ’)
奇淫技巧 :三个阶段能够这么记 捕母猫
(捕捉,指标,冒泡)
18 new
结构调用:
- 发明一个全新的对象
- 这个对象会被执行 [[Prototype]] 连贯,将这个新对象的 [[Prototype]] 链接到这个构造函数.prototype 所指向的对象
- 这个新对象会绑定到函数调用的 this
- 如果函数没有返回其余对象,那么 new 表达式中的函数调用会主动返回这个新对象
19 手写 promise
20 js 脚本加载问题,async、defer
20-1 失常模式
这种状况下 JS 会阻塞浏览器,浏览器必须期待 index.js 加载和执行结束能力去做其它事件。
<script src="index.js"></script>
20-2 async(异步) 模式
async 模式下,JS 不会阻塞浏览器做任何其它的事件。它的加载是异步的,当它加载完结,JS 脚本会立刻执行。
<script async src="index.js"></script>
20-3 defer(延缓) 模式
defer 模式下,JS 的加载是异步的,执行是被推延的。等整个文档解析实现、DOMContentLoaded 事件行将被触发时,被标记了 defer 的 JS 文件才会开始顺次执行。
<script defer src="index.js"></script>
从利用的角度来说,个别当咱们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,咱们会选用 async;当脚本依赖于 DOM 元素和其它脚本的执行后果时,咱们会选用 defer。
21 性能优化
什么是性能优化,为什么要优化,咱们优化的对象是什么,思考分明这几个问题,咱们能力找到对应的解决办法。
什么是性能优化?
在不影响零碎运行正确性的前提下,使之运行地
更快
,实现
特定性能
所需的工夫更短
。简而言之,就是让咱们的程序更快的运行。
为什么要性能优化?
性能是留住用户很重要的一环,个别人们能忍耐一个网页的加载时长是少于 5 秒,性能优化也是程序高效运行的保障。
咱们优化的对象是什么?
优化对象是
程序
,以及程序所运行在的载体(如浏览器)
咱们曾经晓得了性能优化的对象是什么了,那么接下来就能够依据优化对象离开几个大类总结,对于前端来说,程序和载体无非以下 5 点:
- html
- css
- js
- 程序相干的工具
- 浏览器
这样咱们再开展去说就能说的分明,不会脱漏。
总结每个大类的时候,先从整个 文档格局
开始,再到 内部资源
, 再到 代码层面
1. html
html 应该首先想到语义化标签,正确的语义化能够咱们的文档构造更加清晰。
js 文件写在 body 标签之后,能够让避免阻塞页面的加载。
- 语义化标签,构造清晰
- js 文件正确搁置,避免阻塞
2.css
- css 文件应该放在 body 标签顶部,避免页面二次渲染而抖动。
- 当小图片多的时候能够应用雪碧图,缩小页面申请。
- 小图标能够用 base64 格局,缩小页面申请。
- 公共的 css 抽离,代码复用
- 多个 css 合并,缩小 HTTP 申请
- 选择器优化嵌套,尽量避免层级过深,缩短查找过程
- 充分利用 css 继承属性,缩小代码量
- 缩小页面的重绘,能够先用一个变量操作所有款式后,最初一步把这个变量关联到 dom 上
3.js
- 抽离公共的 js,代码复用
- 抽离公共的组件,代码复用
- 定时器记得革除,较少内存的耗用
- v-if 和 v -show 正确应用,较少页面 dom 的构建
- 节流、防抖,避免意外的触发
- 长列表滚动到可视区域动静加载(大数据渲染)
- computed 和 watch 辨别应用场景,缩小性能耗费
- v-for 遍历必须为 item 增加 key,且防止同时应用 v-if
4.webpack
- 去除代码正文,压缩程序
- Webpack 对图片进行压缩 ——- 先引入 npm install image-webpack-loader –save-dev,而后在 webpack.config.js 中配置
- 缩小 ES6 转为 ES5 的冗余代码
- 提取公共代码
- 模板预编译
- 优化 SourceMap
- 构建后果输入剖析
- 应用 webpack-bundle-analyzer 查看我的项目所有包及体积大小
5. 浏览器
- 首屏加载 loading,优化体验
- 应用缓存,缩小反复申请
- 启用 gzip 压缩,缩小申请
- 应用 cnd,缩短申请链
- 应用图片懒加载,组件懒加载,路由懒加载,缩小申请
- 第三方插件的按需引入
- 服务端渲染 SSR or 预渲染
- 应用 Chrome Performance 查找性能瓶颈,针对性的优化
掘金主页
csdn 主页