js数据类型
七种数据类型
复制代码
Boolean
Null
Undefined
Number
String
Symbol
(ECMAScript 6 新定义)Object
(ES6以前)其中5种为基本类型:string,number,boolean,null,undefined
javascript
ES6出来的Symbol也是原始数据类型 ,表示独一无二的值
css
Object 为引用类型(范围挺大),也包括数组、函数html
大致说一下,想要知其因此然请引擎搜索前端
相同点:java
差别:webpack
null
转为数字类型值为0,而undefined转为数字类型为NaN(Not a Number)
undefined
是表明调用一个值而该值却没有赋值,这时候默认则为undefined
null
是一个很特殊的对象,最为常见的一个用法就是做为参数传入(说明该参数不是对象)null
的变量或者对象会被内存收集器回收JS对象
pop、push、reverse、shift、sort、splice、unshift,以及两个ES6新增的方法copyWithin 和 fill;
复制代码
concat、join、slice、toString、toLocaleString、indexOf、lastIndexOf、
未标准的toSource以及ES7新增的方法includes;
复制代码
forEach、every、some、filter、map、reduce、reduceRight
以及ES6新增的方法entries、find、findIndex、keys、values。
复制代码
- 1.
slice(start,end)
:方法能够从已有数组中返回选定的元素,返回一个新数组,包含从start
到end
(不包含该元素)的数组方法 注意:该方法不会更新原数组,而是返回一个子数组- 2.
splice()
:该方法想或者从数组中添加或删除项目,返回被删除的项目。(该方法会改变原数组)splice(index, howmany,item1,...itemx)
·index
参数:必须,整数规定添加或删除的位置,使用负数,从数组尾部规定位置·howmany
参数:必须,要删除的数量,·item1..itemx:
可选,向数组添加新项目- 3.
map()
:会返回一个全新的数组。使用于改变数据值的时候。会分配内存存储空间数组并返回,forEach()
不会返回数据- 4.
forEach()
: 不会返回任何有价值的东西,而且不打算改变数据,单纯的只是想用数据作一些事情,他容许callback
更改原始数组的元素- 5.
reduce()
: 方法接收一个函数做为累加器,数组中的每个值(从左到右)开始缩减,最终计算一个值,不会改变原数组的值- 6.
filter()
: 方法建立一个新数组,新数组中的元素是经过检查指定数组中符合条件的全部元素。它里面经过function
去作处理
方法 | 描述 |
---|---|
charAt() | 返回在指定位置的字符。 |
charCodeAt() | 返回在指定的位置的字符的 Unicode 编码。 |
concat() | 链接字符串。 |
indexOf() | 检索字符串。 |
match() | 找到一个或多个正则表达式的匹配。 |
replace() | 替换与正则表达式匹配的子串。 |
search() | 检索与正则表达式相匹配的值。 |
slice() | 提取字符串的片段,并在新的字符串中返回被提取的部分。 |
split() | 把字符串分割为字符串数组。 |
toLocaleLowerCase() | 把字符串转换为小写。 |
toLocaleUpperCase() | 把字符串转换为大写。 |
toLowerCase() | 把字符串转换为小写。 |
toUpperCase() | 把字符串转换为大写。 |
substr() | 从起始索引号提取字符串中指定数目的字符。 |
substring() | 提取字符串中两个指定的索引号之间的字符。 |
-** Array**es6
方法 | 描述 |
---|---|
slice[start,end) | 返回从原数组中指定开始下标到结束下标之间的项组成的新数组(不影响原数组) |
: | . 1个参数:n.即:n到末尾的全部 |
: | . 2个参数:[start,end] |
splice(): | . 删除:2个参数,起始位置,删除的项数 |
: | . 插入:3个参数,起始位置,删除的项数,插入的项 |
: | . 替换:任意参数,起始位置,删除的项数,插入任意数量的项 |
pop() | 删除数组的最后一个元素,减小数组的长度,返回删除的值。(无参) |
push() | 将参数加载到数组的最后,返回新数组的长度。 (参数不限) |
shift() | 删除数组的第一个元素,数组长度减1,返回删除的值。 (无参) |
unshift() | 向数组的开头添加一个或更多元素,并返回新的长度。(参数不限) |
sort() | 按指定的参数对数组进行排序 ,返回的值是通过排序以后的数组(无参/函数) |
concat(3,4) | 把两个数组拼接起来。 返回的值是一个副本 (参数不限) |
join() | 将数组的元素组起一个字符串,以separator为分隔符,省略的话则用默认用逗号为分隔符 |
indexOf() | 从数组开头向后查找,接受两个参数,要查找的项(可选)和查找起点位置的索引 |
lastIndexOf() | 从数组末尾开始向前查找,接受两个参数,要查找的项(可选)和查找起点位置的索引 |
every() | 对数组中的每一项运行给定函数,若是该函数对每一项都返回true,则返回true。 |
filter() | 对数组中的每一项运行给定函数,返回该函数会返回true的项组成数组。 |
forEach() | 对数组的每一项运行给定函数,这个方法没有返回值。 |
map() | 对数组的每一项运行给定函数,返回每次函数调用的结果组成的数组。 |
some() | 对数组的每一项运行给定参数,若是该函数对任一项返回true,则返回true。以上方法都不会修改数组中的包含的值。 |
reduce()和reduceRight() | 缩小数组的方法,这两个方法都会迭代数组的全部项,而后构建一个最终返回的值。 |
方法 | 描述 |
---|---|
compile | 编译正则表达式。 |
exec | 检索字符串中指定的值。返回找到的值,并肯定其位置。 |
test | 检索字符串中指定的值。返回 true 或 false。 |
search | 检索与正则表达式相匹配的值。 |
match | 找到一个或多个正则表达式的匹配。 |
replace | 替换与正则表达式匹配的子串。 |
split | 把字符串分割为字符串数组。 |
推荐:知道这20个正则表达式,能让你少写1,000行代码web
原型(prototype): 一个简单的对象,用于实现对象的 属性继承。能够简单的理解成对象的爹。在 Firefox 和 Chrome 中,每一个JavaScript对象中都包含一个__proto__
(非标准)的属性指向它爹(该对象的原型),可obj.__proto__
进行访问。正则表达式
构造函数: 能够经过new来 新建一个对象 的函数。
实例: 经过构造函数和new
建立出来的对象,即是实例。 实例经过__proto__指向原型,经过constructor
指向构造函数。
工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了建立具体对象的过程
function createPerson(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
console.log(this.name);
}
return o;
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");
复制代码
工厂模式虽然解决了建立多个类似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)
function Person(name,age,job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log(this.name);
}
}
let person1 = createPerson("Nicholas", 29, "Software Engineer");
let person2 = createPerson("Greg", 27, "Doctor");
复制代码
使用构造函数的主要问题,就是每一个方法都要在每一个实例上从新建立一遍 (person1.sayName !== person2.sayName)
咱们建立的每一个函数都有一个prototype
(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
复制代码
原型中全部属性是被实例共享的, 引用类型的属性会出问题
function Person(){
}
Person.prototype = {
constructor: Person, // 重写原型必定要将constructor属性赋值为原构造函数,不然原型丢失
name : "Nicholas",
age : 29,
job : "Software Engineer",
friends : ["Shelby", "Court"],
sayName : function () {
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
console.log(person1.friends); //"Shelby,Court,Van"
console.log(person2.friends); //"Shelby,Court,Van"
console.log(person1.friends === person2.friends); //true
复制代码
建立自定义类型的最多见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每一个实例都会有本身的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
console.log(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
console.log(person1.friends); //"Shelby,Count,Van"
console.log(person2.friends); //"Shelby,Count"
console.log(person1.friends === person2.friends); //false
console.log(person1.sayName === person2.sayName); //true
复制代码
在这个例子中,实例属性都是在构造函数中定义的,而由全部实例共享的属性constructor和方法sayName()则是在原型中定义的。而修改了person1.friends(向其中添加一个新字符串),并不会影响到person2.friends,由于它们分别引用了不一样的数组。 这种构造函数与原型混成的模式,是目前在ECMAScript中使用最普遍、认同度最高的一种建立自定义类型的方法。能够说,这是用来定义引用类型的一种默认模式。
把全部信息都封装在了构造函数中,而经过在构造函数中初始化原型(仅在必要的状况下),又保持了同时使用构造函数和原型的优势。换句话说,能够经过检查某个应该存在的方法是否有效,来决定是否须要初始化原型。
function Person(name, age, job){
//属性
this.name = name;
this.age = age;
this.job = job;
if (typeof this.sayName != "function"){
console.log(1);
Person.prototype.sayName = function(){
console.log(this.name);
};
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer"); //1
var person2 = new Person("Greg", 27, "Doctor");
person1.sayName();
person2.sayName();
复制代码
这里只在sayName()方法不存在的状况下,才会将它添加到原型中。这段代码只会在初次调用构造函数时才会执行。此后,原型已经完成初始化,不须要再作什么修改了。不过要记住,这里对原型所作的修改,可以当即在全部实例中获得反映。所以,这种方法确实能够说很是完美其中,if语句检查的能够是初始化以后应该存在的任何属性或方法——没必要用一大堆if语句检查每一个属性和每一个方法;只要检查其中一个便可。对于采用这种模式建立的对象,还可使用instanceof
操做符肯定它的类型。
这种模式的基本思想是建立一个函数,该函数的做用仅仅是封装建立对象的代码,而后再返回新建立的对象;但从表面上看,这个函数又很像是典型的构造函数
function Person(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var p1 = new Person("Nicholas", 29, "Software Engineer");
p1.sayName(); //"Nicholas"
复制代码
Person函数建立了一个新对象,并以相应的属性和方法初始化该对象,而后又返回了这个对象。除了使用new操做符
并把使用的包装函数叫作构造函数以外,这个模式跟工厂模式
实际上是如出一辙的,构造函数在不返回值的状况下,默认会返回新对象实例。而经过在构造函数的末尾添加一个return语句
,能够重写调用构造函数时返回的值。
这个模式能够在特殊的状况下用来为对象建立构造函数。假设咱们想建立一个具备额外方法的特殊数组。因为不能直接修改Array构造函数,所以可使用这个模式。
function SpecialArray(){
//建立数组
var values = new Array();
//添加值
values.push.apply(values, arguments);
//添加方法
values.toPipedString = function(){
return this.join("|");
};
//返回数组
return values;
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); //"red|blue|green"
复制代码
关于寄生构造函数模式
,有一点须要说明:首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部建立的对象没有什么不一样。为此,不能依赖instanceof操做符
来肯定对象类型。因为存在上述问题,咱们建议在可使用其余模式的状况下,不要使用这种模式
所谓稳妥对象
,指的是没有公共属性
,并且其方法也不引用this的对象
。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其余应用程序改动时使用。稳妥构造函数遵循与寄生构造函数相似的模式,但有两点不一样:一是新建立对象的实例方法不引用this
;二是不使用new操做符
调用构造函数。按照稳妥构造函数的要求,能够将前面的Person构造函数重写以下。
function Person(name, age, job){
//建立要返回的对象
var o = new Object();
//能够在这里定义私有变量和函数
//添加方法
o.sayName = function(){
alert(name);
};
//返回对象
return o;
}
复制代码
注意,在以这种模式建立的对象中,除了使用sayName()方法以外,没有其余办法访问name的值。能够像下面使用稳妥的Person构造函数。
var person = Person("Nicholas", 29, "Software Engineer");
person.sayName(); //"Nicholas"
复制代码
这样,变量person中保存的是一个稳妥对象,而除了调用sayName()方法外,没有别的方式能够访问其数据成员。即便有其余代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。稳妥构造函数模式提供的这种安全性,使得它很是适合在某些安全执行环境下使用。
改变函数内部this指针的指向函数(bind,apply,call的区别)
经过apply
和call
改变函数的this
指向,他们两个函数的第一个参数都是同样的表示要改变指向的那个对象,第二个参数,apply是数组
,而call则是arg1,arg2...
这种形式。
经过bind
改变this做用域会返回一个新的函数,这个函数不会立刻执行。
手动实现 call 方法:
Function.prototype.myCall = function(context = window, ...rest) {
context.fn = this; //此处this是指调用myCall的function
let result = context.fn(...rest);
//将this指向销毁
delete context.fn;
return result;
};
复制代码
手动实现 apply 方法:
Function.prototype.myCall = function(context = window, params = []) {
context.fn = this; //此处this是指调用myCall的function
let result
if (params.length) {
result = context.fn(...params)
}else {
result = context.fn()
}
//将this指向销毁
delete context.fn;
return result;
};
复制代码
手动实现 bind 方法:
Function.prototype.myBind = function(oThis, ...rest) {
let _this = this;
let F = function() {}
// 根据 bind 规定,若是使用 new 运算符构造 bind 的返回函数时,第一个参数绑定的 this 失效
let resFn = function(...parmas) {
return _this.apply(this instanceof resFn ? this : oThis, [...rest,...parmas]);
};
// 继承原型
if (this.prototype) {
F.prototype = this.prototype;
resFn.prototype = new F;
}
return resFn;
};
复制代码
京东小姐姐
的文章 嗨,你真的懂this吗?
浅拷贝就是把属于源对象的值都复制一遍到新的对象,不会开辟二者独立的内存区域;
深度拷贝则是完彻底全两个独立的内存区域,互不干扰
// 这个 ES5的
function shallowClone(sourceObj) {
// 先判断传入的是否为对象类型
if (!sourceObj || typeof sourceObj !== 'object') {
console.log('您传入的不是对象!!')
}
// 判断传入的 Obj是类型,而后给予对应的赋值
var targetObj = sourceObj.constructor === Array ? [] : {};
// 遍历全部 key
for (var keys in sourceObj) {
// 判断全部属于自身原型链上的 key,而非继承(上游 )那些
if (sourceObj.hasOwnProperty(keys)) {
// 一一复制过来
targetObj[keys] = sourceObj[keys];
}
}
return targetObj;
}
// ES6 能够用 Object.assign(targeObj, source1,source2,source3) 来实现对象浅拷贝
复制代码
// 就是把须要赋值的类型转为基本类型(字符串这些)而非引用类型来实现
// JOSN对象中的stringify能够把一个js对象序列化为一个JSON字符串,parse能够把JSON字符串反序列化为一个js对象
var deepClone = function(sourceObj) {
if (!sourceObj || typeof sourceObj !== 'object') {
console.log('您传入的不是对象!!');
return;
}
// 转->解析->返回一步到位
return window.JSON
? JSON.parse(JSON.stringify(sourceObj))
: console.log('您的浏览器不支持 JSON API');
};
复制代码
原型链和继承
js万物皆对象,用
var a={}
或var a = new Object()
或者用构造函数的形式:var a = new A()
建立一个对象时,该对象不只能够访问它自身的属性,还会根据__proto__
属性找到它原型链上的属性,直到找到Object
上面的null
每一个函数都有prototype
属性,除了 Function.prototype.bind()
,该属性指向原型。
每一个对象都有__proto__
属性,指向了建立该对象的构造函数的原型。其实这个属性指向了 [[prototype]]
,可是 [[prototype]]
是内部属性,咱们并不能访问到,因此使用 _proto_
来访问。
对象能够经过 __proto__
来寻找不属于该对象的属性,__proto__
将对象链接起来组成了原型链。
- 原型链继承:
function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'
; 没法实现多继承- 构造继承:使用父类的构造函数来加强子类实例。
function Cat(name){Animal.call(this);this.name = name || 'Tom';}
没法继承父类原型链上的属性跟方法installof
去检验
- 实例继承:为父类实例添加新特性,做为子类实例的返回
- 拷贝继承:拷贝父类元素上的属性跟方法
- 组合继承:构造继承 和原型继承的组合体
- 寄生组合继承:经过寄生方式,在构造继承上加一个·Super·函数(没有实例和方法)让他的原型链指向父类的原型链砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
--原型链继承
function Cat(){ }
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);
复制代码
使用父类的构造函数来加强子类实例,等因而复制父类的实例属性给子类(没用到原型)
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
复制代码
至关于构造继承和原型链继承的组合体。经过调用父类构造,继承父类的属性并保留传参的优势,而后经过将父类实例做为子类原型,实现函数复用
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name);
复制代码
经过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
(function(){
// 建立一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例做为子类的原型
Cat.prototype = new Super();
})();
// Test Code
var cat = new Cat();
console.log(cat.name);
复制代码
详细请看 路易斯
的文章 JS原型链与继承别再被问倒了
函数做用域
变量做用域的概念:就是一个变量可使用的范围
JS中首先有一个最外层的做用域:称之为全局做用域
JS中还能够经过函数建立出一个独立的做用域,其中函数能够嵌套,因此做用域也能够嵌套
做用域链是由当前做用域与上层一系列父级做用域组成,做用域的头部永远是当前做用域,尾部永远是全局做用域。做用域链保证了当前上下文对其有权访问的变量的有序访问。
做用域链的意义:查找变量(肯定变量来自于哪里,变量是否能够访问)
简单来讲,做用域链能够用如下几句话来归纳:(或者说:肯定一个变量来自于哪一个做用域)
查看当前做用域,若是当前做用域声明了这个变量,就肯定结果
查找当前做用域的上级做用域,也就是当前函数的上级函数,看看上级函数中有没有声明
再查找上级函数的上级函数,直到全局做用域为止
若是全局做用域中也没有,咱们就认为这个变量未声明(xxx is not defined)
函数声明和函数表达式判别的依据是:函数的声明是否以function关键词开始。 以关键词function
开始的声明是函数声明,其他的函数声明所有是函数表达式。
//函数声明
function foo() {
}
//函数表达式
var foo = function () {
};
(function() {
})();
复制代码
function foo() {
}
var foo = function bar() {
}
setTimeout( function foo() {
} )
+function foo() {
}();
复制代码
须要注意:函数声明必定要是具名函数。
var foo = function () {
}
setTimeout( function foo() {
} )
-function foo() {
}();
复制代码
vara=2;
(function foo() {
var a=3;
console.log( a ); // 3
})();
console.log( a ); // 2
复制代码
(function() {
}())
复制代码
当函数能够记住并访问所在的词法做用域,即便函数是在当前词法做用域以外执行,这就产生了闭包。 ----《你不知道的Javascript上卷》
复制代码
闭包就是一个函数,一个能够访问并操做其余函数内部变量的函数。也能够说是一个定义在函数内部的函数。由于JavaScript没有动态做用域,而闭包的本质是静态做用域(静态做用域规则查找一个变量声明时依赖的是源程序中块之间的静态关系),因此函数访问的都是咱们定义时候的做用域,也就是词法做用域。因此闭包才会得以实现。
咱们常见的闭包形式就是a 函数套 b 函数,而后 a 函数返回 b 函数,这样 b 函数在 a 函数之外的地方执行时,依然能访问 a 函数的做用域。其中“b 函数在 a 函数之外的地方执行时”这一点,才体现了闭包的真正的强大之处。
function fn1() {
var name = 'iceman';
function fn2() {
console.log(name);
}
return fn2;
}
var fn3 = fn1();
fn3();
复制代码
这样就清晰地展现了闭包:
fn2
的词法做用域能访问fn1
的做用域
将fn2
当作一个值返回
fn1
执行后,将fn2
的引用赋值给fn3
执行fn3
,输出了变量name
咱们知道经过引用的关系,fn3
就是fn2
函数自己。执行fn3
能正常输出name
,这不就是fn2
能记住并访问它所在的词法做用域,并且fn2
函数的运行仍是在当前词法做用域以外了。
正常来讲,当fn1
函数执行完毕以后,其做用域是会被销毁的,而后垃圾回收器会释放那段内存空间。而闭包却很神奇的将fn1
的做用域存活了下来,fn2
依然持有该做用域的引用,这个引用就是闭包。
总结:某个函数在定义时的词法做用域以外的地方被调用,闭包可使该函数极限访问定义时的词法做用域。
复制代码
每一个函数都是闭包,每一个函数天生都可以记忆本身定义时所处的做用域环境。把一个函数从它定义的那个做用域,挪走,运行。这个函数竟然可以记忆住定义时的那个做用域。无论函数走到哪里,定义时的做用域就带到了哪里。接下来咱们用两个例子来讲明这个问题:
//例题1
var inner;
function outer(){
var a=250;
inner=function(){
alert(a);//这个函数虽然在外面执行,但可以记忆住定义时的那个做用域,a是250
}
}
outer();
var a=300;
inner();//一个函数在执行的时候,找闭包里面的变量,不会理会当前做用域。
复制代码
//例题2
function outer(x){
function inner(y){
console.log(x+y);
}
return inner;
}
var inn=outer(3);//数字3传入outer函数后,inner函数中x便会记住这个值
inn(5);//当inner函数再传入5的时候,只会对y赋值,因此最后弹出8
复制代码
栈内存提供一个执行环境,即做用域,包括全局做用域和私有做用域,那他们何时释放内存的?
通常状况下,函数执行会造成一个新的私有的做用域,当私有做用域中的代码执行完成后,咱们当前做用域都会主动的进行释放和销毁。但当遇到函数执行返回了一个引用数据类型的值,而且在函数的外面被一个其余的东西给接收了,这种状况下通常造成的私有做用域都不会销毁。
以下面这种状况:
function fn(){
var num=100;
return function(){
}
}
var f=fn();//fn执行造成的这个私有的做用域就不能再销毁了
复制代码
也就是像上面这段代码,fn
函数内部的私有做用域会被一直占用的,发生了内存泄漏。
所谓内存泄漏指任何对象在您再也不拥有或须要它以后仍然存在。闭包不能滥用,不然会致使内存泄露,影响网页的性能。闭包使用完了后,要当即释放资源,将引用变量指向null。
接下来咱们看下有关于内存泄漏的一道经典面试题:
function outer(){
var num=0;//内部变量
return function add(){//经过return返回add函数,就能够在outer函数外访问了
num++;//内部函数有引用,做为add函数的一部分了
console.log(num);
};
}
var func1=outer();
func1();//其实是调用add函数, 输出1
func1();//输出2 由于outer函数内部的私有做用域会一直被占用
var func2=outer();
func2();// 输出1 每次从新引用函数的时候,闭包是全新的。
func2();// 输出2
复制代码
JS模块:具备特定功能的js文件,将全部的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包信n个方法的对象或函数,模块的使用者,只须要经过模块暴露的对象调用方法来实现对应的功能。
应用闭包的主要场合是:设计私有的方法和变量。
推荐文章:
推荐 yck 的文章 重学 JS 系列:聊聊 new 操做符
执行上下文能够简单理解为一个对象:
它包含三个部分:
this
指向它的类型:
eval
执行上下文代码执行过程:
(caller)
逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee)
被push
到执行栈顶层caller
被挂起callee
被pop
移除出执行栈,控制权交还全局上下文(caller)
,继续执行DOM
建立:
createDocumentFragment() //建立一个DOM片断
createElement() //建立一个具体的元素
createTextNode() //建立一个文本节点
复制代码
添加:appendChild()
移出:removeChild()
替换:replaceChild()
插入:insertBefore()
复制:cloneNode(true)
查找:
getElementsByTagName() //经过标签名称
getElementsByClassName() //经过标签名称
getElementsByName() //经过元素的Name属性的值
getElementById() //经过元素Id,惟一性
复制代码
子节点
Node.childNodes
//获取子节点列表NodeList; 注意换行在浏览器中被算做了text节点,若是用这种方式获取节点列表,须要进行过滤Node.firstChild
//返回第一个子节点Node.lastChild
//返回最后一个子节点父节点
Node.parentNode
// 返回父节点Node.ownerDocument
//返回祖先节点(整个document)同胞节点
Node.previousSibling
// 返回前一个节点,若是没有则返回nullNode.nextSibling
// 返回后一个节点DOM事件模型分为捕获和冒泡。一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分红三个阶段。
window
对象自上而下目标节点传播的阶段;目标
节点自下而上向window
对象传播的阶段。上文中讲到了addEventListener的第三个参数为指定事件是否在捕获或冒泡阶段执行,设置为true表示事件在捕获阶段执行,而设置为false表示事件在冒泡阶段执行。那么什么是事件冒泡和事件捕获呢?能够用下图来解释:
捕获是从上到下,事件先从window
对象,而后再到document
(对象),而后是html
标签(经过document.documentElement获取html标签),而后是body
标签(经过document.body获取body标签),而后按照普通的html结构一层一层往下传,最后到达目标元素
。咱们只须要将addEventListener
的第三个参数改成true就能够实现事件捕获。
代码以下:
//摘自xyyojl的《深刻理解DOM事件机制》
<!-- CSS 代码 -->
<style>
body{margin: 0;}
div{border: 1px solid #000;}
#grandfather1{width: 200px;height: 200px;}
#parent1{width: 100px;height: 100px;margin: 0 auto;}
#child1{width: 50px;height: 50px;margin: 0 auto;}
</style>
<!-- HTML 代码 -->
<div id="grandfather1">
爷爷
<div id="parent1">
父亲
<div id="child1">儿子</div>
</div>
</div>
<!-- JS 代码 -->
<script>
var grandfather1 = document.getElementById('grandfather1'),
parent1 = document.getElementById('parent1'),
child1 = document.getElementById('child1');
grandfather1.addEventListener('click',function fn1(){
console.log('爷爷');
},true)
parent1.addEventListener('click',function fn1(){
console.log('爸爸');
},true)
child1.addEventListener('click',function fn1(){
console.log('儿子');
},true)
/*
当我点击儿子的时候,触发顺序是爷爷 ——》父亲——》儿子
*/
// 请问fn1 fn2 fn3 的执行顺序?
// fn1 fn2 fn3 or fn3 fn2 fn1
</script>
复制代码
所谓事件冒泡就是事件像泡泡同样从最开始生成的地方一层一层往上冒。咱们只须要将addEventListener
的第三个参数改成false就能够实现事件冒泡。
代码以下:
//html、css代码同上,js代码只是修改一下而已
var grandfather1 = document.getElementById('grandfather1'),
parent1 = document.getElementById('parent1'),
child1 = document.getElementById('child1');
grandfather1.addEventListener('click',function fn1(){
console.log('爷爷');
},false)
parent1.addEventListener('click',function fn1(){
console.log('爸爸');
},false)
child1.addEventListener('click',function fn1(){
console.log('儿子');
},false)
/*
当我点击儿子的时候,触发顺序:儿子——》爸爸——》爷爷
*/
// 请问fn1 fn2 fn3 的执行顺序?
// fn1 fn2 fn3 or fn3 fn2 fn1
复制代码
因为事件会在冒泡阶段向上传播到父节点,所以能够把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫作事件的代理(delegation)
。
若是给每一个列表项一一都绑定一个函数,那对于内存消耗是很是大的,效率上须要消耗不少性能。借助事件代理,咱们只须要给父容器ul绑定方法便可,这样无论点击的是哪个后代元素,都会根据冒泡传播的传递机制,把容器的click行为触发,而后把对应的方法执行,根据事件源,咱们能够知道点击的是谁,从而完成不一样的事。
在不少时候,咱们须要经过用户操做动态的增删列表项元素,若是一开始给每一个子元素绑定事件,那么在列表发生变化时,就须要从新给新增的元素绑定事件,给即将删去的元素解绑事件,若是用事件代理就会省去不少这样麻烦。
标准事件对象:
type
:事件类型target
:事件目标stopPropagation()
方法:阻止事件冒泡preventDefault()
方法:阻止事件的默认行为IE中的事件对象:
(1)type
:事件类型
(2)srcElement
:事件目标
(3)cancelBubble
属性:阻止事件冒泡 true表示阻止冒泡,false表示不阻止
(4)returnValue
属性:阻止事件的默认行为
/*
* @Author: 陈陈陈
*/
var eventUtil{
/*添加事件处理程序 */
addHandler:function(element,type,handler){//参数表元素、事件类型、函数
if(element.addEventListener){//DOM 2级事件处理程序
element.addEventListener(type,handler,false);//false表冒泡事件调用;true表捕获事件调用
}else if(element.attachEvent){//IE事件处理程序
element.attachEvent('on'+type,handler);//没有第三个参数的缘由是:IE8及更早的浏览器版本只支持事件冒泡
}else{//DOM 0级事件处理程序
element['on'+type]=handler;
}
},
/*删除事件处理程序 */
removeHandler:function(element,type,handler){//删除事件处理程序,参数表元素、事件类型、函数
if(element.removeEventListener){//DOM 2级事件处理程序
element.removeEventListener(type,handler,false);//false表冒泡事件调用;true表捕获事件调用
}else if(element.detachEvent){//IE事件处理程序
element.detachEvent('on'+type,handler);//没有第三个参数的缘由是:IE8及更早的浏览器版本只支持事件冒泡
}else{//DOM 0级事件处理程序
element['on'+type]=null;
}
},
getEvent:function(event){
return event?event:window.event;
},
getTarget:function(event){
return target || srcElement;
},
stopPro:function(event){
if(event.stopPropagation){
event.stopPropagation();
}else{
event.cancelBubble=true;
}
},
preventDef:function(event){
if(event.preventDefault){
event.preventDefault();
}else{
event.returnValue=false;
}
},
}
复制代码
JS中级篇
解决页面留白的方案:
1).预加载
2).使用svg站位图片,将一些结构快速搭建起来,等待请求的数据来了以后,替换当前的占位符
实现预加载的方法:
1.使用html标签
2.使用Image对象
3.使用XMLHTTPRequest对像,但会精细控制预加载过程
复制代码
两种技术的本质:二者的行为是相反的,一个是提早加载,一个是迟缓甚至不加载。 懒加载对服务器前端有必定的缓解压力做用,预加载则会增长服务器前端压力。
img
标签src
属性为空,给一个data-xx
属性,里面存放图片真实地址,当页面滚动直至此图片出如今可视区域时,用js取到该图片的data-xx
的值赋给src
。获取屏幕的高度和宽度(屏幕分辨率):window.screen.height/width
复制代码
获取屏幕工做区域的高度和宽度(去掉状态栏):window.screen.availHeight/availWidth
复制代码
网页全文的高度和宽度:document.body.scrollHeight/Width
复制代码
滚动条卷上去的高度和向右卷的宽度:document.body.scrollTop/scrollLeft
复制代码
网页可见区域的高度和宽度(不加边线):document.body.clientHeight/clientWidth
复制代码
网页可见区域的高度和宽度(加边线):document.body.offsetHeight/offsetWidth
复制代码
首先是三个事件,分别是mousedown,mousemove,mouseup 当鼠标点击按下的时候,须要一个tag
标识此时已经按下,能够执行mousemove
里面的具体方法。
clientX,clientY标识的是鼠标的坐标,分别标识横坐标和纵坐标,而且咱们用offsetX和offsetY来表示元素的元素的初始坐标,移动的举例应该是:鼠标移动时候的坐标-鼠标按下去时候的坐标。
也就是说定位信息为:
鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始状况下的offetLeft
.
还有一点也是原理性的东西,也就是拖拽的同时是绝对定位,咱们改变的是绝对定位条件下的left
以及top
等等值。
defer
:只支持IE若是您的脚本不会改变文档的内容,可将 defer
属性加入到script
标签中, 以便加快处理文档的速度。由于浏览器知道它将可以安全地读取文档的剩余部分而不用执行脚本,它将推迟对脚本的解释,直到文档已经显示给用户为止。
async
,HTML5属性仅适用于外部脚本,而且若是在IE中,同时存在defer和async,那么defer的优先级比较高,脚本将在页面完成时执行。
建立script
标签,插入到DOM
中
JS具备自动垃圾收集的机制
JS的内存生命周期(变量的生命)
JS的垃圾收集器每隔固定的时间就执行一次释放操做,通用的是经过标记清除的算法
标记清除算法:js最多见的垃圾回收方式,当变量进入执行环境的时候,好比函数中声明一个变量,垃圾回收器将他标
记为'进入环境',当变量离开(函数执行完后),就其标记为'离开环境'。垃圾回收器会在运行的时候给存储在内存中
的全部变量加上标记,而后去掉环境中的变量以及被环境中该变量所引用的变量(闭包)。在这些完成以后仍存在标记
的就是要删除的变量了
复制代码
dom
引用: dom 元素被删除时,内存中的引用未被正确清空防抖与节流函数是一种最经常使用的 高频触发优化方式,能对性能有较大的帮助。
定义: 合并事件且不会去触发事件,当必定时间内没有触发这个事件时,才真正去触发事件。
原理:对处理函数进行延时操做,若设定的延时到来以前,再次触发事件,则清除上一次的延时操做定时器,从新定时。
场景: keydown
事件上验证用户名,用户输入,只需再输入完成后作一次输入校验便可。
function debounce(fn, wait, immediate) {
let timer = null
return function() {
let args = arguments
let context = this
if (immediate && !timer) {
fn.apply(context, args)
}
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(context, args)
}, wait)
}
}
复制代码
定义: 持续触发事件时,合并必定时间内的事件,在间隔必定时间以后再真正触发事件。每间隔一段时间触发一次。
原理:对处理函数进行延时操做,若设定的延时到来以前,再次触发事件,则清除上一次的延时操做定时器,从新定时。
场景: resize
改变布局时,onscroll
滚动加载下面的图片时。
实现:
方法一:使用时间戳。
当触发事件的时候,咱们取出当前的时间戳,而后减去以前的时间戳(最一开始值设为0),若是大于设置的时间周期,就执行函数,而后更新时间戳为当前的时间戳,若是小于,就不执行。
缺陷:第一次事件会当即执行,中止触发后没办法再激活事件。
function throttle(fn, interval) {
var previousTime = +new Date()
return function () {
var that = this
var args = arguments
var now = +new Date()
if (now - previousTime >= interval) {
previousTime = now
fn.apply(that, args)
}
}
}
复制代码
方法二:使用定时器
当触发事件的时候,咱们设置一个定时器,再触发事件的时候,若是定时器存在,就不执行,直到定时器执行,而后执行函数,清空定时器,这样就能够设置下个定时器。
缺陷:第一次事件会在n秒后执行,中止触发后依然会再执行一次事件。
function throttle(fn, interval) {
var timer
return function (){
var that = this
var args = arguments
if(!timer){
timer = setTimeout(function () {
fn.apply(that, args)
timer = null
}, interval)
}
}
}
复制代码
方法三:优化
鼠标移入能马上执行,中止触发的时候还能再执行一次。
var throttle = function(func,delay){
var timer = null;
var startTime = Date.now();
return function(){
var curTime = Date.now();
var remaining = delay-(curTime-startTime);
var context = this;
var args = arguments;
clearTimeout(timer);
if(remaining<=0){
func.apply(context,args);
startTime = Date.now();
}else{
timer = setTimeout(func,remaining);
}
}
}
复制代码
在一个函数中,首先填充几个参数,而后再返回一个新的函数的技术,称为函数的柯里化。
一般可用于在不侵入函数的前提下,为函数 预置通用参数,供屡次重复调用。
const add = function add(x) {
return function (y) {
return x + y
}
}
const add1 = add(1)
add1(2) === 3
add1(20) === 21
复制代码
高阶函数英文叫 Higher-order function,它的定义很简单,就是至少知足下列一个条件的函数:
也就是说高阶函数是对其余函数进行操做的函数,能够将它们做为参数传递,或者是返回它们。
简单来讲,高阶函数是一个接收函数做为参数传递或者将函数做为返回值输出的函数。
高阶函数:参数值为函数或者返回值为函数。例如map
,reduce
,filter
,sort
方法就是高阶函数。
编写高阶函数,就是让函数的参数可以接收别的函数。
map() 方法建立一个新数组,其结果是该数组中的每一个元素都调用一个提供的函数后返回的结果,原始数组不会改变。传递给 map 的回调函数(callback)接受三个参数,分别是
currentValue、index(可选)、array(可选),除了 callback 以外还能够接受 this 值(可选),用于执行 callback 函数时使用的this 值。
复制代码
来个简单的例子方便理解,如今有一个数组 [1, 2, 3, 4],咱们想要生成一个新数组,其每一个元素皆是以前数组的两倍,那么咱们有下面两种使用高阶和不使用高阶函数的方式来实现。
// 木易杨
const arr1 = [1, 2, 3, 4];
const arr2 = arr1.map(item => item * 2);
console.log( arr2 );
// [2, 4, 6, 8]
console.log( arr1 );
// [1, 2, 3, 4]
复制代码
filter() 方法建立一个新数组, 其包含经过提供函数实现的测试的全部元素,原始数组不会改变。接收的参数和 map 是同样的,其返回值是一个新数组、由经过测试的全部元素组成,若是没有任何数组元素经过测试,则返回空数组。
来个例子介绍下,如今有一个数组 [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4],咱们想要生成一个新数组,这个数组要求没有重复的内容,即为去重。
const arr1 = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4];
const arr2 = arr1.filter( (element, index, self) => {
return self.indexOf( element ) === index;
});
console.log( arr2 );
// [1, 2, 3, 5, 4]
console.log( arr1 );
// [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4]
复制代码
reduce() 方法对数组中的每一个元素执行一个提供的 reducer 函数(升序执行),将其结果汇总为单个返回值。
reduce 的回调函数(callback)接受四个参数,分别是累加器 accumulator、currentValue、currentIndex(可选)、array(可选),除了 callback 以外还能够接受初始值 initialValue 值(可选)。
复制代码
若是没有提供 initialValue
,那么第一次调用callback
函数时,accumulator
使用原数组中的第一个元素,currentValue
便是数组中的第二个元素。 在没有初始值的空数组上调用 reduce
将报错。
若是提供了 initialValue
,那么将做为第一次调用 callback
函数时的第一个参数的值,即 accumulator
,currentValue
使用原数组中的第一个元素。
来个简单的例子介绍下,如今有一个数组 [0, 1, 2, 3, 4],须要计算数组元素的和,需求比较简单,来看下代码实现。
无 initialValue 值
const arr = [0, 1, 2, 3, 4];
let sum = arr.reduce((accumulator, currentValue, currentIndex, array) => {
return accumulator + currentValue;
});
console.log( sum );
// 10
console.log( arr );
// [0, 1, 2, 3, 4]
上面是没有 initialValue 的状况,代码的执行过程以下,callback 总共调用四次。
复制代码
有 initialValue 值
咱们再来看下有 initialValue 的状况,假设 initialValue 值为 10,咱们看下代码。
const arr = [0, 1, 2, 3, 4];
let sum = arr.reduce((accumulator, currentValue, currentIndex, array) => {
return accumulator + currentValue;
}, 10);
console.log( sum );
// 20
console.log( arr );
// [0, 1, 2, 3, 4]
复制代码代码的执行过程以下所示,callback 总共调用五次。
复制代码
例题:
已知以下数组:var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]; 编写一个程序将数组扁平化去并除其中重复部分数据,最终获得一个升序且不重复的数组
答案:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]
// 扁平化
let flatArr = arr.flat(4)
// 去重
let disArr = Array.from(new Set(flatArr))
// 排序
let result = disArr.sort(function(a, b) {
return a-b
})
console.log(result)
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
复制代码
现代浏览器为JavaScript创造的多线程环境。能够新建并将部分任务分配到worker
线程并行运行,两个线程可 独立运行,互不干扰,可经过自带的 消息机制 相互通讯。
基本用法:
// 建立 worker
const worker = new Worker('work.js');
// 向主进程推送消息
worker.postMessage('Hello World');
// 监听主进程来的消息
worker.onmessage = function (event) {
console.log('Received message ' + event.data);
}
复制代码
限制:
(1)单例模式
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
实现方法:先判断实例存在与否,若是存在则直接返回,若是不存在就建立了再返回,这就确保了一个类只有一个实例对象。
适用场景:一个单一对象。好比:弹窗,不管点击多少次,弹窗只应该被建立一次。
(2)发布/订阅模式
定义:又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,全部依赖于它的对象都将获得通知。
场景:订阅感兴趣的专栏和公众号。
(3)策略模式
定义:将一个个算法(解决方案)封装在一个个策略类中。
优势:
应用场景:根据不一样的员工绩效计算不一样的奖金;表单验证中的多种校验规则。
(4)代理模式
定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。
应用场景:图片懒加载(先经过一张loading图占位,而后经过异步的方式加载图片,等图片加载好了再把完成的图片加载到img标签里面。)
(5)中介者模式
定义:经过一个中介者对象,其余全部相关对象都经过该中介者对象来通讯,而不是互相引用,当其中的一个对象发生改变时,只要通知中介者对象就能够。能够解除对象与对象之间的紧耦合关系。
应用场景: 例如购物车需求,存在商品选择表单、颜色选择表单、购买数量表单等等,都会触发change事件,那么能够经过中介者来转发处理这些事件,实现各个事件间的解耦,仅仅维护中介者对象便可。
(6)装饰者模式
定义:在不改变对象自身的基础上,在程序运行期间给对象动态的添加方法。
应用场景: 有方法维持不变,在原有方法上再挂载其余方法来知足现有需求;函数的解耦,将函数拆分红多个可复用的函数,再将拆分出来的函数挂载到某个函数上,实现相同的效果但加强了复用性。
思路,用定时器去实现,以及如何实现平滑的滚动效果。详情请看: 原生js实现轮播图
前端模块化
前端模块化就是复杂的文件编程一个一个独立的模块,好比js文件等等,分红独立的模块有利于重用(复用性)和维护(版本迭代),这样会引来模块之间相互依赖的问题,因此有了commonJS
规范,
AMD,
CMD规范等等,以及用于js打包(编译等处理)的工具
webpack`。
将一个复杂的程序依据必定的规则(规范)封装成几个块(文件), 并进行组合在一块儿 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通讯
1)全局function模式: 将不一样的功能封装成不一样的全局函数
编码: 将不一样的功能封装成不一样的全局函数
问题: 污染全局命名空间, 容易引发命名冲突或数据不安全,并且模块成员之间看不出直接关系
2)namespace模式 : 简单对象封装
做用: 减小了全局变量,解决命名冲突
问题: 数据不安全(外部能够直接修改模块内部的数据)
3)IIFE模式:匿名函数自调用(闭包)
做用: 数据是私有的, 外部只能经过暴露的方法操做
编码: 将数据和行为封装到一个函数内部, 经过给window添加属性来向外暴露接口
问题: 若是当前这个模块依赖另外一个模块怎么办?
解决:IIFE模式加强 : 引入依赖
请求过多
首先咱们要依赖多个模块,那样就会发送多个请求,致使请求过多
复制代码
依赖模糊
咱们不知道他们的具体依赖关系是什么,也就是说很容易由于不了解他们之间的依赖关系致使加载前后顺序出错。
复制代码
难以维护
以上两种缘由就致使了很难维护,极可能出现牵一发而动全身的状况致使项目出现严重的问题。
复制代码
模块化当然有多个好处,然而一个页面须要引入多个js文件,就会出现以上这些问题。而这些问题能够经过模块化规范来解决,下面介绍开发中最流行的commonjs, AMD, ES6, CMD规范。
CommonJS
规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,由于同步意味着阻塞加载,浏览器资源是异步加载的,所以有了AMD CMD
解决方案。
AMD
规范在浏览器环境中异步加载模块,并且能够并行加载多个模块。不过,AMD
规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不畅。
CMD
规范与AMD
规范很类似,都用于浏览器编程,依赖就近,延迟执行,能够很容易在Node.js
中运行。不过,依赖SPM 打包,模块的加载逻辑偏重
ES6
在语言标准的层面上,实现了模块功能,并且实现得至关简单,彻底能够取代CommonJS
和AMD
规范,成为浏览器和服务器通用的模块解决方案。
require与import的区别
require
支持 动态导入,import
不支持,正在提案 (babel 下可支持)require
是 同步 导入,import
属于 异步 导入require
是 值拷贝,导出值变化不会影响导入值;import
指向 内存地址,导入值会随导出值而变化Model
用于封装和应用程序的业务逻辑相关的数据以及对数据的处理方法;View
做为视图层,主要负责数据的展现;Controller
定义用户界面对用户输入的响应方式,它链接模型和视图,用于控制应用程序的流程,处理用户的行为和数据上的改变。MVC将响应机制封装在controller对象中,当用户和你的应用产生交互时,控制器中的事件触发器就开始工做了。
MVVM
把View
和Model
的同步逻辑自动化了。之前Controller负责的View和Model同步再也不手动地进行操做,而是交给框架所提供的数据绑定功能进行负责,只须要告诉它View
显示的数据对应的是Model
哪一部分便可。也就是双向数据绑定,就是View
的变化能实时让Model
发生变化,而Model
的变化也能实时更新到View
。
async
和 await
相比直接使用 Promise
来讲,优点在于处理 then
的调用链,可以更清晰准确的写出代码。
缺点在于滥用await
可能会致使性能问题,由于 await
会阻塞代码,也许以后的异步代码并不依赖于前者,但仍然须要等待前者完成,致使代码失去了并发性。
下面来看一个使用 await
的代码。
var a = 0
var b = async () => {
a = a + await 10
console.log('2', a) // -> '2' 10
a = (await 10) + a
console.log('3', a) // -> '3' 20
}
b()
a++
console.log('1', a) // -> '1' 1
复制代码
首先函数
b
先执行,在执行到await 10
以前变量a
仍是0
,由于在await
内部实现了generators
,generators
会保留堆栈中东西,因此这时候a = 0
被保存了下来
由于
await
是异步操做,遇到await
就会当即返回一个pending
状态的Promise
对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行,因此会先执行console.log('1', a)
这时候同步代码执行完毕,开始执行异步代码,将保存下来的值拿出来使用,这时候
a = 10
而后后面就是常规执行代码了
推荐:
浪里行舟
的 前端模块化详解(完整版)
subwaydown
的 前端模块化:CommonJS,AMD,CMD,ES6
1)基于任务运行的工具:
Grunt、Gulp
复制代码
它们会自动执行指定的任务,就像流水线,把资源放上去而后经过不一样插件进行加工,它们包含活跃的社区,丰富的插件,能方便的打造各类工做流。
2)基于模块化打包的工具:
Browserify、Webpack、rollup.js
复制代码
有过 Node.js 开发经历的应该对模块很熟悉,须要引用组件直接一个 require 就 OK,这类工具就是这个模式,还能够实现按需加载、异步加载模块。
3)整合型工具:
Yeoman、FIS、jdf、Athena、cooking、weflow
复制代码
使用了多种技术栈实现的脚手架工具,好处是即开即用,缺点就是它们约束了技术选型,而且学习成本相对较高。
WebPack
是一个模块打包工具,你可使用WebPack
管理你的模块依赖,并编绎输出模块们所需的静态文件。它可以很好地管理、打包Web开发中所用到的HTML
、JavaScript
、CSS
以及各类静态文件(图片、字体等),让开发过程更加高效。对于不一样类型的资源,webpack
有对应的模块加载器。webpack
模块打包器会分析模块间的依赖关系,最后 生成了优化且合并后的静态资源。
webpack的两大特点:
1.code splitting(能够自动完成)
2.loader 能够处理各类类型的静态文件,而且支持串联操做
webpack
是以commonJS
的形式来书写脚本滴,但对AMD/CMD
的支持也很全面,方便旧项目进行代码迁移。
webpack
具备requireJs
和browserify
的功能,但仍有不少本身的新特性:
- 对
CommonJS
、AMD
、ES6
的语法作了兼容
- 对
js
、css
、图片等资源文件都支持打包
- 串联式模块加载器以及插件机制,让其具备更好的灵活性和扩展性,例如提供
对CoffeeScript
、ES6
的支持
- 有独立的配置文件
webpack.config.js
- 能够将代码切割成不一样的
chunk
,实现按需加载,下降了初始化时间
- 支持
SourceUrls
和SourceMaps
,易于调试
- 具备强大的
Plugin
接口,大可能是内部插件,使用起来比较灵活
webpack
使用异步IO
并具备多级缓存。这使得webpack
很快且在增量编译上更加快
从提取公共模块,区分开发环境,移除重复没必要要的 css 和 js 文件等方面说。
推荐arlendp2012
的文章 Webpack打包优化
初始化:启动构建,读取与合并配置参数,加载 Plugin
,实例化 Compiler
。
编译:从 Entry
发出,针对每一个Module
串行调用对应的Loader
去翻译文件内容,再找到该Module
依赖的 Module
,递归地进行编译处理。
输出:对编译后的Module
组合成 Chunk
,把 Chunk
转换成文件,输出到文件系统。
推荐whjin
的文章 webpack原理
- 依赖管理:方便引用第三方模块、让模块更容易复用,避免全局注入致使的冲突、避免重复加载或者加载不须要的模块。会一层一层的读取依赖的模块,添加不一样的入口;同时,不会重复打包依赖的模块。
- 合并代码:把各个分散的模块集中打包成大文件,减小
HTTP
的请求连接数,配合UglifyJS
(压缩代码)能够减小、优化代码的体积。- 各路插件:统一处理引入的插件,
babel
编译ES6
文件,TypeScript
,eslint
能够检查编译期的错误。
一句话总结:webpack
的做用就是处理依赖,模块化,打包压缩文件,管理插件。
参考文章: