前端面试六
26、用js或jQuery获取id为container的div里边的所有a标签,并把字体大小设置为18px;同 时把第三个a标签设置为块级元素,并把他的宽高分别设置为300px和500px。 这里主要是第几个子元素使用css属性nth-child(index),index从1开始
27、请用jquery和原生js分别实现创建、添加、复制、移除、移动、和查找DOM结点
● js 创建DOM节点,并且添加到DOM中
document.createTextNode("sss")//创建文本节点
document.createElement("p")//创建元素节点
document.createDocumentFragment();//创建文档碎片节点
//insertBefore() 方法,如果不是在末尾插入节点,而是想放在特定的位置上,用这个方法,
//该方法接受2个参数,第一个是要插入的节点,第二个是参照节点,返回要添加的元素节点
var ul = document.getElementById("myList"); //获得ul
var li = document.createElement("li"); //创建li
li.innerHTML= "项目四"; //向li内添加文本
ul.insertBefore(li,ul.firstChild); //把li添加到ul的第一个子节点前
appendChild()方法。添加在末尾
//为body添加一个内容为sss的文本节点
document.body.appendChild(document.createTextNode("sss"));
● jQuery 创建DOM节点,并且添加到DOM中
<!-- jQuery创建DOM节点 -->
var str = $("<a href='http://www.baidu.com'>百度</a>");
<!--append 追加在父元素的最后一个子节点后面
prepend插入到父元素的第一个子节点前面
after在元素后面追加,同级
befor在元素的前面追加,同级 -->
$("ul").append(str); //将动态创建的str元素做为ul的最后一个子节点
$("ul").prepend(str); //将动态创建的str元素做为ul的第一个子节点
$("ul").after(str); //将动态创建的str元素放在ul的下面,两者是堂兄弟的关系
$("ul").before(str); //将动态创建的str元素放在ul的上面,两者是堂兄弟的关系
● js移除DOM节点,removeChild()移除某个节点的子节点
var ccn=document.getElementById("sd").childNodes[0];//获取到要移除的节点
document.getElementById("sd").removeChild(ccn);//将id为sd的第一个子节点移除
● jQuery移除DOM节点
<!-- remove() - 删除被选元素(及其子元素) -->
<!-- empty() - 从被选元素中删除子元素 -->
$('ul').remove();//删除ul及其子元素
$('ul').empty();//删除ul的子元素,不包括ul,返回被删除的 元素
● js移动DOM节点
//html如下
<div id='sd' style="position: absolute; left: 10px;">sd</div>
// 将id为sd的节点向右边移动50px 通过style属性只能获取到行内元素样式,多行元素获取到的是udnefined,通过classList属性可以获取多行元素样式属性
var sdds = document.getElementById("sd");
sdds.style.left = parseInt(sdds.style.left) + 50 + "px";
● js查找DOM节点
document.getElementById('oo');//根据id查找
document.getElementsByTagName("p");//根据标签名字查找节点
document.getElementsByClassName("one")//根据className
● jQuery查找DOM节点
$('#name') //根据id查找
$('.color')//根据类名查找
$('p')//根据tagName查找
● js复制DOM节点 cloneNode()
//复制节点
//cloneNode() 方法,用于复制节点, 接受一个布尔值参数,
// true 表示深复制(复制节点及其所有子节点), false
// 表示浅复制(复制节点本身,不复制子节点)
var ul = document.getElementById("myList"); //获得ul
var deepList = ul.cloneNode(true); //深复制
var shallowList = ul.cloneNode(false); //浅复制
● jQuery复制DOM节点 clone()
$('ul').clone(true).appendTo("p"); // 复制ul节点,并将它追加到<p>元素
js怎样获取div的id为‘content’的文本内容
document.getElementById('content').innerHTML
document.getElementById('content').innerText
document.getElementById('content').value
js获取节点的名称
document.getElementById('content').nodeName
28、jquery怎样获取div的id为‘content’的文本内容
$('#content').text();//获取文本的内容
$('#content').html();//获取html的内容,包括标签
$('#con').val();//获取<input> <textarea>的值
29、你怎么看AMD vs.Commonjs?
共同点:两者都是为了实现模块化编程而出现的。对于大型项目,参与人数多,代码逻辑复杂,是最适合使用模块化的思想来完成整个项目的。同时采用这种思想也很便于对整个项目进行管控。
区别:
CommonJS:
● 适用于服务器端,Node.js的执行环境就是采用CommonJS模式
● 同步加载不同模块文件。之所以采用同步加载方式,是因为模块文件都存放在服务器的各个硬盘上,实际的加载时间就是硬盘的文件读取时间
AMD,Asynchronous Module Definition,即异步模块定义
● 适用于浏览器端的一种模块加载方式
● AMD采用的是异步加载方式(js中最典型的异步例子就是ajax)
● 浏览器需要使用的js文件(第一次加载,忽略缓存)都存放在服务器端,从服务器端加载文件到浏览器是受网速等各种环境因素的影响的,如果采用同步加载方式,一旦js文件加载受阻,后续在排队等待执行的js语句将执行出错,会导致页面的‘假死’,用户无法使用一些交互。所以在浏览器端是无法使用CommonJS的模式的。
目前,主要有两个Javascript库实现了AMD规范:require.js和curl.js
下面程序输出的结果,以及改进的方法
for( var i=0;i<3;i++ ){
setTimeout(function(){console.log(i)},0); //3次3
}
29、ES6模块和CommonJS模块的差异
1)ES6输入的模块变量,只是一个符号链接,所以这个变量是只读的,对它重新进行赋值会报错
2)CommonJS模块输出的是一个值的拷贝,ES6模块输出的是一个值的引用
3)CommonJS模块是运行时加载,ES6模块是编译时输出接口
29、模块化加载的顺序如何
1)commonjs:同步、顺序执行
2)AMD:提前加载,不管是否调用模块,先解析所有模块,requirejs的速度快,有可能浪费资源
3)CMD: 提前加载,在真正需要使用(依赖)模块时才解析该模块,seajs 按需解析,性能比AMD差
30、Requirejs的使用过程是怎样的(怎样搭建一个模块化项目)?
什么是Requirejs?
● RequireJS是一个非常小巧的JavaScript模块载入框架
● 是AMD规范最好的实现者之一
● 最新版本的RequireJS压缩后只有14K,堪称非常轻量
● 它还同时可以和其他的框架协同工作
● 使用RequireJS必将使您的前端代码质量得以提升
使用RequireJS能带来什么样的好处?为什么使用RequireJS
传统的引入js文件的形式是连续加载多个js文件,如下面的形式:
<script src="1.js"></script>
<script src="2.js"></script>
<script src="3.js"></script>
<script src="4.js"></script>
<script src="5.js"></script>
<script src="6.js"></script>
上面的写法存在两点缺陷:
● 加载的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长
● 由于js文件之间存在依赖关系,因此必须严格保证加载顺序(比如上例的1.js要在2.js的前面)。依赖性最大的模块一定要放到最后加载,当依赖关系很复杂的时候,代码的编写和维护都会变得困难
使用RequireJS就是为了解决上面的问题
● 实现js文件的异步加载,避免网页失去响应
● 管理模块之间的依赖性,便于代码的编写和维护
● 可以按需、并行、延时载入js库
RequireJS搭建一个模块化项目的过程
a) 在html中引入Requirejs
<script data-main="js/app.js" src="js/require.js"></script>
data-main的作用是指定网页模块的主程序,即对应的路径是Requirejs配置的主入口, data-main和 src中路径都是相对html的路径,或者是绝对路径。
b) 主模块(整个网页的入口代码,所有的代码都从这运行)的写法,即data-main指定的文件的内容
requirejs.config({ //模块的加载
baseUrl:'js/lib',
paths :{
app:'../app'
}
});
require(["app/start"], function(app) { //主模块
app.hello();
});
● baseUrl为模块(module)的根目录,如果require(依赖) 的模块直接是用文件名作为id的话,会直接在这个目录寻找同名文件资源
● paths 中的属性可以给不同的路径或者文件别名,如果require(依赖) 的模块使用路径作为id的时候可以通过别名匹配path中路径
c) 定义模块,采用AMD规范,即用define()定义模块。(requirejs提供了多种定义模块的方式,可以使用/不使用依赖,返回变量,返回对象,函数,可使用CommonJs的方法在export,module中返结果)
define(['myLib'], function(myLib){
function foo(){
myLib.doSomething();
}
return {
foo : foo
};
});
d) shim配置:将某个依赖中的某个全局变量暴露给requirejs,当作这个模块本身的引用
如官网中例子,把backbone.js引入,抛出Backbone作为模块名引入,deps中申明它需要的依赖,backbone依赖underscore 和jquery
requirejs.config({
shim: {
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
},
'underscore': {
exports: '_'
}
}
});
e) 打包压缩:Require.js官网提供了r.js打包工具,只要配置对应的Build.js就可以帮助我们自动化构建
31、请谈谈你都使用过哪些javascript模板
react.js vue.js angular.js jquery
32、svg 与canvas的不同
svg与canvas的共同点:
都允许在浏览器中创建图形
SVG的定义:
● SVG 是一种使用 XML 描述 2D 图形的语言
● SVG 基于 XML,这意味着 SVG DOM 中的每个元素都是可用的。您可以为某个元素附加 JavaScript 事件处理器。
● 在 SVG 中,每个被绘制的图形均被视为对象。如果 SVG 对象的属性发生变化,那么浏览器能够自动重现图形。
Canvas定义:
● Canvas 通过 JavaScript 来绘制 2D 图形
● Canvas 是逐像素进行渲染的
● 在 canvas 中,一旦图形被绘制完成,它就不会继续得到浏览器的关注。如果其位置发生变化,那么整个场景也需要重新绘制,包括任何或许已被图形覆盖的对象
svg与canvas的不同点:
Canvas:
● 依赖分辨率
● 不支持事件处理器
● 弱的文本渲染能力
● 能够以 .png 或 .jpg 格式保存结果图像
● 最适合图像密集型的游戏,其中的许多对象会被频繁重绘
svg:
● 不依赖分辨率
● 支持事件处理器
● 最适合带有大型渲染区域的应用程序(比如谷歌地图)
● 复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)
● 不适合游戏应用
37、Promise的实现原理
https://zhuanlan.zhihu.com/p/25178630 解释1
https://juejin.im/post/5a30193051882503dc53af3c 解释2
大致思路:
1、Promise的构造函数,只有一个函数参数resolver(resolve,reject),而函数参数resolver的两个参数都是函数参数,第一个参数resolve表示处理成功执行的函数,reject表示失败执行的函数
在构造函数中的属性有state(保存当前promise的状态);value(保存resolve或发生reject的错误原因);queue(Array,同一个promise可以多次调用then方法,这里是保存调用then方法之后生成的promise对象及其对应的resolve reject函数)
构造函数主要职责有:
a)初始化Promise的状态,this.state = PENDING
b)初始化resolve或reject的参数值,this.value = void 0
c)初始化then()注册的回调函数,this.queue = []
d)立即执行resolver(resolve,reject),即调用函数safelyResolveThen(self, then)-
立即执行resolver(resolve,reject)被封装在函数safelyResolveThen(self, then)----self为当前promise对象,resolver=then
这里就是执行函数resolver.
通过闭包对变量called的持有,使变量一直保存在内存中,达到resolve reject只执行一次,即同一个promise的状态由pending=>fulfilled或者pending => rejected只发生一次
在函数safelyResolveThen(self, then)里面调用了全局函数doResolve(self, value)和doReject(self, error)
doResolve(self, value)---doResolve 结合 safelyResolveThen 使用,不断地解包 promise 对象后,当值不是一个thenable对象时设置 promise 的状态和值。解包promise对象使用了方法getThen(value)。同时对queue遍历,通知queue中的每个promise的子promise,子promise 根据传入的值设置自己的状态和值
getThen(value)----如果value是一个thenable对象函数,那么就返回一个函数(在函数内,通过apply将value.then方法的作用域改成value,并执行then方法)
doReject(self, error) -----设置 promise的state为 REJECTED,value 为 error。同时遍历queue,对每个成员对象调用 callRejected,通知子 promise,子 promise 根据传入的错误设置自己的状态和值
2、Promise.prototype.then(onFulfilled, onRejected)----两个参数,成功时执行的回调函数onFulfilled,失败时执行的回调函数
onRejected。返回值是一个新生成的promise对象
根据当前promise的state状态来做相应的处理:
1)存在值穿透问题,即then方法没有参数或者参数为一个值
2)state不为PENDING,则执行unwrap(promise, resolver, this.value)。判断state为FULFILLED则resolver=onFulfilled,为REJECTED则resolver=onRejected。
3)否则promise的状态没有改变,即state=PENDIGN,将生成的promise对象和对应的onFulfilled, onRejected加入到当前promise的回调队列queue里面
unwrap(promise, resolver, this.value)----调用immediate.js将同步代码生成异步代码。同时执行回调函数,并验证处理的结果returnValue,如果为promise本身则抛出错误,否则调用doResolve(promise, returnValue)
Promise的静态方法
3、Promise.resolve(value)------返回一个promise对象;
a)value是一个promise对象则直接返回value;否则调用doResolve函数
b)value是一个thenable对象,将其转为promise对象,立即执行then方法,该promise的状态由这个thenable对象的then方法的具体设置而定;
c)value是一个原始值,或者是一个不具有then
方法的对象,返回promise对象,状态为resolve
d)不带任何参数,直接返回一个promise对象
4、Promise.reject(reason)----返回一个promise对象;该实例的状态为rejected
直接返回doReject(promise, reason)处理后的结果
5、Promise.all(iterable)-------接受一个参数,返回一个promise。将多个promise封装成一个promise
返回promise的回调函数的参数值:
a)只有iterable中所有的promise对象的状态都变成fulfilled的时候,返回promise的状态才为fulfilled,且传递给返会promise对象的回调函数的参数为Array,元素由这些promise resolve之后的值组成
b)否则返回第一个被reject的实例的返回值,作为返回promise的回到函数的参数
6、Promise.race(iterable)-------接受一个参数,返回一个promise。将多个promise封装成一个promise
返回promise的回调函数的参数值:
a)返回第一个状态先改变的promise对象处理之后的值
'use strict';
var immediate = require('immediate'); //用immediate.js库将同步代码装成异步代码
function INTERNAL() {}
function isFunction(func) {
return typeof func === 'function';
}
function isObject(obj) { //Array Object Null Set Map 都为true
return typeof obj === 'object';
}
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
var PENDING = 0; //控制promise对象的状态
var FULFILLED = 1;
var REJECTED = 2;
module.exports = Promise; //输出模块
function Promise(resolver) { //Promise构造函数
if (!isFunction(resolver)) {
throw new TypeError('resolver must be a function');
}
this.state = PENDING; //初始化promise的状态为PENDIGN
this.value = void 0; //初始化resolve或reject的参数值
this.queue = []; //then方法可被同一个promise对象多次调用,用于注册新生成的promise对象
if (resolver !== INTERNAL) {
safelyResolveThen(this, resolver); //立即执行传进来的resolver函数
}
}
function safelyResolveThen(self, then) {
var called = false; //called被用在闭包里面,使得它的值一直保存在内存中,用于promise的
try { //called 控制 resolve 或 reject 只执行一次,多次调用没有任何作用
then(function (value) {
if (called) {
return;
}
called = true;
doResolve(self, value);
}, function (error) {
if (called) {
return;
}
called = true;
doReject(self, error);
});
} catch (error) {
if (called) {
return;
}
called = true;
doReject(self, error);
}
}
function doResolve(self, value) { //doResolve 结合 safelyResolveThen 使用,不断地解包
// promise 对象后,设置 promise 的状态和值
try {
var then = getThen(value);
if (then) {
safelyResolveThen(self, then); //value是一个thenable对象,则递归遍历直到是一个值
} else {
self.state = FULFILLED;
self.value = value;
self.queue.forEach(function (queueItem) { //对queue遍历,执行当前promise对应的callFulfilled()
queueItem.callFulfilled(value); //callFulfilled通知子 promis, 子promise 根据传入的值设置自己的状态和值。
});
}
return self; //返回promise对象
} catch (error) {
return doReject(self, error);
}
}
function doReject(self, error) { // 设置 promise的state为 REJECTED,value 为 error
self.state = REJECTED;
self.value = error;
self.queue.forEach(function (queueItem) {
queueItem.callRejected(error); // callRejected通知子 promise,子 promise 根据传入的错误设置自己的状态和值
});
return self; //返回当前promise对象
}
function getThen(obj) { //传入的obj不为空,然后访问obj是否有then方法,有就返回obj.then(),否则为undefined
var then = obj && obj.then; // obj是一个thenable对象
if ( obj && ( isObject(obj) || isFunction(obj) ) && isFunction(then) ) {
return function appyThen() {
then.apply(obj, arguments);
};
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
if( !isFunction(onFulfilled) && this.state === FULFILLED ||
!isFunction(onRejected) && this.state === REJECTED) {
return this; //实现了值穿透
}
// 生成新的promise对象
var promise = new this.constructor(INTERNAL);
// promise 的状态改变了,则调用 unwrap
if (this.state !== PENDING) {
var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
unwrap(promise, resolver, this.value);
} else {
//promise对象状态没有改变,将生成的promise对象加入到当前promise的回调队列queue里面
this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
}
return promise;
};
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
};
function unwrap(promise, func, value) { //解包,参数分别为子promise 父promise的then的回调onFulfilled/onRejected
immediate(function () { // immediate()将同步代码变成异步代码 //父 promise 的值(正常值/错误)
var returnValue;
try { //捕获promise.then() promise.catch()内部的异常
returnValue = func(value);
} catch (error) {
return doReject(promise, error);
}
if (returnValue === promise) { //返回的值不能是 promise 本身,否则会造成死循环
doReject(promise, new TypeError('Cannot resolve promise with itself'));
} else {
doResolve(promise, returnValue);
}
});
}
// 类QueueItem具有的属性this.promise this.callFulfilled this.callRejected
function QueueItem(promise, onFulfilled, onRejected) {
this.promise = promise;
this.callFulfilled = function (value) {
doResolve(this.promise, value); //设置当前promise的状态和值
};
this.callRejected = function (error) {
doReject(this.promise, error);
};
if (isFunction(onFulfilled)) {
this.callFulfilled = function (value) {
unwrap(this.promise, onFulfilled, value);
};
}
if (isFunction(onRejected)) {
this.callRejected = function (error) {
unwrap(this.promise, onRejected, error);
};
}
}
Promise.resolve = resolve;
function resolve(value) { //返回一个promise对象
if (value instanceof this) { //当 Promise.resolve 参数是一个 promise 时,直接返回该值
return value;
}
return doResolve(new this(INTERNAL), value); //否则调用doResolve函数
}
Promise.reject = reject;
function reject(reason) { //返回一个promise对象。
var promise = new this(INTERNAL);
return doReject(promise, reason); //调用doReject
}
Promise.all = all;
function all(iterable) {
var self = this;
if (!isArray(iterable)) {
return this.reject(new TypeError('must be an array'));
}
var len = iterable.length;
var called = false;
if (!len) {
return this.resolve([]);
}
var values = new Array(len);
var resolved = 0;
var i = -1;
var promise = new this(INTERNAL);
while (++i < len) {
allResolver(iterable[i], i);
}
return promise;
function allResolver(value, i) {
self.resolve(value).then(resolveFromAll, function (error) {
if (!called) {
called = true;
doReject(promise, error);
}
});
function resolveFromAll(outValue) {
values[i] = outValue;
if (++resolved === len && !called) {
called = true;
doResolve(promise, values);
}
}
}
}
Promise.race = race;
function race(iterable) {
var self = this;
if (!isArray(iterable)) {
return this.reject(new TypeError('must be an array'));
}
var len = iterable.length;
var called = false;
if (!len) {
return this.resolve([]);
}
var i = -1;
var promise = new this(INTERNAL);
while (++i < len) {
resolver(iterable[i]);
}
return promise;
function resolver(value) {
self.resolve(value).then(function (response) {
if (!called) {
called = true;
doResolve(promise, response);
}
}, function (error) {
if (!called) {
called = true;
doReject(promise, error);
}
});
}
}
37、使用Promises 而非回调(callbacks)优缺点是什么?
Promise的定义
● Promise,简单来说是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
● 从语法上说,Promise是一个对象,从它可以获取异步操作的消息
● Promise提供统一的API,各种异步操作都可以用同样的方法进行处理
Promise的特点
a)对象的状态不受外界影响
Promise对象有3种状态,每个promise对象只能是3种状态的一种
1)pending: 表示一个初始状态, 非 fulfilled 或 rejected
2)fulfilled: 成功的操作
3)rejected: 失败的操作
b)状态一旦改变,就不会再发生变化
状态的改变是单向的,只能由pending -> fulfilled pending -> rejected,只要这两种情况发生状态就凝固了,这时称为resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果
Promise的优点
● Promise 对象用于延迟(deferred) 计算和异步(asynchronous ) 计算,是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象
● 有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,简化代码,使代码清晰明了
● 此外,Promise对象提供统一的接口,使得控制异步操作更加容易
Promise的缺点
● 无法取消Promise,一旦新建它,就会立即执行,无法中途取消
● 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
● 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
Promise的基本用法
Promise对象是一个构造函数,用来生成Promise实例
// resolve--状态从“未完成”变为“成功”(即从 pending 变为 resolved),
// 在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
// reject--状态从“未完成”变为“失败”(即从 pending 变为 rejected),
// 在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去
// Promise 新建后就会立即执行
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
// Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数
// 分别为resolve(param1)-必写,reject(param2)--可选
// param1--可以是普通变量也可以是Promise对象
// then(resolve(param1),reject(param2))
promise.then(function(value) {
// success
}, function(error) {
// failure
});
宏任务macrotask和微任务microtask----------异步任务的两种分类
在挂起任务时,JS 引擎会将所有任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫做 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。
-
macro-task: script(整体代码),
setTimeout
,setInterval
,setImmediate
, I/O, UI rendering -
micro-task:
process.nextTick
,Promises
(这里指浏览器实现的原生 Promise),Object.observe
,MutationObserver
//由于在macrotask中先执行了整个js文件,然后去遍历microtask里面的事件
setTimeout(function () { //-----macrotask
console.log('setTimeout');
}, 0);
Promise.resolve().then(function () { //-----microtask
console.log('promise1');
}).then(function () {
console.log('promise2');
});
// 输出结果的顺序
promise1
promise2
setTimeout
同一个promise对象可以多次调用then方法
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('haha')
console.log('1')
}, 1000)
})
promise.then(() => { //当promise的状态发生改变的时候,才会触发then中的参数函数,所以先输出'3'
promise.then(() => { //同一个promise对象可以多次调用then方法
console.log('3')
})
console.log('2')
})
// 1 2 3
setTimeout(function () {
console.log('three'); //在下一轮“事件循环”开始时执行
}, 0);
Promise.resolve().then(function () {
console.log('two'); //在本轮“事件循环”结束时执行
});
console.log('one'); //立即执行代码
//执行结果 one two three
Promise模拟终止
构造函数Promise(fn)中的参数fn(resolve,reject),当初始化Promise对象实例的时候没有传进来resolve参数,那么这个promise对象的状态就是pending状态,此时原Promise链将会终止执行
Promise对象的几种API---race() all() resolve() reject()是静态方法
1)Promise.prototype.then():promise实例具有then方法,它的作用是为promise实例添加状态改变时的回调函数
then()方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。then方法返回的是一个新的promise实例,非原来的那个promise实例,因此可以采用链式写法,then方法之后还可调用一个then方法。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。then方法可以被同一个promise对象调用多次
var p=new Promise(function(resolve,eject){
resolve("ok");
});
p.then(function(value){console.log(val)},
function(err)(console.log(err))
);
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
2)Promise.prototype.catch():这个方法是Promise.prototype.then(null,rejection)的别名,专门只能用来捕获错误信息,用于指定发生错误时的回调函数,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
Promise放在try...catch里面的结果是什么
Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止,即错误总是会被下一个catch语句捕获
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
Promise 在resolve语句后面,再抛出错误,错误不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了;Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise.then(function(value) { console.log(value) }).catch(function(error) { console.log(error) });
// ok
3)Promise.resolve():将现有的对象转为Promise对象,状态是resolved,进而可以执行这些方法
参数种类:
a) 参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例
b) 参数是一个thenable对象,将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法,状态为resolved
c) 参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved
d) Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象
4)Promise.reject():返回一个新的 Promise 实例,状态为rejected,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数
5)Promise.all():将多个promise实例,包装成一个新的promise实例,用于一次性处理多个请求
// 参数为数组,数组里面每个元素都是Promise实例
// p1、p2、p3都是 Promise 实例
// 参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例
// p的状态由p1、p2、p3决定:只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled
// 此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
// 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返
// 回值,会传递给p的回调函数
const p = Promise.all([p1, p2, p3]);
6)Promise.race():将多个promise实例,包装成一个新的promise实例
// 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。
// 那个率先改变的 Promise 实例的返回值,就传递给p的回调函数
const p = Promise.race([p1, p2, p3]);
详细介绍的链接:https://www.cnblogs.com/huangzhilong/p/5358493.html
37、Promise中的链式操作实现原理,JQuery中链式操作实现原理
jQuery中的链式操作实现原理大致思路:
1)就是在jQuery对象上的方法的最后面返回this对象,由于js中的this对象的值是由函数的运行环境决定的,当调用方法的时候最后面返回this对象,这个this对象就指向方法前面的对象,从而可以继续调用该对象中的方法,从而实现链式操作
2)Promise中的链式操作实现原理
在方法中返回一个新的Promise对象
通过Promise.prototype.then方法将观察者方法注册到被观察者Promise对象中,同时返回一个新的Promise对象,以便可以链式调用。被观察者管理内部pending、fulfilled和rejected的状态转变,同时通过构造函数中传递的resolve和reject方法以主动触发状态
转变和通知观察者。
37、使用Promises实现ajax请求(函数返回值是一个Promise对象)
思路:定义一个函数,在函数里面new Promise,并将其作为函数的返回值。Promise对象的唯一参数是一个函数fn,fn函数有两个参数分别是resolve,reject,在函数fn里面实现ajax,当请求成功调用resolve()将请求到的数据返回,当请求失败,调用reject(),将错误抛出。
用jQuery封装的ajax()方法实现ajax:
1)定义一个函数,在函数里面new Promise,并将其作为函数的返回值
2)Promise对象的唯一参数是一个函数fn,fn函数有两个参数分别是resolve,reject
3)在函数fn里面调用$.ajax()方法
4)当请求成功调用resolve(data)将请求到的数据返回;当请求失败,调用reject(data),将错误抛出
// (jquery)封装Promise对象和ajax过程
var jqGetAjaxPromise = function(param){
return new Promise(function(resolve, reject){
$.ajax({
url: param.url,
type: 'get',
data: param.data || '',
success: function(data){
resolve(data);
},
error: function(error){
reject(error)
}
});
});
};
// 调用示例
var p2 = jqGetAjaxPromise({
url: 'cross-domain1.txt'
});
p2.then(function(data){
console.log(data);
return jqGetAjaxPromise({
url:'cross-domain2.txt'
});
}).then(function(data2){
console.log(data2);
}).catch(function(err){
console.log(err);
});
用原生的js实现ajax,用Promise实现ajax
// 使用get实现ajax
function PromiseAjax (url) {
return new Promise( (resolve, reject) => {
// new一个XMLHTTPRequest对象
var xhr;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
}else{
xhr = new ActiveXObjext('Microsoft.XMLHTTP');
}
// 建立连接
xhr.open('GET', url, true)
// 发送请求
xhr.send()
// 监听状态变化
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200) {
resolve(this.responseText)
} else {
var resJson = { code: this.status, response: this.statusText }
reject(resJson);
}
}
}
})
}
// 使用Post实现ajax
function postJSON(url, data) {
return new Promise( (resolve, reject) => {
// new一个XMLHTTPRequest对象
var xhr;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
}else{
xhr = new ActiveXObjext('Microsoft.XMLHTTP');
}
// 建立连接
xhr.open("POST", url, true)
// 设置请求头,内容类型
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// 发送请求
xhr.send(JSON.stringify(data))
// 监听状态变化
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200) {
resolve(JSON.parse(this.responseText), this)
} else {
var resJson = { code: this.status, response: this.statusText }
reject(resJson, this)
}
}
}
})
}
Vue的diff实现原理????
Vue通过以下措施来提升diff的性能
(一)优化处理特殊场景
(1)、头部的同类型节点、尾部的同类型节点
这类节点更新前后位置没有发生变化,所以不用移动它们对应的DOM
(2)、头尾/尾头的同类型节点
这类节点位置很明确,不需要再花心思查找,直接移动DOM就好
处理了这些场景之后,一方面一些不需要做移动的DOM得到快速处理,另一方面待处理节点变少,缩小了后续操作的处理范围,性能也得到提升
“原地复用”是指Vue会尽可能复用DOM,尽可能不发生DOM的移动。Vue在判断更新前后指针是否指向同一个节点,其实不要求它们真实引用同一个DOM节点,实际上它仅判断指向的是否是同类节点(比如2个不同的div,在DOM上它们是不一样的,但是它们属于同类节点),如果是同类节点,那么Vue会直接复用DOM,这样的好处是不需要移动DOM。再看上面的实例,假如10个节点都是div,那么整个diff过程中就没有移动DOM的操作了。
从下载文档到渲染页面的过程中
1)浏览器会通过解析HTML文档来构建DOM树
2)解析CSS产生CSS render tree(不包括位置和大小属性)
3)javascript在代码解析的过程中,可能会修改生成的dom树、和css render tree,之后根据dom树和css render tree构建构建树(包括位置和大小属性),在这个过程中css会根据选择器匹配HTML元素。渲染树包括了每个元素的大小,边距等样式属性
渲染树中不包含隐藏元素及head元素等不可见元素。最后浏览器根据元素的坐标和大小来计算每个元素的位置,并绘制这些元素到页面上
虚拟DOM的定义(VDOM的定义)
1)vdom是树状结构,其节点为VNode
2)vnode和浏览器DOM中的Node一一对应,通过vnode的elm属性可以访问到对应的Node
3)vdom因为是纯粹的JS对象,所以操作它会很高效,但是vdom的变更最终会转换成DOM操作,为了实现高效的DOM操作,Vue中采用了diff算法,其是基于snabbdom改造过来的
Vue中的Virtual DOM
1) Virtual DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多
2)在 Vue.js 中,Virtual DOM 是用 VNode
这么一个 Class 去描述
3)实际上 Vue.js 中 Virtual DOM 是借鉴了一个开源库 snabbdom 的实现,然后加入了一些 Vue.js 特色的东西
4)vue中的VNode 是对真实 DOM 的一种抽象描述,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的。由于 VNode 只是用来映射到真实 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常轻量和简单的
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
fnScopeId: ?string; // functional scope id support
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}
为什么需要虚拟DOM(VDOM)
当用传统的源生api或jQuery去操作DOM时,浏览器会从构建DOM树从头到尾执行一遍流程。比如当你在一次操作时,需要更新10个DOM节点,理想状态是一次性构建完DOM树,再执行后续操作。但浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程。显然前面做的都是无用功,只有最后一次有效。同时浏览器的标准把DOM设计的很复杂,所以频繁操作DOM的代价很高,会出现页面卡顿,影响用户体验
VDOM就是为了解决这个浏览器性能问题而被设计出来的。Virtual DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多。例如前面的例子,假如一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,通知浏览器去执行绘制工作,这样可以避免大量的无谓的计算量
用js对象模拟DOM节点的好处是,页面的更新可以先全部反映在js对象上,操作内存中的js对象的速度显然要快多了。等更新完后,再将最终的js对象映射成真实的DOM,交由浏览器去绘制。
vue中的computed的实现原理---------需要建立数据依赖搜集,动态计算实现原理
1)问题:计算属性如何与属性建立依赖关系?属性发生变化又如何通知到计算属性重新计算?
如何建立依赖关系?----------利用 JavaScript 单线程原理和 Vue 的 Getter 设计,通过一个简单的发布订阅,就可以在一次计算属性求值的过程中收集到相关依赖
2)data 属性初始化 getter setter:通过Object.defineProperty()给data设置setter getter
// src/observer/index.js
// 这里开始转换 data 的 getter setter,原始值已存入到 __ob__ 属性中
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 判断是否处于依赖收集状态
if (Dep.target) {
// 建立依赖关系
dep.depend()
...
}
return value
},
set: function reactiveSetter (newVal) {
...
// 依赖发生变化,通知到计算属性重新计算
dep.notify()
}
})
3)computed属性初始化
// src/core/instance/state.js
// 初始化计算属性
function initComputed (vm: Component, computed: Object) {
...
// 遍历 computed 计算属性
for (const key in computed) {
...
// 创建 Watcher 实例
// create internal watcher for the computed property.
watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)
// 创建属性 vm.reversedMessage,并将提供的函数将用作属性 vm.reversedMessage 的 getter,
// 最终 computed 与 data 会一起混合到 vm 下,所以当 computed 与 data 存在重名属性时会抛出警告
defineComputed(vm, key, userDef)
...
}
}
export function defineComputed (target: any, key: string, userDef: Object | Function) {
...
// 创建 get set 方法
sharedPropertyDefinition.get = createComputedGetter(key)
sharedPropertyDefinition.set = noop
...
// 创建属性 vm.reversedMessage,并初始化 getter setter
Object.defineProperty(target, key, sharedPropertyDefinition)
}
function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
// watcher 暴露 evaluate 方法用于取值操作
watcher.evaluate()
}
// 同第1步,判断是否处于依赖收集状态
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
}
4)无论是属性还是计算属性,都会生成一个对应的 watcher 实例
5)Dep 的代码短小精悍,但却承担着非常重要的依赖收集环节
用js实现原生的Promise对象????(Promise实现原理)
Vue源码,JQuery源码
38、请使用任意一种mvvm框架演示如何实现双向绑定 angualr react vue
vue实现双向绑定的原理 https://blog.****.net/tangxiujiang/article/details/79594860
vue中组件: 点击打开链接 https://blog.****.net/tangxiujiang/article/details/79620542
vue中的内置组件: 点击打开链接 https://blog.****.net/tangxiujiang/article/details/80144195
Vuex的简单介绍:点击打开链接 https://blog.****.net/tangxiujiang/article/details/80645416
vue中的混入对象mixins:点击打开链接 https://blog.****.net/tangxiujiang/article/details/80644144
Vue开发环境搭建:点击打开链接 https://blog.****.net/tangxiujiang/article/details/79802639
vue中的生命周期lifesircle:点击打开链接 https://blog.****.net/tangxiujiang/article/details/79608841
vue中的集成指令:点击打开链接 https://blog.****.net/tangxiujiang/article/details/79635431
vm.$nextTick()的作用
将回调延迟到下次 DOM 更新循环之后执行。
在修改数据之后,可能数据还没有完全映射到DOM上,这个方法使得更新的数据全部映射到DOM上之后,调用回调函数,然后等待 DOM 更新。它跟全局方法 Vue.nextTick
一样,不同的是回调的 this
自动绑定到调用它的实例上
38、vue中data、props、computed、methods、watch他们的数据在页面的加载顺序
在生命周期函数里面定义的数据不具有响应式;
data:
props:
computed:computed是在el挂载成功之后执行(也就是HTML DOM加载成功之后马上执行)
methods:有一定的触发条件才执行,如click事件
watch:观察vue实例上的数据变动,然后执行一定的事件
computed methods watch的执行顺序
默认加载时:先computed在watch,不执行methods
触发某事件时:先method,在watch
加载顺序
props(对象 [成员是属性]) > methods (对象 [成员是函数] )> data (对象或函数----返回值为对象)> computed(对象 [成员是函数,有返回值] )> watch(对象 [成员是函数,无返回值])
加载时间:在beforeCreate和created之间
作用:
props:接受父组件传递过来的数据
methods:提供相对复杂的数据计算
data:定义以及初始化数据。最好是用于视图上展示的数据,否则最好定义在外面或者vm对象内(减少开支,提高性能);组件内只接受函数
computed:提供相对简单的数据计算(实时更新数据)
watch:当数据发生变化的时候,进行一些操作(提供新值和旧值)(观察数据,有变化执行一些方法)
data与computed的联系
data一般是定义想要在view中显示的数据;computed对数据进行复杂的操作转化(也有可能用在view中)
methods与computed的联系
相同点:可以把同一函数定义为一个方法而不是计算属性,两种方式最后的结果一样的
不同点:计算属性基于他们的依赖进行缓存的,只有相关依赖的值发生改变才会重新求值;而方法只要事件被触发就会再次执行该函数。如果你不希望有缓存,请用方法来代替。
watch与computed的联系
watch:观察某一特定的值,执行特定的函数,有时候会异步调用 API 返回相应的值,computed做不到这一点
computed:依赖变动,实时更新数据
38、Vue中实现window.localStorage的封装(vue中实现window.sessionStorage的封装)
点击打开链接 https://blog.****.net/tangxiujiang/article/details/80237914
38、Vue组件的通信方式----实现页面组件之间的通信
一、父子组件通信
1)父组件向子组件传递数据
在父组件的<template>中的标签通过v-bind绑定一个数据属性,然后在子组件的props选项里面定义该属性
// 子组件food.vue
<food @add="addFood" :food="selectedFood" ref="food"></food>
props: {
food: {
type: Object
}
}
// 父组件goods.vue
<food @add="addFood" :food="selectedFood" ref="food"></food>
data() {
return {
selectedFood: {}
};
}
2)子组件向父组件传递数据
在子组件里面通过$emit()函数向父组件传递数据,然后在父组件的页面引用子组件的标签通过v-on指令绑定与子组件同名的事件名
// 子组件food.vue
<cartcontrol @add="addFood" :food="food"></cartcontrol>
methods:{
addFood(target) {
this.$emit('add', target);
}
}
// 父组件goods.vue
<food @add="addFood" :food="selectedFood" ref="food"></food>
methods:{
addFood(target) {
this._drop(target);
}
}
二、非父子组件通信方式----传递数据
1)、可以用Vue.$emit自定义事件来解决----Vue的eventBus事件
// 1、创建 EventBus
//新建一个 js 文件,写下如下代码就创建好了一个 eventbus
import Vue from 'vue'
export default new Vue;
// 在 main.js 中导入 eventbus ,然后将它挂载到 vue 的原型上,这样就可以全局调用了
import bus from './utils/eventBus'
Vue.prototype.bus = bus;
// 2、发送事件,在触发事件的地方发送事件
// $emit(),里面需要一个string 类型的事件名,我这里是用的当前路由的 path 作为事件名
this.bus.$emit(this.$route.path);
// 3、接收事件,事件已经发送,接下来只需要在需要接收事件的地方接
// 收这个事件,然后对事件进行响应就可以了
this.bus.$on(this.$route.path,()=>{
this.getData(); //接收事件的时候同样需要一个事件名,
//然后是一个函数来进行事件响应,我这里是调用了下获取数据的接口
});
var bus = new Vue() //新建eventBus
// 组件A,发送事件
bus.$emit('id-selected', 1)
// 组件B接收事件,对事件进行响应
bus.$on('id-selected', function (id) {
console.log(id)
})
问题:重复触发事件问题
a)事件会重复触发,而且每次切换过路由后,事件执行次数就会加一,假如用户非常频繁的切换页面,那事件执行次数会越来越多
原因:事件是全局的,它并不会随着组件的销毁而自动注销,需要手动调用注销方法来注销。
解决方法:在组件的 beforeDestroy ,或 destroy 生命周期中执行注销方法,手动注销事件
beforeDestroy() {
//组件销毁前需要解绑事件,否则会出现重复触发事件的问题
this.bus.$off(this.$route.path);
}
https://www.jianshu.com/p/fde85549e3b0 例子1
https://blog.****.net/zgh0711/article/details/80284830 例子2
2)使用Vuex
1)vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
2)把组件的共享状态抽取出来,以一个全局单例模式管理
3)通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,代码变得更结构化、易维护
4)Vuex是专门用来解决多个视图依赖于同一状态,来自不同视图的行为需要变更同一状态的问题
vuex有五个核心概念,其中state和getters主要是用于数据的存储与输出,而mutations和actions是用于提交事件并修改state中的数据,module模块把数据按照组件之间的功能联系来分离,并把它们拆分到一个个小文件中,避免了vuex文件太大
// 这是vuex总的管理文件,将各模块统一在一起,从而将每一个分支都连接到vuex这个总的状态树上
// 引入vue
import Vue from 'vue'
// 引入vuex
import Vuex from 'vuex'
// 引入应用状态管理
import goodshandleStore from './goodshandle'
import orderStore from './order'
import addressStore from './address'
import shopcarStore from './shopcar'
import merchantStore from './merchant'
import couponStore from './coupon'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
handle: goodshandleStore,
order: orderStore,
address: addressStore,
shopcar: shopcarStore,
merchant: merchantStore,
coupon: couponStore
}
})
vue中的computed选项的原理:详情 https://blog.****.net/tangxiujiang/article/details/81170490
vue-router路由懒加载-----懒加载也叫作延迟加载,在需要的时候在加载
为什么需要懒加载?
像vue这种单页面应用,如果没有应用懒加载,运用webpack打包后的文件将会异常的大,造成进入首页时,需要加载的内容过多,时间过长,会出现长时间的白屏,即使做了loading也是不利于用户体验,而运用懒加载则可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力,减少首页加载用时
简单的说就是:进入首页,不用一次加载过多资源,造成用时过长
路由懒加载----就是在创建路由的时候在component属性上是一个函数,然后函数返回通过require引进来的组件
路由非懒加载:在页面顶部把路由用到的所有组件引进来,然后在创建路由的时候component属性就是引进来的组件名
问题:用了懒加载后打完包直接运行那个index.html会报错,报文件引用错误其实是打包时候路径配置有点问题修改下就好了
找到build下面的webpack.prod.conf.js 添加 publicPath:"./"
什么是vue-loader?
vue-router、vue-loader、vue-cli,vue-source,vuex的解析:打开链接
https://blog.****.net/tangxiujiang/article/details/76582955
实现调用delayHello之后,隔1秒打印5
var hello = function(a){
console.log(a);
};
function delay(func,t){
return function(n){
setTimeout(function(){
func(n);
},t);
}
}
var delayHello = delay(hello,1000);
delayHello(5);
loader完之后,WebPack输出什么?
本质上来说,loader 就是一个 node 模块,在 webpack 的定义中,loader 导出一个函数,loader 会在转换源模块(resource)的时候调用该函数。在这个函数内部,我们可以通过传入this上下文给 Loader API 来使用它们, loader 的功能:把源模块转换成通用模块
webpack中的loader,plugin是什么?
答:loader是用来告诉webpack如何转化处理某一类文件,并且引入到打包出的文件中,loader一定对应的是文件。
plugin是用来自定义webpack打包过程中的方式,一个插件是含有apply方法的一个对象,通过这个方法,可以参与到整个
webpack打包的各个流程(生命周期)
loader的作用------处理源文件,一次处理一个
1、实现对不同格式文件的处理,比如说将scss/styl/less转换为css,或者typescript转化为js
2、转换这些文件,从而使其能够被添加到依赖图中
loader是webpack最重要的部分之一,通过使用不同的Loader,我们能够调用外部的脚本或者工具,实现对不同格式文件的处理,loader需要在webpack.config.js里边单独用module进行配置
webpack中几个常用的loader:
babel-loader: 让下一代的js文件转换成现代浏览器能够支持的JS文件
css-loader,style-loader:两个建议配合使用,用来解析css文件,能够解释@import,url()
less-loader:解析.less文件
file-loader: 生成的文件名就是文件内容的MD5哈希值,并会保留所引用资源的源
url-loader: 功能类似 file-loader,但是文件大小低于指定的限制时,可以返回一个DataURL事实上,在使用less,scss,stylus这些的时候,npm会提示你差什么插件,差什么,你就安上就行了
babel的工作原理(babel将ES6转成ES5之类的原理)---babel就是一个编译器
1)解析:将代码字符串解析成抽象语法树
2)变换:对抽象语法树进行变换操作
3)在建:在根据变换后的抽象语法树,生成字符串代码
webpack配置问题------如何自动生成webpack配置
webpack-cli,vue-cli等
webpack开发问题------webpack-dev-server和http服务器如nginx有什么区别?
webpack-dev-server使用内存来存储webpack开发环境下的打包文件,并且可以使用模块热更新,他比传统的http服务对开发更加有效
webpack-dev-server就是express+hot**组合,增加一些配置而成的
什么是webpack热更新?
1)模块热更新,是webpack的一个功能,他可以使得代码修改后,不用刷新浏览器,页面就可以更新
2)webpack-hot-middleware中间件是webpack的一个plugin,通常结合webpack-dev-middleware一起使用。借助它可以实现浏览器的无刷新更新(热更新),即webpack里的HMR(Hot Module Replacement)
webpack热更新原理
使用层面:
1)webpack加入webpack-hot-middleware后,内存中的页面将包含HMR相关js,加载页面后,Network栏可以看到有__webpack_hmr请求,如下图:
2)__webpack_hmr是一个type为eventsource的请求, 从Time栏可以看出:默认情况下,服务器每十秒推送一条信息到浏览器
3)如果此时关闭开发服务器,浏览器由于重连机制,将持续抛出类似GET http://www.test.com/__webpack_hmr 502 (Bad Gateway)
这样的错误。重新启动开发服务器后,重连将会成功,此时便会刷新页面
使用EventSource技术实现热更新
1)SSE(Server-Sent Event,服务器发送事件)是围绕只读Comet交互推出的API或者模式
2)SSE API用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据
3)服务器响应的MIME类型必须是text/event-stream,而且是浏览器中的javascript API能解析格式输出
4)SSE支持短轮询、长轮询、HTTP流,能在断开连接时自动确定何时重新连接
需要一个高效的节省资源的方式去获取服务器信息,一旦服务器资源有更新,能够及时地通知到客户端,从而实时地反馈到用户界面上,SSE API中的EventSource对象就是这样的技术,它本质上还是HTTP,通过response流实时推送服务器信息到客户端
var source = new EventSource('event.php')
注意:传进的url须与创建对象的页面同源
服务端实现event.php接口,需要返回类型为 text/event-stream的响应头
var http = require('http');
http.createServer(function(req,res){
if(req.url === 'event.php'){
res.writeHead(200,{
'Content-Type': 'text/event-stream', //MIME是text/event-stream
'Cache-Control': 'no-cache', //避免缓存
'Connection': 'keep-alive' //能够发送多个response
});
setInterval(function(){
res.write('data: ' + +new Date() + '\n'); //发送数据时,务必保证服务器推送的数据以 data:开始,以\n结束
}, 1000); //这是约定俗成的
}
}).listen(8888);
服务器每隔1s主动向客户端发送当前时间戳,为了接受这个信息,客户端需要监听服务器
es.onmessage = function(e){
console.log(e.data); // 打印服务器推送的信息
}