zsi2017 / blog Goto Github PK
View Code? Open in Web Editor NEW博客;温故而知新
Home Page: https://zsi2017.github.io/blog/
博客;温故而知新
Home Page: https://zsi2017.github.io/blog/
sed 是一种流编辑器,文本处理中常用的工具,通常情况下,会把当前处理的行存储在临时缓存区中,最后将处理结果输出到终端,所以通常不会改变原文本的内容。
常用操作备忘:
sed 's/book/books.' file
sed -i 's/book/books/g file'
sed '2,$d' file
删除第2行到末尾sed 's#book#books#g example'
紧跟在s 命令后的都可以被认为是新的分隔符,sed 'N;s/book/books/g file'
把原文本中的下一行,也就是偶数行,放在缓存区做匹sed -n '/book/p' file
看成grep来使用, 结合 -n 参数一起使用。 就可以打印匹配的那一行。/n
, 结合刚才的N 命令, sed ':a;N;s/\n//;ta' 1.txt
,通过;
进行分割,可以同时执行多个命令。grep 用于查找文件中符合条件的字符串。查找成功,会把那 一行显示出来。 如果后面没有指定文件,就会从标准输入中读取数据,或者经常用到 查看服务的命令 ps -ef | grep nginx
, 就可以在所有的服务中,筛选出nginx 服务。
grep width 1.css
在命令行输出1.css 文件中包含 'width' 字符串的行grep -r width ./
在当前文件目录下,递归查找所有的子目录,找到包含‘width'的文件,已经文件中对应的行内容。grep -v width 1.css
打印导出1.css 文件中,不包含‘width’的行。通常用来筛选出log.-q
不显示任何信息 。 效果 等同于 grep width 1.css > /dev/null
, 利用输出重定向到 /dev/null, 这个文件代表linux的空设备文件,所有往这个文件里面写入的内容都会丢失awk 一个强大的文本分析工具,将文件逐行读入,并且每一行都以空格或者tab进行分割,可以对分割后的字段,利用$1,$2... 获取到,进行分析。
指定域分割符: -F命名 awk -F: ‘print{$1}’ filename
指定分割符为‘:’ ,并打印出:前面的内容。
比如:
cat 1.css
width: 100px;
height: 200px;
awk -F ':' '{print $1}' 1.css
width
height
也可以使用多个分割符, awk -F '[,:]' '{print $1}'
这里表示先用‘;’进行分割,然后再使用‘:’进行分割。
设置变量: -v awk -va=1 '{print a}' 1.css
直接使用脚本文件: awk -f scriptfile filename;
内建变量:
awk 'NR % 2 == 0' filename
awk脚本:
使用BEGIN 和 END
cat 1.css
width: 100px;
height: 200px;
awk -v a= 'single' 'BEGIN {print a} {print $1} END {print 'end'}' 1.css
single
width:
height:
end
目前基本使用三款js代码质量检查工具: jslint, jshint, eslint。许多IDE里面也有对应的检查插件,在每次ctrl + s 保存文件的时候,检查当前文件是否符合规范,保证代码质量。
许多团队都会指定一套代码规范code review,更加严格的检查每次代码修改。 也可以在git commit
之前,检查代码,保证所有提交到版本库中的代码都是符合规范的,
在看vue源码时,不免修改代码,就会触发里面配置好的钩子函数。于是,仔细研究了一下vue配置方法,可以发现配置非常简单。
git 钩子文档上介绍非常详细,git init
后,在.git/hooks
文件中,有一些.simple
结尾的钩子示例脚本,如果想启用对应的钩子函数,只需手动删除后缀。所以,列出两种配置方法:
按照文档上,配置钩子脚本,修改hooks中文件名对应的钩子文件,启用钩子。使用shell脚本检查,可以参考vue1.x 里面如何使用
!/usr/bin/env bash
# get files to be linted
FILES=$(git diff --cached --name-only | grep -E '^src|^test/unit/specs|^test/e2e')
# lint them if any
if [[ $FILES ]]; then
./node_modules/.bin/eslint $FILES
fi
文件名是pre-commit
,在commit 之前启用的钩子函数, 利用 git diff
查看当前有哪些文件修改过,只对指定文件夹中修改的文件使用eslint进行代码检查,渐进式对整个项目实现代码规范。
脚本写好后,不用每次都手动复制到.git/hooks
目录下,只需对当前文件创建软连接,到指定目录,在package.json中配置脚本命令,
"scripts": {
"install-hook": "ln -s ../../build/git-hooks/pre-commit .git/hooks/pre-commit",
}
在项目初始化后, 执行npm run install-hook
,很方便地配置好了pre-commit 钩子
在 vue最新的版本中,已经使用尤大改写的youkie, youkie实际是fork husky,然后做了一些定制化的改动, 使得钩子能从package.json的 "gitHooks"属性中读取,
{
"gitHooks": {
"pre-commit": "foo"
}
}
使用方法跟husky 类似,可以查看husky 文档,介绍非常详细。
npm install husky --save-dev
# or npm install yorkie --save-dev
安装完成后,可以发现已经改写了hooks 目录中的文件,只需在package.json 中配置对应钩子要执行的脚本。
husky 配置:
// package.json
{
"husky": {
"hooks": {
"pre-commit": "npm test",
"pre-push": "npm test",
"...": "..."
}
}
}
回头看看,vue中如何配置
// package.json
"gitHooks": {
"pre-commit": "lint-staged",
"commit-msg": "node scripts/verify-commit-msg.js"
}
"lint-staged": {
"*.js": [
"eslint --fix",
"git add"
]
}
前面提到,利用git diff
,只lint当前改动的文件,lint-staged就非常准确的解决了这一问题,从这个包名,就可以看出,Run linters on git staged files
,只针对改动的文件进行处理。
结合husky一起使用,安装依赖:
npm install --save-dev lint-staged husky
修改package.json 文件
{
+ "husky": {
+ "hooks": {
+ "pre-commit": "lint-staged"
+ }
+ },
+ "lint-staged": {
+ "*.js": ["eslint --fix", "git add"]
+ }
}
使用了eslint,需要配置.eslintrc, lint-staged还有一个好处,可以在lint后,更加灵活,执行其他脚本,尝试进行修改错误,比如 eslint --fix
检查后并修复错误。
上面列出的vue 文件使用了类似的配置,另外增加了 commit-msg 钩子,对提交说明进行检查,在 scripts/verify-commit-msg.js文件中可以找到检查脚本,
const chalk = require('chalk')
const msgPath = process.env.GIT_PARAMS
const msg = require('fs').readFileSync(msgPath, 'utf-8').trim()
const commitRE = /^(revert: )?(feat|fix|polish|docs|style|refactor|perf|test|workflow|ci|chore|types|build)(\(.+\))?: .{1,50}/
if (!commitRE.test(msg)) {
console.log()
console.error(
` ${chalk.bgRed.white(' ERROR ')} ${chalk.red(`invalid commit message format.`)}\n\n` +
chalk.red(` Proper commit message format is required for automated changelog generation. Examples:\n\n`) +
` ${chalk.green(`feat(compiler): add 'comments' option`)}\n` +
` ${chalk.green(`fix(v-model): handle events on blur (close #28)`)}\n\n` +
chalk.red(` See .github/COMMIT_CONVENTION.md for more details.\n`) +
chalk.red(` You can also use ${chalk.cyan(`npm run commit`)} to interactively generate a commit message.\n`)
)
process.exit(1)
}
利用process.env.GIT_PARAMS 找到目录,读取msg 说明,进行检查。
使用 husky 要注意,对应属性名已经改为HUSKY_GIT_PARAMS , 而不是原始的 GIT_PARAMS 环境变量。
在ECMAScript中,每个函数都包含两个继承而来的方法:apply() 和 call(),这两个方法的用途都是在特定的作用域中调用函数,主要作用跟bind一样,用来改变函数体内this的指向,或者说是在函数调用时改变上下文。
文章尽量使用大量实例进行讲解,它们的使用场景。同时,也会由浅入深的引导出一些理论,毕竟这几个常用方法,在MDN上都能找到合理的解释
var fruit = {
fruitName:"apple"
}
function getFruit() {
console.log("I like "+this.fruitName)
}
getFruit(); // log I like undefined
getFruit.call(fruit) // log I like apple
getFruit.apply(fruit) // log I like apple
var newBind = getFruit.bind(fruit)
newBind(); // log I like apple
当 getFruit 并非作为一个对象的属性,而是直接当做一个函数来调用,里面的this
就会被绑定到全局对象上,即window上, 所以直接调用 getFruit
,里面的this
指向了全局对象上,返回 undefined
。
在严格模式下,函数被调用后,里面的this默认是 undefined
后面,通过调用函数上的call
和apply
方法,该变this
指向,函数里面的this
指向fruit
。
区别:
bind
同样实现了改变this
指向的功能,但是它不会立即执行,而是会重新创建一个绑定函数,新函数被调用时,使用bind()
方法里面的第一个参数作为this
这三个方法,从接受的第二参数开始,都直接传递给函数,但是接受参数的方法却很大的不同。
call,从第二个参数开始,以参数列表的形式展示,
apply,则把传递的函数参数,放在一个数组里面作为第二个参数。
fn.call(obj,arg1,arg2);
fn.apply(obj,[arg1,arg2])
bind,从第二个参数开始,同样以参数列表的形式,但是会提前放在新绑定函数的参数之前
var foo = function(name,age){
console.log("name: "+name+"- age: "+age)
}
var p1 = foo.bind(this,"popo"); // "popo" 作为新函数的第一个参数。
p1(13); // logs name: popo- age: 13
p1("bobo",14) // logs name: popo- age: bobo
$('.div-class').on('click',function(event) {
/*TODO*/
}.bind(this));
}
}
通常,我们在改变函数上下文之前,都会使用类似that = this
,或者self,_this
,来把this赋值给一个变量。利用.bind()
,可以传入外层的上下文。
循环中利用闭包来处理回调
for(var i = 0;i < 10;i++){
(function(j){
setTimeout(function(){
console.log(j);
},600);
})(i)
}
每次循环,都会产生一个立即执行的函数,函数内部的局部变量j保存不同时期i的值,循环过程中,setTimeout回调按顺序放入消息队列中,等for循环结束后,堆栈中没有同步的代码,就去消息队列中,执行对应的回调,打印出j的值。
同理,可以利用bind
,每次都创建新的函数,并且已经预先设置了参数,传入不同的指针
function func(i) {
console.log(i)
}
for(var i =0 ;i< 10;i++) {
setTimeout(func.bind(null,i),600)
}
var Person = function(name,age) {
this.name = name;
this.age = age;
}
var P1 = function(name,age) {
// 借用构造函数的方式实现继承
// 利用call 继承了Person
Person.call(this,name,age)
}
P1.prototype.getName = function() {
console.log("name: "+this.name+", age: "+this.age);
}
var newPerson = new P1("popo",20); // logs name: popo, age: 20
newPerson.getName();
实质上,可以看成通过call()
或者apply()
方法,在即将新建的对象,即这里的newPerson
上,执行超类型的构造函数,分别在当前上下文this
上添加name
和age
属性。
function isArray(value) {
return Object.prototype.toString.call(value) == "[object Array]"
}
借用了Object原生的toString()方法,打印出对应变量的构造函数名,
// 实现一个简单的数组 'unshift'方法
Array.prototype.unshift = function(){
this.splice.apply(this,
[0,0].concat(Array.prototype.slice.apply(arguments)));
return this.length;
}
首先,利用this.splice.apply()
,其中splice
,可以直接从数组中移除或者插入变量。apply()
则以数组的形式传递参数,需要利用concat
拼接数组。
当函数被调用时,在函数内部会得到类数组arguments
,它拥有一个length属性,但是没有任何数组的方法。所以,将slice
方法中的this
指向arguments
,获取到arguments
的长度,从而确定方法的start
和end
下标,得到一个数组变量。
同样适用的还有,DOM里面的NodeList对象,它也是一种类数组对象。
bind
方法在ECMAScript5里面被引入,前面提到过,调用该方法时,返回一个新的函数,可以简单使用下面方法实现其改变this
指向的功能。
Function.prototype.bind = function(scope) {
var fn = this;
return function() {
return fn.apply(scope)
}
}
接着,就可以利用concat
把bind传递的预置参数拼接到新函数的参数列表中。
Function.prototype.bind = function(scope) {
var args = Array.prototype.slice.call(arguments,1)
var fn = this
return function() {
return fn.apply(scope,args.concat(Array.prototype.slice.call(arguments)))
}
}
参考链接
浏览器中tab页面之间进行通信,很常见,也是面试中经常被问到的问题,这里总结了一下,不同情况下的通信,大致可以分为是否同源(协议、域名和端口)和是否相关连(包含在当前页面中<iframe>元素,或者由当前页面弹出的窗口)
window.postMessage() 方法,可以安全的进行跨域,跨页面通信
向其它窗口发送消息
otherWindow.postMessage(message,targetOrigin)
接受两个参数:
message 事件
消息传递过程中,都会触发window对象的message事件,以异步的方式触发。事件对象中包含三个参数:
必须拿到其它窗口的一个引用,而不是当前窗口的应用,所以这种方式适合有依赖关系的页面之间通信
不同依赖关系下,获取其它窗口应用的方法:
页面与嵌套的iframe通信。
获取iframe的contentWindow属性。或者通过命名/数值索引的window.frames[0]
页面和其打开的新窗口通信。
执行window.open返回的窗口对象。
var otherWindow = window.open("http://www.baidu.com")
这里使用node
起了两个端口不同的简单服务,直接使用node server.js
,启动服务。server.js文件位置,服务启动后,打开http://localhost/,就可以看到页面。
主页面:
<div>
<h1> 主页面 </h1>
<iframe id="child" src="http://localhost:8000/second.html"></iframe>
<button id="child-btn"> 向子页面发送消息 </button>
</div>
<script>
document.querySelector("#child-btn").addEventListener("click",function(){
sendMessage({type:"parent",data:"子页面接收"})
})
function sendMessage(data) {
document.querySelector("#child").contentWindow.postMessage(JSON.stringify(data), '*');
}
window.addEventListener('message', function(e) {
console.log("received response: ", e.data);
})
</script>
子页面在8000的端口上输出。通过iframe
引入子页面。然后利用contentWindow
拿到子页面窗口的引用,就可以向子页面发送消息,targetOrigin设置成了*
,保证在不同源上的子页面也可以收到消息。
嵌套的子页面:
<h1> 子 iframe 页面</h1>
<button type="button" name="button">向父页面发送消息</button>
<script>
document.querySelector("button").addEventListener("click",function(e){
console.log("子页面 sending...")
window.parent.postMessage(JSON.stringify({type:"child",data:"父页面接收"}),'*')
},false)
window.addEventListener('message', function() {
console.log("received response: ", event.data);
})
</script>
可以看到,子页面通过window.parent
拿到了父页面的窗口引用,同时通过addEventListener
监听了message
事件,实时获取到父页面传过来的信息。这样不同源的两嵌套页面,就可以实现通信
无关页面,相对应的就是既不是页面中包含的<iframe>元素,也不是当前页面弹出的窗口,没法使用之前提到的postMessage()
进行通信。既然是同源页面,我们默认还是在同一浏览器中打开的,可以联想到HTML5
规范中作为持久保存客户端数据的方案localStorage
。
要访问同一个localStorage对象,页面必须同域名,同协议,同端口
将localStorage
作为中转站,分别进行存储消息和获取消息操作,从而完成了页面间的通信,对应localStorage.setItem
和localStorage.getItem
方式。
最重要的是使用storage
事件,及时通知对方获取消息,对storage
对象进行任何修改,都会在文档上触发storage
事件。
<button>click<button>
<script>
window.addEventListener("storage",function(ev){
if(ev.key === "message") {
// removeItem 同样触发storage 事件,此时ev.newValue 为空
if(!ev.newValue)
return;
var message = JSON.parse(ev.newValue);
console.log(message);
}
});
function sendMessage(message) {
console.log("exacted sendMessage");
localStorage.setItem('message',message);
localStorage.removeItem("message");
};
document.querySelector('button').onclick = function(){
sendMessage('this is message from A');
}
// 发送消息给B 页面。
<script>
window.addEventListener("storage",function(ev){
if(ev.key === "message") {
if(!ev.newValue)
return;
var message = ev.newValue;
console.log(message)
// 发送消息给A 页面
sendMessage("message echo from B");
}
});
function sendMessage(message) {
localStorage.setItem('message',JSON.stringify(message));
localStorage.removeItem("message");
}
</script>
从index.html
和second.html
两个页面可以看到,都监听了storage
事件,方便及时获取消息。发送消息,则利用localStorage.setItem
发送,为了不影响真实存储,同时removeItem
了,所有在storage
事件的回调里面,就判断了!ev.newValue
是否为空,避免触发两次事件
使用node启动服务,server.js文件,然后node server.js
,
两页面分别对应http://localhost/
和http://localhost/second
。
即在浏览器上打开了两个没有任何关系的tab,不用域,也任何依赖关系
就可以结合前面使用的postMessage
和localStorage
,利用postMessage
可以跨域访问,localStorage
可以在无关页面之间通信的特点,利用bridge.html
页面作为中转站,实现通信。
首先看下HTML结构,在tabA和tabB是需要通信的两个页面,同时它们都包含了指向bridge.html的<iframe>
元素。
然后,以一次从tabA 到tabB的通信为例:
tabA 发送消息。拿到iframe
的引用,然后postMessage
发送消息到tabB的iframe
。
document.querySelector("#J_bridge").contentWindow.postMessage(JSON.stringify(data),'/');
然后bridge.html
页面中转。
window.addEventListener('message',function(e){}
,监听到了tabA 中iframe
发送的消息。localStorage.setItem
存放消息, window.addEventListener('storage',function(ev){}
的storage
事件, window.parent.postMessage(ev.newValue,'*')
,获取到父页面的窗口引用,这里可以认为获取到了tabB页面的窗口引用,postMessage
传递消息。最后tabB中获取message 消息。
通过 window.addEventListener('message',function(e){})
监听message 消息,获取到bridge.html
传递的内容。
可以发现,在bridge.html
页面中转消息时,最后一步window.parent.postMessage
发送消息,会同时触发tabA和tabB 里的message
事件,因为两页面都通过iframe
引入了bridge.html
页面,所以这要在传递的消息中加上区分字段type
,标识发送方。
演示代码,然后node server.js
启动服务,A页面和B页面对应url是http://localhost/
和http://localhost:8000/second
。
参考链接:
默认值: auto 鼠标不会穿透当前层,
none: 最外层元素不会监听鼠标的点击事件,直接穿透到了下面的元素身上,
如果其他的子元素设置了 pointer-events
默认增量为1;
body {counter-reset:list;}
在父节点上 设置对应的属性,可以重置变量保存的计数;
input:checked { counter-increment: list;}
指定统计的类
:result::after { content: counter(list) }
取出统计数目多少。
可以用于文章标题前的序号。
css 属性用于在元素的 ::before 和 :: after 伪元素中插入内容
可选值:
1) none 不会产生伪元素
2)normal 被视为none
3) string 文本内容 或者 css 特殊符号 '\2724'
4)url(http://www.goole.com) 外部资源
5) attr(x) 返回 元素的x 属性。比如下面,获取元素的dataset 属性值。
<div class = "thumbnail" data-title= "bacon"> .thumbnail::before{ content: attr(data-title) }
6) counter(list) 指定计数器
在进行旋转动画:
transform: rotateX()
或者 transform: rotateY()
进行 3Dtransform 时,管理元素的背面是否可见, 默认值是visible
perspective 可以看成透视,视角,CSS3 3D transform的透视点是在浏览器的前方。
border-bottom-color: rgba(0,0,0,0.8);
width: 0 ;
width 指定为0 ,只为其中一边设置颜色,其他border-color: transparent
或者 只为一边设置border-width: 其他border-width: 0;
在写上一篇js 基础知识整理的过程中,读到了很多文章,感觉对基础知识掌握牢固,是有非常有必要的。于是,这篇文章介绍一些css 容易被忽略的基础知识。
css 权重决定了哪一条规则会被浏览器应用到元素上,id选择器的权重比属性选择器更高,类选择器比任意数量的元素选择器更高,
权重等级:
比较规则
- 1,0,0,0 > 0,99,99,99 从左往右逐个等级比较,前一等级相等才会往后比。
css2.0规范对margin重叠有如下的描述:
1. 水平边距永远不会重合,
2. 垂直边距可能在特定的框之间重合。
相应的计算规则
margin 相邻的条件:
都属于流内块级盒,处于同一个格式化上下文。
新建块级格式化上下文(BFC)的条件:
没有行盒(line box),没有空隙,没有 padding 或者 border 把上下盒子分隔开。
最后一个流内子级的bottom margin和它的父级的bottom margin,如果父级的高度的计算值为'auto'。
都属于竖直相邻盒边
参考链接
变换,偏移,旋转,缩放等属性
过渡,有时间性,连续性,针对常规的css属性,平滑的改变css的值,
transition: property duration timing-function delay;
all
, 可选 none
,property
;linear
,ease
,ease-in
,ease-out
,ease-in-out
,cubic-bezier(n,n,n,n)
动画,
willchange
参考链接:
CPU 即 **处理器,GPU 即图形处理器
CPU擅长逻辑控制,串行的运算,有强大的ALU(算术运算单元),很少的时钟周期内完成算术计算,就像老教授一样,什么都会算。
GPU 基于大的吞吐量设计,它的工作都是计算量大,但是没有什么技术含量,重复很多次。就像几亿次一百以内加减乘除一样,最好的办法就是雇佣几十个小学生。而不是老教授
触发GPU加速的条件:
will-change: transform;
告诉浏览器,内容要动画变化了。 will-change: auto;
重置,关闭加速。通常写在伪元素上,遵循最小化原则。开启GPU 加速,手机耗电量会加快
通常,在管理后台的数据交互中,都会使用到tree 来保存大量的带有父子关系的数据。这种tree ,可能是对象数组的形式存放着
[{
content:"1",
parent1:"parent1",
id:"1",
children:[{
content:"222",
id:"11",
child1:"child1"
},{
content:"333",
id:"22",
child2:"child2"
},{
content:"444",
id:"33",
child3:"child3"
}]
}]
类似上面这样的数据存储格式,非常方便的存储,但是在查找的时候,就需要利用递归或者循环不断的遍历子节点。所以就可以修改成下面这样,加一个pid 字段
[{
id:"1",
content:"1",
parent1:"parent1"
},{
pid:"1",
id:"11",
content:"222",
child1:"child1"
},{
pid:"1",
id:"22",
content:"333",
child2:"child2"
},{
pid:"1",
id:"33",
content:"444",
child3:"child3"
}]
从tree到数组的转换,可以把查找一次循环的范围内,同时在后端存储时,也节省了表空间。
然后,就涉及到了树与数组之间的相互转换
const getTreeToArray = (menu) => {
let result = []
let data = JSON.parse(JSON.stringify(menu))
data.forEach(function(item,idx){
result.push(item)
})
result.forEach(function(item,idx){
if(item.children) {
result = result.concat(item.children);
delete item.children
}
})
return result;
}
const getArrayToTree = (menu) => {
let result = [],cloneObj= {};
let data = Array.prototype.slice.call(menu);
data.forEach(function(item,idx){
cloneObj[item["id"]] = data[idx]
})
data.forEach(function(item,idx){
var haveChild = cloneObj[item['pid']];
if(haveChild) {
!haveChild['children'] && (haveChild['children'] = [])
haveChild['children'].push(item)
}else {
result.push(item)
}
})
return result;
}
想请教您一个问题,不知道能不能加到您的微信
归纳es6 中常用的数组方法。方便平时熟练使用,记忆。
将类对象转为真正的数组:比如一些类数组的对象和可遍历的对象
let arrayLike = {
"0":'a',
"1":"b",
"2":"c",
length:3
}
// ES5 的写法
var arr1 = [].slice.call(arrayLike);
// ES6 写法
var arr2 = Array.from(arrayLike);
或者是 NodeList 集合 和 arguments对象
var nodeList = document.querySelectorAll("p");
var newArray = Array.from(nodeList); // 转换成数组。
// arguments
var args = Array.from(arguments);
of 方法用于将一组值,转换为数组。基本上可以用来替代Array() 或者 new Array(),
Array.of(1,2,3); // [1,2,3]
Array.of(1); //[1]
Array.of(undefined); // [undefined]
可以使用下面代码模拟
function ArrayOf() {
return [].slice.call(arguments);
}
Array.prototype.copyWithin(target,start = 0,end = this.length)
包含三个参数:
[1,2,3,4,5].copyWithin(0,3,4) // [4, 2, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, -2, -1) // [4, 2, 3, 4, 5]
数组实例的find方法,用于找出第一个符合条件的数组成员。
[1,2,3,4].find(function(value,index,arr){
return value>3
})
// 4
接受第二个参数,用来绑定this 值
var p = {number:3}
[1,2,3,4,5].find(function(value,index,arr){
return value>this.number
},p)
// 4
返回第一个符合条件的数组成员的位置
[1,2,3,4].findIndex(function(value,index,arr){
return value>3
})
// 3
fill 方法使用给定值,填充一个数组
new Array(4).fill(4); // [4, 4, 4, 4]
实现类似 [[1,1...],[2,2...],...[n,n,n...]] 这样的二维数组,里面子数组的值为动态的
var n = 5;
new Array(n).fill(2).map(function(value,index){ return new Array(n).fill(index)})
使用ES5写法
Array.fill = function(number,filler){
var arr = [];
for(var i =0;i<number;i++) {
arr.push(filler)
}
return arr;
}
Array.fill(4,1)
返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes 方法类似。
ES2016引入该方法
[1,2,3].includes(1) // true;
[1,2,3,NaN].includes(NaN) // true
// 相对应的 indexOf 就无法检测出 NaN
[1,2,3,NaN].indexOf(NaN); // -1
在基于类的语言中,对象是类的实例,并且类可以从另一个类继承。JavaScript是一门基于原型的语言,这意味着对象直接从其他对象继承,通过构造函数产生对象。
当一个函数对象被创建时,会被赋予一个prototype
属性,它的值是一个包含contructor
属性且属性值为新函数的对象。
prototype
对象则是存放继承特征的地方。
使用new前缀去调用一个函数时,函数执行的方式会被修改。类似下面的方法实现new操作符
Function.method('new',function(){
// 继承自构造函数的原型,创建一个对象
var that = Object.create(this.prototype);
// 调用构造函数,绑定 this 到新对象上;
var other = this.apply(that,arguments);
// 如果它的返回值不是一个对象,就返回新对象
return (typeof other === 'object' && other) || that;
})
使用伪类的方式继承
首先定义一个构造器:
var Mammal = function(name) {
this.name = name;
}
Mammal.prototype.get_name = function(){
return this.name;
}
Mammal.prototype.says = function(){
return this.saying || '';
}
可以构造另一个伪类来继承Mammal,这是通过定义它的constructor
函数并替换它的prototype
为一个Mammal
的实例来实现:
var Cat = function (name) {
this.name = name;
this.saying = 'meow';
};
// 替换 Cat.prototype 为一个新的Mammal 实例
// 利用原型链实现继承
Cat.prototype = new Mammal();
Cat.prototype.get_name = function() {
return this.says() + ' ' + this.name + " " + this.says();
}
var myCat = new Cat('Henrietta');
var says = myCat.says(); // "meow"
var name = myCat.get_name(); //"meow Henrietta meow"
所以,可以定义一个 inherits 方法,来实现继承,同样利用在Function.prototype上实现的method方法
Function.methos('inherits',function(Parent) {
this.prototype = new Parent();
return this;
})
在使用prototype 实现继承的时候,也有一些糟糕的地方:
var myObject = maker(f,l,m,c,s);
替换成下面这样,会更加友好;
var myObject = maker({
fisrt:f,
middle:m,
last:l,
state:s,
city:c
})
使用应用模块模式,从构建一个生成对象的函数开始,实现对象继承。
可以大致分为以下四个步骤:
下面就是一个函数化构造器的伪代码模板;
var constructor = function(spec,my) {
var that // 其他的私有实例变量
my = my || {};
// 把共享的变量和函数添加到my 中
that = 一个新对象
// 添加给that 的特权方法
return that;
}
然后把这个模式运用到上面的mammal 例子中。没有用到my,直接使用了spec 对象。
其中的name 和 saying 属性现在是完全私有的。只有通过get_name 和 says 两个特权方法才可以访问它们。
var mammal = function(spec) {
var that = {};
that.get_name = function() {
return spec.name;
};
that.says = function() {
return spec.saying || '';
};
return that;
}
var myMammal = mammal({name:'Herb'})
在前面提到的伪类模式下,构造函数Cat 不得不重复构造器Mammal已经完成的工作。在函数化中就不再需要了。
var cat = function(spec) {
spec.saying = spec.saying || 'meow';
var that = mammal(spec);
that.purr = function(n) {
var i,s = '';
for(i = 0;i<n;i+=1) {
if(s) {
s+='-';
}
s += 'r';
}
return s;
};
that.get_name = function() {
return that.says() + ' '+spec.name + ' '+that.says();
};
return that;
};
var myCat = cat({name:'Henrietta'})
还可以提供一种处理父类方法的方法,提供一个superior方法,它取得一个方法名并返回调用那个方法的函数。该函数会调用原来的方法,尽管属性已经变化了。
Object.method('superior',function(name) {
var that = this,
method = that[name];
return function () {
return method.apply(that,arguments)
}
})
在下面的例子中,就使用到了父类的get_name
方法,虽然后面重新定义了get_name
方法
var coolcat = function(spec) {
var that = cat(spec),
super_get_name = that.superior('get_name');
that.get_name = function(n) {
return 'like ' + super_get_name() +' baby';
};
return that;
};
var myCoolCat = coolcat({name:'Bix'});
var name = myCoolCat.get_name();
// "like meow Bix meow baby"
函数化模式,让我们得到跟好的封装和信息隐藏,以及访问父类方法的能力。
从一套部件中把对象组装出来,给对象设置原始的功能函数。示例中,构造一个给任何对象添加简单事件处理特性的函数,一个on方法,fire方法和一个私有的事件注册对象。
var eventuality = function(that) {
var registry = {};
that.fire = function(event) {
var array,
func,
handler,
i,
type = typeof event === 'string' ? event:event.type;
if(registry.hasOwnProperty(type)) {
array = registry[type];
for(i =0 ;i<array.length;i+=1) {
handler = array[i];
func = handler.method;
if(typeof func === 'string') {
func = this[func];
}
func.apply(this,handler.parameters || [event]);
}
}
return this;
};
that.on = function(type,method,parameters) {
var handler = {
method:method,
parameters:parameters
};;
if(registry.hasOwnProperty(type)) {
registry[type].push(handler);
} else {
registry[type] = [handler];
}
return this;
};
return that;
};
这样就可以在任何单独的对象上调用eventuality
,或者在that
被返回前在一个构造函数中调用它
eventuality(that);
JavaScript 提供了一种拥有一些类数组(array-like)特性的对象。它把数组的下标转换成字符串
var myArray = [];
myArray.length // 0
myArray[1000] = true;
myArray.length //1001;
length
设小,将导致所有下标大于等于新length的属性被删除。length
,可以附加一个新元素到该数组的尾部。
var number =[1,2,3];
number[3] = 4;
number; // [1,2,3,4]
push
也能达到同样的效果
number.push(3);
number; // [1,2,3,4]
利用 delete
运算符可以用来从数组中移除元素,但是会留下一个空洞undefined,并且数组的长度也不会发生变化
delete number[2];
// logs [1, 2, undefined × 1, 4]
使用splice
方法,删除。被删除属性后面的每个属性必须被移除,并且以一个新的键值重新插入。
number.splice(2,1);
// number [1,2,4]
使用 for in 语句进行枚举,不能保证属性的顺序,还有可能从原型链中得到意外的属性,推荐使用for 循环。
通过给Array.prototype扩充一个函数,每个数组都可以继承这个方法
Array.method('reduce',function(f,value) {
var i;
for(i = 0;i<this.length;i++) {
value = f(this[i],value);
}
return value;
})
这里的method
方法,则是挂载到Function.prototype
上面的,在基本类型的构造函数上添加方法时,就可以省去了 prototype
。
Function.prototype.method = function(name,fn) {
this.prototype[name] = fn;
return this;
}
接受一个函数和一个初始值作为参数,计算出一个新值后,再作为函数的参数执行。
var data = [1,2,3,4,5];
var add = function(a,b) {
return a+b;
}
var sum = data.reduce(add,0) // sum is 15;
var mult = function(a,b) {
return a*b;
}
var result = data.reduce(mult,1) // result is 120
JavaScript 不会预置值,使用[]
得到一个新数组,里面是空的,所以应该提供类似Array.dim
这样的方法来做这件事。
Array.dim = function(dimension,initial){
var a = [],i;
for(i = 0;i<dimension;i++) {
a[i] = initial;
}
return a;
}
var myArray = Array.dim(10,0);
// myArray is [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
目前来看,es6里面提供了一个fill()
方法,就弥补了这个空缺。
var myArray = new Array(10).fill(0);
// myArray is [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
创建一个二维数组,就必须手动去创建
for(i = 0; i<n;i+=1) {
my_array[i] = [];
}
如果使用 Array.dim(n,[]),就会使每个元素指向同一个数组的引用,同样使用fill 也会有这个问题
var my_array = new Array(3).fill([]);
my_array[2][0] = 1;
// my_array; [[1],[1],[1]]
创建矩阵,同时希望它们有不同的初始值,也可以手动实现
Array.matrix = function(m,n,initial){
var a,i,j,mat = [];
for(i = 0;i < m;i++) {
a = [];
for(j = 0; j<n;j++) {
a[j] = initial;
}
mat[i] = a;
}
return mat;
}
然后,构建一个用0 填充的4 * 4矩阵
var myMatrix = Array.matrix(4,4,0);
或者,构建一个单位矩阵。
Array.identity = function(n) {
var i ,mat = Array.matrix(n,n,0);
for(i = 0;i<n;i+=1) {
mat[i][i] = 1;
}
return mat;
}
myMatrix = Array.identity(4);
总结一些js 基础相关知识点,收集网上分析透彻的文章,希望通过整理的文章,对JavaScript有一个全面深刻的认识。当然,这些基础,也是面试过程中非常容易被问到的,有空拿出了回味一下,也许会有意想不到的效果。
以下知识点分类,许多都有共同交叉的地方,尽量全部都通读一遍,才能更加深入理解
ECMAScript的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据。定义变量时使用var关键字。5种基本数据类型:Undefined,Null,Boolean,Number和String,3中引用类型:Array,Function,和Object,严格意义上说,三种都可以归纳为对象
正因为JavaScript的松散类型,参数传递也变得复杂,但是都可以看成按值传递,分别细分为传递的是变量本身还是引用,这里和变量类型一起讲解,可以加深对js中变量类型的理解。
在面向对象编程中,this的指向非常重要,从一个角度上看,它的值取决于调用的模式.this到对象的绑定,发生在函数被调用的时候,称为 延迟绑定。有一种通俗的解释,谁调用它,this就指向谁。
作用域(scope)可以理解为不同变量和函数的访问范围,JavaScript 中全局作用域 ,局部作用域,在不同场景下生成不同的作用域,可以理解为函数外部的为全局作用域,函数内部的为局部作用域。
提及到了闭包的概念,内层函数能够访问外层函数的变量,形成了闭包,可以用来实现每个模块都有属于自己的作用域,不互相影响,间接实现了私用变量(private varibale)
虽然都提倡尽可能延迟声明变量,但JavaScript中缺少块级作用域,所以尽量在函数体顶部声明即将用到的所有变量
JavaScript 中 上下文(context)指的是
this
的值,而执行上下文(execution context)涉及到作用域的概念而不是上下文,JS 单线程语言,每次只能一个任务。刚开始执行上下文中默认会添加进去全局,遇到每个函数都会创造一个属于自己的执行上下文。
执行上下文的代码被分为两个基本的阶段来处理:
进入执行上下文 和 执行代码
执行上下文与变量关系密切。
变量自己应该知道它的数据存储在哪里,并且知道如何访问,这种机制称为变量对象(variable object)
变量对象(缩写为(VO))是一个与执行上下文相关的特殊对象,它存储着在上下文中声明的以下内容(详细解释看看下面的第一篇文章):
1,函数的形参,2,函数声明(FunctionDeclaration,缩写为FD),3,变量声明(var,变量声明)
全局上下文中的变量对象就是全局对象自己,函数上下文中变量对象是不能直接访问的,由活动对象(activeation object,缩写为AO)扮演VO的角色
在全局上下文中,
global = {
Math:<...>,
String:<...>,
...
window:global // 引用自身
}
在函数上下文中,
AO= {
arguments:<Argo>
}
在进入执行上下文,涉及到变量声明和函数声明的问题。在变量声明时,如果变量名称跟已经声明的形式参数或函数相同,则声明不会干扰已经存在的这类属性,相反的,函数声明创建时,如果变量对象已经存在相同名称的属性,则完全替换这个属性。
从此可以看出函数声明提升的优先级要高于变量声明提升。
包括了 三种方法的应用场景,每种方法对改变函数内部的
this
上下文指向和传递给函数参数的方式不同。
setTimeout配合 Promise 完美的封装异步操作,在循环的setTimeou 回调中,如何利用闭包传递变量.理解JavaScript中堆栈中任务优先执行,消息队列中的任务等待同步任务执行完成后才开始执行。
围绕setTimeout的面试题,同时可以考察对变量作用域,闭包,IIFE,异步队列任务,js执行顺序,单线程等,所以下面列出了面试题为主的文章
"prototype“属性值是一个对象,它用来实现继承和共享属性,对象__proto__属性的值就是它所对应的原型对象。
最原始的几个对象:
Object.prototype
是JavaScript中原始对象,所以Object.prototype.__proto__
为null
。为了构建各种类型的函数,产生了另一个原始对象Function.prototype
。
所以object
,Function
,String,
Number,
Boolen',Array
等等的__proto__都是 Function.prototype
。
通过instanceof
验证下 Array instanceof Function
为 true
.
知乎上有一张图,非常生动描述了上面的关系;
每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法
判断的时候,通常会用到instanceof
运算符,来测试一个对象在其原型链中是否存在一个构造函数的prototype
属性
Object instanceof Object
// logs true
Function构造函数本身也算是Function类型的实例吗?
Function.__proto__===Function.prototype // logs true
...
看完上面的概念,基本对JavaScript有了大致掌握,下面可以通过几道题目,考察下情况。
arguments数组: 函数可以通过此参数访问所有它被调用时传递给它的参数列表,包括哪些没有被分配给函数声明时定义的形式参数的多余参数。
类似数组"(array-like)"的对象。arguments拥有一个length属性,没有任何数组方法。
return被执行,函数立刻返回而不再执行余下的语句
异常是干扰程序的正常流程的不寻常(但并非完全是出乎意料的)事故
try {
if(false) {
throw {
name:"TypeError",
message:"number is required"
}
}
}catch(e) {
document.write(e.name + ": "+e.message)
}
throw 语句中断函数的运行。对象被传递到catch 从句中被捕获。
通过给Function.prototype 增加方法,来使得该方法对所有函数可用:
Function.prototype.method = function(name,func) {
this.prototype[name] = func;
return this;
}
这样在函数,数组,字符串,数字,正则表达式和布尔值等基本类型的构造函数上添加方法时,就可以省去prototype 这几个字符。使得对应的所有变量都适用改方法
为Number类型加上一个取整方法
Number.method('integer',function(){
return Math[this<0?'ceil':'floor'](this);
});
为String加上一个去除首尾空白的方法
String.method("trim",function(){
return this.replace(/^\s+|\s+$/g,'');
})
与类库一起混用是,在确定没有该方法时才添加它
// 符合条件时才增加方法
Function.prototype.method = function(name,func) {
if(!this.prototype[name]) {
this.prototype[name] = func;
}
return this;
}
直接或者间接地调用自身的一种函数,它把一个问题分解为一组相似的子问题
经典的递归问题汉诺塔。塔上有3根柱子(1,2,3)和一套子直径各不相同的空心圆盘。
开始盘子从小到大的顺序堆叠在1号柱子上,目标是要经过2号柱子全部移动到3号柱子上,中间不允许较大的盘放在较小的盘上;
分解成子问题:
var hanoi = function(disc,src,aux,dst) {
if(disc > 0) {
hanoi(disc -1,src,dst,aux);
document.writeln('Move disc '+disc + " from "+src+"to "+dst);
hanoi(disc-1,aux,src,dst)
}
};
hanoi(3,'Src','Aux',"Dst");
//
Move disc 1 from Srcto Dst
Move disc 2 from Srcto Aux
Move disc 1 from Dstto Aux
Move disc 3 from Srcto Dst
Move disc 1 from Auxto Src
Move disc 2 from Auxto Dst
Move disc 1 from Srcto Dst
利用递归高效地操作树形结构,比如浏览器端的文档对象模型(DOM),
var walk_the_DOM = function walk(node,func) {
func(node);
node = node.firstChild;
while(node) {
walk(node,func);
node = node.nextSibling;
}
}
// 定义 getElementsByAttribute 函数,
//它以一个属性名称字符串和一个可选的匹配值作为参数。
var getElementsByAttribute = function(att,value) {
var results = [];
walk_the_DOM(document.body,function(node) {
var actual = node.nodeType === 1 $$ node.getAttribute(att);
if(typeof actual === 'string' && (actual === value || typeof value != 'string')) {
results.push(node)
}
});
return results;
}
一些语言提供了尾递归优化。这意味着如果一个函数返回自身递归调用的结果,那么调用的过程会被替换为一个循环,可以显著提高速度。
//构建一个带尾递归的函数。因为它会返回自身调用的结果
// 实现 factorial = n*(n-1)(n-2)... 1;
var factorial = function factorial(i,a) {
a = a || 1;
if(i<2) {
return a;
}
return factorial(i-1,a*i);
};
document.writeln(factorial(4)) // 4*3*2*1 = 24
作用域控制着变量与参数的可见性及生命周期
函数作用域: 定义在函数中的参数和变量在函数外部是不可见的,而在一个函数内部任何位置定义的变量,在该函数内部任何地方都可见。
在JavaScript中缺少会计作用域,最好的做法是在函数体的顶部声明函数中可能用到的所有变量。
闭包:函数可以访问它被创建时所处的上下文环境。
内部函数可以访问外部函数的实际变量而无需复制
作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量(除了this 和arguments)
内部函数拥有比它的外部函数更长的生命周期。
var myObject = (function(){
var value = 0;
return {
increment:function(inc) {
value += typeof inc === 'number' ?inc :1;
},
getValue:function() {
return value;
}
}
})()
上面定义的value变量对increment和getValue方法总是可用的,但函数的作用域使得它对其他的程序来说是不可见的
异步请求,提供一个服务器的响应到达是随即触发的回调函数,
异步函数立即返回。
使用 函数 和 闭包创造模块
模板的一般形式:
利用前面使用的method
方法,为String 增加一个deentityify 方法,寻找字符串中的HTML字符实体并把它们替换为对应的字符。
String.method('deentityify',function(){
// 字符实体表,它映射字符实体的名字到对应的字符
var entity = {
quot: '"',
lt:'<',
gt:'>'
};
// 返回 deetityify 方法
return function() {
// 下面就是deetityify方法,
return this.replace(/&([^&;]+);/g,
function(a,b){
var r = entity[b];
return typeof r === 'string'?r:a;
}
)
}
}());
最后,使用()运算符立刻调用我们刚刚构造出来的函数,这个调用所创建并返回的函数才是deentityify
方法
document.writeln('<">'.deentityify()) // <">
让执行后的函数返回this 而不是 undefined,就可以启用级联
一个级联中就,我们可以在单独一条语句中依次调用同一个对象的很多方法。一个启用了Ajax类库可能允许我们以这样的形式去编码
getElement('myBoxDiv')
.move(300,150)
.width(100)
.height(200)
...
.tip('This box is resizeable')
上面的例子中,每次调用返回的结果都会被下一次调用所用。
柯里化允许我们把函数与传递给它的参数相结合,产生一个新的函数
Function.method('curry',function(){
var slice = Array.prototype.slice,
args = slice.apply(arguments),
that = this;
return function() {
return that.apply(null,args.concat(slice.apply(arguments)))
}
})
function add (a,b) {return a+b}
add.curry(1)(6) //7
函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算,这种优化被称为记忆
以Fibonacci数列为例,一个Fibonacci数字是之前两个Fibonacci数字之和。最前面的两个数字时0和1
var fibonacci = function (n) {
return n<2? n:fibonacci(n-1) + fibonacci(n-2)
}
for(var i =0 ;i< = 10;i+=1) {
document.writeln('//'+i+': '+fibonacci(i))
}
//0: 0
//1: 1
//2: 1
//3: 2
//4: 3
//5: 5
//6: 8
//7: 13
//8: 21
//9: 34
//10: 55
改进,利用memo数组保存我们的存储结果,存储结果可以隐藏在闭包中。
var fibonacci = function() {
var memo = [0,1];
var fib = function(n) {
var result = memo[n];
// 检查结果是否已存在,如果已经存在,就立刻返回这个结果
if(typeof result !== "number") {
result = fib(n-1) + fib(n-2);
memo[n]= result;
}
return result
};
return fib;
}();
fibonacci(10) //55
推而广之,编写一个函数来帮助我们构造带记忆功能的函数。
var memoizer = function(memo,formula) {
var recur = function(n) {
var result = memo[n];
if(typeof result !== "number") {
result = formula(recur,n);
memo[n] = result;
}
return result;
};
return recur;
}
var fibonacci = memoizer([0,1],function(recur,n) {
return recur(n-1) + recur(n-2);
});
fibonacci(10) // 55
nginx 提供了一种无阻塞的内部请求到其它的locations 配置
( 在 一个普通的location中,加入 internal 指令,可以把当前接口设置为内部接口)
res = ngx.location.capture(uri)
返回一个lua table 类型的变量: 包括 res.status, res.header, res.body, and res.truncated
capture 中,第二个参数,对应一个可选的table 变量,
method, 规定请求的方法, ngx.HTTP_POST,
body, 指定请求体(only string)
args, 规定url 里面的请求参数,
ctx, 规定lua table 作为子请求的 ngx.ctx ,
vars 规定一个lua table , 设置在子请求中可以获取到指定的Nginx 变量
copy_all_vars boolean 类型,指定是否拷贝当前请求中的Nginx 变量到 子请求中, 并且 子请求中的
变量改变 不会影响当前 父请求(copy)
share_all_vars 规定是否与子请求分享当前父请求中的所有的nginx 变量, 子请求改变 会影响当前请求
(share)
always_forward_body boolean类型,如果设置了true,表示如果当前父请求没有设置body 参数,就会直接转发请求体到子请求,
与上面capture类似,同时发起多个子请求,
特点:
ngx.location.capture =
function (uri, args)
return ngx.location.capture_multi({ { uri,args} })
end
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.