前端及开发文章及欣赏

认识 ESLint 和 Prettier

seo达人

ESLint

先说是什么:ESLint 是一个检查代码质量与风格的工具,配置一套规则,他就能检查出你代码中不符合规则的地方,部分问题支持自动修复。


使用这么一套规则有什么用呢?如果单人开发的话倒是没什么了,但是一个团队若是存在两种风格,那格式化之后处理代码冲突就真的要命了,统一的代码风格真的很重要!


(其实以前自己做一个项目的时候,公司电脑和家庭电脑的代码风格配置不一样,在家加班的时候也经常顺手格式化了,这么循环了几次不同的风格,导致 diff 极其混乱

JavaScript中的Event Loop(事件循环)机制

seo达人

事件循环

JavaScript是单线程,非阻塞的

浏览器的事件循环


执行栈和事件队列

宏任务和微任务

node环境下的事件循环


和浏览器环境有何不同

事件循环模型

宏任务和微任务

经典题目分析

1. JavaScript是单线程,非阻塞的

单线程:


JavaScript的主要用途是与用户互动,以及操作DOM。如果它是多线程的会有很多复杂的问题要处理,比如有两个线程同时操作DOM,一个线程删除了当前的DOM节点,一个线程是要操作当前的DOM阶段,最后以哪个线程的操作为准?为了避免这种,所以JS是单线程的。即使H5提出了web worker标准,它有很多限制,受主线程控制,是主线程的子线程。


非阻塞:通过 event loop 实现。


2. 浏览器的事件循环

执行栈和事件队列

为了更好地理解Event Loop,请看下图(转引自Philip Roberts的演讲 《Help, I'm stuck in an event-loop》)

Help, I'm stuck in an event-loop


执行栈: 同步代码的执行,按照顺序添加到执行栈中


function a() {

   b();

   console.log('a');

}

function b() {

   console.log('b')

}

a();

我们可以通过使用 Loupe(Loupe是一种可视化工具,可以帮助您了解JavaScript的调用堆栈/事件循环/回调队列如何相互影响)工具来了解上面代码的执行情况。


调用情况


执行函数 a()先入栈

a()中先执行函数 b() 函数b() 入栈

执行函数b(), console.log('b') 入栈

输出 b, console.log('b')出栈

函数b() 执行完成,出栈

console.log('a') 入栈,执行,输出 a, 出栈

函数a 执行完成,出栈。

事件队列: 异步代码的执行,遇到异步事件不会等待它返回结果,而是将这个事件挂起,继续执行执行栈中的其他任务。当异步事件返回结果,将它放到事件队列中,被放入事件队列不会立刻执行起回调,而是等待当前执行栈中所有任务都执行完毕,主线程空闲状态,主线程会去查找事件队列中是否有任务,如果有,则取出排在第一位的事件,并把这个事件对应的回调放到执行栈中,然后执行其中的同步代码。


我们再上面代码的基础上添加异步事件,


function a() {

   b();

   console.log('a');

}

function b() {

   console.log('b')

   setTimeout(function() {

       console.log('c');

   }, 2000)

}

a();

此时的执行过程如下

img


我们同时再加上点击事件看一下运行的过程


$.on('button', 'click', function onClick() {

   setTimeout(function timer() {

       console.log('You clicked the button!');    

   }, 2000);

});


console.log("Hi!");


setTimeout(function timeout() {

   console.log("Click the button!");

}, 5000);


console.log("Welcome to loupe.");

img


简单用下面的图进行一下总结


执行栈和事件队列


宏任务和微任务

为什么要引入微任务,只有一种类型的任务不行么?


页面渲染事件,各种IO的完成事件等随时被添加到任务队列中,一直会保持先进先出的原则执行,我们不能准确地控制这些事件被添加到任务队列中的位置。但是这个时候突然有高优先级的任务需要尽快执行,那么一种类型的任务就不合适了,所以引入了微任务队列。


不同的异步任务被分为:宏任务和微任务

宏任务:


script(整体代码)

setTimeout()

setInterval()

postMessage

I/O

UI交互事件

微任务:


new Promise().then(回调)

MutationObserver(html5 新特性)

运行机制

异步任务的返回结果会被放到一个任务队列中,根据异步事件的类型,这个事件实际上会被放到对应的宏任务和微任务队列中去。


在当前执行栈为空时,主线程会查看微任务队列是否有事件存在


存在,依次执行队列中的事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的事件,把当前的回调加到当前指向栈。

如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;

当前执行栈执行完毕后时会立刻处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。


在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:


执行一个宏任务(栈中没有就从事件队列中获取)

执行过程中如果遇到微任务,就将它添加到微任务的任务队列中

宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)

当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染

渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

简单总结一下执行的顺序:

执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。


宏任务和微任务


深入理解js事件循环机制(浏览器篇) 这边文章中有个特别形象的动画,大家可以看着理解一下。


console.log('start')


setTimeout(function() {

 console.log('setTimeout')

}, 0)


Promise.resolve().then(function() {

 console.log('promise1')

}).then(function() {

 console.log('promise2')

})


console.log('end')

浏览器事件循环


全局代码压入执行栈执行,输出 start

setTimeout压入 macrotask队列,promise.then 回调放入 microtask队列,最后执行 console.log('end'),输出 end

调用栈中的代码执行完成(全局代码属于宏任务),接下来开始执行微任务队列中的代码,执行promise回调,输出 promise1, promise回调函数默认返回 undefined, promise状态变成 fulfilled ,触发接下来的 then回调,继续压入 microtask队列,此时产生了新的微任务,会接着把当前的微任务队列执行完,此时执行第二个 promise.then回调,输出 promise2

此时,microtask队列 已清空,接下来会会执行 UI渲染工作(如果有的话),然后开始下一轮 event loop, 执行 setTimeout的回调,输出 setTimeout

最后的执行结果如下


start

end

promise1

promise2

setTimeout

node环境下的事件循环

和浏览器环境有何不同

表现出的状态与浏览器大致相同。不同的是 node 中有一套自己的模型。node 中事件循环的实现依赖 libuv 引擎。Node的事件循环存在几个阶段。


如果是node10及其之前版本,microtask会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask队列中的任务。


node版本更新到11之后,Event Loop运行原理发生了变化,一旦执行一个阶段里的一个宏任务(setTimeout,setInterval和setImmediate)就立刻执行微任务队列,跟浏览器趋于一致。下面例子中的代码是按照的去进行分析的。


事件循环模型

┌───────────────────────┐

┌─>│        timers         │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

│  │     I/O callbacks     │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

│  │     idle, prepare     │

│  └──────────┬────────────┘      ┌───────────────┐

│  ┌──────────┴────────────┐      │   incoming:   │

│  │         poll          │<──connections───     │

│  └──────────┬────────────┘      │   data, etc.  │

│  ┌──────────┴────────────┐      └───────────────┘

│  │        check          │

│  └──────────┬────────────┘

│  ┌──────────┴────────────┐

└──┤    close callbacks    │

  └───────────────────────┘

事件循环各阶段详解

node中事件循环的顺序


外部输入数据 --> 轮询阶段(poll) --> 检查阶段(check) --> 关闭事件回调阶段(close callback) --> 定时器检查阶段(timer) --> I/O 事件回调阶段(I/O callbacks) --> 闲置阶段(idle, prepare) --> 轮询阶段...


这些阶段大致的功能如下:


定时器检测阶段(timers): 这个阶段执行定时器队列中的回调如 setTimeout() 和 setInterval()。

I/O事件回调阶段(I/O callbacks): 这个阶段执行几乎所有的回调。但是不包括close事件,定时器和setImmediate()的回调。

闲置阶段(idle, prepare): 这个阶段仅在内部使用,可以不必理会

轮询阶段(poll): 等待新的I/O事件,node在一些特殊情况下会阻塞在这里。

检查阶段(check): setImmediate()的回调会在这个阶段执行。

关闭事件回调阶段(close callbacks): 例如socket.on('close', ...)这种close事件的回调

poll:

这个阶段是轮询时间,用于等待还未返回的 I/O 事件,比如服务器的回应、用户移动鼠标等等。

这个阶段的时间会比较长。如果没有其他异步任务要处理(比如到期的定时器),会一直停留在这个阶段,等待 I/O 请求返回结果。

check:

该阶段执行setImmediate()的回调函数。


close:

该阶段执行关闭请求的回调函数,比如socket.on('close', ...)。


timer阶段:

这个是定时器阶段,处理setTimeout()和setInterval()的回调函数。进入这个阶段后,主线程会检查一下当前时间,是否满足定时器的条件。如果满足就执行回调函数,否则就离开这个阶段。


I/O callback阶段:

除了以下的回调函数,其他都在这个阶段执行:


setTimeout()和setInterval()的回调函数

setImmediate()的回调函数

用于关闭请求的回调函数,比如socket.on('close', ...)

宏任务和微任务

宏任务:


setImmediate

setTimeout

setInterval

script(整体代码)

I/O 操作等。

微任务:


process.nextTick

new Promise().then(回调)

Promise.nextTick, setTimeout, setImmediate的使用场景和区别

Promise.nextTick

process.nextTick 是一个独立于 eventLoop 的任务队列。

在每一个 eventLoop 阶段完成后会去检查 nextTick 队列,如果里面有任务,会让这部分任务优先于微任务执行。

是所有异步任务中最快执行的。


setTimeout:

setTimeout()方法是定义一个回调,并且希望这个回调在我们所指定的时间间隔后第一时间去执行。


setImmediate:

setImmediate()方法从意义上将是立刻执行的意思,但是实际上它却是在一个固定的阶段才会执行回调,即poll阶段之后。


经典题目分析

一. 下面代码输出什么

async function async1() {

   console.log('async1 start');

   await async2();

   console.log('async1 end');

}

async function async2() {

   console.log('async2');

}

console.log('script start');

setTimeout(function() {

   console.log('setTimeout');

}, 0)

async1();

new Promise(function(resolve) {

   console.log('promise1');

   resolve();

}).then(function() {

   console.log('promise2');

});

console.log('script end');

先执行宏任务(当前代码块也算是宏任务),然后执行当前宏任务产生的微任务,然后接着执行宏任务


从上往下执行代码,先执行同步代码,输出 script start

遇到setTimeout,现把 setTimeout 的代码放到宏任务队列中

执行 async1(),输出 async1 start, 然后执行 async2(), 输出 async2,把 async2() 后面的代码 console.log('async1 end')放到微任务队列中

接着往下执行,输出 promise1,把 .then()放到微任务队列中;注意Promise本身是同步的立即执行函数,.then是异步执行函数

接着往下执行, 输出 script end。同步代码(同时也是宏任务)执行完成,接下来开始执行刚才放到微任务中的代码

依次执行微任务中的代码,依次输出 async1 end、 promise2, 微任务中的代码执行完成后,开始执行宏任务中的代码,输出 setTimeout

最后的执行结果如下


script start

async1 start

async2

promise1

script end

async1 end

promise2

setTimeout

二. 下面代码输出什么

console.log('start');

setTimeout(() => {

   console.log('children2');

   Promise.resolve().then(() => {

       console.log('children3');

   })

}, 0);


new Promise(function(resolve, reject) {

   console.log('children4');

   setTimeout(function() {

       console.log('children5');

       resolve('children6')

   }, 0)

}).then((res) => {

   console.log('children7');

   setTimeout(() => {

       console.log(res);

   }, 0)

})

这道题跟上面题目不同之处在于,执行代码会产生很多个宏任务,每个宏任务中又会产生微任务


从上往下执行代码,先执行同步代码,输出 start

遇到setTimeout,先把 setTimeout 的代码放到宏任务队列①中

接着往下执行,输出 children4, 遇到setTimeout,先把 setTimeout 的代码放到宏任务队列②中,此时.then并不会被放到微任务队列中,因为 resolve是放到 setTimeout中执行的

代码执行完成之后,会查找微任务队列中的事件,发现并没有,于是开始执行宏任务①,即第一个 setTimeout, 输出 children2,此时,会把 Promise.resolve().then放到微任务队列中。

宏任务①中的代码执行完成后,会查找微任务队列,于是输出 children3;然后开始执行宏任务②,即第二个 setTimeout,输出 children5,此时将.then放到微任务队列中。

宏任务②中的代码执行完成后,会查找微任务队列,于是输出 children7,遇到 setTimeout,放到宏任务队列中。此时微任务执行完成,开始执行宏任务,输出 children6;

最后的执行结果如下


start

children4

children2

children3

children5

children7

children6

三. 下面代码输出什么

const p = function() {

   return new Promise((resolve, reject) => {

       const p1 = new Promise((resolve, reject) => {

           setTimeout(() => {

               resolve(1)

           }, 0)

           resolve(2)

       })

       p1.then((res) => {

           console.log(res);

       })

       console.log(3);

       resolve(4);

   })

}



p().then((res) => {

   console.log(res);

})

console.log('end');

执行代码,Promise本身是同步的立即执行函数,.then是异步执行函数。遇到setTimeout,先把其放入宏任务队列中,遇到p1.then会先放到微任务队列中,接着往下执行,输出 3

遇到 p().then 会先放到微任务队列中,接着往下执行,输出 end

同步代码块执行完成后,开始执行微任务队列中的任务,首先执行 p1.then,输出 2, 接着执行p().then, 输出 4

微任务执行完成后,开始执行宏任务,setTimeout, resolve(1),但是此时 p1.then已经执行完成,此时 1不会输出。

最后的执行结果如下


3

end

2

4

你可以将上述代码中的 resolve(2)注释掉, 此时 1才会输出,输出结果为 3 end 4 1。


const p = function() {

   return new Promise((resolve, reject) => {

       const p1 = new Promise((resolve, reject) => {

           setTimeout(() => {

               resolve(1)

           }, 0)

       })

       p1.then((res) => {

           console.log(res);

       })

       console.log(3);

       resolve(4);

   })

}



p().then((res) => {

   console.log(res);

})

console.log('end');

3

end

4

1

最后强烈推荐几个非常好的讲解 event loop 的视频:


What the heck is the event loop anyway? | Philip Roberts | JSConf EU

Jake Archibald: In The Loop - JSConf.Asia

小程序入门到精通:了解小程序开发4个重要文件

前端达人

点击查看原图


1. 小程序没有DOM对象,一切基于组件化

2. 小程序的四个重要的文件

  • *.js —> view逻辑 —> javascript
  • *.wxml —> view结构 ----> html
  • *.wxss —> view样式 -----> css
  • *. json ----> view 数据 -----> json文件

注意:为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名。

2.1 WXML

WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件事件系统,可以构建出页面的结构。WXML 充当的就是类似 HTML 的角色
要完整了解 WXML 语法,请参考WXML 语法参考

2.2 WXSS

WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。

WXSS 用来决定 WXML 的组件应该怎么显示。

为了适应广大的前端开发者,WXSS 具有 CSS 大部分特性。同时为了更适合开发微信小程序,WXSS 对 CSS 进行了扩充以及修改。

与 CSS 相比,WXSS 扩展的特性有:



尺寸单位

样式导入

2.3 json

JSON 是一种数据格式,并不是编程语言,在小程序中,JSON扮演的静态配置的角色。



全局配置

小程序根目录下的 app.json 文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。



页面配置

每一个小程序页面也可以使用同名 .json 文件来对本页面的窗口表现进行配置,页面中配置项会覆盖 app.json 的 window 中相同的配置项。



工具配置 project.config.json

通常大家在使用一个工具的时候,都会针对各自喜好做一些个性化配置,例如界面颜色、编译配置等等,当你换了另外一台电脑重新安装工具的时候,你还要重新配置。

考虑到这点,小程序开发者工具在每个项目的根目录都会生成一个 project.config.json,你在工具上做的任何配置都会写入到这个文件,当你重新安装工具或者换电脑工作时,你只要载入同一个项目的代码包,开发者工具就自动

注意:

JSON文件都是被包裹在一个大括号中 {},通过key-value的方式来表达数据。JSON的Key必须包裹在一个双引号中,在实践中,编写 JSON 的时候,忘了给 Key 值加双引号或者是把双引号写成单引号是常见错误。

JSON的值只能是以下几种数据格式,其他任何格式都会触发报错,例如 JavaScript 中的 undefined。



数字,包含浮点数和整数

字符串,需要包裹在双引号中

Bool值,true 或者 false

数组,需要包裹在方括号中 []

对象,需要包裹在大括号中 {}

Null

还需要注意的是 JSON 文件中无法使用注释,试图添加注释将会引发报错。


2.4 js

一个服务仅仅只有界面展示是不够的,还需要和用户做交互:响应用户的点击、获取用户的位置等等。在小程序里边,我们就通过编写 JS 脚本文件来处理用户的操作。


注册页面

对于小程序中的每个页面,都需要在页面对应的 js 文件中进行注册,指定页面的初始数据、生命周期回调、事件处理函数等



使用 Page 构造器注册页面

简单的页面可以使用 Page() 进行构造。



使用 Component 构造器构造页面

Page 构造器适用于简单的页面。但对于复杂的页面, Page 构造器可能并不好用。

此时,可以使用 Component 构造器来构造页面。 Component 构造器的主要区别是:方法需要放在 methods: { } 里面。

————————————————

版权声明:本文为CSDN博主「前端岚枫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/yilanyoumeng3/java/article/details/106292742





2020年,令人惊叹的Echarts!

前端达人

点击查看原图


1.可视化面板介绍

应对现在数据可视化的趋势,越来越多企业需要在很多场景(营销数据,生产数据,用户数据)下使用,可视化图表来展示体现数据,让数据更加直观,数据特点更加突出。

01-技术要点

  1. div + css 布局
  2. flex 布局
  3. Less
  4. 原生js + jquery 使用
  5. rem适配
  6. echarts基础

02-案例适配方案

  1. 设计稿是1920px
  2. flexible.js 把屏幕分为 24 等份
  3. cssrem 插件的基准值是 80px
    插件-配置按钮—配置扩展设置–Root Font Size 里面 设置。
    但是别忘记重启vscode软件保证生效


03-页面主体布局

  1. header布局
  2. mainbox布局
  3. 公共面板模块 panel
  4. 柱形图 bar
因为我们今天的主题是echarts部分所以前面的这些,我就为大家写好框架,里面的布局相信以大家的能力都是分分钟解决的事情。


2.Echarts(重点)

echarts介绍

常见的数据可视化库:

D3.js 目前 Web 端评价最高的 Javascript 可视化工具库(入手难)
ECharts.js 百度出品的一个开源 Javascript 数据可视化库
Highcharts.js 国外的前端数据可视化库,非商用免费,被许多国外大公司所使用
AntV 蚂蚁金服全新一代数据可视化解决方案 等等
Highcharts 和 Echarts 就像是 Office 和 WPS 的关系

ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图表。

官网地址:https://www.echartsjs.com/zh/index.html

echarts体验
下载echarts https://github.com/apache/incubator-echarts/tree/4.5.0

使用步骤(5大步骤):
1.引入echarts 插件文件到html页面中
2.准备一个具备大小的DOM容器

<div id="main" style="width: 600px;height:400px;"></div>

3.初始化echarts实例对象

var myChart = echarts.init(document.getElementById('main'));

4.指定配置项和数据(option)

var option = {
    xAxis: {
        type: 'category',
        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
    },
    yAxis: {
        type: 'value'
    },
    series: [{
        data: [820, 932, 901, 934, 1290, 1330, 1320],
        type: 'line'
    }]
};

5.将配置项设置给echarts实例对象


myChart.setOption(option);  


echarts基础配置

这是要求同学们知道以下配置每个模块的主要作用干什么的就可以了

series

系列列表。每个系列通过 type 决定自己的图表类型

大白话:图标数据,指定什么类型的图标,可以多个图表重叠。

xAxis:直角坐标系 grid 中的 x 轴

boundaryGap: 坐标轴两边留白策略 true,这时候刻度只是作为分隔线,标签和数据点都会在两个刻度之间的带(band)中间。

yAxis:直角坐标系 grid 中的 y 轴

grid:直角坐标系内绘图网格。

title:标题组件

tooltip:提示框组件

legend:图例组件

color:调色盘颜色列表

数据堆叠,同个类目轴上系列配置相同的stack值后 后一个系列的值会在前一个系列的值上相加。



option = {

    // color设置我们线条的颜色 注意后面是个数组

    color: ['pink', 'red', 'green', 'skyblue'],

    // 设置图表的标题

    title: {

        text: '折线图堆叠123'

    },

    // 图表的提示框组件 

    tooltip: {

        // 触发方式

        trigger: 'axis'

    },

    // 图例组件

    legend: {

       // series里面有了 name值则 legend里面的data可以删掉

    },

    // 网格配置  grid可以控制线形图 柱状图 图表大小

    grid: {

        left: '3%',

        right: '4%',

        bottom: '3%',

        // 是否显示刻度标签 如果是true 就显示 否则反之

        containLabel: true

    },

    // 工具箱组件  可以另存为图片等功能

    toolbox: {

        feature: {

            saveAsImage: {}

        }

    },

    // 设置x轴的相关配置

    xAxis: {

        type: 'category',

        // 是否让我们的线条和坐标轴有缝隙

        boundaryGap: false,

        data: ['星期一', '周二', '周三', '周四', '周五', '周六', '周日']

    },

     // 设置y轴的相关配置

    yAxis: {

        type: 'value'

    },

    // 系列图表配置 它决定着显示那种类型的图表

    series: [

        {

            name: '邮件营销',

            type: 'line',

           

            data: [120, 132, 101, 134, 90, 230, 210]

        },

        {

            name: '联盟广告',

            type: 'line',



            data: [220, 182, 191, 234, 290, 330, 310]

        },

        {

            name: '视频广告',

            type: 'line',

          

            data: [150, 232, 201, 154, 190, 330, 410]

        },

        {

            name: '直接访问',

            type: 'line',

          

            data: [320, 332, 301, 334, 390, 330, 320]

        }

    ]

};



3.Echarts快速使用

1.官网实例

点击查看原图



官网默认为我们提供了大量的案例,我们需要使用那一种只需要直接点击打开后复制他们的相关配置,然后按照我上面说的5大步骤进行使用即可。

2.社区Gallery

点击查看原图



官方自带的图例,可能在很多时候并不能满足我们的需要,在社区这里可以找到一些基于echart的高度定制好的图表,相当于基于jquery开发的插件,这里是基于echarts开发的第三方的图表。

本案例中使用了地图模拟飞行的案例就是从社区中进行引用的,
参考社区的例子:https://gallery.echartsjs.com/editor.html?c=x0-ExSkZDM (模拟飞机航线)
实现步骤:

第一需要下载china.js提供中国地图的js文件
第二个因为里面代码比较多,我们新建一个新的js文件 myMap.js 引入
使用社区提供的配置即可。
代码已经上传至我的码云如有需要的小伙伴可自行下载:
https://gitee.com/jiuyueqi/echarts

ps:最后呢,如果大家看完楼主的文章觉得对echarts的学习和了解有所帮助,麻烦大家路过点个赞点个关注呗!楼主后续还会继续更新有关前端方面的面试题资料或者其他方面的知识。
————————————————
版权声明:本文为CSDN博主「程序猿玖月柒」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45257157/java/article/details/106300587

关于JavaScript获取时间函数及实现倒计时

前端达人

JavaScript数组对象的迭代方法详解

上一篇博客讲到了数组的方法,当然里边比较复杂的就是数组的迭代方法,因为涉及到了回调函数,所以这篇博客我们来详细讲解一下js数组迭代方法的使用。


1.forEach(funcrion(value,index,arr){}):对数组的每一项运行给定函数,这个方法不进行返回,所以一般用于让数组循环执行某方法。

  var arr=[1,2,3,4,5,6];

    arr.forEach(function(val,index,arr){

        console.log(val,index,arr);

    })

    // 其中:

    // value:每一个数组项的值 必填项

    // index:每一个数组项对应的索引

    // arr:当前的数组


注意:forEach()方法不返回值,所以回调函数中使用return会打印出来undefined

2.map(funcrion(value,index,arr){}):对数组的每一项运行给定函数,它将返回执行函数后的结果组成的新数组。

 var aNum2 = [1.2, 1.8, 2.0, 4.3];

    console.log(aNum2.map(Math.floor()));// [1,1,2,4]

    

    var arr=[1,2,3];

    console.log(arr.map(function(val,index){

        return val*3

    }));// 3 6 9

    // 其中:

    // value:每一个数组项的值 必填项

    // index:每一个数组项对应的索引

    // arr:当前的数组

注意:map()方法有返回值,返回值为新的数组,所以可以直接再回调函数中return

3.every(funcrion(value,index,arr){}):对数组的每一项都运行给定函数,进项判断,若对于每项执行函数都返回了true,则其结果为true。

 var arr=[10,20,30];

    console.log(arr.every(function(val){

        return val>20;

    }));// false

    

    console.log(arr.every(function(val){

        return val>0;

    }));// true

    

    // 其中:

    // value:每一个数组项的值 必填项

    // index:每一个数组项对应的索引

    // arr:当前的数组



注意:every()方法所有的数组项都符合判断时返回true,否则返回false。

4.some(funcrion(value,index,arr){}):对数组的每一项都运行给定函数,进行判断,若存在一项符合条件的数组项,则其结果为true。

    var arr=[10,20,30];

    console.log(arr.some(function(val){

        return val>20;

    }));// true

    

    console.log(arr.some(function(val){

        return val>0;

    }));// true

    

    console.log(arr.some(function(val){

        return val<0;

    }));// false

    

    arr.some(function(val){

        console.log(val<0);

    });//fasle false false

    // 其中:

    // value:每一个数组项的值 必填项

    // index:每一个数组项对应的索引

    // arr:当前的数组


注意:some()方法如果回调函数执行完会根据结果返回true或false,但是回调函数中打印判断是,只会作为判断条件的返回值,则会打印多遍。

5.fliter(funcrion(value,index,arr){}):对数组的每一项都运行给定函数,进行过滤,将符合条件的数组项添加到新的数组中,并返回新的数组。

   var aNum=[1,2,3,4];
    console.log(aNum.filter(function (num) {
        return num > 1;
    }));//[2,3,4,]
    aNum.filter(function (num) {
        console.log(num > 1);//true true true
    })

注意:filter()方法对数组项进行过滤,然后将符合条件的数组项添加到一个新的数组并返回,但是如果直接打印这个判断条件,相当于打印的判断条件的结果,只会返回true或者false。

6.ES6中新增的迭代方法

1.find():返回第一个符合传入测试(函数)条件的数组元素。


  var aNum=[10,20,30,40];

    console.log(aNum.find(function (num) {

        return num > 19;

    }));//1

    console.log(aNum.find(function (num) {

        return num < 0;

    }));//undefined



2.findIndex():返回符合传入测试(函数)条件的数组元素索引。


console.log(aNum.findIndex(function (num) { return num > 19; }));//3


3.includes():判断一个数组是否包含一个指定的值。

总结:

forEach()与map()是一对,用于数组遍历执行指定函数,前者不返回数组,后者返回 处理过的新数组。
every()与some()是一对,分别适用于检测数组是否全部满足某条件或者存在满足的数组项,返回true或false。
filter()则是相当于过滤器的存在,过滤掉数组中不符合条件的数据,将符合条件的数组项添加到新数组,并返回。
————————————————
版权声明:本文为CSDN博主「Mr_Han119」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_39155611/java/article/details/106294417


了不起的 tsconfig.json 指南

seo达人

在 TypeScript 开发中,tsconfig.json 是个不可或缺的配置文件,它是我们在 TS 项目中最常见的配置文件,那么你真的了解这个文件吗?它里面都有哪些优秀配置?如何配置一个合理的 tsconfig.json 文件?本文将全面带大家一起详细了解 tsconfig.json 的各项配置。



本文将从以下几个方面全面介绍 tsconfig.json 文件:

了不起的 tsconfig.json 指南.png




水平有限,欢迎各位大佬指点~~


一、tsconfig.json 简介


1. 什么是 tsconfig.json

TypeScript 使用 tsconfig.json 文件作为其配置文件,当一个目录中存在 tsconfig.json 文件,则认为该目录为 TypeScript 项目的根目录。

通常 tsconfig.json 文件主要包含两部分内容:指定待编译文件和定义编译选项。



从《TypeScript编译器的配置文件的JSON模式》可知,目前 tsconfig.json 文件有以下几个顶层属性:


compileOnSave

compilerOptions

exclude

extends

files

include

references

typeAcquisition


文章后面会详细介绍一些常用属性配置。



2. 为什么使用 tsconfig.json

通常我们可以使用 tsc 命令来编译少量 TypeScript 文件:


/*

 参数介绍:

 --outFile // 编译后生成的文件名称

 --target  // 指定ECMAScript目标版本

 --module  // 指定生成哪个模块系统代码

 index.ts  // 源文件

*/

$ tsc --outFile leo.js --target es3 --module amd index.ts

但如果实际开发的项目,很少是只有单个文件,当我们需要编译整个项目时,就可以使用 tsconfig.json 文件,将需要使用到的配置都写进 tsconfig.json 文件,这样就不用每次编译都手动输入配置,另外也方便团队协作开发。



二、使用 tsconfig.json

目前使用 tsconfig.json 有2种操作:


1. 初始化 tsconfig.json

在初始化操作,也有 2 种方式:


手动在项目根目录(或其他)创建 tsconfig.json 文件并填写配置;

通过 tsc --init 初始化 tsconfig.json 文件。


2. 指定需要编译的目录

在不指定输入文件的情况下执行 tsc 命令,默认从当前目录开始编译,编译所有 .ts 文件,并且从当前目录开始查找 tsconfig.json 文件,并逐级向上级目录搜索。


$ tsc

另外也可以为 tsc 命令指定参数 --project 或 -p 指定需要编译的目录,该目录需要包含一个 tsconfig.json 文件,如:


/*

 文件目录:

 ├─src/

 │  ├─index.ts

 │  └─tsconfig.json

 ├─package.json

*/

$ tsc --project src

注意,tsc 的命令行选项具有优先级,会覆盖 tsconfig.json 中的同名选项。



更多 tsc 编译选项,可查看《编译选项》章节。



三、使用示例

这个章节,我们将通过本地一个小项目 learnTsconfig 来学着实现一个简单配置。

当前开发环境:windows / node 10.15.1 / TypeScript3.9



1. 初始化 learnTsconfig 项目

执行下面命令:


$ mkdir learnTsconfig

$ cd .\learnTsconfig\

$ mkdir src

$ new-item index.ts

并且我们为 index.ts 文件写一些简单代码:


// 返回当前版本号

function getVersion(version:string = "1.0.0"): string{

   return version;

}


console.log(getVersion("1.0.1"))

我们将获得这么一个目录结构:


 └─src/

    └─index.ts


2. 初始化 tsconfig.json 文件

在 learnTsconfig 根目录执行:


$ tsc --init


3. 修改 tsconfig.json 文件

我们设置几个常见配置项:


{

 "compilerOptions": {

   "target": "ES5",             // 目标语言的版本

   "module": "commonjs",        // 指定生成代码的模板标准

   "noImplicitAny": true,       // 不允许隐式的 any 类型

   "removeComments": true,      // 删除注释

   "preserveConstEnums": true,  // 保留 const 和 enum 声明

   "sourceMap": true            // 生成目标文件的sourceMap文件

 },

 "files": [   // 指定待编译文件

   "./src/index.ts"  

 ]

}

其中需要注意一点:

files 配置项值是一个数组,用来指定了待编译文件,即入口文件。

当入口文件依赖其他文件时,不需要将被依赖文件也指定到 files 中,因为编译器会自动将所有的依赖文件归纳为编译对象,即 index.ts 依赖 user.ts 时,不需要在 files 中指定 user.ts , user.ts 会自动纳入待编译文件。



4. 执行编译

配置完成后,我们可以在命令行执行 tsc 命令,执行编译完成后,我们可以得到一个 index.js 文件和一个 index.js.map 文件,证明我们编译成功,其中 index.js 文件内容如下:


function getVersion(version) {

   if (version === void 0) { version = "1.0.0"; }

   return version;

}

console.log(getVersion("1.0.1"));

//# sourceMappingURL=index.js.map

可以看出,tsconfig.json 中的 removeComments 配置生效了,将我们添加的注释代码移除了。



到这一步,就完成了这个简单的示例,接下来会基于这个示例代码,讲解《七、常见配置示例》。



四、tsconfig.json 文件结构介绍


1. 按顶层属性分类

在 tsconfig.json 文件中按照顶层属性,分为以下几类:

tsconfig.json 文件结构(顶层属性).png


了不起的 tsconfig.json 指南.png



2. 按功能分类

tsconfig.json 文件结构(功能).png




五、tsconfig.json 配置介绍


1. compileOnSave

compileOnSave 属性作用是设置保存文件的时候自动编译,但需要编译器支持。


{

   // ...

 "compileOnSave": false,

}


2. compilerOptions

compilerOptions 属性作用是配置编译选项。

若 compilerOptions 属性被忽略,则编译器会使用默认值,可以查看《官方完整的编译选项列表》。

编译选项配置非常繁杂,有很多配置,这里只列出常用的配置。


{

 // ...

 "compilerOptions": {

   "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度

   "tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置

   "diagnostics": true, // 打印诊断信息

   "target": "ES5", // 目标语言的版本

   "module": "CommonJS", // 生成代码的模板标准

   "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",

   "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",

   "allowJS": true, // 允许编译器编译JS,JSX文件

   "checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用

   "outDir": "./dist", // 指定输出目录

   "rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构

   "declaration": true, // 生成声明文件,开启后会自动生成声明文件

   "declarationDir": "./file", // 指定生成声明文件存放目录

   "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件

   "sourceMap": true, // 生成目标文件的sourceMap文件

   "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中

   "declarationMap": true, // 为声明文件生成sourceMap

   "typeRoots": [], // 声明文件目录,默认时node_modules/@types

   "types": [], // 加载的声明文件包

   "removeComments":true, // 删除注释

   "noEmit": true, // 不输出文件,即编译后不会生成任何js文件

   "noEmitOnError": true, // 发送错误时不输出任何文件

   "noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用

   "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块

   "downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现

   "strict": true, // 开启所有严格的类型检查

   "alwaysStrict": true, // 在代码中注入'use strict'

   "noImplicitAny": true, // 不允许隐式的any类型

   "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量

   "strictFunctionTypes": true, // 不允许函数参数双向协变

   "strictPropertyInitialization": true, // 类的实例属性必须初始化

   "strictBindCallApply": true, // 严格的bind/call/apply检查

   "noImplicitThis": true, // 不允许this有隐式的any类型

   "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)

   "noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)

   "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)

   "noImplicitReturns": true, //每个分支都会有返回值

   "esModuleInterop": true, // 允许export=导出,由import from 导入

   "allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块

   "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入

   "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录

   "paths": { // 路径映射,相对于baseUrl

     // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置

     "jquery": ["node_modules/jquery/dist/jquery.min.js"]

   },

   "rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错

   "listEmittedFiles": true, // 打印输出文件

   "listFiles": true// 打印编译的文件(包括引用的声明文件)

 }

}


3. exclude

exclude 属性作用是指定编译器需要排除的文件或文件夹。

默认排除 node_modules 文件夹下文件。


{

   // ...

 "exclude": [

   "src/lib" // 排除src目录下的lib文件夹下的文件不会编译

 ]

}

和 include 属性一样,支持 glob 通配符:


* 匹配0或多个字符(不包括目录分隔符)

? 匹配一个任意字符(不包括目录分隔符)

**/ 递归匹配任意子目录


4. extends

extends 属性作用是引入其他配置文件,继承配置。

默认包含当前目录和子目录下所有 TypeScript 文件。


{

   // ...

 // 把基础配置抽离成tsconfig.base.json文件,然后引入

   "extends": "./tsconfig.base.json"

}


5. files

files 属性作用是指定需要编译的单个文件列表。

默认包含当前目录和子目录下所有 TypeScript 文件。


{

   // ...

 "files": [

   // 指定编译文件是src目录下的leo.ts文件

   "scr/leo.ts"

 ]

}


6. include

include 属性作用是指定编译需要编译的文件或目录。


{

   // ...

 "include": [

   // "scr" // 会编译src目录下的所有文件,包括子目录

   // "scr/*" // 只会编译scr一级目录下的文件

   "scr/*/*" // 只会编译scr二级目录下的文件

 ]

}


7. references

references 属性作用是指定工程引用依赖。

在项目开发中,有时候我们为了方便将前端项目和后端node项目放在同一个目录下开发,两个项目依赖同一个配置文件和通用文件,但我们希望前后端项目进行灵活的分别打包,那么我们可以进行如下配置:


{

   // ...

 "references": [ // 指定依赖的工程

    {"path": "./common"}

 ]

}


8. typeAcquisition

typeAcquisition 属性作用是设置自动引入库类型定义文件(.d.ts)相关。

包含 3 个子属性:


enable  : 布尔类型,是否开启自动引入库类型定义文件(.d.ts),默认为 false;

include  : 数组类型,允许自动引入的库名,如:["jquery", "lodash"];

exculde  : 数组类型,排除的库名。

{

   // ...

 "typeAcquisition": {

   "enable": false,

   "exclude": ["jquery"],

   "include": ["jest"]

 }

}


六、常见配置示例

本部分内容中,我们找了几个实际开发中比较常见的配置,当然,还有很多配置需要自己摸索哟~~



1. 移除代码中注释

tsconfig.json:


{

 "compilerOptions": {

   "removeComments": true,

 }

}

编译前:


// 返回当前版本号

function getVersion(version:string = "1.0.0"): string{

   return version;

}

console.log(getVersion("1.0.1"))

编译结果:


function getVersion(version) {

   if (version === void 0) { version = "1.0.0"; }

   return version;

}

console.log(getVersion("1.0.1"));


2. 开启null、undefined检测

tsconfig.json:


{

   "compilerOptions": {

       "strictNullChecks": true

   },

}

修改 index.ts 文件内容:


const leo;

leo = new Pingan('leo','hello');


这时候编辑器也会提示错误信息,执行 tsc 后,控制台报错:


src/index.ts:9:11 - error TS2304: Cannot find name 'Pingan'.


9 leo = new Pingan('leo','hello');


Found 1 error.


3. 配置复用

通过 extends 属性实现配置复用,即一个配置文件可以继承另一个文件的配置属性。

比如,建立一个基础的配置文件 configs/base.json :


{

 "compilerOptions": {

   "noImplicitAny": true,

   "strictNullChecks": true

 }

}

在tsconfig.json 就可以引用这个文件的配置了:


{

 "extends": "./configs/base",

 "files": [

   "main.ts",

   "supplemental.ts"

 ]

}


4. 生成枚举的映射代码

在默认情况下,使用 const 修饰符后,枚举不会生成映射代码。

如下,我们可以看出:使用 const 修饰符后,编译器不会生成任何 RequestMethod 枚举的任何映射代码,在其他地方使用时,内联每个成员的值,节省很大开销。


const enum RequestMethod {

 Get,

 Post,

 Put,

 Delete

}


let methods = [

 RequestMethod.Get,

 RequestMethod.Post

]

编译结果:


"use strict";

let methods = [

   0 /* Get */,

   1 /* Post */

];

当然,我们希望生成映射代码时,也可以设置 tsconfig.json 中的配置,设置 preserveConstEnums 编译器选项为 true :


{

 "compilerOptions": {

   "target": "es5",

   "preserveConstEnums": true

 }

}


最后编译结果变成:


"use strict";

var RequestMethod;

(function (RequestMethod) {

   RequestMethod[RequestMethod["Get"] = 0] = "Get";

   RequestMethod[RequestMethod["Post"] = 1] = "Post";

   RequestMethod[RequestMethod["Put"] = 2] = "Put";

   RequestMethod[RequestMethod["Delete"] = 3] = "Delete";

})(RequestMethod || (RequestMethod = {}));

let methods = [

   0 /* Get */,

   1 /* Post */

];


5. 关闭 this 类型注解提示

通过下面代码编译后会报错:


const button = document.querySelector("button");

button?.addEventListener("click", handleClick);

function handleClick(this) {

console.log("Clicked!");

this.removeEventListener("click", handleClick);

}


报错内容:


src/index.ts:10:22 - error TS7006: Parameter 'this' implicitly has an 'any' type.

10 function handleClick(this) {

Found 1 error.


这是因为 this 隐式具有 any 类型,如果没有指定类型注解,编译器会提示“"this" 隐式具有类型 "any",因为它没有类型注释。”。



解决方法有2种:


指定 this 类型,如本代码中为 HTMLElement 类型:

HTMLElement 接口表示所有的 HTML 元素。一些HTML元素直接实现了 HTMLElement 接口,其它的间接实现HTMLElement接口。

关于 HTMLElement 可查看详细。


使用 --noImplicitThis 配置项:


在 TS2.0 还增加一个新的编译选项: --noImplicitThis,表示当 this 表达式值为 any 类型时生成一个错误信息。我们设置为 true 后就能正常编译。


{

 "compilerOptions": {

   "noImplicitThis": true

 }

}


七、Webpack/React 中使用示例


1. 配置编译 ES6 代码,JSX 文件

创建测试项目 webpack-demo,结构如下:


webpack-demo/

 |- package.json

 |- tsconfig.json

 |- webpack.config.js

 |- /dist

   |- bundle.js

   |- index.html

 |- /src

   |- index.js

   |- index.ts

 |- /node_modules

安装 TypeScript 和 ts-loader:


$ npm install --save-dev typescript ts-loader

配置 tsconfig.json,支持 JSX,并将 TypeScript 编译为 ES5:


{

 "compilerOptions": {

   "outDir": "./dist/",

   "noImplicitAny": true,

+   "module": "es6",

+   "target": "es5",

+   "jsx": "react",

   "allowJs": true

 }

}

还需要配置 webpack.config.js,使其能够处理 TypeScript 代码,这里主要在 rules 中添加 ts-loader :


const path = require('path');


module.exports = {

 entry: './src/index.ts',

 module: {

   rules: [

     {

       test: /\.tsx?$/,

       use: 'ts-loader',

       exclude: /node_modules/

     }

   ]

 },

 resolve: {

   extensions: [ '.tsx', '.ts', '.js' ]

 },

 output: {

   filename: 'bundle.js',

   path: path.resolve(__dirname, 'dist')

 }

};


2. 配置 source map

想要启用 source map,我们必须配置 TypeScript,以将内联的 source map 输出到编译后的 JavaScript 文件中。

只需要在 tsconfig.json 中配置 sourceMap 属性:


 {

   "compilerOptions": {

     "outDir": "./dist/",

+     "sourceMap": true,

     "noImplicitAny": true,

     "module": "commonjs",

     "target": "es5",

     "jsx": "react",

     "allowJs": true

   }

 }

然后配置 webpack.config.js 文件,让 webpack 提取 source map,并内联到最终的 bundle 中:


 const path = require('path');


 module.exports = {

   entry: './src/index.ts',

+   devtool: 'inline-source-map',

   module: {

     rules: [

       {

         test: /\.tsx?$/,

         use: 'ts-loader',

         exclude: /node_modules/

       }

     ]

   },

   resolve: {

     extensions: [ '.tsx', '.ts', '.js' ]

   },

   output: {

     filename: 'bundle.js',

     path: path.resolve(__dirname, 'dist')

   }

 };


八、总结

本文较全面介绍了 tsconfig.json 文件的知识,从“什么是 tsconfig.js 文件”开始,一步步带领大家全面认识 tsconfig.json 文件。

文中通过一个简单 learnTsconfig 项目,让大家知道项目中如何使用 tsconfig.json 文件。在后续文章中,我们将这么多的配置项进行分类学习。最后通过几个常见配置示例,解决我们开发中遇到的几个常见问题。

vue.js路由与vuex数据模型设计

seo达人

路由设计

本则路由考虑验证进入登录页面,完成登录操作进入首页。


import Vue from "vue";

import Router from "vue-router";

Vue.use(Router);


import store from "@/store/store";


// (延迟加载)

const Login = () => import("@/views/login");

const Home = () => import("@/views/home");


const HomeRoute = {

 path: "/",

 name: "首页",

 component: Home

};


export { HomeRoute };


const router = new Router({

 base: process.env.BASE_URL,

 routes: [

   {

     path: "/login",

     name: "登录",

     component: Login

   },

   HomeRoute

 ]

});


router.beforeEach((to, from, next) => {

 let loginName = store.state.user.loginName;

 if (to.path === "/" && loginName == "") {

   next("/login");

 } else {

   next();

 }

});


export default router;

数据模型

const state = {

 loginName: ""

};

const mutations = {

 SET_LOGINNAME(state, loginName) {

   state.loginName = loginName;

 }

};

const actions = {

 login({ commit }, userInfo) {

   return new Promise((res, ret) => {

     commit("SET_LOGINNAME", userInfo);

     res();

   });

 },

 logout({ commit }) {

   return new Promise((res, ret) => {

     commit("SET_LOGINNAME", "");

     res();

   });

 }

};

export default {

 namespaced: true,

 state,

 mutations,

 actions

};

import Vue from "vue";

import Vuex from "vuex";

Vue.use(Vuex);


import user from "./modules/user";


const store = new Vuex.Store({

 modules: {

   user

 }

});


export default store;

组件

<div class="modify">

 <input

   type="text"

   @keydown.enter.prevent="handleKeydown"

   v-model="currentVal"

   placeholder="使用enter键切换频道"

 />

 <button @click="reset" style="margin-left:5px;outline:none;cursor:pointer;">复位</button>

</div>

import { mapState, mapMutations, mapActions } from "vuex";

export default {

 name: "login",

 data() {

   return {

     currentVal: "",

     list: ["咨询服务", "音悦台", "体育台", "财经频道", "时尚资讯"],

     index: 0

   };

 },

 computed: {

   ...mapState({

     loginName: state => state.user.loginName

   })

 },

 methods: {

   ...mapActions({

     login: "user/login"

   }),

   handleToHome() {

     let userInfo = "user";

     this.login(userInfo);

     this.$router.push({

       path: "/"

     });

   },

RN和React路由详解及对比

seo达人

前言

在平时H5或者RN开发时,我们业务场景中大部分都不是单页面的需求,那这时我们就能使用路由在进行多页面的切换。下面会对比一下react路由和RN路由的本质区别和使用方法。


路由(routing)是指分组从源到目的地时,决定端到端路径的网络范围的进程

React路由

简介

使用React构建的单页面应用,要想实现页面间的跳转,首先想到的就是使用路由。在React中,常用的有两个包可以实现这个需求,那就是react-router和react-router-dom。本文主要针对react-router-dom进行说明


在根组件上配置路由,引用react-router-dom结构{ HashRouter as Router, Route ,Link ,Redirect ,Switch },HashRouter组件是路由的根组件,定义属性和方法传递给子组件。Router组件进行路由,指定每个路由跳转到相应的组件。Link组件指定跳转链接。Redirect组件路由重定向,不管什么情况下,都会跳转当前指定的路径,和switch组件联合起来一起调用,当路径匹配到路由,不在往下匹配


两类路由

HashRouter:利用监听hash变化(有一个事件hashchange)实现路由切换,它是路由容器,

渲染子组件,并向下层子组件传递(Context上下文传递)loaction,history等路由信息


BrowserHistory:利用H5Api实现路由切换,是路由容器,渲染子组件,

并向子组件传递loaction,history等路由信息

路由配置

image-20200601110809995


路由实现原理

HashRouter只是一个容器,本身并没有DOM结构

它渲染的就是它的子组件,并向下层传递location

组件挂载完成之后根据hash改变pathname的值,如果没有hash值就默认展示根组件

需要跳转路由页面时我们使用link或者push去赋值hash的pathname 如this.props.history.push({ pathname: preview, param: { pic, index } });

当hash值发生变化的时候会通过hashchange捕获变化,并给pathname重新赋值

拿到上下文中传过来的location,然后取出pathname。再对它的子组件进行遍历,如果子组件的path属性和当前上下文中传过来的pathname属性相匹配就进行渲染,若不匹配就返回null。

总结

React路由是实质就是,根据遍历识别路由的pathname,来切换router路由容器中component组件的加载渲染。每次更改pathname就都是组件的重新渲染流程,页面也都会呈现出刷新的效果。


RN路由

简介

RN把导航和路由都集中到了react-navigation库里面

组件使用堆栈式的页面导航来实现各个页面跳转

构造函数:StackNavigator(RouteConfigs, StackNavigatorConfig)

RouteConfigs:页面路由配置

StackNavigatorConfig:路由参数配置

路由配置

image-20200601111333107


参数详解

navigationOptions:配置StackNavigator的一些属性。


   title:标题,如果设置了这个导航栏和标签栏的title就会变成一样的,不推荐使用

   header:可以设置一些导航的属性,如果隐藏顶部导航栏只要将这个属性设置为null

   headerTitle:设置导航栏标题,推荐

   headerBackTitle:设置跳转页面左侧返回箭头后面的文字,默认是上一个页面的标题。可以自定义,也可以设置为null

   headerTruncatedBackTitle:设置当上个页面标题不符合返回箭头后的文字时,默认改成"返回"

   headerRight:设置导航条右侧。可以是按钮或者其他视图控件

   headerLeft:设置导航条左侧。可以是按钮或者其他视图控件

   headerStyle:设置导航条的样式。背景色,宽高等

   headerTitleStyle:设置导航栏文字样式

   headerBackTitleStyle:设置导航栏‘返回’文字样式

   headerTintColor:设置导航栏颜色

   headerPressColorAndroid:安卓独有的设置颜色纹理,需要安卓版本大于5.0

   gesturesEnabled:是否支持滑动返回手势,iOS默认支持,安卓默认关闭



screen:对应界面名称,需要填入import之后的页面


mode:定义跳转风格


  card:使用iOS和安卓默认的风格


  modal:iOS独有的使屏幕从底部画出。类似iOS的present效果


headerMode:返回上级页面时动画效果


  float:iOS默认的效果


  screen:滑动过程中,整个页面都会返回


  none:无动画


cardStyle:自定义设置跳转效果


  transitionConfig: 自定义设置滑动返回的配置


  onTransitionStart:当转换动画即将开始时被调用的功能


  onTransitionEnd:当转换动画完成,将被调用的功能


path:路由中设置的路径的覆盖映射配置


initialRouteName:设置默认的页面组件,必须是上面已注册的页面组件


initialRouteParams:初始路由参数

路由首页

react:


image-20200601111638902


在react中初始化时没有指定hash值,route会匹配路由表里面的根组件”/”


RN:


image-20200601111722749


RN 需要在StackNavigatorConfig里面指定首页


RN路由使用

image-20200601112012191


在入口路由列表注册完成之后 在导航器中的每一个页面,都有 navigation 属性 通过提供的navigate方法来提供跳转


navigation

在导航器中每一个页面都有navigation属性,该属性有以下几个属性/方法

navigate 跳转到其他页面 常用参数如下

routeName 导航器中配置的路由名称

params 传递到下一个页面的参数

state:state 里面包含有传递过来的参数 params 、 key 、路由名称 routeName

setParams 更改当前页面路由参数(后面详细介绍)

goBack: 回退可穿参数

navigate



setParams




在Vue中创建可重用的 Transition

seo达人

原始transition组件和CSS

定义transition的最简单方法是使用transition·或transition-group 组件。这需要为transition定义一个name`和一些CSS。


<template>

 <div id="app">

   <button v-on:click="show = !show">

     Toggle

   </button>

   <transition name="fade">

     <p v-if="show">hello</p>

   </transition>

 </div>

</template>

<script>

export default {

 name: "App",

 data() {

   return {

     show: true

   };

 }

};

</script>

<style>

.fade-enter-active,

.fade-leave-active {

 transition: opacity 0.3s;

}

.fade-enter,

.fade-leave-to {

 opacity: 0;

}

</style>

图片描述


看起来容易,对吧?然而,这种方法有一个问题。我们不能在另一个项目中真正重用这个transition。


封装transition组件

如果我们将前面的逻辑封装到一个组件中,并将其用作一个组件,结果会怎样呢?


// FadeTransition.vue

<template>

 <transition name="fade">

   <slot></slot>

 </transition>

</template>

<script>

export default {

 

};

</script>

<style>

.fade-enter-active,

.fade-leave-active {

 transition: opacity 0.3s;

}

.fade-enter,

.fade-leave-to {

 opacity: 0;

}

</style>


// App.vue


<template>

 <div id="app">

   <button v-on:click="show = !show">

     Toggle transition

   </button>

   <fade-transition>

     <div v-if="show" class="box"></div>

   </fade-transition>

 </div>

</template>

<script>...</script>

<style>...</style>

图片描述


通过在transition组件中提供一个slot,我们几乎可以像使用基本transition组件一样使用它。这比前面的例子稍微好一点,但是如果我们想要传递其他特定于transition的prop,比如mode或者一些hook,该怎么办呢


封装的包装器transition组件

幸运的是,Vue 中有一个功能,使我们可以将用户指定的所有额外props和监听器传递给我们的内部标签/组件。 如果你还不知道,则可以通过$attrs访问额外传递的 props,并将它们与v-bind结合使用以将它们绑定为props。 这同样适用于通过$listeners进行的事件,并通过v-on对其进行应用。


// FadeTransition.vue


<template>

 <transition name="fade" v-bind="$attrs" v-on="$listeners">

   <slot></slot>

 </transition>

</template>

<script>

export default {};

</script>

<style>

.fade-enter-active,

.fade-leave-active {

 transition: opacity 0.3s;

}

.fade-enter,

.fade-leave-to {

 opacity: 0;

}

</style>


// App.vue


...


<fade-transition mode="out-in">

 <div key="blue" v-if="show" class="box"></div>

 <div key="red" v-else class="red-box"></div>

</fade-transition>


...

图片描述


完整事例地址:https://codesandbox.io/s/yjl1...


现在,我们可以传递普通transition组件可以接受的任何事件和支持,这使得我们的组件更加可重用。但为什么不更进一步,增加通过 prop 轻松定制持续时间的可能性。


显式持续时间 prop

Vue 为transition组件提供了一个duration prop,然而,它是为更复杂的动画链接而设计的,它帮助 Vue 正确地将它们链接在一起。


在我们的案例中,我们真正需要的是通过组件prop控制CSS animation/transition。 我们可以通过不在CSS中指定显式的CSS动画持续时间,而是将其作为样式来实现。 我们可以借助transition hook来做到这一点,该transition hook与组件生命周期 hook 非常相似,但是它们在过渡所需元素之前和之后被调用。 让我们看看效果如何。


// FadeTransition.vue


<template>

 <transition name="fade"

             enter-active-class="fadeIn"

             leave-active-class="fadeOut"

             v-bind="$attrs"

             v-on="hooks">

     <slot></slot>

 </transition>

</template>

<script>

export default {

 props: {

   duration: {

     type: Number,

     default: 300

   }

 },

 computed: {

   hooks() {

     return {

       beforeEnter: this.setDuration,

       afterEnter: this.cleanUpDuration,

       beforeLeave: this.setDuration,

       afterLeave: this.cleanUpDuration,

       ...this.$listeners

     };

   }

 },

 methods: {

   setDuration(el) {

     el.style.animationDuration = `${this.duration}ms`;

   },

   cleanUpDuration(el) {

     el.style.animationDuration = "";

   }

 }

};

</script>

<style>

@keyframes fadeIn {

 from {

   opacity: 0;

 }

 to {

   opacity: 1;

 }

}

.fadeIn {

 animation-name: fadeIn;

}

@keyframes fadeOut {

 from {

   opacity: 1;

 }

 to {

   opacity: 0;

 }

}

.fadeOut {

 animation-name: fadeOut;

}

</style>

图片描述


完整事例地址:https://codesandbox.io/s/j4qn...


现在,我们可以控制实际的可见过渡时间,这使我们可重用的过渡变得灵活且易于使用。 但是,如何过渡多个元素(如列表项)呢?


Transition group 支持

你想到的最直接的方法可能是创建一个新组件,比如fade-transition-group,然后将当前transition标签替换为transition-group标签,以实现 group transition。如果我们可以在相同的组件中这样做,并公开一个将切换到transition-group实现的group prop,那会怎么样呢?幸运的是,我们可以通过render函数或component和is属性来实现这一点。


// FadeTransition.vue


<template>

 <component :is="type"

            :tag="tag"

            enter-active-class="fadeIn"

            leave-active-class="fadeOut"

            move-class="fade-move"

            v-bind="$attrs"

            v-on="hooks">

     <slot></slot>

 </component>

</template>

<script>

export default {

 props: {

   duration: {

     type: Number,

     default: 300

   },

   group: {

     type: Boolean,

     default: false

   },

   tag: {

     type: String,

     default: "div"

   }

 },

 computed: {

   type() {

     return this.group ? "transition-group" : "transition";

   },

   hooks() {

     return {

       beforeEnter: this.setDuration,

       afterEnter: this.cleanUpDuration,

       beforeLeave: this.setDuration,

       afterLeave: this.cleanUpDuration,

       leave: this.setAbsolutePosition,

       ...this.$listeners

     };

   }

 },

 methods: {

   setDuration(el) {

     el.style.animationDuration = `${this.duration}ms`;

   },

   cleanUpDuration(el) {

     el.style.animationDuration = "";

   },

   setAbsolutePosition(el) {

     if (this.group) {

       el.style.position = "absolute";

     }

   }

 }

};

</script>

<style>

@keyframes fadeIn {

 from {

   opacity: 0;

 }

 to {

   opacity: 1;

 }

}

.fadeIn {

 animation-name: fadeIn;

}

@keyframes fadeOut {

 from {

   opacity: 1;

 }

 to {

   opacity: 0;

 }

}

.fadeOut {

 animation-name: fadeOut;

}

.fade-move {

 transition: transform 0.3s ease-out;

}

</style>


// App.vue


...


<div class="box-wrapper">

 <fade-transition group :duration="300">

   <div class="box"

        v-for="(item, index) in list"

        @click="remove(index)"

        :key="item"

    >

   </div>

 </fade-transition>

</div>


...

图片描述


完整事例地址:https://codesandbox.io/s/pk9r...


文档中介绍了一个带有transition-group元素的警告。 我们基本上必须在元素离开时将每个项目的定位设置为absolute,以实现其他项目的平滑移动动画。 我们也必须添加一个move-class并手动指定过渡持续时间,因为没有用于移动的 JS hook。我们将这些调整添加到我们的上一个示例中。


再做一些调整,通过在mixin中提取 JS 逻辑,我们可以将其应用于轻松创建新的transition组件,只需将其放入下一个项目中即可。


Vue Transition

在此之前描述的所有内容基本上都是这个小型 transition 集合所包含的内容。它有 10 个封装的transition组件,每个约1kb(缩小)。我认为它非常方便,可以轻松地在不同的项目中使用。你可以试一试:)


总结

我们从一个基本的过渡示例开始,并最终通过可调整的持续时间和transition-group支持来创建可重用的过渡组件。 我们可以使用这些技巧根据并根据自身的需求创建自己的过渡组件。 希望读者从本文中学到了一些知识,并且可以帮助你们建立功能更好的过渡组件。

web安全之XSS实例解析

seo达人

XSS

跨站脚本攻击(Cross Site Script),本来缩写是 CSS, 但是为了和层叠样式表(Cascading Style Sheet, CSS)有所区分,所以安全领域叫做 “XSS”;


XSS攻击,通常是指攻击者通过 “HTML注入”篡改了网页,插入了恶意的脚本,从而在用户浏览网页时,对用户的浏览器进行控制或者获取用户的敏感信息(Cookie, SessionID等)的一种攻击方式。


页面被注入了恶意JavaScript脚本,浏览器无法判断区分这些脚本是被恶意注入的,还是正常的页面内容,所以恶意注入Javascript脚本也拥有了所有的脚本权限。如果页面被注入了恶意 JavaScript脚本,它可以做哪些事情呢?


可以窃取 cookie信息。恶意 JavaScript可以通过 ”doccument.cookie“获取cookie信息,然后通过 XMLHttpRequest或者Fetch加上CORS功能将数据发送给恶意服务器;恶意服务器拿到用户的cookie信息之后,就可以在其他电脑上模拟用户的登陆,然后进行转账操作。

可以监听用户行为。恶意JavaScript可以使用 "addEventListener"接口来监听键盘事件,比如可以获取用户输入的银行卡等信息,又可以做很多违法的事情。

可以修改DOM 伪造假的登陆窗口,用来欺骗用户输入用户名和密码等信息。

还可以在页面内生成浮窗广告,这些广告会严重影响用户体验。

XSS攻击可以分为三类:反射型,存储型,基于DOM型(DOM based XSS)


反射型

恶意脚本作为网络请求的一部分。


const Koa = require("koa");

const app = new Koa();


app.use(async ctx => {

   // ctx.body 即服务端响应的数据

   ctx.body = '<script>alert("反射型 XSS 攻击")</script>';

})


app.listen(3000, () => {

   console.log('启动成功');

});

访问 http://127.0.0.1:3000/ 可以看到 alert执行


反射型XSS1


举一个常见的场景,我们通过页面的url的一个参数来控制页面的展示内容,比如我们把上面的一部分代码改成下面这样


app.use(async ctx => {

   // ctx.body 即服务端响应的数据

   ctx.body = ctx.query.userName;

})

此时访问 http://127.0.0.1:3000?userName=xiaoming 可以看到页面上展示了xiaoming,此时我们访问 http://127.0.0.1:3000?userName=<script>alert("反射型 XSS 攻击")</script>, 可以看到页面弹出 alert。


反射型XSS2


通过这个操作,我们会发现用户将一段含有恶意代码的请求提交给服务器,服务器在接收到请求时,又将恶意代码反射给浏览器端,这就是反射型XSS攻击。另外一点需要注意的是,Web 服务器不会存储反射型 XSS 攻击的恶意脚本,这是和存储型 XSS 攻击不同的地方。


在实际的开发过程中,我们会碰到这样的场景,在页面A中点击某个操作,这个按钮操作是需要登录权限的,所以需要跳转到登录页面,登录完成之后再跳转会A页面,我们是这么处理的,跳转登录页面的时候,会加一个参数 returnUrl,表示登录完成之后需要跳转到哪个页面,即这个地址是这样的 http://xxx.com/login?returnUrl=http://xxx.com/A,假如这个时候把returnUrl改成一个script脚本,而你在登录完成之后,如果没有对returnUrl进行合法性判断,而直接通过window.location.href=returnUrl,这个时候这个恶意脚本就会执行。


存储型

存储型会把用户输入的数据“存储”在服务器。


比较常见的一个场景就是,攻击者在社区或论坛写下一篇包含恶意 JavaScript代码的博客文章或评论,文章或评论发表后,所有访问该博客文章或评论的用户,都会在他们的浏览器中执行这段恶意的JavaScript代码。


存储型攻击大致需要经历以下几个步骤


首先攻击者利用站点漏洞将一段恶意JavaScript代码提交到网站数据库中

然后用户向网站请求包含了恶意 JavaScript脚本的页面

当用户浏览该页面的时候,恶意脚本就会将用户的cookie信息等数据上传到服务器

存储型XSS


举一个简单的例子,一个登陆页面,点击登陆的时候,把数据存储在后端,登陆完成之后跳转到首页,首页请求一个接口将当前的用户名显示到页面


客户端代码


<!DOCTYPE html>

<html lang="en">


<head>

   <meta charset="UTF-8">

   <meta name="viewport" content="width=device-width, initial-scale=1.0">

   <meta http-equiv="X-UA-Compatible" content="ie=edge">

   <title>XSS-demo</title>

   <style>

       .login-wrap {

           height: 180px;

           width: 300px;

           border: 1px solid #ccc;

           padding: 20px;

           margin-bottom: 20px;

       }

       input {

           width: 300px;

       }

   </style>

</head>


<body>

   <div class="login-wrap">

       <input type="text" placeholder="用户名" class="userName">

       <br>

       <input type="password" placeholder="密码" class="password">

       <br>

       <br>

       <button class="btn">登陆</button>

   </div>

</body>

<script>

   var btn = document.querySelector('.btn');

   

   btn.onclick = function () {

       var userName = document.querySelector('.userName').value;

       var password = document.querySelector('.password').value;

       

       fetch('http://localhost:3200/login', {

           method: 'POST',

           body: JSON.stringify({

               userName,

               password

           }),

           headers:{

               'Content-Type': 'application/json'

           },

           mode: 'cors'

       })

           .then(function (response) {

               return response.json();

           })

           .then(function (res) {

               alert(res.msg);

               window.location.href= "http://localhost:3200/home";

           })

           .catch(err => {

               message.error(`本地测试错误 ${err.message}`);

               console.error('本地测试错误', err);

           });

   }

</script>


</html>

服务端代码


const Koa = require("koa");

const app = new Koa();

const route = require('koa-route');

var bodyParser = require('koa-bodyparser');

const cors = require('@koa/cors');


// 临时用一个变量来存储,实际应该存在数据库中

let currentUserName = '';


app.use(bodyParser()); // 处理post请求的参数


const login = ctx => {

   const req = ctx.request.body;

   const userName = req.userName;

   currentUserName = userName;


   ctx.response.body = {

       msg: '登陆成功'

   };

}


const home = ctx => {

   ctx.body = currentUserName;

}

app.use(cors());

app.use(route.post('/login', login));

app.use(route.get('/home', home));

app.listen(3200, () => {

   console.log('启动成功');

});

点击登陆将输入信息提交大服务端,服务端使用变量 currentUserName来存储当前的输入内容,登陆成功后,跳转到 首页, 服务端会返回当前的用户名。如果用户输入了恶意脚本内容,则恶意脚本就会在浏览器端执行。


在用户名的输入框输入 <script>alert('存储型 XSS 攻击')</script>,执行结果如下


存储型XSS


基于DOM(DOM based XSS)

通过恶意脚本修改页面的DOM节点,是发生在前端的攻击


基于DOM攻击大致需要经历以下几个步骤


攻击者构造出特殊的URL,其中包含恶意代码

用户打开带有恶意代码的URL

用户浏览器接受到响应后执行解析,前端JavaScript取出URL中的恶意代码并执行

恶意代码窃取用户数据并发送到攻击者的网站,冒充用户行为,调用目标网站接口执行攻击者指定的操作。

举个例子:


<body>

   <div class="login-wrap">

       <input type="text" placeholder="输入url" class="url">

       <br>

       <br>

       <button class="btn">提交</button>

       <div class="content"></div>

   </div>

</body>

<script>

   var btn = document.querySelector('.btn');

   var content = document.querySelector('.content');

   

   btn.onclick = function () {

       var url = document.querySelector('.url').value;

       content.innerHTML = `<a href=${url}>跳转到输入的url</a>`

   }

</script>

点击提交按钮,会在当前页面插入一个超链接,其地址为文本框的内容。


在输入框输入 如下内容


'' onclick=alert('哈哈,你被攻击了')

执行结果如下


基于DOM型XSS


首先用两个单引号闭合调 href属性,然后插入一个onclick事件。点击这个新生成的链接,脚本将被执行。


上面的代码是通过执行 执行 alert来演示的攻击类型,同样你可以把上面的脚本代码修改为任何你想执行的代码,比如获取 用户的 cookie等信息,<script>alert(document.cookie)</script>,同样也是可以的.

防御XSS

HttpOnly

由于很多XSS攻击都是来盗用Cookie的,因此可以通过 使用HttpOnly属性来防止直接通过 document.cookie 来获取 cookie。


一个Cookie的使用过程如下


浏览器向服务器发起请求,这时候没有 Cookie

服务器返回时设置 Set-Cookie 头,向客户端浏览器写入Cookie

在该 Cookie 到期前,浏览器访问该域下的所有页面,都将发送该Cookie

HttpOnly是在 Set-Cookie时标记的:


通常服务器可以将某些 Cookie 设置为 HttpOnly 标志,HttpOnly 是服务器通过 HTTP 响应头来设置的。


const login = ctx => {

   // 简单设置一个cookie

   ctx.cookies.set(

       'cid',

       'hello world',

       {

         domain: 'localhost',  // 写cookie所在的域名

         path: '/home',       // 写cookie所在的路径

         maxAge: 10 * 60 * 1000, // cookie有效时长

         expires: new Date('2021-02-15'),  // cookie失效时间

         httpOnly: true,  // 是否只用于http请求中获取

         overwrite: false  // 是否允许重写

       }

     )

}

HttpOnly


需要注意的一点是:HttpOnly 并非阻止 XSS 攻击,而是能阻止 XSS 攻击后的 Cookie 劫持攻击。


输入和输出的检查

永远不要相信用户的输入。


输入检查一般是检查用户输入的数据是都包含一些特殊字符,如 <、>, '及"等。如果发现特殊字符,则将这些字符过滤或编码。这种可以称为 “XSS Filter”。


安全的编码函数


针对HTML代码的编码方式是 HtmlEncode(是一种函数实现,将字符串转成 HTMLEntrities)


& --> &amp;

< --> &lt;

> --> &gt;

" --> &quot;

相应的, JavaScript的编码方式可以使用 JavascriptEncode。


假如说用户输入了 <script>alert("你被攻击了")</script>,我们要对用户输入的内容进行过滤(如果包含了 <script> 等敏感字符,就过滤掉)或者对其编码,如果是恶意的脚本,则会变成下面这样


&lt;script&gt;alert("你被攻击了");&lt;/script&gt;

经过转码之后的内容,如 <script>标签被转换为 &lt;script&gt;,即使这段脚本返回给页面,页面也不会指向这段代码。


防御 DOM Based XSS

我们可以回看一下上面的例子


btn.onclick = function () {

   var url = document.querySelector('.url').value;

   content.innerHTML = `<a href=${url}>跳转到输入的url</a>`

}

事实上,DOM Based XSS 是从 JavaScript中输出数据到HTML页面里。


用户输入 '' onclick=alert('哈哈,你被攻击了'),然后通过 innerHTML 修改DOM的内容,就变成了 <a href='' onclick=alert('哈哈,你被攻击了')>跳转到输入的url</a>, XSS因此产生。


那么正确的防御方法是什么呢?

从JavaScript输出到HTML页面,相当于一次 XSS输出的过程,需要根据不同场景进行不同的编码处理


变量输出到 <script>,执行一次 JavascriptEncode

通过JS输出到HTML页面


输出事件或者脚本,做 JavascriptEncode 处理

输出 HTML内容或者属性,做 HtmlEncode 处理

会触发 DOM Based XSS的地方有很多,比如


xxx.interHTML

xxx.outerHTML

document.write

页面中所有的inputs框

XMLHttpRequest返回的数据

...


项目中如果用到,一定要避免在字符串中拼接不可信的数据。


利用CSP

CSP (Content Security Policy) 即内容安全策略,是一种可信白名单机制,可以在服务端配置浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截是由浏览器自己实现的。我们可以通过这种方式来尽量减少 XSS 攻击。


通常可以通过两种方式来开启 CSP:


设置 HTTP Header 的 Content-Security-Policy

Content-Security-Policy: default-src 'self'; // 只允许加载本站资源

Content-Security-Policy: img-src https://*  // 只允许加载 HTTPS 协议图片

Content-Security-Policy: child-src 'none'    // 允许加载任何来源框架

设置 meta 标签的方式

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档