(1) Function()构造器javascript
var f =new Function()
(2) 函数声明css
function f (){ console.log(2); }
(3) 函数表达式html
var f = function() { console.log(1); }
(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不存在变量提高html5
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声明造成块做用域java
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能够node
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) 暂存死区jquery
var a = 100; if(1){ a = 10; let a = 1; //在当前块做用域中存在a使用let/const声明的状况下,给a赋值10时,只会在当前做用域查找变量a, // 而这时,还未到声明时候,因此控制台Error:a is not defined // 即let 和 const 不会声明提早 }
(6) constwebpack
一旦声明必须赋值,不能使用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'}
其中 Symbol
和 BigInt
是ES6
中新增的数据类型:es6
注意:NaN不是数据类型
typeof NaN === 'number' //true NaN==NaN //false
A.内置对象/原生对象
String、Number、Boolean、Array、Date、RegExp、Math、 Error、 Object、Function、 Global
B.宿主对象
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-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位存储其类型信息
可是, 对于 undefined 和 null 来讲,这两个值的信息存储是有点特殊的。
因此,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-1 delete 运算符
delete 运算符用来删除对象属性
或者数组元素
,若是删除成功
或所删除的目标不存在
,delete 将返回 true
。
然而,并非全部的属性均可删除:
例如:
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
当代码运行时,会产生一个对应的执行环境,在这个环境中,全部变量会被事先提出来(变量提高),有的直接赋值,有的为默认值 undefined,代码从上往下开始执行,就叫作执行上下文。
「执行环境有三种」:
「执行上下文特色:」
「执行3个阶段:」
1.建立阶段
2.执行阶段
3.销毁阶段
「概念:」
自动分配
内存空间,它由系统自动释放
;存放基本
类型,简单的数据段,占据固定
大小的空间动态分配
的内存,大小不定也不会自动释放
。存放引用
类型,那些可能由多个值构成的对象
,保存在堆内存中
MDN上有说: 从2012年起,全部现代浏览器都使用了
标记-清除垃圾
回收算法。全部对于js垃圾回收算法的改进都是基于标记-清除算法的改进
「什么是垃圾:」 通常来讲,没有
被引用
的对象就是垃圾,就是要才清除的。但有个例外,若是几个对象相互引用造成一个环,但根访问不到他们,他们也是垃圾(引用计数法,没法清除他们)
「垃圾回收的几种算法:」
概念: 记录有多少“程序”在引用本身,当引用的数值为0时,就开始清除它。
优点:
立刻
回收垃圾,当被引用数值为0
时,对象立刻会把本身做为空闲空间
连到空闲链表
上,也就是说。在变成垃圾的时候就马上
被回收。最大暂停
时间很短
。劣势:
占很大
的位置
,由于不能预估被引用的上限,打个比方,可能出现32位即2的32次方个对象同时引用一个对象,那么计数器就须要32位。没法解决循环引用
没法回收的问题 这就是前文中IE9以前出现的问题主要将GC的垃圾回收过程分为两个阶段
标记阶段:把全部活动
对象作上标记
。
清除阶段:把没有
标记(也就是非活动对象)销毁
。
优点:
缺点
From
空间,另外一部分是To
空间From空间
里面的活动
对象复制
到To空间
From
空间互换
,那么就完成了一次GC。「概念:」 申请的内存
没有及时回收
掉,形成系统内存
的浪费
,致使程序运行
速度减慢
甚至系统崩溃等严重后果
「内存泄漏发生的场景:」
(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) 网络回调
「如何监控内存泄漏」
扩展:
JavaScript是门动态语言,跟Java不同,JavaScript能够随意定义全局变量和局部变量,每个函数都是一个做用域,当函数执行时会优先查找当前做用域,而后逐级向上。
JavaScript是静态做用域,在对变量进行查询时,变量值由函数定义时的位置决定,和执行时的所处的做用域无关。
ES6已经有块级做用域了,并且用 let 和 const 定义的变量不会提高。
概念:
做用域:变量或者函数的有效做用范围
做用域链:咱们须要查找某个变量值,会先在当前做用域查找,若是找不到会往上一级查,若是找到的话,就返回中止查找,返回查找的值,这种向上查找的链条关系,叫做用域
(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) “变量的值由函数定义时的位置决定”
这句话有歧义,准确说是查找变量时,是去定义这个函数时
所在的做用域链查找。
「概念:」
闭包就是一个函数,这个函数可以访问其余函数
的做用域
中的变量
「应用场景:」
JavaScript代码的整个执行过程,分为两个阶段,代码编译阶段
与代码执行阶段
。编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段做用域规则
会肯定。执行阶段由引擎完成,主要任务是执行可执行代码
,执行上下文在这个阶段建立。
ES5中:
this 永远指向最后调用
它的那个对象
ES6箭头函数:
箭头函数的 this 始终指向函数定义时
的 this,而非执行时
。
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对象。
「Tips:」 众所周知,ES6 的箭头函数是能够避免 ES5 中使用 this 的坑的。箭头函数
的 this 始终指向函数定义时
的 this,而非执行时
。
箭头函数须要记着这句话:“箭头函数中没有 this 绑定,必须经过查找做用域链来决定其值(箭头函数自己没有this,可是在它声明时能够捕获别人的this供本身使用。),若是箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,不然,this 为 undefined”。
特色:
一个函数能够当作一个类,原型是全部类都有的一个属性,原型的做用就是给这个类的每个对象都添加一个统一的方法
「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
奇淫技巧: 韩信对饮(函数的显示原型 = 对象的隐士原型)
详细点击这里
一个对象可使用另外
一个对象的属性
或者方法
,就称之为继承
。
具体是经过将这个对象的原型设置为
另一个对象
,这样根据原型链的规则,若是查找一个对象属性且在自身不存在时,就会查找另一个对象,至关于一个对象可使用另一个对象的属性和方法了。
function shallowClone(obj) { let cloneObj = {}; for (let i in obj) { cloneObj[i] = obj[i]; } return cloneObj; }
深克隆:
破解循环引用
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; }
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,因此最终获得去重后的结果。
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", ""]
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)
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)
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
「背景:」
V8的垃圾回收策略基于分代回收
机制,该机制又基于 世代假说
。
该假说有两个特色:
新生
对象倾向于早死
;不死
的对象,会活
得更久
。基于这个理论,现代垃圾回收算法根据对象的存活时间将内存进行了分代,并对不一样分代的内存采用不一样的高效算法进行垃圾回收。
「V8的内存分代」
在V8中,将内存分为了新生代
(new space)和老生代
(old space)。
它们特色以下:
新生代:对象的存活
时间较短
。新生对象或只通过一次垃圾回收
的对象。
老生代:对象存活
时间较长
。经历过一次或屡次
垃圾回收的对象。
「V8堆的空间」
V8堆的空间等于新生代空间加上老生代空间。咱们能够经过 --max-old-space-size命令设置老生代空间的最大值,--max-new-space-size 命令设置新生代空间的最大值。老生代与新生代的空间大小在程序初始化时
设置,一旦生效则不能动态改变。
默认设置下,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 |
ECMAScript 5 中引入的一种将更好的错误检查
引入代码中的方法, 如今已经被大多浏览器实现. 这种模式使得Javascript在更严格
的条件
下运行
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等 |
「(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中的类其实就是构造函数的另一种写法!
注意项:
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 捕获,须要改为其中一种:
由于返回任意一个非 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变量声明
事件流是网页元素接收事件的顺序,"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事件流的顺序执行事件处理程序:
父级捕获=》子级捕获=》子级冒泡=》父级冒泡
奇淫技巧:三个阶段能够这么记 捕母猫
(捕获,目标,冒泡)
构造调用:
这种状况下 JS 会阻塞浏览器,浏览器必须等待 index.js 加载和执行完毕才能去作其它事情。
<script src="index.js"></script>
async 模式下,JS 不会阻塞浏览器作任何其它的事情。它的加载是异步的,当它加载结束,JS 脚本会当即执行。
<script async src="index.js"></script>
defer 模式下,JS 的加载是异步的,执行是被推迟的。等整个文档解析完成、DOMContentLoaded 事件即将被触发时,被标记了 defer 的 JS 文件才会开始依次执行。
<script defer src="index.js"></script>
从应用的角度来讲,通常当咱们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,咱们会选用 async;当脚本依赖于 DOM 元素和其它脚本的执行结果时,咱们会选用 defer。
什么是性能优化,为何要优化,咱们优化的对象是什么,考虑清楚这几个问题,咱们才能找到对应的解决办法。
什么是性能优化?
在不影响系统运行正确性的前提下,使之运行地更快
,完成
特定功能
所需的时间更短
。简而言之,就是让咱们的程序更快的运行。
为何要性能优化?
性能是留住用户很重要的一环,通常人们能忍受一个网页的加载时长是少于5秒,性能优化也是程序高效运行的保障。
咱们优化的对象是什么?
优化对象是
程序
,以及程序所运行在的载体(如浏览器)
咱们已经知道了性能优化的对象是什么了,那么接下来就能够根据优化对象分开几个大类总结,对于前端来讲,程序和载体无非如下5点:
这样咱们再展开去说就能说的清楚,不会遗漏。
总结每一个大类的时候,先从整个文档格式
开始,再到外部资源
,再到代码层面
1. html
html应该首先想到语义化标签,正确的语义化能够咱们的文档结构更加清晰。
js文件写在body标签以后,可让防止阻塞页面的加载。
2.css
3.js
4.webpack
5. 浏览器