Giter Site home page Giter Site logo

blog's People

Contributors

zsi2017 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

jinggege77

blog's Issues

sed , grep , awk命令

sed 是一种流编辑器,文本处理中常用的工具,通常情况下,会把当前处理的行存储在临时缓存区中,最后将处理结果输出到终端,所以通常不会改变原文本的内容。
常用操作备忘:

  1. 替换文本中的字符串: sed 's/book/books.' file
  2. 直接编辑源文件: sed -i 's/book/books/g file'
  3. 删除操作: sed '2,$d' file 删除第2行到末尾
  4. 分隔符: sed 's#book#books#g example' 紧跟在s 命令后的都可以被认为是新的分隔符,
  5. N命名: sed 'N;s/book/books/g file' 把原文本中的下一行,也就是偶数行,放在缓存区做匹
    配。相当于两行合并到一起进 行匹配。最后的效果就是 lin1 \n lin2
  6. a, i 命令: a 命令就是 append, i 命令就是insert
  7. p: sed -n '/book/p' file看成grep来使用, 结合 -n 参数一起使用。 就可以打印匹配的那一行。
  8. :a ,ta, 这对符号中 :a 是先做一个标记,然后 如果 ta 之前执行成功,则跳转到 :a 标识继续循环执行。相当于c 语言中的goto 语句。
    具体可以 删除文本中所有的 /n, 结合刚才的N 命令, sed ':a;N;s/\n//;ta' 1.txt ,通过; 进行分割,可以同时执行多个命令。

grep 用于查找文件中符合条件的字符串。查找成功,会把那 一行显示出来。 如果后面没有指定文件,就会从标准输入中读取数据,或者经常用到 查看服务的命令 ps -ef | grep nginx , 就可以在所有的服务中,筛选出nginx 服务。

  1. 基本查找: grep width 1.css 在命令行输出1.css 文件中包含 'width' 字符串的行
    2 .递归查找: grep -r width ./ 在当前文件目录下,递归查找所有的子目录,找到包含‘width'的文件,已经文件中对应的行内容。
  2. 反向查找: grep -v width 1.css 打印导出1.css 文件中,不包含‘width’的行。通常用来筛选出log.
  3. 不显示任何形象: -q 不显示任何信息 。 效果 等同于 grep width 1.css > /dev/null , 利用输出重定向到 /dev/null, 这个文件代表linux的空设备文件,所有往这个文件里面写入的内容都会丢失

awk 一个强大的文本分析工具,将文件逐行读入,并且每一行都以空格或者tab进行分割,可以对分割后的字段,利用$1,$2... 获取到,进行分析。

  1. 指定域分割符: -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}' 这里表示先用‘;’进行分割,然后再使用‘:’进行分割。

  2. 设置变量: -v awk -va=1 '{print a}' 1.css

  3. 直接使用脚本文件: awk -f scriptfile filename;

  4. 内建变量:

    • $n 当前记录的第n 个字段;
    • $0 完整的输入记录
    • NF 一条记录的字段的数目
    • NR 当前记录的行号 (从1开始)
    • RS 记录分割符(默认是一个换行符号)
      ...
      打印出偶数行数据:
      awk 'NR % 2 == 0' filename
  5. awk脚本:

    • BEGIN {第一行记录前执行的语句}
    • {处理每一行要执行的语句}
    • END {所有行执行后要执行的语句}

    使用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

pre-commit钩子,代码质量检查

目前基本使用三款js代码质量检查工具: jslint, jshint, eslint。许多IDE里面也有对应的检查插件,在每次ctrl + s 保存文件的时候,检查当前文件是否符合规范,保证代码质量。
许多团队都会指定一套代码规范code review,更加严格的检查每次代码修改。 也可以在git commit之前,检查代码,保证所有提交到版本库中的代码都是符合规范的,

在看vue源码时,不免修改代码,就会触发里面配置好的钩子函数。于是,仔细研究了一下vue配置方法,可以发现配置非常简单。

git 钩子文档上介绍非常详细,git init后,在.git/hooks文件中,有一些.simple结尾的钩子示例脚本,如果想启用对应的钩子函数,只需手动删除后缀。所以,列出两种配置方法:

1. 手动修改钩子文件

按照文档上,配置钩子脚本,修改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 钩子

2. 利用yorkie or husky + lint-staged 构建钩子

在 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 环境变量。

call,apply and bind in JavaScript

call,apply and bind in JavaScript

在ECMAScript中,每个函数都包含两个继承而来的方法:apply() 和 call(),这两个方法的用途都是在特定的作用域中调用函数,主要作用跟bind一样,用来改变函数体内this的指向,或者说是在函数调用时改变上下文。

文章尽量使用大量实例进行讲解,它们的使用场景。同时,也会由浅入深的引导出一些理论,毕竟这几个常用方法,在MDN上都能找到合理的解释

基本功能

改变this的指向

  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

后面,通过调用函数上的callapply方法,该变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上添加nameage属性。

  • 数组验证的终极方法:
  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的长度,从而确定方法的startend下标,得到一个数组变量。

同样适用的还有,DOM里面的NodeList对象,它也是一种类数组对象。

深入理解

实现bind 方法

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 页面间通信

浏览器中tab页面之间进行通信,很常见,也是面试中经常被问到的问题,这里总结了一下,不同情况下的通信,大致可以分为是否同源(协议、域名和端口)和是否相关连(包含在当前页面中<iframe>元素,或者由当前页面弹出的窗口)

相关依赖页面之间的相互通信

window.postMessage() 方法,可以安全的进行跨域,跨页面通信

向其它窗口发送消息

 otherWindow.postMessage(message,targetOrigin)

接受两个参数:

  • message: 要传递的数据,可以是任意基本类型或对象
  • targetOrigin: 字符串参数,指明目标窗口的源,协议+域名+端口。这个参数是为了安全考虑。同时,设置成* ,表示可以传递给任意窗口,设置成"/",表明必须传递给与当前窗口同源

message 事件
消息传递过程中,都会触发window对象的message事件,以异步的方式触发。事件对象中包含三个参数:

  • data: 消息主体
  • origin: 消息来源的域,"http://www.123.com"
  • source: 消息来源的window对象的代理,然后向来源窗口发送回执。如果是同一个域,则对象就是window。

必须拿到其它窗口的一个引用,而不是当前窗口的应用,所以这种方式适合有依赖关系的页面之间通信
不同依赖关系下,获取其它窗口应用的方法:

  • 页面与嵌套的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.setItemlocalStorage.getItem方式。
最重要的是使用storage事件,及时通知对方获取消息,对storage对象进行任何修改,都会在文档上触发storage事件。

index.html:

<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 页面。

second.html

<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.htmlsecond.html两个页面可以看到,都监听了storage事件,方便及时获取消息。发送消息,则利用localStorage.setItem发送,为了不影响真实存储,同时removeItem了,所有在storage事件的回调里面,就判断了!ev.newValue是否为空,避免触发两次事件

使用node启动服务,server.js文件,然后node server.js
两页面分别对应http://localhost/http://localhost/second

无关不同源页面通信

即在浏览器上打开了两个没有任何关系的tab,不用域,也任何依赖关系
就可以结合前面使用的postMessagelocalStorage,利用postMessage可以跨域访问,localStorage可以在无关页面之间通信的特点,利用bridge.html页面作为中转站,实现通信。

首先看下HTML结构,在tabAtabB是需要通信的两个页面,同时它们都包含了指向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

参考链接:

css 技巧

You-Dont-Need-JavaScript 总结

1. pointer-events (禁用手势)

 默认值: auto  鼠标不会穿透当前层,
none: 最外层元素不会监听鼠标的点击事件,直接穿透到了下面的元素身上,
          如果其他的子元素设置了 pointer-events

2. :before 和 :after 伪元素

3. counter-increment 和 :checked 统计符合条件的元素个数

默认增量为1;

body {counter-reset:list;} 在父节点上 设置对应的属性,可以重置变量保存的计数;
input:checked { counter-increment: list;} 指定统计的类
:result::after { content: counter(list) } 取出统计数目多少。

可以用于文章标题前的序号。

实时统计 checked input

4. content attr 获取元素 data 属性

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) 指定计数器

5. backface-visibility 背面是否可见

在进行旋转动画:
transform: rotateX() 或者 transform: rotateY() 进行 3Dtransform 时,管理元素的背面是否可见, 默认值是visible

正反面动画

6. -webkit-perspective 透视点

perspective 透视效果

perspective 可以看成透视,视角,CSS3 3D transform的透视点是在浏览器的前方。

7. border: solid transparent 5px; 悬浮三角形

          border-bottom-color: rgba(0,0,0,0.8);  
          width: 0 ;

width 指定为0 ,只为其中一边设置颜色,其他border-color: transparent
或者 只为一边设置border-width: 其他border-width: 0;

css 基础知识整理

在写上一篇js 基础知识整理的过程中,读到了很多文章,感觉对基础知识掌握牢固,是有非常有必要的。于是,这篇文章介绍一些css 容易被忽略的基础知识。

  • css选择器权重

    css 权重决定了哪一条规则会被浏览器应用到元素上,id选择器的权重比属性选择器更高,类选择器比任意数量的元素选择器更高,

权重等级:

  1. 行内样式,指的是html文档中定义的style。权重 1000
  2. id 选择器, 比如 #div。 权重 0100
  3. 类,属性选择器 和 伪类选择器,比如: class, [type= ...], :link - :visited - :hover - :active。 权重 0010
  4. 元素 和 伪元素,比如:p , ::before,::after,::first-letter,::first-line,::selecton 权重 0001.
    (伪类表示的是一种"状态",比如hover,active 等等,而伪元素表示文档的某个确定部分的表现,比如::first-line 伪元素)
  5. 通配符,子选择器,相邻选择器等,如 *,>,+ 权重 为 0000,
  6. 继承的样式没有权值。

比较规则
- 1,0,0,0 > 0,99,99,99 从左往右逐个等级比较,前一等级相等才会往后比。

  • css 权重相关

  • css 父子margin 上下层叠,计算

    css2.0规范对margin重叠有如下的描述:
    1. 水平边距永远不会重合,
    2. 垂直边距可能在特定的框之间重合。
    相应的计算规则

    • 当两个或者更多的margin合并时,产生的margin宽度为合并margin宽度中的最大值。
    • 至于负margin,就从正相邻margin的最大值中减去负相邻margin的绝对值的最大值。
    • 如果没有正margin,就用0 减去相邻margin的绝对值的最大值。

    margin 相邻的条件:

    • 都属于流内块级盒,处于同一个格式化上下文。
      新建块级格式化上下文(BFC)的条件:

      • html 根元素
      • float的值,除了none 以外的值。
      • position 的值为absolute,或者 fixed。
      • display为inline-blocks, table-cells, table-captions.
      • overflow 除了visible以外的值(hidden,auto,scroll)
        在一个块级格式化上下文中,盒子在垂直方向上一个接一个的放置,从 包含块的顶部开始,两个相邻的盒子在垂直方向上的距离由margin 决定。同一个块级格式化上下文中的相邻块级盒之间的垂直margin会合并。
    • 没有行盒(line box),没有空隙,没有 padding 或者 border 把上下盒子分隔开。

    • 最后一个流内子级的bottom margin和它的父级的bottom margin,如果父级的高度的计算值为'auto'。

  • 都属于竖直相邻盒边

参考链接

  • css margin的相关属性,问题及应用

  • 你不一定知道的css知识——margin不重叠的情形

  • css3 动画

  • transform

    变换,偏移,旋转,缩放等属性

    • matrix,2D 转换,使用6值矩阵,matrix3d,使用16值转换
    • translate(x,y) 2D 转换,偏移。translate3d(x,y,z)3D转换,多了translateZ(z) 3d 转换
    • scale(x,y) 2D 缩放。 scale3d(x,y,z) 3D缩放
    • rotate(angle)2D 旋转。 rotate3d(x,y,z,angle)定义3D 旋转,eg:rotateZ(angle)
    • skew(x-angle,y-angle) 定义沿着x 和 y 轴的2D倾斜转换
    • perspective(n)为 3D 转换元素定义透视试图
  • transtion

    过渡,有时间性,连续性,针对常规的css属性,平滑的改变css的值,
    transition: property duration timing-function delay;

    • transition-property: css 属性的名称: 默认 all, 可选 none,property;
    • transiton-duration:time ,过渡效果需要花费的时间 2s。
    • transition-timing-function ,速度曲线,linear,ease,ease-in,ease-out,ease-in-out,cubic-bezier(n,n,n,n)
  • animations

    动画

    • animation-name。 规定@Keyframes 动画的名称
    • animation-duration。 规定动画完成一个周期所花费的 秒或毫秒。默认是 0.
    • animation-timing-function。规定动画的速度曲线,默认是 “ease”
    • animation-delay。 规定动画何时开始。默认是 0,
    • animation-iteration-count。规定动画被播放的次数。默认是1,可选 n|infinite
    • animation-direction。定义是否应该轮流反向播放动画,normal| alternate 应该轮流反向播放。
    • animation-play-state: 属性规定动画正在运行还是暂停。paused|running,规定动画已暂停或正在播放
    • animation-fill-mode: 属性规定动画在播放之前或之后,其动画效果是否可见。表示动画完成之前或者之后对应的分别保持在第一个关键帧或最后一个关键帧中定义的属性。 none | forwards | backwards | both;
  • willchange

参考链接:

CPU 即 **处理器,GPU 即图形处理器
CPU擅长逻辑控制,串行的运算,有强大的ALU(算术运算单元),很少的时钟周期内完成算术计算,就像老教授一样,什么都会算。
GPU 基于大的吞吐量设计,它的工作都是计算量大,但是没有什么技术含量,重复很多次。就像几亿次一百以内加减乘除一样,最好的办法就是雇佣几十个小学生。而不是老教授

触发GPU加速的条件:

  • 3D tarnsform 比如其中的 translate3D,scaleZ 之类。 业界称为hack加速法,实际上并不需要z轴的变化,但还是欺骗了浏览器
  • will-change 提前告诉浏览器,马上要触发大规模的绘制了,要开启GPU 加速。比如 will-change: transform; 告诉浏览器,内容要动画变化了。 will-change: auto;重置,关闭加速。通常写在伪元素上,遵循最小化原则。

开启GPU 加速,手机耗电量会加快

js保存树状数据

通常,在管理后台的数据交互中,都会使用到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到数组的转换,可以把查找一次循环的范围内,同时在后端存储时,也节省了表空间。
然后,就涉及到了树与数组之间的相互转换

  • tree -> array

      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;
      }
    
  • array -> tree

    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 扩展数组方法

归纳es6 中常用的数组方法。方便平时熟练使用,记忆。


from

将类对象转为真正的数组:比如一些类数组的对象和可遍历的对象

  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

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);
  }

copyWithin

Array.prototype.copyWithin(target,start = 0,end = this.length)
包含三个参数:

  • target(必需): 从该位置开始替换,如果为负值,则表示倒数。
  • start(可选): 从该位置开始读取数据,默认为0。负值 表示倒数。
  • end(可选): 从该位置停止读取数据,默认等于数组长度。负值,表示倒数。
 [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

数组实例的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

findIndex

返回第一个符合条件的数组成员的位置

[1,2,3,4].findIndex(function(value,index,arr){
   return value>3
})
// 3

fill

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

返回一个布尔值,表示某个数组是否包含给定的值,与字符串的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 语言精粹 - 继承

在基于类的语言中,对象是类的实例,并且类可以从另一个类继承。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 实现继承的时候,也有一些糟糕的地方:

  • 没有私有环境,所有属性都是公开的
  • 在调用构造函数的时候忘记在前面加上 new 前缀,那么this在非严格模式下,将会被绑定到全局对象上,破坏了全局变量环境。
  • 所有构造函数都约定命名或者首字母大写的形式,

对象说明符

  var myObject = maker(f,l,m,c,s);

替换成下面这样,会更加友好;

  var myObject = maker({
    fisrt:f,
    middle:m,
    last:l,
    state:s,
    city:c
  })

函数化

使用应用模块模式,从构建一个生成对象的函数开始,实现对象继承。
可以大致分为以下四个步骤:

  • 创建一个新对象。有非常多的方法去创建:
    • 比如构造一个对象字面量,
    • 或者使用new操作符,调用一个构造函数,
    • 或者使用Object.create方法去构造一个已经存在的对象的新实例
    • 或者直接调用任意一个会返回一个对象的函数。
  • 有选择性的定义私有实例变量和方法。比如通过var变量定义的普通变量。
  • 在这个新对象扩充方法
  • 返回那个新对象。

下面就是一个函数化构造器的伪代码模板;

  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 语言精粹 - 数组

JavaScript 语言精粹 - 数组

JavaScript 提供了一种拥有一些类数组(array-like)特性的对象。它把数组的下标转换成字符串

长度

  • 使用大于或者等于当前length的数字作为下标来存储一个元素,那么length值会被增大以容纳新元素
  • length 属性的值是这个数组的最大整数属性名加上1,不一定等于数组里的属性的个数
       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 基础知识整理

总结一些js 基础相关知识点,收集网上分析透彻的文章,希望通过整理的文章,对JavaScript有一个全面深刻的认识。当然,这些基础,也是面试过程中非常容易被问到的,有空拿出了回味一下,也许会有意想不到的效果。

以下知识点分类,许多都有共同交叉的地方,尽量全部都通读一遍,才能更加深入理解

变量类型与函数中的参数传递

ECMAScript的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据。定义变量时使用var关键字。5种基本数据类型:Undefined,Null,Boolean,Number和String,3中引用类型:Array,Function,和Object,严格意义上说,三种都可以归纳为对象

正因为JavaScript的松散类型,参数传递也变得复杂,但是都可以看成按值传递,分别细分为传递的是变量本身还是引用,这里和变量类型一起讲解,可以加深对js中变量类型的理解。

this

在面向对象编程中,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>
 }

在进入执行上下文,涉及到变量声明和函数声明的问题。在变量声明时,如果变量名称跟已经声明的形式参数或函数相同,则声明不会干扰已经存在的这类属性,相反的,函数声明创建时,如果变量对象已经存在相同名称的属性,则完全替换这个属性。
从此可以看出函数声明提升的优先级要高于变量声明提升。

call,apply and bind

包括了 三种方法的应用场景,每种方法对改变函数内部的this上下文指向和传递给函数参数的方式不同。

js 异步 回调操作

setTimeout配合 Promise 完美的封装异步操作,在循环的setTimeou 回调中,如何利用闭包传递变量.理解JavaScript中堆栈中任务优先执行,消息队列中的任务等待同步任务执行完成后才开始执行。

围绕setTimeout的面试题,同时可以考察对变量作用域,闭包,IIFE,异步队列任务,js执行顺序,单线程等,所以下面列出了面试题为主的文章

prototype、_proto_、原型链

"prototype“属性值是一个对象,它用来实现继承和共享属性,对象__proto__属性的值就是它所对应的原型对象。

最原始的几个对象:
Object.prototype是JavaScript中原始对象,所以Object.prototype.__proto__null。为了构建各种类型的函数,产生了另一个原始对象Function.prototype
所以object,Function,String,Number,Boolen',Array等等的__proto__都是 Function.prototype
通过instanceof验证下 Array instanceof Functiontrue.
知乎上有一张图,非常生动描述了上面的关系;

v2-1b90d4ec60713acce99df0c498fff794_hd

每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法
判断的时候,通常会用到instanceof运算符,来测试一个对象在其原型链中是否存在一个构造函数的prototype属性

Object instanceof Object // logs true

Function构造函数本身也算是Function类型的实例吗?
Function.__proto__===Function.prototype // logs true

...

练习题

看完上面的概念,基本对JavaScript有了大致掌握,下面可以通过几道题目,考察下情况。

《JavaScript 语言精粹》(修订版) 读书笔记 - 第四章 函数 Functions (二)

参数 arguments

arguments数组: 函数可以通过此参数访问所有它被调用时传递给它的参数列表,包括哪些没有被分配给函数声明时定义的形式参数的多余参数。

类似数组"(array-like)"的对象。arguments拥有一个length属性,没有任何数组方法。


返回 return

return被执行,函数立刻返回而不再执行余下的语句

  • 一个函数总是会返回一个值,没有指定返回值,则返回 undefined
  • 使用 new 调用函数,且返回值不是一个对象,则返回this (该新对象)

异常 Exceptions

异常是干扰程序的正常流程的不寻常(但并非完全是出乎意料的)事故

 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号柱子上,中间不允许较大的盘放在较小的盘上;

分解成子问题:

  • 将1号柱子上的从上到下的n-1个盘利用3号柱子移动到2号柱子上。
  • 将1号柱子上最下面的一个盘,直接移动到3号柱子上.
  • 最后把2号柱子上n-1个盘利用递归调用方法,全部移动到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中缺少会计作用域,最好的做法是在函数体的顶部声明函数中可能用到的所有变量。


闭包 Closure

闭包:函数可以访问它被创建时所处的上下文环境。

内部函数可以访问外部函数的实际变量而无需复制

作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量(除了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方法总是可用的,但函数的作用域使得它对其他的程序来说是不可见的


回调 Callbacks

异步请求,提供一个服务器的响应到达是随即触发的回调函数,
异步函数立即返回。


模块 Module

使用 函数 和 闭包创造模块

模板的一般形式:

  • 一个定义了私有变量和函数的函数
  • 利用闭包创建可以访问私有变量和函数的特权函数
  • 返回这个特权函数,或者把它们保存到一个可访问到的地方。

利用前面使用的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('&lt;&quot;&gt;'.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

ngx.location.capture 内部调用

nginx 提供了一种无阻塞的内部请求到其它的locations 配置

capture

( 在 一个普通的location中,加入 internal 指令,可以把当前接口设置为内部接口)
res = ngx.location.capture(uri)

返回一个lua table 类型的变量: 包括 res.status, res.header, res.body, and res.truncated

  • res. status 保存子请求的状态码
  • res.header 保存所有的响应头,一个lua table 变量, 如果是多个子请求,则放在一个 lua array 中
  • res.body 保存响应的请求体,可能会被阶段
  • res.truncated boolean 类型变量,对应上面的body结构中是否包含被截断的数据。

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_multi

与上面capture类似,同时发起多个子请求,

特点:

  • 请求返回结果中的顺序,与发起子请求的顺序相同
  • 所有子请求都返回,才会返回最终的结果
  • 请求的时间,取决于所有子请求返回时间中最长的一个,(平行)
  • ngx.location.capture 其实是capture_multi 的一种特殊格式
        ngx.location.capture =  
                 function (uri, args)
                      return ngx.location.capture_multi({ { uri,args} })
                 end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.