继承
//父类
function Person(name) {
this.name = name;
this.age = 10;
this.sum = function () {
console.log(this.name);
}
}
//原型链继承
function per() {
this.name = "ker";
}
per.prototype = new Person()
var per1 = new per();
console.log(per1.age);
//组合继承
function subtype(name) {
Person.call(this, name);
}
subtype.prototype = new Person();
var sub = new subtype('sss');
console.log(sub);
手写promise
//JS实现pormise
function myPromise(constructor) {
let self = this;
self.status = "pending" //定义状态改变前的初始状态
self.value = undefined;//定义状态为resolved的时候的状态
self.reason = undefined;//定义状态为rejected的时候的状态
function resolve(value) {
//两个==="pending",保证了了状态的改变是不不可逆的
if (self.status === "pending") {
self.value = value;
self.status = "resolved";
}
}
function reject(reason) {
//两个==="pending",保证了了状态的改变是不不可逆的
if (self.status === "pending") {
self.reason = reason;
self.status = "rejected";
}
}
//捕获构造异常
try {
constructor(resolve, reject);
} catch (e) {
reject(e);
}
}
myPromise.prototype.then = function (onFullfilled, onRejected) {
let self = this;
switch (self.status) {
case "resolved": onFullfilled(self.value); break;
case "rejected": onRejected(self.reason); break;
default:
}
}
// 测试
var p = new myPromise(function (resolve, reject) { resolve('pormise') });
p.then(function (x) { console.log(x) })
算法
let eop = new Proxy({}, {
i: 1,
get: function () {
return () => this.i++;
}
})
if (eop == 1 && eop == 2 && eop == 3) {
console.log(123456)
}
//创建一个可关闭的代理
var revocable = Proxy.revocable({}, {
get: function (target, name) {
if (target.hasOwnProperty(name)) {
return target[name]
} else {
console.log("没找到你要的属性")
return
}
},
set: function (target, name, value) {
target[name] = value;
},
deleteProperty: function (target, name) {
delete target[name]
console.log(target, "删除属性", name)
}
})
var canf = revocable.proxy;
canf.foo = "1";
canf.a = "1";
delete canf.foo
revocable.revoke();
function foore() {
let poe = 2231;
return function ser() {
console.log(poe)
}
}
let funsae = foore();
funsae();
//算法
// 2、写一个归并排序
function update(letft, right) {
let arr = []
while (letft.length && right.length) {
if (letft[0] < right[0]) {
arr.push(letft.shift())
} else {
arr.push(right.shift())
}
}
return [...arr, ...letft, ...right]
}
function makesotr(arry) {
const half = arry.length / 2;
if (arry.length < 2) {
return arry
}
const left = arry.splice(0, half);
return update(makesotr(left), makesotr(arry))
}
var arryst = [1, 6, 4, 3, 2, 5, 7, 8, 10]
var data = arryst.sort((a, b) => { return a - b })//
console.log(data)
console.log(makesotr(arryst))
//随机排序
var arrqq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arrqq.sort(function () {
return Math.random() - 0.5;
})
console.log(arrqq);
//js实现反转链表
var reverseList = function (head) {
let prev = null;
let curr = head;
while (curr !== null) {
let cnext = curr.next;
curr.next = prev === null ? null : prev
prev = curr;
curr = cnext;
}
return prev
};
//深度优先遍历
let deepTraversal3 = (node) => {
let stack = []
let nodes = []
if (node) {
// 推入当前处理的node
stack.push(node)
while (stack.length) {
let item = stack.pop()
let children = item.children
nodes.push(item)
// node = [] stack = [parent]
// node = [parent] stack = [child3,child2,child1]
// node = [parent, child1] stack = [child3,child2,child1-2,child1-1]·
for (let i = children.length - 1; i >= 0; i--) {
stack.push(children[i])
}
}
}
return nodes
}
//爬楼梯
// 有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。要求用程序来求出一共有多少种走法?
function getClimbingWays(n) {
if (n < 1) {
return 0;
}
if (n === 1) {
return 1;
}
if (n === 2) {
return 2;
}
return getClimbingWays(n - 1) + getClimbingWays(n - 2);
}
手写call
Function.prototype.call2 = function (context) {
var context = context || window
context.fn = this;
var agrs = [];
for (let i = 0; i < arguments.length; i++) {
//不这么做的话 字符串的引号会被自动去掉 变成了变量 导致报错
args.push("arguments[" + i + "]");
}
//join() 方法用于把数组中的所有元素放入一个字符串。
args = args.join(",");
//相当于执行了context.fn(arguments[1], arguments[2]);
// eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
var result = eval("context.fn(" + args + ")");
delete context.fn;
return result; //因为有可能this函数会有返回值return
}
手写pomise
// 简易版本的promise
// 第一步: 列出三大块 this.then resolve/reject fn(resolve,reject)
// 第二步: this.then负责注册所有的函数 resolve/reject负责执行所有的函数
// 第三步: 在resolve/reject里面要加上setTimeout 防止还没进行then注册 就直接执行resolve了
// 第四步: resolve/reject里面要返回this 这样就可以链式调用了
// 第五步: 三个状态的管理 pending fulfilled rejected
// *****promise的链式调用 在then里面return一个promise 这样才能then里面加上异步函数
// 加上了catch
function PromiseM(fn) {
var value = null;
var callbacks = [];
//加入状态 为了解决在Promise异步操作成功之后调用的then注册的回调不会执行的问题
var state = 'pending';
var _this = this;
//注册所有的回调函数
this.then = function (fulfilled, rejected) {
//如果想链式promise 那就要在这边return一个new Promise
return new PromiseM(function (resolv, rejec) {
//异常处理
try {
if (state == 'pending') {
callbacks.push(fulfilled);
//实现链式调用
return;
}
if (state == 'fulfilled') {
var data = fulfilled(value);
//为了能让两个promise连接起来
resolv(data);
return;
}
if (state == 'rejected') {
var data = rejected(value);
//为了能让两个promise连接起来
resolv(data);
return;
}
} catch (e) {
_this.catch(e);
}
});
}
//执行所有的回调函数
function resolve(valueNew) {
value = valueNew;
state = 'fulfilled';
execute();
}
//执行所有的回调函数
function reject(valueNew) {
value = valueNew;
state = 'rejected';
execute();
}
function execute() {
//加入延时机制 防止promise里面有同步函数 导致resolve先执行 then还没注册上函数
setTimeout(function () {
callbacks.forEach(function (cb) {
value = cb(value);
});
}, 0);
}
this.catch = function (e) {
console.log(JSON.stringify(e));
}
//经典 实现异步回调
fn(resolve, reject);
}
手写apply
Function.prototype.apply2 = function (context, arr) {
var context = context || window; //因为传进来的context有可能是null
context.fn = this;
var args = [];
var params = arr || [];
for (var i = 0; i < params.length; i++) {
args.push("params[" + i + "]"); //不这么做的话 字符串的引号会被自动去掉 变成了变量 导致报错
}
//join() 方法用于把数组中的所有元素放入一个字符串。
args = args.join(",");
//相当于执行了context.fn(arguments[1], arguments[2]);
// eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
var result = eval("context.fn(" + args + ")");
delete context.fn;
return result; //因为有可能this函数会有返回值return
}
手写apply
Function.prototype.bind2 = function (context) {
var _this = this;
var argsParent = Array.prototype.slice.call(arguments, 1);
return function () {
var args = argsParent.concat(Array.prototype.slice.call(arguments)); //转化成数组
_this.apply(context, args);
};
}
手写ajax
function createXMLHTTPRequest() {
//1.创建XMLHttpRequest对象
//这是XMLHttpReuquest对象无部使用中最复杂的一步
//需要针对IE和其他类型的浏览器建立这个对象的不同方式写不同的代码
var xmlHttpRequest;
if (window.XMLHttpRequest) {
//针对FireFox,Mozillar,Opera,Safari,IE7,IE8
xmlHttpRequest = new XMLHttpRequest();
//针对某些特定版本的mozillar浏览器的BUG进行修正
if (xmlHttpRequest.overrideMimeType) {
xmlHttpRequest.overrideMimeType("text/xml");
}
} else if (window.ActiveXObject) {
//针对IE6,IE5.5,IE5
//两个可以用于创建XMLHTTPRequest对象的控件名称,保存在一个js的数组中
//排在前面的版本较新
var activexName = [ "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" ];
for ( var i = 0; i < activexName.length; i++) {
try {
//取出一个控件名进行创建,如果创建成功就终止循环
//如果创建失败,回抛出异常,然后可以继续循环,继续尝试创建
xmlHttpRequest = new ActiveXObject(activexName[i]);
if(xmlHttpRequest){
break;
}
} catch (e) {
}
}
}
return xmlHttpRequest;
}
GET请求
function get(){
var req = createXMLHTTPRequest();
if(req){
req.open("GET", "http://test.com/?keywords=手机", true);
req.onreadystatechange = function(){
if(req.readyState == 4){
if(req.status == 200){
alert("success");
}else{
alert("error");
}
}
}
req.send(null);
}
}
POST请求
function post(){
var req = createXMLHTTPRequest();
if(req){
req.open("POST", "http://test.com/", true);
req.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=gbk;");
req.send("keywords=手机");
req.onreadystatechange = function(){
if(req.readyState == 4){
if(req.status == 200){
alert("success");
}else{
alert("error");
}
}
}
}
}
Async/await和Promise那个性能更好
观察下面代码, 假设b()返回一个promise
const a = () => {
b().then(() => c())
}
当调用a()函数时,这些事情同步发生,b()函数产生一个promise对象,
调用then方法,Promise会在将来的某个时刻resolve,
也就是把then里的回调函数添加到回调链。
(如果这一块不太明白,可以仔细学习promise,或者读一读promise源码
并尝试写一写,相信你更通透),
这样,a()函数就执行完了,在这个过程中,a()函数并不会暂停,
因此在异步函数resolve的时候,a()的作用域已经不存在了,
那要如何生成包含a()的堆栈信息呢? 为了解决这个问题,
JavaScripts引擎要做一些额外的工作;它会及时记录并保存堆栈信息。
对于V8引擎来说,这些堆栈信息随着Promise在Promise链中传递,
这样c()函数在需要的时候也能获取堆栈信息。但是这无疑造成了额外
的开销,会降低性能;保存堆栈信息会占用额外的内存。
我们可以用Async/await来实现一下
const a = () => {
await b()
c()
}
使用await的时候,无需存储堆栈信息,因为存储b()到a()的指针的足够
了。当b()函数执行的时候,a()函数被暂停了,因此a()函数的作用域还
在内存可以访问。如果b()抛出一个错误,堆栈通过指针迅速生成。如果c()函数抛出一个错误,
堆栈信息也可以像同步函数一样生成,因为c()是在a()中执行的。
不论是b()还是c(),我们都不需要去存储堆栈信息,因为堆栈信息可以
在需要的时候立即生成。而存储指针,显然比存储堆栈更加节省内存
结论
能使用async多使用async
深拷贝简易版
let obj = '1'
var a = JSON.parse(JSON.stringify(obj))
深拷贝加强版
function clone(obj, hash = new WeakMap()) {
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Date) return new Date(obj);
if (obj === null || typeof obj !== 'object') {
return obj;
}
//hash.has根据是否有key关联对象返回一个Boolean值。
if (hash.has(obj)) {
//hash.get返回key关联对象, 或者 undefined(没有key关联对象时)。
return hash.get(obj)
}
//如果obj是数组那么obj.constructor是[Function:Array]
//如果obj是对象那么obj.constructor是[Function:Object]
let t = new obj.constructor();
//在 hash.set中设置一组key关联对象,返回这个 WeakMap对象
hash.set(obj, t);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
t[key] = clone(obj[key], hash);
}
}
return t
}
var wanset = {
obj: [1, 23, 4], obk: 'string', owan: function () {
return 1234
}
};
let seteqw = clone(wanset);
console.log('deepclone:', seteqw)
防抖返回值版本
一般应用场景:按钮提交,搜索框联想
function debouncereturn(func, wait, immediate) {
// 创建一个标记用来存放定时器的返回值,和函数的返回值
let timeout, result;
return function () {
const context = this;
const args = arguments;
// 每次当触发的时候,把前一个定时器清除
if (timeout) clearTimeout(timeout);
// 然后设置一个新的定时器,
//immediate为true时候有返回值
if (immediate) {
const callnow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
//第一次触发的时候立即执行
if (callnow) result = func.apply(context, args);
}
//immediate为flase时候没有返回值
else {
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
}
return result;
}
}
节流--定时器版本
一般应用场景:鼠标滚动,拖拽场景,缩放场景
// 当触发事件的时候,我们设置一个定时器,再触发事件的时候,
// 如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。
function throttle(func, wait) {
let timeout;
return function () {
const context = this;
const args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, arguments);
}, wait);
}
}
}
函数柯里化
function add(a) {
return function (b) {
return function (c) {
return a + b + c;
}
}
}
let obj=add(1)(2)(3);
console.log(obj)
链式调用
function class1() {
console.log("初始化")
this.method = (pramt) => {
console.log(pramt)
console.log(this)
return this
}
}
let c1 = new class1()
c1.method('第一次调用').method('第二次调用').method('第三次调用');
串行并行理解
//串行
ajax('first');
ajax('second');
ajax('third');
//需要按顺序来执行怎么办?
let promise = new Promise((resolve, reject) => {
ajax('first').success(function (res) {
resolve(res);
})
})
promise.then(res => {
return new Promise((resovle, reject) => {
ajax('second').success(function (res) {
resolve(res)
})
})
}).then(res => {
return new Promise((resovle, reject) => {
ajax('second').success(function (res) {
resolve(res)
})
})
}).then(res => {
// 串行完毕你要做的xxx可以开始了
})
//并行,等所有任务完成在进行下一步
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(1);
}, 10000)
});
let promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 9000)
});
let promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 11000)
});
let promiseAll = Promise.all([promise1, promise2, promise3]);
promiseAll.then(res => {
console.log(res);
}, err => {
console.log(err)
})
首屏优化
第三方插件按需引入安装 babel-Plugin-component修改.babelrc文件gzip压缩图片懒加载 安装 vue-lazyload插件imgsrc改成 v-lazy路由懒加载 import方式图片压缩 css合并压缩 js合并压缩 babel 进行转码兼容提取公共代码
import和require区别
import 是编译时,只选择import的接口进行编译,性能比require好require是把目标文件运行然后拿到结果赋值。require是把目标文件运行然后拿到结果赋值
ES6 模块化与 CommonJS 模块化区别
// 导出 a.js
/** 写法一 **/
var name = 'sheep'
function getSheep() {
name = 'hourse'
}
export {getSheep}
// 引入 b.js
import {getSheep} from './a.js'
/** 写法二 **/
var name = 'sheep'
export function getSheep() {
name = 'hourse'
}
// 引入 b.js
import {getSheep} from './a.js'
/// import 与 export defalut
//export 可以有多个,export default 仅有一个
// 导出 a.js
let obj = {
name: 'hello',
getName: function (){
return this.name
}
export default obj
// 引入 b.js
import obj from './a.js'
///CommonJS 模块化
//一、 require 与 module.exports
//require 在 ES6(bable将import转化为require) 和 CommonJS 中都支持
// 导出 a.js
let obj = {
name: 'hello',
getName: function (){
return this.name
}
module.exports = obj
// 引入 b.js
let obj = require('./a.js')
http的状态码中,499是什么?
499对应的是 “client has closed connection”,
客户端请求等待链接已经关闭,这很有可能是因为服务器端处理的时间过长,
客户端等得“不耐烦”了。还有一种原因是两次提交post过快就会出现499。
解决方法:前端将timeout最大等待时间设置大一些,nginx上配置proxy_ignore_client_abort on;
微任务,宏任务
setTimeout(() => {
console.log('内层宏任务1')
}, 0)
console.log('外层宏任务1')
new Promise((resolve, reject) => {
console.log('外层宏任务2')
resolve(111)
}).then(() => {
console.log('微任务1');
}).then(() => {
console.log('微任务2');
})
// 输出顺序---外层宏任务1>外层宏任务2>微任务1>微任务2>内层宏任务1
HTTP1和HTTP2
HTTP1.0 浏览器每次都需要与服务器建立起一个TCP连接,服务器处理完了
立即断开连接,服务器不记录每个客户端过去的请求也没有状态
HTTP1.1每次一次处理1个请求,请求多了会变慢。持久连接,
增加Host字段、支持断点传输等(把文件分成几部分)
默认使用Connection:keep-alive,避免建立和释放的开销,
不允许并行请求
HTTP2使用二进制超文本传输,消息头进行压缩,
多路复用,可以并行,不会堵塞,
HTTP2可以让服务器主动相应到客户端
HTTP缓存策略
客户端 HTTP请求 => 缓存数据库()=> 服务器。
如果缓存数据库里面有数据,就不会请求服务器,直接返回到客户端
缓存规则信息包含在响应header中。
对于强制缓存来说,响应header中会有两个字段来标明失效规则(Expires:缓存到期时间 / Cache - Control)
Cache - Control:
private: 客户端可以缓存(默认值)
public: 客户端和代理服务器都可缓存(前端的同学,可以认为public和private是一样的)
max - age=xxx: 缓存的内容将在 xxx 秒后失效
no - cache: 需要使用对比缓存来验证缓存数据(后面介绍)
no - store: 所有内容都不会缓存,强制缓存,对比缓存都不会触发(对于前端开发来说,缓存越多越好,so…基本上和它说886)
HTTP状态码
状态码是由3位数组成,第一个数字定义了响应的类别,且有五种可能取值:
1xx:指示信息–表示请求已接收,继续处理。
2xx:成功–表示请求已被成功接收、理解、接受。
3xx:重定向–要完成请求必须进行更进一步的操作。
4xx:客户端错误–请求有语法错误或请求无法实现。
5xx:服务器端错误–服务器未能实现合法的请求。
平时遇到比较常见的状态码有:200, 204, 301, 302, 304, 400, 401, 403, 404, 422, 500。
200 OK 表示客户端请求成功
204 No Content 成功,但不返回任何实体的主体部分
206 Partial Content 成功执行了一个范围(Range)请求
301 Moved Permanently 永久性重定向,响应报文的Location首部应该有该资源的新URL
302 Found 临时性重定向,响应报文的Location首部给出的URL用来临时定位资源
303 See Other 请求的资源存在着另一个URI,客户端应使用GET方法定向获取请求的资源
304 Not Modified 服务器内容没有更新,可以直接读取浏览器缓存
400 Bad Request 表示客户端请求有语法错误,不能被服务器所理解
401 Unauthonzed 表示请求未经授权,该状态代码必须与 WWW-Authenticate 报头域一起使用
403 Forbidden 表示服务器收到请求,但是拒绝提供服务,通常会在响应正文中给出不提供服务的原因
404 Not Found 请求的资源不存在,例如,输入了错误的URL
500 Internel Server Error 表示服务器发生不可预期的错误,导致无法完成客户端的请求
503 Service Unavailable 表示服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常
SEO
使用prerender。npm install prerender-spa-plugin --save
const path = require('path');
module.exports = {
configureWebpack: config => {
if (process.env.NODE_ENV !== 'production') return;
return {
plugins: [
new PrerenderSPAPlugin({
// 生成文件的路径,也可以与webpakc打包的一致。
// 下面这句话非常重要!!!
// 这个目录只能有一级,如果目录层次大于一级,在生成的时候不会有任何错误提示,在预渲染的时候只会卡着不动。
staticDir: path.join(__dirname,'dist'),
// 对应自己的路由文件,比如a有参数,就需要写成 /a/param1。
routes: ['/', '/product','/about'],
// 这个很重要,如果没有配置这段,也不会进行预编译
renderer: new Renderer({
inject: {
foo: 'bar'
},
headless: false,
// 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
renderAfterDocumentEvent: 'render-event'
})
}),
],
};
}
}
在main.js中增加
new Vue({
router,
store,
render: h => h(App),
mounted() {
document.dispatchEvent(new Event('render-event'))
}
}).$mount('#app')
但是回答prerender,面试官肯定会问你,
如果不用prerender,让你直接去实现,好的,请看下面的第二个答案。
先去 https://www.baidu.com/robots.txt 找出常见的爬虫,
然后在nginx上判断来访问页面用户的User-Agent是否是爬虫,如果是爬虫,
就用nginx方向代理到我们自己用nodejs + puppeteer实现的爬虫服务器上,
然后用你的爬虫服务器爬自己的前后端分离的前端项目页面,增加扒页面的接收延时,
保证异步渲染的接口数据返回,最后得到了页面的数据,返还给来访问的爬虫即可。
XSS:跨站脚本攻击
XSS:跨站脚本攻击
攻击者想尽一切办法将可以执行的代码插入网页
存储型
场景:见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。
反射型
与存储型的区别在于,存储型的恶意代码存储在数据库中,反射型的恶意代码在URL上
通过 URL 传递参数的功能,如网站搜索、跳转等。
Dom 型(浏览器端)
DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞
,而其他两种 XSS 都属于服务端的安全漏洞。
场景:通过 URL 传递参数的功能,如网站搜索、跳转等。
CSRF:跨站请求伪造
攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。 利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证, 达到冒充用户对被攻击的网站执行某项操作的目的。
隐藏页面中的某个元素的方法有哪些?
display: none;HTML5 新增属性,<div hidden>
缩放 transform: scale(0); height: 0;
设置 宽高为0,字体大小为0:
设置透明度为0
层级覆盖,z-index 属性
css如何开启硬件加速
css3 filter(滤镜) opacity(不透明度) transform(移动) 这3个属性会开启硬件加速
//尽量脱离文档流,这样会避免重流和重绘
@keyframes xxx ---创建动画
animation :xxxx --动画时间各种属性设置
calc(100%-200px) --计算属性
如何让(a == 1 && a == 2 && a == 3) 的值为true
// 利用数据劫持
let i = 1;
let eop = new Proxy({}, {
i: 1,
get: function () {
return () => this.i++;
}
})
if (eop == 1 && eop == 2 && eop == 3) {
console.log(123456)
}
JS执行上下文
首次运行JavaScript代码的时候,会创建一个全局执行的上下文并Push到当前的执行栈中
每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push当前执行栈的栈顶。
当栈顶的函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,
上下文的控制权将移动到当前执行栈的下一个执行上下文。
作用域链
作用域链就是从当前作用域开始一层一层向上寻找某个变量, 直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链。
让JS脚本异步加载方式2种
<script> 标签中增加 async(html5) 或者 defer(html4) 属性,脚本就会异步加载。
<script src="../XXX.js" defer>
defer 要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成)
在window.onload 之前执行;
async 一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
如果有多个 defer 脚本,会按照它们在页面出现的顺序加载
多个 async 脚本不能保证加载顺序
页面中不使用http缓存机制配置
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
js堵塞问题
css加载会阻塞dom渲染,css加载会堵塞后面的js语句执行 对css进行压缩 合理使用缓存 将多个css合并,或者写内联样式(内联样式不缓存) 或者使用定时器来让js线程空闲得以渲染或者使用alert语句将执行权让给gui渲染线程
teoprget和currentTarget具体指向
teoprget: 触发事件的某个具体对象。(子元素)
currentTarget:绑定事件的对象(父元素)
<div id='a'><a></a></div> 在div上绑定一个点击事件
a.addEventListener("click",function (event)
{console.log(event.target)输出子元素a||||console.log(event.currentTarget)输出父元素div })
webpack中的treeShaking
tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码通过 package.json 的 "sideEffects" 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 "pure(纯的 ES2015 模块)",由此可以安全地删除文件中未使用的部分。
webpack中的loader和plugin的区别
//什么是loader
loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,
诸如编译、压缩等,最终一起打包到指定的文件中
处理一个文件可以使用多个loader,loader的执行顺序和配置中的顺序是相反的,
即最后一个loader最先执行,第一个loader最后执行第一个执行的loader接收源文件内容作为参数,
其它loader接收前一个执行的loader的返回值作为参数,最后执行的loader会返回此模块的JavaScript源码
//什么是plugin
在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,
在合适的时机通过webpack提供的API改变输出结果。
//loader和plugin的区别
对于loader,它是一个转换器,将A文件进行编译形成B文件,这里操作的是文件,
比如将A.scss转换为A.css,单纯的文件转换过程plugin是一个扩展器,它丰富了webpack本身,
针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,
而是基于事件机制工作,会监听webpack打包过程中的某些节点,
执行广泛的任务