<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>小宇的博客</title><description>越努力，越幸运</description><link>https://blog.cxyxiaoyu.top/</link><language>zh_CN</language><item><title>setup语法糖的使用</title><link>https://blog.cxyxiaoyu.top/posts/setup%E8%AF%AD%E6%B3%95%E7%B3%96%E7%9A%84%E4%BD%BF%E7%94%A8/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/setup%E8%AF%AD%E6%B3%95%E7%B3%96%E7%9A%84%E4%BD%BF%E7%94%A8/</guid><pubDate>Sat, 26 Feb 2022 15:24:16 GMT</pubDate><content:encoded>&lt;h3&gt;前言&lt;/h3&gt;
&lt;p&gt;在setup()中手动暴露状态和方法会很冗长。幸运的是,只有在不使用构建步骤时不是必要的.当使用单文件中，我们可以通过&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;大大简化使用&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md&quot;&gt;参考链接&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;基本使用&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
  // imported components are also directly usable in template
  import Foo from &apos;./Foo.vue&apos;
  import { ref } from &apos;vue&apos;

  // write Composition API code just like in a normal setup()
  // but no need to manually return everything
  const count = ref(0)
  const inc = () =&amp;gt; {
    count.value++
  }
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;Foo :count=&quot;count&quot; @click=&quot;inc&quot; /&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译结果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Foo from &apos;./Foo.vue&apos;
import { ref } from &apos;vue&apos;

export default {
  setup() {
    const count = ref(1)
    const inc = () =&amp;gt; {
      count.value++
    }

    return function render() {
      return h(Foo, {
        count,
        onClick: inc
      })
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;定义 props 和 emit&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
  // expects props options
  const props = defineProps({
    foo: String
  })
  console.log(props.foo)

  // expects emits options
  const emit = defineEmits([&apos;update&apos;, &apos;delete&apos;])
  emit(&apos;update&apos;)
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译结果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export default {
  props: {
    foo: String
  },
  emits: [&apos;change&apos;, &apos;delete&apos;],
  setup(props, { emit }) {
    // setup code
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;导入组件&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; 内导入的组件，可以直接在模版中使用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
  import Foo from &apos;./Foo.vue&apos;
  import MyComponent from &apos;./MyComponent.vue&apos;
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;Foo /&amp;gt;
  &amp;lt;my-component /&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译结果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Foo from &apos;./Foo.vue&apos;
import MyComponent from &apos;./MyComponent.vue&apos;

export default {
  setup(){
    return function render(){
      return [h(Foo),h(MyComponent)]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;使用指令&lt;/h4&gt;
&lt;p&gt;指令以类似的方式工作--除了名为 v-my-dir 的指令名 映射到 setup作用域中 名为 vMyDir 的变量&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
  import { directive as vClickOutside } from &apos;v-click-outside&apos;
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div v-click-outside /&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译结果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { directive as vClickOutside } from &apos;v-click-outside&apos;

export default {
  setup() {
    return function render() {
      return withDirectives(h(&apos;div&apos;), [[vClickOutside]])
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;使用 slots 和 attrs&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
  import { useSlots, useAttrs } from &apos;vue&apos;

  const slots = useSlots()
  const attrs = useAttrs()
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;暴露组件公用接口&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
  const a = 1
  const b = ref(2)

  defineExpose({
    a,
    b
  })
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;与普通的script标签一起使用&lt;/h4&gt;
&lt;p&gt;有些情况下，代码必须在模块范围内执行，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;申报命名出口&lt;/li&gt;
&lt;li&gt;应该只执行一次的全局副作用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在这种情况下，正常的&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;块可以与&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;一起使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  performGlobalSideEffect()

  // this can be imported as `import { named } from &apos;./*.vue&apos;`
  export const named = 1
&amp;lt;/script&amp;gt;

&amp;lt;script setup&amp;gt;
  import { ref } from &apos;vue&apos;

  const count = ref(0)
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译结果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import {ref} from &apos;vue&apos;
performGlobalSideEffect()
export const named = 1
export default {
  setup(){
    const count = ref(0)
    return { count }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;声明其他选项&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;语法提供了表达大多数现有选项API选项的等效功能的能力，但以下几个选项除外：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;name&lt;/li&gt;
&lt;li&gt;inheritAttrs&lt;/li&gt;
&lt;li&gt;插件或库所需的自定义选项&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果需要声明这些选项，请使用带有导出默认值的单独&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  export default {
    name: &apos;CustomName&apos;,
    inheritAttrs: false,
    customOptions: {}
  }
&amp;lt;/script&amp;gt;

&amp;lt;script setup&amp;gt;
  // script setup logic
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;provide &amp;amp; inject&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;
import { ref, provide } from &apos;vue&apos;
const name = ref(&apos;xiaoyu&apos;)
provide(&apos;name&apos;,name)
&amp;lt;/script&amp;gt;

&amp;lt;!-- 子组件 --&amp;gt;
&amp;lt;script setup&amp;gt;
import { inject } from &apos;vue&apos;
const name:string = inject(&apos;name&apos;)
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>vue获取组件实例 父子组件实例</title><link>https://blog.cxyxiaoyu.top/posts/vue%E8%8E%B7%E5%8F%96%E7%BB%84%E4%BB%B6%E5%AE%9E%E4%BE%8B/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/vue%E8%8E%B7%E5%8F%96%E7%BB%84%E4%BB%B6%E5%AE%9E%E4%BE%8B/</guid><pubDate>Sat, 26 Feb 2022 14:17:10 GMT</pubDate><content:encoded>&lt;h3&gt;前言&lt;/h3&gt;
&lt;p&gt;最近再写弹框组件时 通过调用函数的方式调用弹框 需要获取到组件实例 在此总结一下&lt;/p&gt;
&lt;h3&gt;Vue2&lt;/h3&gt;
&lt;h4&gt;获取当前组件实例&lt;/h4&gt;
&lt;h5&gt;1 render()&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;// render 获取组件实例
function create(Component, props) {
  const vm = new Vue({
    render: (h) =&amp;gt; h(Component, { props }), // h() 返回vnode 虚拟dom 需要挂载才能变成真实dom
  }).$mount(); // 不指定宿主元素，会创建真实dom 但是不会追加操作

  //  现在vm 就是根组件实例
  // 获取真实dom  vm.$el 就是真实dom  将真实dom 放在body上
  document.body.appendChild(vm.$el);

  // Component 是组件描述对象 comp是组件的实例
  const comp = vm.$children[0];

  // 删除
  comp.remove = () =&amp;gt; {
    document.body.removeChild(vm.$el);
    vm.$destroy();
  };
  return comp;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2 Vue.extend()&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;// Vue.extend() 获取组件构造函数
function create(Component, props) {
  // 获取组件构造函数
  const Constructor = Vue.extend(Component);
  const component = new Constructor({
    propsData: props,
  }).$mount();
  // mount 后才有$el 属性
  document.body.appendChild(component.$el);
  component.remove = () =&amp;gt; {
    document.body.removeChild(component.$el);
    component.$destroy();
  };
  return component;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;获取父组件实例&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;this.$parent
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;获取子组件实例&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;this.$children
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Vue3&lt;/h3&gt;
&lt;h4&gt;获取当前组件实例&lt;/h4&gt;
&lt;p&gt;createApp 获取组件实例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createApp, h } from &quot;vue&quot;;
const div = document.createElement(&quot;div&quot;);
const app = createApp({
  render() {
    return h(Component, {
      title: &quot;提示&quot;,
      content: &quot;内容&quot;,
      onXxx: () =&amp;gt; {
        console.log(&quot;xxx&quot;);
      },
    });
  },
}).mount(div);
document.body.appendChild(div);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果是title 和 content 是插槽&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createApp, h } from &quot;vue&quot;;
const div = document.createElement(&quot;div&quot;);
const app = createApp({
  render() {
    return h(
      MessageBox,
      {
        onXxx: () =&amp;gt; {
          console.log(&quot;xxx&quot;);
        },
      },
      {
        // h 第三个参数传递插槽
        title,
        content,
      }
    );
  },
}).mount(div);
document.body.appendChild(div);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;获取父组件实例&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import { getCurrentInstance } from &apos;vue&apos;
const instance = getCurrentInstance()
const parent = instance.parent
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;获取子组件实例&lt;/h4&gt;
&lt;p&gt;Vue3 中实例没有$children 属性了 那么子组件实例如何获取呢？&lt;/p&gt;
&lt;h5&gt;1 ref&lt;/h5&gt;
&lt;p&gt;以 form 组件为例 点击登录需要获取 loginForm 的 validate 方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- demo.vue --&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;x-form :model=&quot;userInfo&quot; :rules=&quot;rules&quot; ref=&quot;loginForm&quot;&amp;gt;
    &amp;lt;x-form-item label=&quot;用户名&quot; prop=&quot;username&quot;&amp;gt;
      &amp;lt;x-input
        v-model=&quot;userInfo.username&quot;
        placeholder=&quot;请输入用户名&quot;
        clear
      &amp;gt;&amp;lt;/x-input&amp;gt;
    &amp;lt;/x-form-item&amp;gt;
    &amp;lt;x-form-item&amp;gt;
      &amp;lt;x-button @click=&quot;login&quot; type=&quot;primary&quot;&amp;gt;登录&amp;lt;/x-button&amp;gt;
    &amp;lt;/x-form-item&amp;gt;
  &amp;lt;/x-form&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script lang=ts setup&amp;gt;
  const loginForm = ref(null) // ref 获取loginForm 实例
  const login = () =&amp;gt; {
    loginForm.value.validate(valid =&amp;gt; {
    if(valid){
      openMessageBox({title: &apos;提示&apos;,content: &apos;登录成功&apos;})
    }
  }
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是需要在loginForm 中暴露validate方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// form.vue
const validate = (callback) =&amp;gt; {
  ...
};
defineExpose({validate})
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;2 slots.default()&lt;/h5&gt;
&lt;p&gt;如果子组件是通过插槽的方式传递进来的 可以通过slots.default() 方法获取&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;setup(props,{slots}){
  const children = slots.default()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// setup 语法糖
import { useSlots } from &quot;vue&quot;;
const slots = useSlots();
const children = slots.default();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以通过这种方式约束子组件标签&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 检查每个子组件的类型
import ChildCompont form &apos;./ChildChomponent&apos;
children.forEach( tag =&amp;gt; {
  if(tag.type !== ChildCompont){
    throw new Error(&apos;子组件必须是 ChildComponent&apos;)
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这种方法可以获取到子组件实例 但是不能调用子组件中的方法&lt;/p&gt;
</content:encoded></item><item><title>实现一个拖动改变列宽的table组件</title><link>https://blog.cxyxiaoyu.top/posts/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E6%8B%96%E5%8A%A8%E6%94%B9%E5%8F%98%E5%88%97%E5%AE%BD%E7%9A%84table%E7%BB%84%E4%BB%B6/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E6%8B%96%E5%8A%A8%E6%94%B9%E5%8F%98%E5%88%97%E5%AE%BD%E7%9A%84table%E7%BB%84%E4%BB%B6/</guid><pubDate>Wed, 26 Jan 2022 16:25:13 GMT</pubDate><content:encoded>&lt;h3&gt;前言&lt;/h3&gt;
&lt;p&gt;一直以来都很想实现一个拖拽改变列宽的 table 组件,这几天终于有机会去研究它。哈哈，笔者不才，没能自己实现，还是去网上找了个案例，研究了下。&lt;/p&gt;
&lt;p&gt;乍一看好像挺简单的 其实就是原生 js 那些事，还是自己基础太差,想的太复杂了。&lt;/p&gt;
&lt;h4&gt;核心思想&lt;/h4&gt;
&lt;p&gt;在鼠标移动单元格右边距离右边距小于 10px 的时候 将鼠标改为&lt;code&gt;col-resize&lt;/code&gt;样式, 如果当前单元格(哈哈哈，注意这个&lt;strong&gt;当前单元格&lt;/strong&gt; 后面有坑)鼠标是按下的状态 那就鼠标移动多少距离 当前单元格就增加或减少多少宽度&lt;/p&gt;
&lt;p&gt;大致代码如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;utf-8&quot; /&amp;gt;
    &amp;lt;title&amp;gt;火星黑洞&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;table id=&quot;table&quot; cellspacing=&quot;0&quot; cellpadding=&quot;1&quot; width=&quot;100%&quot; border=&quot;1&quot;&amp;gt;
      &amp;lt;tbody&amp;gt;
        &amp;lt;tr align=&quot;center&quot; bgcolor=&quot;#1890ff&quot;&amp;gt;
          &amp;lt;td style=&quot;width:100px;&quot;&amp;gt;id&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;公司名称&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;姓名&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;岗位&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
        &amp;lt;tr&amp;gt;
          &amp;lt;td&amp;gt;20&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;科技有限公司&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;火星黑洞&amp;lt;/td&amp;gt;
          &amp;lt;td&amp;gt;web开发&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
      &amp;lt;/tbody&amp;gt;
    &amp;lt;/table&amp;gt;
    &amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
      var tTD;
      var table = document.getElementById(&quot;table&quot;);
      for (i = 0; i &amp;lt; table.rows[0].cells.length; i++) {
        table.rows[0].cells[i].onmousedown = function () {
          console.log(&quot;onmounseDown&quot;);
          tTD = this;
          if (event.offsetX &amp;gt; tTD.offsetWidth - 10) {
            tTD.mouseDown = true;
            tTD.oldX = event.x;
            tTD.oldWidth = tTD.offsetWidth;
          }
        };
        table.rows[0].cells[i].onmouseup = function () {
          if (tTD == undefined) tTD = this;
          tTD.mouseDown = false;
          tTD.style.cursor = &quot;default&quot;;
        };
        table.rows[0].cells[i].onmousemove = function () {
          console.log(event.offsetX, this.offsetWidth);
          if (event.offsetX &amp;gt; this.offsetWidth - 10)
            this.style.cursor = &quot;col-resize&quot;;
          else this.style.cursor = &quot;default&quot;;
          if (tTD == undefined) tTD = this;
          console.log(tTD.mouseDown, &quot;ttd.mouseDown&quot;);
          if (tTD.mouseDown != null &amp;amp;&amp;amp; tTD.mouseDown == true) {
            if (tTD.oldWidth + (event.x - tTD.oldX) &amp;gt; 0)
              tTD.width = tTD.oldWidth + (event.x - tTD.oldX); // 现在的宽度
            tTD.style.width = tTD.width;
            tTD.style.cursor = &quot;col-resize&quot;;
            table = tTD;
            while (table.tagName != &quot;TABLE&quot;) table = table.parentElement;
            for (j = 0; j &amp;lt; table.rows.length; j++) {
              table.rows[j].cells[tTD.cellIndex].width = tTD.width;
            }
          }
        };
      }
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;vue 实现 table 组件&lt;/h3&gt;
&lt;p&gt;感觉自己可以了 很快上手 table 组件&lt;/p&gt;
&lt;h4&gt;给 th 绑定事件&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;thead&amp;gt;
  &amp;lt;tr&amp;gt;
    &amp;lt;th
      v-for=&quot;head in thead&quot;
      @mousedown=&quot;mouseDown&quot;
      @mousemove=&quot;mouseMove&quot;
      @mouseup=&quot;mouseUp&quot;
    &amp;gt;
      {{ head.prop ? head.prop : head }}
    &amp;lt;/th&amp;gt;
  &amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;实现相关事件&lt;/h4&gt;
&lt;p&gt;按照原生 js 那一套 我事先按照自己的理解将它的 tTD 都改为了 event.target, 想着 event.target 就是事件发生的元素
但是很遗憾,是可以实现拖动改变列宽效果,但是必须慢慢拖动,才可以改变列宽,一旦鼠标移速过快,该单元格宽度不会跟着鼠标变化, 鼠标会滑到下一个单元格上
百思不得其解 感觉逻辑没有问题 代码也几乎改成了一模一样 只有源码是&lt;code&gt;tTD&lt;/code&gt; 我用的&lt;code&gt;event.target&lt;/code&gt; 为什么源码写的表格拖拽起来是如此丝滑，鼠标到哪 单元格右边边框线跟到哪 而我的组件,就是跟不上我的鼠标
这到底是怎么回事呢？&lt;/p&gt;
&lt;p&gt;又将源码中的 tTD 全部改为 &lt;code&gt;event.target&lt;/code&gt; bug 重现了，那重点还是在于它这个 tTD 的作用
于是又一行一行改，然后又打印对比 tTD 与 event.target 到底为什么不一样&lt;/p&gt;
&lt;p&gt;当我在公司名称那一列快速往右移时, tTD 永远都是公司名称那一个单元格 &lt;code&gt;event.targt&lt;/code&gt;会变成 姓名那一单元格&lt;/p&gt;
&lt;p&gt;恍然一下 想到了在写 canvase 画板的时候 画笔的速度跟不上鼠标 move 的速度 中间会出现空白
也就是说 鼠标移动太快的话 改变宽度的代码还来不及执行 而当前的 event.target 已经变成了下一列 所以我要改变的那一列宽度就不会改变&lt;/p&gt;
&lt;p&gt;终于知道原因了,这也就是源码中定义了全局变量 tTD 的原因所在 tTD 并不是事件发生的当前元素 而是记录要改变宽度的那一单元格!!!&lt;/p&gt;
&lt;p&gt;重新修改代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let th = undefined;

const mouseDown = (e, xxx) =&amp;gt; {
  if (!border) return;
  th = e.target;

  if (e.offsetX &amp;gt; th.offsetWidth - 10) {
    th.down = true;
    th.oldWidth = th.offsetWidth;
    th.oldX = e.x;
  }
};

const mouseMove = (e) =&amp;gt; {
  if (!border) return;
  if (e.target.offsetWidth - e.offsetX &amp;lt; 10) {
    e.target.style.cursor = &quot;col-resize&quot;;
  } else {
    e.target.style.cursor = &quot;default&quot;;
  }
  if (!th) th = e.target;
  if (th.down) {
    th.width = th.oldWidth + (e.x - th.oldX);
    th.style.cursor = &quot;col-resize&quot;;
  }
};

const mouseUp = (e) =&amp;gt; {
  if (!border) return;
  if (!th) th = e.target;
  th.down = false;
  th.style.cursor = &quot;defult&quot;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;终于可以很顺畅的滑动了 单元格的宽度变化终于跟得上我的手速了~~~~~~~&lt;/p&gt;
&lt;h4&gt;给定初始列宽&lt;/h4&gt;
&lt;p&gt;但是我们在使用组件的时候 都会给每一列一个初始宽度 用 colgroup 统一管理列&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;table&amp;gt;
  &amp;lt;colgroup&amp;gt;
    &amp;lt;col v-for=&quot;head in thead&quot; :width=&quot;head.width&quot; /&amp;gt;
  &amp;lt;/colgroup&amp;gt;
  ...
&amp;lt;/table&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用组件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;x-table border :thead=&quot;theadData&quot; :data=&quot;tableData&quot;&amp;gt;&amp;lt;/x-table&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang=&quot;ts&quot;&amp;gt;
  import { defineComponent, ref } from &quot;vue&quot;;
  export default defineComponent({
    setup() {
      const theadData = ref([
        { prop: &quot;日期&quot;, width: 200 },
        { prop: &quot;姓名&quot;, width: 200 },
        { prop: &quot;地址&quot; },
      ]);
      const tableData = ref([
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9],
        [11, 12, 13],
      ]);
      return { tableData, theadData };
    },
  });
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;任意改变列宽&lt;/h4&gt;
&lt;p&gt;这样给定了初始宽度后 发现无法将列改变到很小的宽度 应该是初始宽度限制了列的最小宽度 我们应该改变 col 的宽度&lt;/p&gt;
&lt;p&gt;vue3 v-for 中的 ref 数组 获取 col&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;colgroup&amp;gt;
  &amp;lt;col
    :ref=&quot;setColRef&quot;
    v-for=&quot;head in thead&quot;
    :width=&quot;head.width ?? head.width&quot;
  /&amp;gt;
&amp;lt;/colgroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;组合式 API&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let colRefs = [];
const setColRef = (el) =&amp;gt; {
  if (el) {
    colRefs.push(el);
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终代码 同样需要一个变量记录当前需要改变列宽的索引值 否则会引发同样的问题&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let th = undefined;
let currentIndex = undefined;

let colRefs = [];
const setColRef = (el) =&amp;gt; {
  if (el) {
    colRefs.push(el);
  }
};

const mouseDown = (e, index) =&amp;gt; {
  if (!border) return;
  th = e.target;
  currentIndex = index;

  if (e.offsetX &amp;gt; th.offsetWidth - 10) {
    th.down = true;
    th.oldWidth = th.offsetWidth;
    th.oldX = e.x;
  }
};

const mouseMove = (e, index) =&amp;gt; {
  if (!border) return;
  if (e.target.offsetWidth - e.offsetX &amp;lt; 10) {
    e.target.style.cursor = &quot;col-resize&quot;;
  } else {
    e.target.style.cursor = &quot;default&quot;;
  }
  if (!th) {
    th = e.target;
    currentIndex = index;
  }
  if (th.down) {
    // 改变当前索引 col 的宽度
    colRefs[currentIndex].width = th.oldWidth + (e.x - th.oldX);
    th.style.cursor = &quot;col-resize&quot;;
  }
};

const mouseUp = (e) =&amp;gt; {
  if (!border) return;
  if (!th) {
    th = e.target;
    currentIndex = undefined;
  }
  th.down = false;
  th.style.cursor = &quot;defult&quot;;
};
return { setColRef, mouseDown, mouseMove, mouseUp };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;大功告成 ~~~~~~~&lt;/p&gt;
</content:encoded></item><item><title>vite构建库并发布</title><link>https://blog.cxyxiaoyu.top/posts/vite%E6%9E%84%E5%BB%BA%E5%BA%93%E5%B9%B6%E5%8F%91%E5%B8%83/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/vite%E6%9E%84%E5%BB%BA%E5%BA%93%E5%B9%B6%E5%8F%91%E5%B8%83/</guid><pubDate>Mon, 17 Jan 2022 14:18:15 GMT</pubDate><content:encoded>&lt;h3&gt;添加入口文件 src/lib/index.ts&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;export { default as Button } from &apos;./Button.vue&apos;
export { default as Switch } from &apos;./Switch.vue&apos;
// 相当于
// import Switch from &apos;./Switch.vue&apos;
// export { Switch }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;修改vite.config.js&lt;/h3&gt;
&lt;p&gt;参考链接:   &lt;a href=&quot;https://vitejs.cn/guide/build.html#library-mode&quot;&gt;vite构建库&lt;/a&gt; &lt;a href=&quot;https://vitejs.cn/config/#build-lib&quot;&gt;构件库配置项&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;build: {
  lib: {
    entry: path.resolve(__dirname, &apos;src/lib/index.ts&apos;),
    name: &apos;xinyi-ui&apos;,
    fileName: (format) =&amp;gt; `xinyi-ui.${format}.js`
  },
  rollupOptions: {
    // 确保外部化处理那些你不想打包进库的依赖
    external: [&apos;vue&apos;],
    output: {
      // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
      globals: {
        vue: &apos;Vue&apos;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;format 默认是&lt;code&gt;[&apos;es&apos;,&apos;umd&apos;]&lt;/code&gt; 所以会生成 xinyi-ui.es.js 和 xinyi-ui.umd.js 两个文件&lt;/p&gt;
&lt;h3&gt;修改package.json&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&quot;files&quot;: [&quot;dist&quot;],
&quot;main&quot;: &quot;./dist/xinyi-ui.umd.js&quot;,
&quot;module&quot;: &quot;./dist/xinyi-ui.es.js&quot;,
&quot;exports&quot;: {
  &quot;.&quot;: {
    &quot;import&quot;: &quot;./dist/my-lib.es.js&quot;,
    &quot;require&quot;: &quot;./dist/my-lib.umd.js&quot;
  }
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;各字段意义&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;files 字段是要包含在项目中(npm publish 打包的tarball)的文件数组。&lt;/li&gt;
&lt;li&gt;main CommonJS 规范的包入口文件&lt;/li&gt;
&lt;li&gt;module ES Module 的包入口文件 &lt;a href=&quot;https://segmentfault.com/a/1190000014286439&quot;&gt;package.json module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;exports Node.js 12+ 支持作为&lt;code&gt;&quot;main&quot;&lt;/code&gt; 的替代方案，它可以支持定义子路径导出和条件导出，同时封装内部未导出的模块。
&lt;a href=&quot;https://nodejs.org/api/packages.html#exports&quot;&gt;package.json exports&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;发布npm 包&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;npm login 登录在https://www.npmjs.com/ 注册的账号&lt;/li&gt;
&lt;li&gt;npm whomi 确认登录&lt;/li&gt;
&lt;li&gt;npm publish&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;注意事项&lt;/h4&gt;
&lt;p&gt;name:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必须是小写字母 可用 - 或 _ 连接&lt;/li&gt;
&lt;li&gt;不能跟npm上现有的name 重名&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;version&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每次publish 的版本不能与之前每一次的相同&lt;/li&gt;
&lt;li&gt;所以从第二次开始 先改version 再publish&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;测试是否发布成功&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;vue create test-xinyi-ui 新建一个项目目录&lt;/li&gt;
&lt;li&gt;在 App.vue 中引入相关组件 和 css&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import { Button } from &apos;xinyi-ui&apos;
import &apos;xinyi-ui/dist/style.css&apos;
export default {
  components: { Button }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在模板中使用组件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Button&amp;gt;按钮&amp;lt;/Button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>如何使用cli及脚手架原理</title><link>https://blog.cxyxiaoyu.top/posts/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8cli%E5%8F%8A%E8%84%9A%E6%89%8B%E6%9E%B6%E5%8E%9F%E7%90%86/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8cli%E5%8F%8A%E8%84%9A%E6%89%8B%E6%9E%B6%E5%8E%9F%E7%90%86/</guid><pubDate>Fri, 14 Jan 2022 22:35:06 GMT</pubDate><content:encoded>&lt;h3&gt;如何使用cli&lt;/h3&gt;
&lt;p&gt;在《写一个cli脚手架中》使用了npm link 来使用 my-cli
但是使用npm link 只是为了方便在开发的时候测试 但是我们要想在一个项目中使用脚手架的命令怎么用呢？&lt;/p&gt;
&lt;p&gt;很简单 直接&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install &amp;lt;cli-package-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;原理分析&lt;/h3&gt;
&lt;p&gt;但是为什么可以这样做呢?
我发现 npm install 的时候其实是做了两件事&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;根据package.json 安装项目依赖&lt;/li&gt;
&lt;li&gt;在node_modules/.bin 下创建所有依赖项bin字段下所有命令的软链接&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;先将&lt;code&gt;{prefix}/bin/{name}&lt;/code&gt;下的test2命令删除&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;比如 现在我在my-cli 同级目录下新建 test-my-cli 目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir test-my-cli
cd test-my-cli
npm init -y
npm install ../my-cli
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./test-my-cli.png&quot; alt=&quot;test-my-cli&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到node_modules/.bin 下存在两个软链接&lt;/p&gt;
&lt;h4&gt;npm run-script&lt;/h4&gt;
&lt;p&gt;那么现在我们就可以执行my-cli中的命令了么?
直接执行test2 肯定不行(先将环境变量中的test2命令删除) 因为test2 是放在 node_modules/.bin目录下的
所以我们需要&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./node_modules/.bin/test2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./run-test2.png&quot; alt=&quot;run-test2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;但是这样使用起来很麻烦 我们通常都不是这么用的 而是将命令写在package.json 中的scripts 字段中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
  &quot;test&quot;: &quot;test2 init aaa&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后再运行 &lt;code&gt;npm run test&lt;/code&gt; 也可以执行test2 命令&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./npm-run-test.png&quot; alt=&quot;npm-run-test&quot; /&gt;&lt;/p&gt;
&lt;p&gt;那么这是为什么呢？为什么放在scripts 中就可以执行test2命令了？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In addition to the shell&apos;s pre-existing PATH, npm run adds node_modules/.bin to the PATH provided to scripts.
Any binaries provided by locally-installed dependencies can be used without the node_modules/.bin prefix.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For example, if there is a devDependency on tap in your package, you should write:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {&quot;test&quot;: &quot;tap test/\*.js&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead of&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {&quot;test&quot;: &quot;node_modules/.bin/tap test/\*.js&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原来是npm run 的时候将当前目录下的 node_modules/.bin 添加到了环境变量中&lt;/p&gt;
</content:encoded></item><item><title>写一个cli脚手架</title><link>https://blog.cxyxiaoyu.top/posts/%E5%86%99%E4%B8%80%E4%B8%AAcli%E8%84%9A%E6%89%8B%E6%9E%B6/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%86%99%E4%B8%80%E4%B8%AAcli%E8%84%9A%E6%89%8B%E6%9E%B6/</guid><pubDate>Fri, 14 Jan 2022 19:27:13 GMT</pubDate><content:encoded>&lt;h3&gt;1 新建项目目录&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;mkdir my-cli
cd my-cli
npm init —y  # 生成package.json 文件
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2 新建 bin 目录 创建脚本 test.js&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env node       //指定一个解释器  告诉shell 用node解释器去执行脚本
console.log(&apos;test&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3 在package.json 配置bin 字段&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;bin字段用来指定各个内部命令对应的可执行文件的位置。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;test 有毒不生效 所以这里新建了一个test2.js 脚本(命令)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;bin&quot;: {
  &quot;test&quot;: &quot;./bin/test.js&quot;,    // 好像test 命令有毒 不打印 其他可以
  &quot;test2&quot;: &quot;./bin/test2.js&quot;
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4 运行 npm link (参考博客 npm link 详解)&lt;/h3&gt;
&lt;p&gt;将包中的任何bin链接到{prefix}/bin/{name}   npm config get prefix 查看前缀
test2 命令已被添加到环境变量中
&lt;img src=&quot;./npmlink.png&quot; alt=&quot;npmlink&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;5 执行test2&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./test2.png&quot; alt=&quot;test2&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;6 引入commander 添加子命令&lt;/h3&gt;
&lt;p&gt;test2.js  初始化项目&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env node
const program = require(&apos;commander&apos;)
// 以package.json 的version 作为test2 的version
program.version(require(&apos;../package.json&apos;).version)
program
  .command(&apos;init &amp;lt;name&amp;gt;&apos;)
  .description(&apos;init project  &apos;)
  .action(require(&apos;../lib/init&apos;))

// 解析主进程参a
program.parse(process.argv)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7 使用 figlet chalk 输出欢迎界面&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// init.js
const { promisify } = require(&apos;util&apos;)
const figlet = promisify(require(&apos;figlet&apos;))
const chalk = require(&apos;chalk&apos;)   // chalk 5 是ESM  chalk 4 是CSM
const log = content =&amp;gt; console.log(chalk.green(content))
module.exports = async name =&amp;gt; {
  const data = await figlet(&apos;Welcome&apos;)
  log(data)
  log(`下载项目: ${name}`)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./welcome.png&quot; alt=&quot;welcome&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;8 download-git-repo 从远程仓库拉取项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const {clone} = require(&apos;./download&apos;)
// init.s
log(`下载项目: ${name}`)
// 现在默认分支不是master了 所以要指定分支main
await clone(&apos;github:cxyxiaoyuyu/xinyi-ui#main&apos;,name)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 ora 在下载时显示loading&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// download.js
const { promisify } = require(&apos;util&apos;)
const ora = require(&apos;ora&apos;)

module.exports.clone = async function(repo,desc){
  const download = promisify(require(&apos;download-git-repo&apos;))
  const process = ora(`下载..... ${repo}`)
  process.start()
  await download(repo,desc)
  console.log(repo,desc)
  process.succeed()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行完成后项目会多了一个执行命令时输入的目录&lt;/p&gt;
&lt;h3&gt;9 执行命令 自动安装依赖&lt;/h3&gt;
&lt;p&gt;使用 spawn 执行 命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//init.js
const {spawn} = require(&apos;child_process&apos;)
/**
spawn 开一个子进程 用来执行脚本(暂且这么了解吧)
_spawn 自己封装的函数 开一个子进程 让子进程的输出信息打印给主进程 并监听子进程结束
直接用spawn 命
*/
const _spawn = async (...args) =&amp;gt; {
  return new Promise((resolve)=&amp;gt;{
    const proc = spawn(...args)
    proc.stdout.pipe(process.stdout)  // 为了打印出日志输出
    proc.stderr.pipe(process.stderr)
    proc.on(&apos;close&apos;,()=&amp;gt;{     // 知道子进程结束了
      resolve()
    })
  })
}
log(&apos;安装依赖&apos;)
await _spawn(&apos;npm&apos;,[&apos;install&apos;],{cwd: `./${name}`})
log(`安装完成`)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;10 自动启动项目&lt;/h3&gt;
&lt;p&gt;使用open 打开浏览器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// init.js
log(&apos;启动&apos;)
// 直接写spawn 是在主进程执行 不会打印输出信息
// 如果需要打印信息 需要使用自己封装的_spawn  但是这个进程不会结束 所以下面的open 不会执行 所以open 要放在上面
// spawn(&apos;npm&apos;,[&apos;run&apos;,&apos;dev&apos;],{cwd: `./${name}`})

open(`http://localhost:3001`)
_spawn(&apos;npm&apos;,[&apos;run&apos;,&apos;dev&apos;],{cwd: `./${name}`})
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;11 实现约定路由功能&lt;/h3&gt;
&lt;p&gt;根据views下的文件 自动生成路由
使用handlebars 模板引擎&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const fs = require(&apos;fs&apos;)
const handlebars = require(&apos;handlebars&apos;)

const program = require(&apos;commander&apos;)
program
  .command(&apos;create component &amp;lt;name&amp;gt;&apos;)
  .description(&apos;init project  &apos;)
  .action(refresh)
program.parse(process.argv)

// module.exports = async () =&amp;gt; {
async function refresh (name){
  console.log(name)
  // 获取列表
  const list = fs.readdirSync(&apos;./src/views&apos;)
    .filter(v =&amp;gt; v !== &apos;Home.vue&apos;)
    .map(v =&amp;gt; ({
      name: v.replace(&apos;.vue&apos;,&apos;&apos;).toLowerCase(),
      file: v
    }))
  console.log(list)

  // 生成路由定义
  compile({list},&apos;./src/router/index.js&apos;,&apos;./template/router.js.hbs&apos;)

  // 生成菜单
  compile({list},&apos;./src/App.vue&apos;,&apos;./template/App.vue.hbs&apos;)

  /**
   * 模板编译
   * @param {*} meta
   * @param {*} filePath
   * @param {*} templatePath
   */
  function compile(meta,filePath,templatePath){
    if(fs.existsSync(templatePath)){
      const content = fs.readFileSync(templatePath).toString()
      const result = handlebars.compile(content)(meta)
      fs.writeFileSync(filePath,content)
      console.log(`${filePath}创建成功`)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>安装nvm</title><link>https://blog.cxyxiaoyu.top/posts/%E5%AE%89%E8%A3%85nvm/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%AE%89%E8%A3%85nvm/</guid><pubDate>Fri, 14 Jan 2022 16:18:04 GMT</pubDate><content:encoded>&lt;h2&gt;卸载之前的node&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;brew uninstall node --force
sudo rm -rf /usr/local/{bin/{node,npm},lib/node_modules/npm,lib/node,share/man/*/node.*}

sudo npm uninstall npm -g
sudo rm -rf /usr/local/lib/node /usr/local/lib/node_modules /var/db/receipts/org.nodejs.*
sudo rm -rf /usr/local/include/node /Users/$USER/.npm
sudo rm /usr/local/bin/node
sudo rm /usr/local/share/man/man1/node.1
sudo rm /usr/local/lib/dtrace/node.d
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;安装nvm&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt; curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后提示 重新打开terminal  或者执行以下三行命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ -s &quot;$NVM_DIR/nvm.sh&quot; ] &amp;amp;&amp;amp; \. &quot;$NVM_DIR/nvm.sh&quot;
[ -s &quot;$NVM_DIR/bash_completion&quot; ] &amp;amp;&amp;amp; \. &quot;$NVM_DIR/bash_completion&quot;
export PATH=&quot;$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;遇到的问题&lt;/h4&gt;
&lt;p&gt;执行完可以使用nvm 命令  但是重启terminal  会报 nvm cammand not found&lt;/p&gt;
&lt;h4&gt;原因&lt;/h4&gt;
&lt;p&gt;是因为最新的mac 系统catalina 默认shell 是zsh&lt;/p&gt;
&lt;h4&gt;解决&lt;/h4&gt;
&lt;p&gt;所以将三行命令放进 ~/.zshrc 中 再重启终端 或 source ~/.zshrc&lt;/p&gt;
&lt;h4&gt;nvm 基本使用&lt;/h4&gt;
&lt;p&gt;nvm install                      &amp;lt;node 版本&amp;gt;
nvm ls-remote               查看远程所有可安装的node版本
nvm ls                                 本地已安装的node版本
nvm use &amp;lt;node版本&amp;gt;           切换node 版本&lt;/p&gt;
&lt;h4&gt;vscode 中不能使用node 命令&lt;/h4&gt;
&lt;p&gt;然后发现vscode 的terminal 没有node 命令了
因为vscode 的terminal 是基于bash 的
解决方法有很多 知道原因就好办&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;复制那三行命令，粘贴到~/.bash_profile 里 重新打开vscode  或 source ~/.bash_profile&lt;/li&gt;
&lt;li&gt;也可以将~/.zshrc 里的内容改为 source ~/.bash_profile&lt;/li&gt;
&lt;li&gt;或者在 ~/.bash_profile 里 写source ~/.zshrc&lt;/li&gt;
&lt;li&gt;将vscode 的terminal 改为 zsh&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>npm link详解</title><link>https://blog.cxyxiaoyu.top/posts/npm-link%E8%AF%A6%E8%A7%A3/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/npm-link%E8%AF%A6%E8%A7%A3/</guid><pubDate>Thu, 13 Jan 2022 22:43:33 GMT</pubDate><content:encoded>&lt;h3&gt;npm link 分为两个步骤&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Package linking is a two-step process.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;npm link&lt;/li&gt;
&lt;li&gt;npm link package-name&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;npm link 的时候发生了什么&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;首先 &lt;code&gt;npm link&lt;/code&gt; 会在全局目录&lt;code&gt;{prefix}/lib/node_modules/&amp;lt;package&amp;gt;&lt;/code&gt;
创建一个当前目录的软连接&lt;/li&gt;
&lt;li&gt;将包中bin下的命令 链接到&lt;code&gt;{prefix}/bin/{name}&lt;/code&gt; 目录&lt;/li&gt;
&lt;li&gt;npm config get prefix 可以查看prefix&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./npmlink.png&quot; alt=&quot;npmlink&quot; /&gt;
&lt;img src=&quot;./npmlink2.png&quot; alt=&quot;创建全局软连接&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// package.json
&quot;bin&quot;:{
  &quot;test&quot;: &quot;./bin/test.js&quot;,
  &quot;test2&quot;: &quot;./bin/test2.js&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./npmlink3.png&quot; alt=&quot;bin下的命令链接到全局&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;第二步 npm link package-name&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;另一个目录 &lt;code&gt;npm link package-name&lt;/code&gt; 将创建一个从全局安装的包名到当前文件夹的node_modules/的符号链接。&lt;/li&gt;
&lt;li&gt;注意 package-name 是从package.json 中的名字 不是目录名&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./npmlink4.png&quot; alt=&quot;另一个目录创建软连接&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The package name can be optionally prefixed with a scope. See npm-scope. The scope must be preceded by an @-symbol and followed by a slash.
包名称可以选择性地加上作用域前缀。参见npm-scope。作用域前面必须有@-符号，后面必须有斜杠。&lt;/p&gt;
&lt;p&gt;When creating tarballs for npm publish, the linked packages are &quot;snapshotted&quot; to their current state by resolving the symbolic links.
通过&lt;code&gt;npm publish&lt;/code&gt; 命令去发布 链接的包将“快照”到其当前状态。&lt;/p&gt;
&lt;p&gt;This is handy for installing your own stuff, so that you can work on it and test it iteratively without having to continually rebuild.
这对于安装您自己的东西很方便，因此您可以对其进行操作并进行迭代测试，而无需不断地重新构建。&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd ~/projects/node-redis    # go into the package directory
npm link                    # creates global link
cd ~/projects/node-bloggy   # go into some other package directory.
npm link redis              # link-install the package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, any changes to ~/projects/node-redis will be reflected in ~/projects/node-bloggy/node_modules/node-redis/. Note that the link should be to the package name, not the directory name for that package.&lt;/p&gt;
&lt;p&gt;You may also shortcut the two steps in one. For example, to do the above use-case in a shorter way:
cd ~/projects/node-bloggy  # go into the dir of your main project
npm link ../node-redis     # link the dir of your dependency&lt;/p&gt;
&lt;p&gt;The second line is the equivalent of doing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(cd ../node-redis; npm link)
npm link redis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is, it first creates a global link, and then links the global installation target into your project&apos;s node_modules folder.&lt;/p&gt;
&lt;p&gt;Note that in this case, you are referring to the directory name, node-redis, rather than the package name redis.
If your linked package is scoped (see npm-scope) your link command must include that scope, e.g.
npm link @myorg/privatepackage&lt;/p&gt;
</content:encoded></item><item><title>实现node中的util.promisify</title><link>https://blog.cxyxiaoyu.top/posts/%E5%AE%9E%E7%8E%B0node%E4%B8%AD%E7%9A%84util-promisify/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%AE%9E%E7%8E%B0node%E4%B8%AD%E7%9A%84util-promisify/</guid><pubDate>Thu, 13 Jan 2022 22:33:41 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;将原本需要通过传入回调参数来实现回调执行改为利用&lt;code&gt;promise.then&lt;/code&gt;的方式来调用,或者通过 &lt;code&gt;async/await&lt;/code&gt; 的方式调用    从而实现逻辑上的同步操作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;实现原理:&lt;/h1&gt;
&lt;p&gt;改变callback 在执行callback 的时候将参数resolve 出去 这样就可以在执行函数的时候拿到回调函数的参数 再执行真正的callback&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  function a(str,callback){
    callback(str+&apos;456&apos;)
  }

  function promisify(fn) {
    return function (...args) {
        return new Promise(function (resolve, reject) {
            let callback = function(...args) {
              resolve(...args)
            }
            fn.apply(null, [...args,callback]);
        });
    }
  }

  const aa = promisify(a)

  async function xxx(){
    const data = await aa(&apos;123’)   // data 就是回调函数中的参数
    console.log(data)
  }

  xxx()
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>vite支持custom-block功能显示源码</title><link>https://blog.cxyxiaoyu.top/posts/%E9%A1%B9%E7%9B%AE%E4%B8%8B%E6%94%AF%E6%8C%81custom-block%E5%8A%9F%E8%83%BD%E6%98%BE%E7%A4%BA%E6%BA%90%E7%A0%81/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E9%A1%B9%E7%9B%AE%E4%B8%8B%E6%94%AF%E6%8C%81custom-block%E5%8A%9F%E8%83%BD%E6%98%BE%E7%A4%BA%E6%BA%90%E7%A0%81/</guid><pubDate>Thu, 13 Jan 2022 20:19:17 GMT</pubDate><content:encoded>&lt;p&gt;在写xinyi-ui的时候就想实现一个展示demo 并显示源代码的功能 了解到custom-block这个功能&lt;/p&gt;
&lt;p&gt;这个问题折腾了我一整天,不过真是不负有心人,还好我没有放弃,现在对vite的原理不是很了解,只是记一下步骤,等以后再看吧&lt;/p&gt;
&lt;p&gt;参考链接&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://vitejs.cn/guide/api-plugin.html&quot;&gt;vite插件API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vue-loader.vuejs.org/zh/guide/custom-blocks.html#example&quot;&gt;custom-block&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/weixin_39942785/article/details/113414172&quot;&gt;vue3.0中compiler-core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vitejs/vite/tree/main/packages/plugin-vue&quot;&gt;@vitejs/plugin-vue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;在demo.vue 中添加自定义标签 如demo&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!--SwitchDemo--&amp;gt;
&amp;lt;demo&amp;gt;常规用法&amp;lt;/demo&amp;gt;

&amp;lt;template&amp;gt;
   &amp;lt;Switch v-model:value=&quot;value&quot; /&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang=&quot;ts&quot;&amp;gt;
import Switch from &quot;../lib/Switch.vue&quot;;
import { defineComponent, ref } from &quot;vue&quot;;
export default defineComponent({
  name: &quot;&quot;,
  setup() {
    const value = ref(true)
    return {value};
  },
  components: { Switch },
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;实现插件获取源码 添加源码到组件&lt;/h4&gt;
&lt;p&gt;打印了好多东西才发现在请求这个添加了自定义块的vue的时候会有两个请求 而我要匹配的就是type=demo的那个请求&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./path.png&quot; alt=&quot;path&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后根据这个路径获取到源码 添加到组件的__sourCode属性上&lt;/p&gt;
&lt;p&gt;如果style标签有内容的话 也会有type=style 的请求 这其中怎么分的 后面一定要了解下
tranform 函数怎么return 的不一样呢? 什么时候返回字符串 什么时候返回对象？
在plugin-vue 中对这种自定义块是怎么处理的？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const vueDemoPlugin = {
  name: &apos;vue-i18n&apos;,
  transform(code, path) {
    if (/vue&amp;amp;type=demo/.test(path)) {
      const file = fs.readFileSync(path.split(&apos;?&apos;)[0]).toString()
      const parsed = baseParse(file).children.find(n =&amp;gt; n.tag === &apos;demo&apos;)
      const main = file.split(parsed.loc.source).join(&apos;&apos;).trim()
      return `export default Comp =&amp;gt; {
        Comp.__sourceCode = ${JSON.stringify(main)}
        Comp.__sourceCodeTitle = ${JSON.stringify(code)}
      }`
    }
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;组件中显示源码&lt;/h3&gt;
&lt;p&gt;在组件中引入SwitchDemo.vue&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import SwitchDemo from &apos;xxx&apos;
console.log(SwtichDemo)  // 发现SwitchDemo是一个对象 __sourceCode含有源码
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>在vite项目下支持markdown文件</title><link>https://blog.cxyxiaoyu.top/posts/%E5%9C%A8vite%E9%A1%B9%E7%9B%AE%E4%B8%8B%E6%94%AF%E6%8C%81markdown/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%9C%A8vite%E9%A1%B9%E7%9B%AE%E4%B8%8B%E6%94%AF%E6%8C%81markdown/</guid><pubDate>Thu, 13 Jan 2022 20:15:48 GMT</pubDate><content:encoded>&lt;h3&gt;前言&lt;/h3&gt;
&lt;p&gt;需求如下 在写xinyi-ui 官网时,需要对组件进行文档说明,说明文档都是md格式的文件,能否直接将md文件显示到页面上,而不改为html 呢？
现在有 install.md 和 get-started.md 两个文件&lt;/p&gt;
&lt;h3&gt;第一步 实现一个vite插件 md =&amp;gt; html&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;将对md文件的请求进行数据处理&lt;/li&gt;
&lt;li&gt;通过markdown-it 将源文件转为html字符串&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;var MarkdownIt = require(&apos;markdown-it&apos;)
const marked = new MarkdownIt()

const mdToJs = str =&amp;gt; {
  const content = JSON.stringify(marked.render(str))
  return `export default ${content}`
}

export function md() {
  return {
    name: &apos;md&apos;,
    transform(src,url){
      if(url.endsWith(&apos;.md&apos;)){
        return {
          code: mdToJs(src),
          map: null // 如果可行将提供 source map
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;第二步创建组件模板 Markdown.vue&lt;/h3&gt;
&lt;p&gt;因为只是md文件不同,可以使用同一个组件模板,通过文件路径显示不同的html内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;article v-html=&quot;content&quot;&amp;gt;&amp;lt;/article&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么如何获取这个content 呢?
只要引入md 文件 vite插件就会自动将md文件转为html 字符串&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//router.js
// 通过渲染函数将 path 传递给 markdown.vue 在markdown.vue 中引入md文件
const md = (filename:string) =&amp;gt; h(Markdown, { path: `../markdown/${filename}.md`, key: filename })
const routes = {
  // component 可以使VNode
  { path: &apos;install&apos;, component: md(&apos;install&apos;)},
  { path: &apos;get-started&apos;, component: md(&apos;get-started&apos;) },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是我们不能再setup 中使用 &lt;code&gt;import xxx from ...&lt;/code&gt; 的语法，可以使用异步引入的方式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Markdown.vue
export default {
  props: {
    path: {
      type: String,
      required: true,
    },
  },
  setup(props:any) {
    const content = ref&amp;lt;string&amp;gt;(&apos;&apos;);
    import(/* @vite-ignore */props.path).then((result) =&amp;gt; {
      content.value = result.default;    // 这里可以获取到
    });
    return {
      content,
    };
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;显示在页面 并添加样式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;安装 github-markdown-css&lt;/li&gt;
&lt;li&gt;在main.ts 或 MarkDown.vue中引入 css&lt;/li&gt;
&lt;li&gt;在article上添加class markdown-body&lt;/li&gt;
&lt;li&gt;v-html 显示 html&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;article class=&quot;markdown-body&quot; v-html=&quot;content&quot;&amp;gt;&amp;lt;/article&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;将动态引入改为静态引入&lt;/h3&gt;
&lt;p&gt;build后发现在生产环境会报错 是因为rollup打包不接受这种拼接字符串的模式
所以将动态引入文件 改为静态引入&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这样也将异步引入改为了同步引入了&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// main.ts
import install from &apos;../markdown/install.md&apos;
import getStarted from &apos;../markdown/get-started.md&apos;

// 直接将content 传给Markdown.vue
const md = (content:string) =&amp;gt; h(Markdown, { content })
const routes = {
  { path: &apos;install&apos;, component: md(install)},
  { path: &apos;get-started&apos;, component: md(getStarted) },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样直接引入 ts 会报错 可以加 &lt;code&gt;// @ts-ignore&lt;/code&gt; 不管
也可以在markdown中添加一个声明文件 markdown.d.ts&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;declare module &quot;*.md&quot; {
  const content: string;
  export default content;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样打开就不会报错啦&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Markdown.vue&lt;/code&gt;中显示markdown&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;!--content 就是 html 格式的字符串--&amp;gt;
  &amp;lt;article class=&quot;markdown-body&quot; v-html=&quot;content&quot;&amp;gt;&amp;lt;/article&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script lang=&quot;ts&quot;&amp;gt;
import { ref } from &quot;vue&quot;;
export default {
  props: {
    content: {
      type: String,
      required: true,
    },
  }
};
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>手写系列-手写深拷贝</title><link>https://blog.cxyxiaoyu.top/posts/%E6%89%8B%E5%86%99%E7%B3%BB%E5%88%97-%E6%89%8B%E5%86%99%E6%B7%B1%E6%8B%B7%E8%B4%9D/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E6%89%8B%E5%86%99%E7%B3%BB%E5%88%97-%E6%89%8B%E5%86%99%E6%B7%B1%E6%8B%B7%E8%B4%9D/</guid><pubDate>Sun, 13 Sep 2020 18:39:57 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;什么是深拷贝？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;简单理解: b 是 a 的一份拷贝，b 中没有对 a 中对象的引用
另一种理解: b 是 a 的一份拷贝，a、b 各自画图，a 与 b 没有连接&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;简单的方法&lt;/h2&gt;
&lt;p&gt;JSON 序列化与反序列化&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let obj = {a:1,b:[2,3],c:{xx:4,yy:5}}
let obj2 = JSON.parse(JSON.stringify(obj))
obj2.a = 11
obj2.b[0] = 11
obj2.c.xx = 44
console.log(obj)  // {a:1,b:[2,3],c:{xx:4,yy:5}} 原先的obj不会被改变
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;缺点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不支持函数&lt;/li&gt;
&lt;li&gt;不支持 undefind(因为 JSON 只有 null)&lt;/li&gt;
&lt;li&gt;不支持日期(会把 date 变成 iso8601 格式的日期字符串)&lt;/li&gt;
&lt;li&gt;不支持正则&lt;/li&gt;
&lt;li&gt;不支持 Error 对象&lt;/li&gt;
&lt;li&gt;不支持环状结构&lt;/li&gt;
&lt;li&gt;NaN Infinity 会变成 null&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;const obj = {
  a: &quot;a&quot;,
  b: undefined, // undefinde 丢失
  c: function () {
    // 函数丢失
    console.log(&quot;this is a fnction&quot;);
  },
  d: NaN, // NaN 变为 null
  e: Infinity, // Infinity 变为 null
  f: new Date(), // 时间对象变为 iso8601格式字符串
  g: /123/, // 正则 变为 空对象
  h: new Error(&quot;error&quot;), // 变为空对象
};
console.log(JSON.parse(JSON.stringify(obj)));
// {a: &apos;a&apos;,d:null,e: null,f: &apos;iso8601格式字符串&apos;，g:{},h: {}}

obj.i = obj;
console.log(JSON.parse(JSON.stringify(obj))); // 报错
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;自己实现一个深拷贝&lt;/h2&gt;
&lt;h3&gt;复制基本类型&lt;/h3&gt;
&lt;h4&gt;基本类型：&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Number&lt;/li&gt;
&lt;li&gt;String&lt;/li&gt;
&lt;li&gt;Boolean&lt;/li&gt;
&lt;li&gt;Symbol&lt;/li&gt;
&lt;li&gt;undefined&lt;/li&gt;
&lt;li&gt;null&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;基本类型直接返回就好了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function deepClone(souce) {
  return souce;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;引用类型 递归克隆&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;普通对象&lt;/h5&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;function deepClone(source) {
  if (source instanceof Object) {
    const dist = new Object();
    for (let key in source) {
      dist[key] = deepClone(source[key]);
    }
    return dist;
  } else {
    return source;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;Array 数组对象&lt;/h5&gt;
如果是数组的话 用上面的方法去拷贝 返回的是一个伪数组 &lt;strong&gt;且不会拷贝 length 属性&lt;/strong&gt;
所以我们的 dist 要是一个数组对象&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;function deepClone(source) {
  if (source instanceof Object) {
    if (source instanceof Array) {
      // 数组
      const dist = new Array();
      for (let key in source) {
        dist[key] = deepClone(source[key]);
      }
      return dist;
    } else {
      // 普通对象
      const dist = new Object();
      for (let key in source) {
        dist[key] = deepClone(source[key]);
      }
      return dist;
    }
  } else {
    return source;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;Function 对象&lt;/h5&gt;
函数的话 有两种方法 第一种就是再封装一个函数去调用源函数&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;if(source instance of Function){
  const dist = function () {
    return source.apply(this, arguments);
  };
  for (let key in source) {
    dist[key] = deepClone(source[key]);
  }
  return dist;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是这种方法不会拷贝 length 属性 也就是函数形参个数 可以通过正则匹配函数体 函数参数 然后重新生成函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const sourceString = source.toString();
let body = sourceString.match(/(?&amp;lt;={)((\n*.*)*)(?=})/m)[0];
let params = sourceString.match(/(?&amp;lt;=\().*(?=\))/)[0];
dist = new Function(...params.split(&quot;,&quot;), body);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;RegExp 正则对象&lt;/h5&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;else if (source instanceof RegExp) {
  dist = new RegExp(source.source, source.flags);
  for (let key in source) {
    dist[key] = deepClone(source[key]);
  }
  return dist
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;Date 日期对象&lt;/h5&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;else if (source instanceof Date) {
  dist = new Date(source);
  for (let key in source) {
    dist[key] = deepClone(source[key]);
  }
  return dist
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;环结构&lt;/h5&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;a = { name: &quot;a&quot; };
a.self = a;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;a 就是环状结构
可以通过缓存的方式去拷贝环结构 在拷贝 a.self 的时候我们已经拷贝过 a 了 可以将 a 与 a 的深克隆结果缓存起来&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const cache = []
function deepClone(source){
  if(source instanceof Object){
   const sourceDist = cache.find((value) =&amp;gt; value[0] === source)?.[1];
    if (sourceDist) {
      return sourceDist;
    } else {
      let dist
      ...
      // 放在for 循环的上面 否则会进入死循环
      cache.push([source,dist])
      for (let key in source) {
        dist[key] = deepClone(source[key]);
      }
      return dist
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;最终代码&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 会有bug catch 没有清空 会有影响 可以用面向对象的方法解决
const cache = [];
module.exports = function deepClone(source) {
  // 判断是不是对象
  if (source instanceof Object) {
    const sourceDist = cache.find((value) =&amp;gt; value[0] === source)?.[1];
    if (sourceDist) {
      return sourceDist;
    } else {
      let dist;

      // 数组
      if (source instanceof Array) {
        dist = new Array();

        // 函数
      } else if (source instanceof Function) {
        // dist = function () {
        //   return source.apply(this, arguments);
        // };

        const sourceString = source.toString();
        let body = sourceString.match(/(?&amp;lt;={)((\n*.*)*)(?=})/m)[0];
        let params = sourceString.match(/(?&amp;lt;=\().*(?=\))/)[0];
        dist = new Function(...params.split(&quot;,&quot;), body);

        // 正则
      } else if (source instanceof RegExp) {
        dist = new RegExp(source.source, source.flags);

        // 日期
      } else if (source instanceof Date) {
        dist = new Date(source);

        // 普通对象
      } else {
        dist = new Object();
      }

      cache.push([source, dist]);
      for (let key in source) {
        // 跳过原型上的属性
        if (source.hasOwnProperty(key)) {
          dist[key] = deepClone(source[key]);
        }
      }
      return dist;
    }

    // 基本类型
  } else {
    return source;
  }
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>JS数据类型-Number类型</title><link>https://blog.cxyxiaoyu.top/posts/js%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B-number%E7%B1%BB%E5%9E%8B/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/js%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B-number%E7%B1%BB%E5%9E%8B/</guid><pubDate>Thu, 03 Sep 2020 22:35:47 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最近在做题目LeetCode&lt;a href=&quot;https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/&quot;&gt;表示数值的字符串&lt;/a&gt;这道题的时候
对数值类型又有了新的认识 打算重新总结下&lt;/p&gt;
&lt;h2&gt;1. 整数、浮点数&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;JavaScript 内部，所有数字都是以64位浮点数形式储存，即使整数也是如此。所以，1与1.0是相同的，是同一个数   &lt;code&gt;1 === 1.0&lt;/code&gt;  // true&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;保留两位小数&lt;/h3&gt;
&lt;h4&gt;1 四舍五入&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    var num = 2.446242342
    num = num.toFixed(2)     // 输出是字符串 需要用parseFloat或Number再转一下
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2 不四舍五入&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    Math.floor(15.7784514000 * 100) / 100       // 15.77
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 数值的进制&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;十进制：没有前导0的数值&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;八进制：有前缀0o或0O的数值，或者有前导0、且只用到0-7的八个阿拉伯数字的数值&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;012  // 10
0o12 // 10
0O12 // 10
078  // 78   超出范围 无效的八进制 解析为十进制
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;十六进制：有前缀0x或0X的数值&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0xA   // 10
0x12  // 18
0XAB  //171
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;二进制：有前缀0b或0B的数值&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0b111 // 7
0B11  // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;默认情况下，JavaScript 内部会自动将八进制、十六进制、二进制转为十进制&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;3. 进制之间的转换&lt;/h2&gt;
&lt;h3&gt;1) Number.prototype.toString([radix])&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;toString() 方法返回指定 Number 对象的字符串表示形式。
radix 指定要用于数字到字符串的转换的基数(从2到36)。如果未指定 radix 参数，则默认值为 10。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;(12).toString()   // &quot;12&quot;   十进制转十进制
(012).toString()  // &quot;10&quot;     八进制转十进制
(10).toString(2)  // &quot;1010&quot;   十进制转二进制
(0b1010).toString(8)  // &quot;12&quot;  二进制转八进制
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;toString()以后是字符串格式 在通过格式转换 转为数值类型就好啦 怎么转换，后续有介绍哦&lt;/p&gt;
&lt;h3&gt;2) parseInt(string,[radix])&lt;/h3&gt;
&lt;p&gt;parseInt() 可以将任意进制的数转为10进制&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;parseInt(111,2) // 3
parseInt(12,8)  // 10
parseInt(0xA)   // 10
parseInt(014)   // 12
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关于parseInt()的详细解释在后面&lt;/p&gt;
&lt;h2&gt;4. Number() 函数&lt;/h2&gt;
&lt;p&gt;在说NaN 之前先说下Number函数&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Number() 函数是数值类型的构造函数 同时也可以将其他类型转为数值类型
如果无法转为数值 就返回NaN&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Number()函数的转换规则如下&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Boolean false =&amp;gt; 0 true =&amp;gt; 1&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Number  直接返回&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;null =&amp;gt; 0&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;undefined =&amp;gt; NaN&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;字符串
1 空字符串转为0
2 只包含数字(包含前导正负号) 将其转为十进制
3 如果包含有效的进制格式 将转为相同大小的十进制
4 如果包含有效的浮点格式 将其转为对应的浮点数值
5 如果不符合以上条件 返回NaN&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对象 调用对用的valueOf()方法 然后按照前面的规则进行转换 如果转换的结果是NaN 则调用对象的toString()方法，然后再依照前面的规则进行转换&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Number(false)      // 0 布尔值false对应0
Number(123)        // 123 数值类型直接返回
Number(null)       // 0
Number(undefined)  // NaN
Number(&apos;&apos;)         // 0
Number(&apos;abc&apos;)      // NaN &apos;abc&apos; 不能被转为数字
Number(&apos;123&apos;)      // 123  &apos;123&apos; 可以被转为数字
Number(&apos;123a)      // NaN  &apos;123a&apos; 不是只包含数字不能被转换
Number(&apos;0x12&apos;)     // 18    0x12是有效的进制格式 站位10进制
Number(&apos;1.1&apos;)      // 1.1   有效的浮点格式 转为相应的浮点格式

Number([])        // 0   因为[].toString() ==&amp;gt; &apos;&apos;  Number(&apos;&apos;) ==&amp;gt; 0
Number([2])       // 2   因为[2].toString() ==&amp;gt; &apos;2&apos; Number(&apos;2&apos;) ==&amp;gt; 2
Number([1,2,3])   // NaN  [1,2,3].toString() ==&amp;gt; &apos;1,2,3&apos; 不能转为数值

Number({})        // NaN   ({}).toString() ==&amp;gt; &quot;[object Object]&quot;  不能转为数值
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以对于leetCode 这道题我们可以利用Number()函数对于字符串的转换规则 如果结果是NaN 就不符合条件 否则就是可以转为数值的字符串&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var isNumber = function (s) {
      s = s.trim()
      if (s == &apos;&apos;) return false  // 因为 Number(&apos;&apos;) === 0 所以需要做特殊判断
      return !isNaN(Number(s))
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. NaN&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;NaN 表示“非数字”（Not a Number） 是一个特殊的&lt;strong&gt;数值&lt;/strong&gt; 数据类型依然属于Number&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;主要出现在将字符串解析成数字出错的场合&lt;/li&gt;
&lt;li&gt;一些数学函数的运算结果会出现NaN   比如 0除以0也会得到NaN&lt;/li&gt;
&lt;li&gt;NaN不等于任何值，包括它本身&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;针对NaN的特性 ES定义了isNaN()函数 该函数接受一个参数 可以为任何类型 isNaN()会尝试将这个参数转为数值 转换规则与Number()函数一致 所以isNaN方法可以用来判断一个值是否 &quot;不是数值&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    NaN === NaN  // false
    isNaN(NaN)   // true
    isNaN(123)   // false

    // isNaN()只对数值有效，如果传入其他值，会先转成数值类型
    isNaN(&apos;&apos;)             // false
    isNaN(&apos;hello&apos;)        // true
    //相当于
    isNaN(Number(&apos;hello&apos;)) //true

    isNaN(false)           // false  false会转为0
    isNaN({})              //true
    isNaN([1,2])           //true
    // 所以isNaN() 为true的时候，有可能是一个字符串，或对象或数组
    // 所以使用isNaN()时，需要先判断下数据类型
    // 但是当数组为空，或数组长度为1时返回false
    isNaN([]) //false
    isNaN([1]) //false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于isNaN()函数会对参数进行转换 那这题又可以直接写为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var isNumber = function (s) {
      s = s.trim()
      if (s == &apos;&apos;) return false  // 因为 Number(&apos;&apos;) === 0 所以需要做特殊判断
      return !isNaN(s)
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. parseInt()&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;parseInt()解析一个字符串并返回指定基数的十进制整数， radix 是2-36之间的整数，表示被解析字符串的基数。 如果不能解析的话返回NaN&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;parseInt 参数&lt;/h3&gt;
&lt;p&gt;parseInt(string,[radix]) 接受两个参数&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;string 如果参数不是字符串类型 会通过toString()方法转为字符串&lt;/li&gt;
&lt;li&gt;radix  从2-36 表示字符串的基数 需要注意的是 &lt;strong&gt;10不是默认值&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果 radix 是 undefined、0或未指定的，JavaScript会假定以下情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果输入的 string以 &quot;0x&quot;或 &quot;0x&quot;（一个0，后面是小写或大写的X）开头，那么radix被假定为16，字符串的其余部分被当做十六进制数去解析。&lt;/li&gt;
&lt;li&gt;如果输入的 string以 &quot;0&quot;（0）开头， radix被假定为8（八进制）或10（十进制）。具体选择哪一个radix取决于实现。ECMAScript 5 澄清了应该使用 10 (十进制)，但不是所有的浏览器都支持。因此，在使用 parseInt 时，一定要指定一个 radix。&lt;/li&gt;
&lt;li&gt;如果输入的 string 以任何其他值开头， radix 是 10 (十进制)。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;parseInt(110,2)  // 6 二进制110 转为十进制是 6
parseInt(123,8)  // 83 八进制的123 转为十进制是83
parseInt(89,8)   // NaN 不是合法的八进制
parseInt(&apos;014&apos;)  // 14  被当做十进制解析
parseInt(014)    // 12  因为(014).toString() =&amp;gt; 12
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;一道有意思的面试题&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[&quot;1&quot;, &quot;2&quot;, &quot;3&quot;].map(parseInt)  // [1,NaN,NaN]
// 相当于parseInt(1,0)  =&amp;gt; 1 string不以任何其他值开头 radix为十进制 parseInt(1,10) =&amp;gt; 1
// parseInt(2,1) =&amp;gt; NaN  1进制不合法 所以返回NaN
// parseInt(3,2) =&amp;gt; NaN  3 是不合法的二进制 所以返回NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;parseInt 忽略后续字符&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;如果 parseInt 遇到的字符不是指定 radix 参数中的数字，它将忽略该字符以及所有后续字符，并返回到该点为止已解析的整数值。 parseInt 将数字截断为整数值。 允许前导和尾随空格。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;parseInt(&apos;123a&apos;,2) // 1 因为符合二进制的只有1
parseInt(&apos;1123110&apos;,2)  // 3  符合二进制的只有前面的11 找到2不符合 会忽略后面的字符 解析为十进制为3
parseInt(&apos;123abc&apos;)     // 123 这通常用来在字符串中提取前面的数值
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;7. parseFloat()&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;parseFloat() 函数解析一个参数（必要时先转换为字符串）并返回一个浮点数。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;如果 parseFloat 在解析过程中遇到了正号（+）、负号（- U+002D HYPHEN-MINUS）、数字（0-9）、小数点（.）、或者科学记数法中的指数（e 或 E）以外的字符，则它会忽略该字符以及之后的所有字符，返回当前已经解析到的浮点数。&lt;/li&gt;
&lt;li&gt;第二个小数点的出现也会使解析停止（在这之前的字符都会被解析）。&lt;/li&gt;
&lt;li&gt;参数首位和末位的空白符会被忽略。&lt;/li&gt;
&lt;li&gt;如果参数字符串的第一个字符不能被解析成为数字,则 parseFloat 返回 NaN。&lt;/li&gt;
&lt;li&gt;parseFloat 也可以解析并返回 Infinity。&lt;/li&gt;
&lt;li&gt;parseFloat解析 BigInt 为 Numbers, 丢失精度。因为末位 n 字符被丢弃。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// 以下的栗子全部返回3.14
parseFloat(3.14);
parseFloat(&apos;3.14&apos;);
parseFloat(&apos;  3.14  &apos;);
parseFloat(&apos;314e-2&apos;);
parseFloat(&apos;0.0314E+2&apos;);
parseFloat(&apos;3.14some non-digit characters&apos;);
parseFloat({ toString: function() { return &quot;3.14&quot; } });
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;8. 其他类型转换为数值类型&lt;/h2&gt;
&lt;p&gt;有三个函数可以将非数值转为数值类型: Number() parseInt() parseFloat()
这三个函数对于同样的输入会返回不同的类型&lt;/p&gt;
&lt;h3&gt;方法1  &lt;code&gt;Number()&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;转换规则上面已说过&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    Number(&apos;1&apos;) // 1
    Number(&apos;1.123&apos;) // 1.123
    Number(&apos;1.abc&apos;) // NaN
    Number(&apos;&apos;)    // 0
    Number(true)  // 1
    Number(false) // 0
    Number(null) // 0
    Number(undefined) // NaN
    Number({a:1})  // NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方法2  &lt;code&gt;parseInt()&lt;/code&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;parseInt()&lt;/code&gt; 主要是将字符串转为整数
它会从第一个字符开始，直到不是数字结束&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    parseInt(&apos;1&apos;) //1
    parseInt(&apos;1.23&apos;) // 1
    parseInt(&apos;  123&apos;) // 123
    parseInt(&apos;1234abc&apos;)  // 1234
    parseInt(&apos;abc&apos;) // NaN
    parseInt(&apos;a567bc&apos;) // NaN
    parseInt(true) // NaN
    parseInt(null) // NaN
    parseInt(undefined) // NaN
    parseInt({a:1}) // NaN
    parseInt([1,2,3]) // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方法3  &lt;code&gt;parseFloat()&lt;/code&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;parseFloat() 主要是将字符串转为浮点数&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    parseFloat(&apos;3.14&apos;) // 3.14
    parseFloat(&apos;314e-2&apos;) // 3.14
    parseFloat(&apos;0.0314E+2&apos;) // 3.14
    // 其他与parseInt()结果相同
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方法4  &lt;code&gt;- 0&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    &apos;1&apos; - 0 // 1
    &apos;1.23&apos; - 0  // 1.23
    &apos;1.abc&apos; -0 // NaN
    &apos;&apos; - 0  // 0
    true -0 // 1
    false - 0  // 0
    null - 0 // 0
    undefined - 0 // NaN
    ({a:1}) - 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方法5  取正&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    + &apos;1&apos; // 1
    + &apos;1.23&apos; // 1.23
    + &apos;1.abc&apos; // NaN
    + &apos;&apos; // 0
    + true // 1
    + false // 0
    + null // 0
    + undefined // NaN
    + {a:1} // NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;9. 补充一个比较运算符的坑&lt;/h2&gt;
&lt;h3&gt;不同类型&lt;/h3&gt;
&lt;p&gt;比较&lt;strong&gt;不同类型&lt;/strong&gt;的数据也许会出现不可预料的结果。
如果将字符串与数字进行比较，那么在做比较时 JavaScript 会把字符串转换为数值。
空字符串将被转换为 0  非数值字符串将被转换为始终为 false 的 NaN。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos; &apos; &amp;gt;= 0 &amp;amp;&amp;amp; &apos; &apos; &amp;lt;= 9  // true 因为空字符串会转为0
&apos;a&apos; &amp;gt;= 0 || &apos;a&apos; &amp;lt;= 9  // false  &apos;a&apos; 转为数值 NaN
&apos;123a&apos; &amp;gt;= &apos;123&apos;       // false &apos;123a&apos;也会转为NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;相同类型都为字符串&lt;/h3&gt;
&lt;p&gt;字符串大小会按照字母排序&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;2&apos; &amp;gt; &apos;12&apos;   // true
&apos;2020-6-8&apos; &amp;gt; &apos;2020-6-14&apos;  // true 因为 &apos;8&apos; &amp;gt; &apos;14&apos;
// 所以时间格式比较日期格式要yyyy-MM-dd去比较
&apos;2020-06-08&apos; &amp;gt; &apos;2020-06-14&apos; // false  &apos;08&apos; &amp;lt; &apos;14&apos;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>JS基础巩固篇</title><link>https://blog.cxyxiaoyu.top/posts/js%E5%9F%BA%E7%A1%80%E5%B7%A9%E5%9B%BA%E7%AF%87/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/js%E5%9F%BA%E7%A1%80%E5%B7%A9%E5%9B%BA%E7%AF%87/</guid><pubDate>Sun, 30 Aug 2020 21:27:48 GMT</pubDate><content:encoded>&lt;h1&gt;一些关于JS的基础知识巩固复习、整理&lt;/h1&gt;
&lt;p&gt;学了忘 忘了学。。。&lt;/p&gt;
&lt;h2&gt;一 instanceof&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;arr = [1,2,3]
arr instanceof Array
// true  因为 arr.__proto__ === Array.prototype
arr instanceof Object
// true  因为 arr.__proto__.__proto__ === Object.prototype
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一般情况下instanceof 不能判断基本类型&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;abc&apos; instanceof String   // false
123 instanceof Number     // false
true instanceof Boolean   // false
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;自己实现 instanceof&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const myInstanceof = function(instance, constructor){
  // 如果是基本类型就返回false 用 typeof 判断
  if(typeof instance !== &apos;object&apos; &amp;amp;&amp;amp; typeof instance !== &apos;function&apos; || instance === null){
    return false
  }

  let proto = instance.__proto__
  // 或者 proto = Object.getPrototypeOf(instance)

  while(proto !== null){   // 因为原型链的终点是 null
    if(proto === constructor.prototype) return true
    proto = proto.__proto__
  }
  return false

  // 或者直接用isPrototypeOf判断
  // return constructor.prototype.isPrototypeOf(instance)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;myInstanceof(123,Object)         // false
myInstanceof(&apos;abc&apos;,String)       // false
myInstanceof([],Array)           // true
myInstanceof([],Object)          // true
myInstanceof({},Array)           // false
myInstanceof({},Object)          // true
myInstanceof(Math.max,Function)  // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二 call bind apply的区别&lt;/h2&gt;
&lt;p&gt;这三个方法都是在函数的原型链上的方法，作用是用来改变this指向
这三个函数的第一个参数都是指定this&lt;/p&gt;
&lt;h3&gt;call方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 通过数组的方法将类数组转为数组avascript
let arr3 = {0:1,1:0,2:3,3:2,length:4}
Array.prototype.slice.call(arr3)  // 这样arr3本来没有slice方法 现在也可以使用了
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;apply方法&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;apply() 方法调用一个具有给定this值的函数，以及以一个数组（或类数组对象）的形式提供的参数。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;appyl与call的区别&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;call的第二个参数传的是参数列表&lt;/li&gt;
&lt;li&gt;apply的第二个参数传的的数组&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// 求最大值
Math.max(1,3,4,2)  // 4
Math.max.call(null,1,3,2,4)
Math.max.apply(null,[1,3,2,4])  // 这样可以直接求数组中的最大值
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;bind方法&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;bind() 方法创建一个新的函数，在 bind() 被调用时，这个新函数的 this 被指定为 bind() 的第一个参数，而其余参数将作为新函数的参数，供调用时使用。
bind改变this指向后不会立即调用 而是返回一个yongq8&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;let obj = {
  say(){
    console.log(this)
  }
}
setTimeout(obj.say,1000)   // 调用的时候打印的this是window,1000)
// 我们可以通过bind绑定this
setTimeout(obj.say.bind(obj),2000)   // 打印的时候是obj

const fn = function(){
  console.log(this.name)
}
let obj = {
  name: &apos;obj 的name&apos;
}
let objFn = fn.bind(obj)
objFn()  // &apos;obj&apos;的name
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三 Object.defineProperty&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Object.defineProperty() 方法会直接在一个对象上定义一个新属性，或者修改一个对象的现有属性，并返回此对象。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对象数据属性默认有四个特性&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;value  值 默认为undefined&lt;/li&gt;
&lt;li&gt;configurable (是否可修改描述符对象 是否能通过delete删除属性 默认为false)&lt;/li&gt;
&lt;li&gt;enumerable (是否可被遍历 默认为false)&lt;/li&gt;
&lt;li&gt;writable (是否可改变 默认为false)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对象访问器属性 不包含数据值 有四个特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configurable&lt;/li&gt;
&lt;li&gt;Enumerable&lt;/li&gt;
&lt;li&gt;Get&lt;/li&gt;
&lt;li&gt;Set&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要修改属性的默认特性，必须使用Object.defineProperty()方法 接受三个参数： 属性所在的对象  属性的名字  一个描述符对象&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const object = {};

Object.defineProperty(object, &apos;property1&apos;, {
  value: 42,
  writable: false,      // 值不可变
  configurable: true,   // 可以被修改 可以被delete
  enumerable: false
})

object.property1 = 77   // throws an error in strict mode
console.log(object.property1)  // 42 因为该属性的值是不可改变的

Object.defineProperty(object, &apos;property1&apos;, {
  value: 42,
  writable: true,      // 重新修改为值可变
})

object.property1 = 77
console.log(object.property1)   // 77 现在值是可变的

delete object.property1   // true 可以被删除

Object.defineProperty(object,&apos;property2&apos;,{
  value: 55,
  enumerable: true
})

// 可被遍历的属性
Object.keys(object)   // [&apos;property2&apos;] 因为property2的enumerable是true
for(let key in object) {
  console.log(key)       // &apos;property2&apos;  只有一个属性可被遍历到
}

Object.property2 = 77  // throws an error in strict mode
console.log(Object.property2)  // 55 默认就是不可变的

Object.defineProperty(object,&apos;property2&apos;,{
  writable: true
})   // 报错 因为 configurable 默认为false 不可再重新定义描述符对象

delete object.property2  // false configurable 默认为false 属性不可被删除

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;定义访问器属性&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var book = {
   _year: 2004,
   edition: 1
}

Object.defineProperty(book,&apos;year&apos;,{
  get: function(){
    return this._year
  },
  set: function(newValue){
    if(newValue){
      this._year = newValue
      this.edition += newValue - 2004
    }
  }
})
// {_year: 2004, edition: 1}
book.year = 2005  // 2005
book.edition      // 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;定义多个属性时可以用 Object.defineProperties方法&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var obj = {};
Object.defineProperties(obj, {
  &apos;property1&apos;: {
    value: true,
    writable: true
  },
  &apos;property2&apos;: {
    value: &apos;Hello&apos;,
    writable: false
  }
  // etc. etc.
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四 Object.getOwnPropertyDescriptor&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Object.getOwnPropertyDescriptor()方法可以读取属性的描述符&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Object.getOwnPropertyDescriptor(object,&apos;property1&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./getOwnPropertyDescriptor.png&quot; alt=&quot;getOwnPropertyDescriptor&quot; /&gt;&lt;/p&gt;
&lt;p&gt;同理 &lt;strong&gt;Object.getOwnPropertyDescriptors()方法用来获取一个对象的所有自身属性的描述符&lt;/strong&gt;
&lt;img src=&quot;./getOwnPropertyDescriptors.png&quot; alt=&quot;getOwnPropertyDescriptors&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Vue-组件通信</title><link>https://blog.cxyxiaoyu.top/posts/vue-%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/vue-%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1/</guid><pubDate>Sun, 30 Aug 2020 15:15:55 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;vue组件通信的方式如此之多 今天我们来总结一下 &lt;a href=&quot;https://github.com/cxyxiaoyuyu/vue-demo&quot;&gt;完整代码&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;一、 props、$emit/v-on&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;父组件通过props向子组件传递数据
子组件使用事件抛出一个值&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;父组件代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;parent&quot;&amp;gt;
    &amp;lt;h4&amp;gt;我是父组件&amp;lt;/h4&amp;gt;
    &amp;lt;child :title=&quot;title&quot; @getMsg=&quot;getMsgFromChild&quot;&amp;gt;&amp;lt;/child&amp;gt;
    &amp;lt;div&amp;gt;子组件向我传递的消息 &amp;lt;b&amp;gt;{{ msgFromChild }}&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
// @ is an alias to /src
import child from &apos;@/components/child.vue&apos;

export default {
  data(){
    return {
      title: &apos;我是来自父组件的title&apos;,
      msgFromChild: &apos;&apos;
    }
  },
  methods: {
    getMsgFromChild(msg){
      this.msgFromChild = msg
    }
  },
  components: {
    child
  }
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;子组件代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;child&quot;&amp;gt;
    &amp;lt;h4&amp;gt;我是子组件&amp;lt;/h4&amp;gt;
    &amp;lt;div&amp;gt;这是来自父组件的title: &amp;lt;b&amp;gt;{{ title }}&amp;lt;/b&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;button @click=&quot;sendMsgToParent&quot;&amp;gt;点我向父组件传递信息&amp;lt;/button&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script&amp;gt;
  export default {
    props: [&apos;title&apos;],
    methods: {
      sendMsgToParent(){
        this.$emit(&apos;getMsg&apos;,&apos;我是来自子组件的信息&apos;)
      }
    }
  }
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二、 事件总线 $on/$emit&lt;/h2&gt;
&lt;p&gt;通过事件总线的话我们需要在Vue原型上添加一个Vue实例作为事件总线，
实现&lt;strong&gt;组件间&lt;/strong&gt;相互通信,从而不受组件关系影响&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 在main.js中
Vue.prototype.$bus = new Vue()

// 子组件中通过 $bus.$emit发送事件
this.$bus.$emit(&apos;getMsgByBus&apos;,&apos;我是通过事件总线传递的信息&apos;)

// 父组件通过 $bus.$on 来监听事件
this.$bus.$on(&apos;getMsgByBus&apos;,(msg)=&amp;gt;{
  this.msgFromChild = msg
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在原型上添加一个vue实例其实就是需要用到实例的$emit和$on 方法
我们也可以自己写一个事件总线&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Bus {
  constructor(){
    this.callbacks = {}
  }
  $on(name, fn){
    this.callbacks[name] = this.callbacks[name] || []
    this.callbacks[name].push(fn)
  }
  $emit(name, args){
    if(this.callbacks[name]){
     this.callbacks[name].forEach(cb =&amp;gt; cb(args))
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么现在就可以在原型上添加Bus的实例达到同样的效果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Vue.prototype.$bus = new Bus()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、 vuex&lt;/h2&gt;
&lt;h2&gt;四、 provide/inject&lt;/h2&gt;
&lt;h2&gt;五 attrs/listeners&lt;/h2&gt;
&lt;h2&gt;六、边界情况 $root $parent $children $refs&lt;/h2&gt;
&lt;h3&gt;$root&lt;/h3&gt;
&lt;h3&gt;$parent&lt;/h3&gt;
&lt;h3&gt;$children&lt;/h3&gt;
&lt;h3&gt;$refs&lt;/h3&gt;
</content:encoded></item><item><title>Vue-双向绑定v-model</title><link>https://blog.cxyxiaoyu.top/posts/vue-%E5%8F%8C%E5%90%91%E7%BB%91%E5%AE%9Av-model/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/vue-%E5%8F%8C%E5%90%91%E7%BB%91%E5%AE%9Av-model/</guid><pubDate>Sun, 30 Aug 2020 13:48:42 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;我们都知道v-model可以实现双向绑定，之前对v-model的了解还比较单一，今天来总结一下&lt;/p&gt;
&lt;h2&gt;单向绑定与双向绑定&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;单向绑定就是当我们用JavaScript代码更新Model时，View就会自动更新。
如果用户更新了View，Model的数据也自动被更新了，这种情况就是双向绑定。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首先 单向绑定 vue的响应式系统已经帮我们做了，那么我们如何在用户更新视图时更新数据呢？
对于文本输入框，在用户修改时，就会触发input事件，那么可以监听这个input事件，将这个输入框的值赋值到对应的数据&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;input type=&apos;text&apos; :value=&quot;value&quot; @input=&quot;value = $event.target.value&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;v-model是个语法糖&lt;/h3&gt;
&lt;p&gt;以上我们已经实现了双向绑定 用v-model刚好可以替代这种复杂的语法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;input type=&apos;text&apos; v-model=&quot;value&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;哈哈，简洁了不少&lt;/p&gt;
&lt;h2&gt;其他元素上的v-model&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;v-model会根据控件类型自动选取正确的方法来更新元素&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;text 和 textarea 元素使用 value property 和 input 事件；&lt;/li&gt;
&lt;li&gt;checkbox 和 radio 使用 checked property 和 change 事件；&lt;/li&gt;
&lt;li&gt;select 字段将 value 作为 prop 并将 change 作为事件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;具体参考 &lt;a href=&quot;https://cn.vuejs.org/v2/guide/forms.html&quot;&gt;表单输入绑定&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;自定义组件实现v-model&lt;/h2&gt;
&lt;p&gt;当用在组件上时，v-model 则会这样：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;custom-input
      :value=&quot;searchText&quot;
      @input=&quot;searchText = $event&quot;&amp;gt;
    &amp;lt;/custom-input&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;这里的$event表示子组件传回来的值&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了让它正常工作，这个组件内的&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;必须：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将其 value attribute 绑定到一个名叫 value 的 prop 上&lt;/li&gt;
&lt;li&gt;在其 input 事件被触发时，将新的值通过自定义的 input 事件抛出&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    Vue.component(&apos;custom-input&apos;, {
      props: [&apos;value&apos;],
      template: `
        &amp;lt;input
          v-bind:value=&quot;value&quot;
          v-on:input=&quot;$emit(&apos;input&apos;, $event.target.value)&quot;
        &amp;gt;
      `
    })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在 v-model 就应该可以在这个组件上完美地工作起来了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;custom-input v-model=&quot;searchText&quot;&amp;gt;&amp;lt;/custom-input&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;vue3的v-model&lt;/h2&gt;
&lt;p&gt;以上都是vue2的v-model vue3对v-model进行了&lt;a href=&quot;https://v3.vuejs.org/guide/migration/v-model.html&quot;&gt;一些改进&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先属性名任意 不一定要是value (这里以xxx举例)&lt;/li&gt;
&lt;li&gt;事件变成了update:xxx&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那么vue3的v-model 对searchText属性进行双向绑定时就是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;custom-input
      :xxx=&quot;searchText&quot;
      @updae:xxx=&quot;searchText = $event&quot;&amp;gt;
    &amp;lt;/custom-input&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;子组件接收xxx属性 并触发&lt;code&gt;update:xxx&lt;/code&gt;事件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    Vue.component(&apos;custom-input&apos;, {
      props: [&apos;xxx&apos;],
      template: `
        &amp;lt;input
          v-bind:value=&quot;xxx&quot;
          v-on:input=&quot;$emit(&apos;update:xxx&apos;, $event.target.value)&quot;
        &amp;gt;
      `
    })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在父组件中使用v-model&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;custom-input v-model:xxx=&quot;searchText&quot;&amp;gt;&amp;lt;/custom-input&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意的是 vue3的v-model语法取代了.sync语法&lt;/p&gt;
</content:encoded></item><item><title>位操作符与逻辑操作符</title><link>https://blog.cxyxiaoyu.top/posts/%E4%BD%8D%E8%BF%90%E7%AE%97%E7%AC%A6%E4%B8%8E%E9%80%BB%E8%BE%91%E8%BF%90%E7%AE%97%E7%AC%A6/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E4%BD%8D%E8%BF%90%E7%AE%97%E7%AC%A6%E4%B8%8E%E9%80%BB%E8%BE%91%E8%BF%90%E7%AE%97%E7%AC%A6/</guid><pubDate>Mon, 17 Aug 2020 21:36:55 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;在判断分支中使用符号&quot;&amp;amp;“和”&amp;amp;&amp;amp;&quot;作为逻辑判断是经常见的，但是这两者又有什么区别呢？
他们分别代表的位运算符和逻辑运算符又有什么区别呢？&lt;/p&gt;
&lt;h2&gt;位操作符&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;位操作符用于在最基本的层次上，即按内存中表示数值的位来操作数值&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;补充知识点 负数存储&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;负数同样是以二进制存储 但使用的格式是二进制补码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;计算一个数值(负数)的二进制补码，需要经过三个步骤
1）求这个数值&lt;strong&gt;绝对值&lt;/strong&gt;的二进制码 例如 要求 -18的二进制补码 先求出18的二进制码
2）求二进制反码 即将0换为1 1换为0
3）最后二进制反码 + 1
&lt;img src=&quot;./%E4%BA%8C%E8%BF%9B%E5%88%B6%E8%A1%A5%E7%A0%81.png&quot; alt=&quot;二进制补码&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;1 按位非( ~ )&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;执行按位非的结果就是返回数值的反码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;~25 = -26
~10 = -11
~-10 = 9
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后我们发现了规律 好像就是 &lt;strong&gt;这个数的负数减一&lt;/strong&gt;
我们知道一个数的负数二进制存储是 该数取反 +1  那么很容易推出一个数的反码就是 该数的负数 -1&lt;/p&gt;
&lt;h3&gt;2 按位与( &amp;amp; )&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;按位与只有在两个数值的对应位都是1时才返回1 任何一位是0 结果都是0&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;0b110 &amp;amp; 0b011  // 2
25 &amp;amp; 3     // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%8C%89%E4%BD%8D%E4%B8%8E.png&quot; alt=&quot;按位与&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;3 按位或( | )&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;只有两个数值的对应位都是0的情况下才返回0 否则返回1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;0b110 | 0b011  // 0b111 =&amp;gt; 7
0b001 | 0b011  // 0b011 =&amp;gt; 3
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4 按位异或( ^ )&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这两个数值的对应位上有一个1时才返回1 否则返回0&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;0b011 ^ 0b0101    // 0b0110 =&amp;gt; 6
25 ^ 3   // 26
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%BC%82%E6%88%96.png&quot; alt=&quot;异或&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;异或的应用&lt;/h4&gt;
&lt;p&gt;任何数和0异或得到他本身 和自己异或得到0&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;15 ^ 0   // 15
15 ^ 15  // 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5 左移( &amp;lt;&amp;lt; )&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这个操作符会将所有位向左移动指定的位数
向左移动后原始值的右侧会多出空位，左移操作会用0来填充这些空位
以便得到的结果是一个32位的二进制数&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;2 &amp;lt;&amp;lt; 5  // 64   10 =&amp;gt; 1000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意的是，&lt;strong&gt;左移不会影响操作数的符号位&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-2 &amp;lt;&amp;lt; 5 // - 64
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;左移的应用&lt;/h4&gt;
&lt;p&gt;左移一位相当于在右边补一个0 相当于原数 * 2&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2 &amp;lt;&amp;lt; 1  // 4
5 &amp;lt;&amp;lt; 1  // 10
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6 有符号的右移( &amp;gt;&amp;gt; )&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;有符号的右移会将所有位向右移动 但保留符号位
有符号的右移与左移相反&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;64 &amp;gt;&amp;gt; 5   // 2   1000000 =&amp;gt; 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;移动过程中也会出现空位 不过空位是出现在原数值的左侧 符号位的右侧 而此时ES会用&lt;strong&gt;符号位的值&lt;/strong&gt;来填补空位&lt;/p&gt;
&lt;h3&gt;7 无符号的右移( &amp;gt;&amp;gt;&amp;gt; )&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这个操作会将所有的32位都像右移动 即符号位也右移
无符号右移是以0来填充空位
所以,对正数来说, 无符号的右移与有符号右移的结果相同&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;64 &amp;gt;&amp;gt; 5   // 2
64 &amp;gt;&amp;gt;&amp;gt; 5  // 2
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;但对于负数来说，情况就不一样了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;-64 &amp;gt;&amp;gt;&amp;gt; 5  //  134217726
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为负数是以其绝对值的二进制补码表示,无符号右移会把这个二进制当做正数表示，所以会导致右移后的结果非常大&lt;/p&gt;
&lt;h2&gt;逻辑操作符&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;逻辑运算符用于进行逻辑运算的，他的返回值只有true和false&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;逻辑非( ! )&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;逻辑非首先会操作数转换为对应的布尔值，然后对其求反&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我们知道js中有五大falsy值 分别是  &lt;strong&gt;NaN 0 空字符串 undefined null&lt;/strong&gt;
所以对这五个falsy值取反得到的是true&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;!undefined  // true
!null       // true
!&apos;&apos;         // true
!0          // true
!NaN        // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意的是js中 空数组和空对象对应的布尔值都是true&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;!{}  // false
![]  // false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用两次逻辑非就可以得到一个数的布尔值 相当于Boolean()函数&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;!!{}  // true
!!0   // false
!!undefined // false
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;逻辑或( || )&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;两个表达式只要有一个是true 那么结果就是true
短路操作符 如果第一个表达式结果是true 那么就不会看第二个表达式&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;2 &amp;gt; 3 || 3 &amp;lt; 5    // true
2 &amp;gt; 3 || 3 &amp;gt; 5    // false
false || true     // true
false || false    // false
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;逻辑与( &amp;amp;&amp;amp; )&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;只有两个表达式结果都为true  结果才为true 否则为false
短路操作符 如果第一个表达式是false  那么直接返回false 不会看第二个表达式&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;2 &amp;gt; 3 &amp;amp;&amp;amp; 3 &amp;lt; 5    // false
2 &amp;gt; 3 &amp;amp;&amp;amp; 3 &amp;gt; 5    // false
2 &amp;lt; 3 &amp;amp;&amp;amp; 3 &amp;lt; 5    // true
false &amp;amp;&amp;amp; true     // true
true &amp;amp;&amp;amp; true      // true
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>排序算法--快速排序</title><link>https://blog.cxyxiaoyu.top/posts/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95--%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95--%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F/</guid><pubDate>Sun, 09 Aug 2020 12:50:45 GMT</pubDate><content:encoded>&lt;h1&gt;快速排序&lt;/h1&gt;
&lt;h3&gt;什么是快速排序 ？&lt;/h3&gt;
&lt;p&gt;通过一趟排序将要排序的数据分割成独立的两部分，其中一部分的所有数据都比另外一部分的所有数据都要小，然后再按此方法对这两部分数据分别进行快速排序，整个排序过程可以递归进行，以此达到整个数据变成有序序列&lt;/p&gt;
&lt;h4&gt;排序流程&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;(1)首先设定一个分界值pivot，通过该分界值将数组分成左右两部分。&lt;/li&gt;
&lt;li&gt;(2)大于pivot的放到右边,小于pivot的放到左边&lt;/li&gt;
&lt;li&gt;(3)对于左侧和右侧的数据又可以用递归的方式进行排序&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;非原地快速排序&lt;/h3&gt;
&lt;p&gt;非原地排序即不需要在原数组上修改顺序 所以我们可以自己定义一个结果数组
以arr[0]作为分界值&lt;/p&gt;
&lt;p&gt;代码如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function quick_sort(arr){
    if(arr.length &amp;lt; 2){
        return arr
    }else{
        let pivot = arr[0]
        let left = [], right = []
        for(let i=1;i&amp;lt;arr.length;i++){
            if(arr[i] &amp;lt;= pivot){
                left.push(arr[i])
            }else{
                right.push(arr[i])
            }
        }
        return [...quick_sort(left),pivot,...quick_sort(right)]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;原地快速排序&lt;/h3&gt;
&lt;p&gt;不能创建新数组 必须&lt;strong&gt;就地排序&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function quick_sort_in_place(arr){
    var quick_sort = (arr,start,end)=&amp;gt;{
        if(end - start &amp;lt;=1) return
        let pivotIndex = handlePivot(arr,start,end)
        quick_sort(arr,start,pivotIndex)
        quick_sort(arr,pivotIndex+1,end)
    }
    quick_sort(arr,0,arr.length)
}

// 原地将小的放左边 大的放右边  找到基准值的下标
function handlePivot(arr,start,end){
    if(end - start &amp;lt;= 1) return end - start - 1
    let k = start + 1
    while(end - k &amp;gt; 0){
        if(arr[k] &amp;gt; arr[start]){
            swap(arr,k,end-1)   // 当最后一次arr[k] &amp;gt; arr[start]时 会自己跟自己交换
            end --
        }else{
            k++
        }
    }
    swap(arr,start,k-1)
    return k - 1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;随机化快排&lt;/h3&gt;
&lt;p&gt;每一次都以第一个数作为分界值 如果数据不太巧的话 会让交换的次数更多
所以可以随机选择排序数列的任意一个作为分界值 找到后与第一个数交换位置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function handlePivot(arr,start,end){
    if(end - start &amp;lt;= 1) return end - start - 1

    // 在[start,end)之间随机生成一个位置  然后与第一个数交换位置
    let random = Math.floor(Math.random()*(end - start)+start)
    swap(arr[random],arr[0])

    let k = start + 1
    while(end - k &amp;gt; 0){
        if(arr[k] &amp;gt; arr[start]){
            swap(arr,k,end-1)   // 当最后一次arr[k] &amp;gt; arr[start]时 会自己跟自己交换
            end --
        }else{
            k++
        }
    }
    swap(arr,start,k-1)
    return k - 1
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>关于CSS的一些零碎应用整理</title><link>https://blog.cxyxiaoyu.top/posts/css%E7%9A%84%E4%B8%80%E4%BA%9B%E5%BA%94%E7%94%A8/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/css%E7%9A%84%E4%B8%80%E4%BA%9B%E5%BA%94%E7%94%A8/</guid><pubDate>Wed, 29 Jul 2020 22:56:26 GMT</pubDate><content:encoded>&lt;h1&gt;关于CSS的一些零碎应用整理&lt;/h1&gt;
&lt;h3&gt;1 隐藏滚动条&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;.menuList::-webkit-scrollbar {
    display:none
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1 :after+content 清除浮动&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 是给浮动元素的父元素加上
.floatEleParent::after{
    display:block;
    content:&quot;clear&quot;;
    height:0;
    clear:both;
    overflow:hidden;
    visibility:hidden;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3 select 去掉默认的下拉箭头&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;select {
    appearance:none;
    -moz-appearance:none;
    -webkit-appearance:none;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4 修改input placeholder 样式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;input::-moz-placeholder {
    color:#7DC0B8; font-size:16px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5 background linear-gradient  线性渐变 设置位置&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 从上到下 开始是#11998E 到62%开始渐变为#38EF7D
linear-gradient(#11998E 62%, #38EF7D);

// 渐变轴为45度，从蓝色渐变到红色
linear-gradient(45deg, blue, red);

// 从右下到左上、从蓝色渐变到红色
linear-gradient(to left top, blue, red);

// 从下到上，从蓝色开始渐变、到高度40%位置是绿色渐变开始、最后以红色结束
linear-gradient(0deg, blue, green 40%, red);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6 background-clip: text 背景被裁剪成文字的前景色&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./background-clip.png&quot; alt=&quot;背景被裁剪成文字的前景色&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;7 单行文字、多行文字溢出省略&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 单行文字
div {
    overflow: hidden;
    text-overflow:ellipsis;
    white-space: nowrap;
}

// 多行文字
div {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8&lt;/h3&gt;
</content:encoded></item><item><title>发布自己的npm包</title><link>https://blog.cxyxiaoyu.top/posts/%E5%8F%91%E5%B8%83%E8%87%AA%E5%B7%B1%E7%9A%84npm%E5%8C%85/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%8F%91%E5%B8%83%E8%87%AA%E5%B7%B1%E7%9A%84npm%E5%8C%85/</guid><pubDate>Sun, 19 Jul 2020 22:42:45 GMT</pubDate><content:encoded>&lt;h2&gt;创建npm项目&lt;/h2&gt;
&lt;p&gt;1 创建一个目录 进入这个目录
2 &lt;code&gt;npm init&lt;/code&gt; 创建 package.json&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;name 包名
version 版本号 通常是1.0.0
main  入口文件 通常是index.js&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;3  创建入口文件 index.js
4  入口文件 index.js 添加如下内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  // 导出 printMsg函数
  exports.printMsg = function() {
    console.log(&quot;This is a message from the demo package&quot;);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5 创建readme.md 文件  发布后会显示在页面上&lt;/p&gt;
&lt;h2&gt;发布到npm官网&lt;/h2&gt;
&lt;p&gt;1  注册npm账号
2  npm login 登录  npm whoami 确认登录
3  npm publish  发布到官网
4  测试 https://npmjs.com/package/&amp;lt;package&amp;gt;  查看是否发布成功&lt;/p&gt;
&lt;h2&gt;使用自己的npm包&lt;/h2&gt;
&lt;p&gt;1 进入另一个目录
2 npm install &amp;lt;your-package&amp;gt;
3 创建一个test.js文件，requier这个包，并调用printMsg方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let value = require(&apos;xiaoyu-npm-demo&apos;)
value.printMsg()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4 执行node test.js 查看是否有输出&lt;/p&gt;
</content:encoded></item><item><title>动态规划的那些题</title><link>https://blog.cxyxiaoyu.top/posts/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%9A%84%E9%82%A3%E4%BA%9B%E9%A2%98/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%9A%84%E9%82%A3%E4%BA%9B%E9%A2%98/</guid><pubDate>Sun, 12 Jul 2020 21:57:52 GMT</pubDate><content:encoded>&lt;h1&gt;动态规划&lt;/h1&gt;
&lt;h3&gt;1 将数字翻译成字符串&lt;/h3&gt;
&lt;p&gt;给定一个数字，我们按照如下规则把它翻译为字符串：0 翻译成 “a” ，1 翻译成 “b”，……，11 翻译成 “l”，……，25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数，用来计算一个数字有多少种不同的翻译方法。&lt;/p&gt;
&lt;p&gt;输入: 12258
输出: 5
解释: 12258有5种不同的翻译，分别是&quot;bccfi&quot;, &quot;bwfi&quot;, &quot;bczi&quot;, &quot;mcfi&quot;和&quot;mzi&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 主要就是判断最后两个数字是否在10-25之间
var translateNum = function(num) {
    var num_str = num.toString()
    var n = num_str.length
    var d = [1]
    d[-1] = 1
    for(let i=1;i&amp;lt;n;i++){
        let dig = +num_str.substr(i-1,2)
        if(dig &amp;gt;= 10 &amp;amp;&amp;amp; dig &amp;lt;=25){
            d[i] = d[i-1] + d[i-2]
        }else{
            d[i] = d[i-1]
        }
    }
    return d[n-1]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2 最长重复子数组&lt;/h3&gt;
&lt;p&gt;给两个整数数组 A 和 B ，返回两个数组中公共的、长度最长的子数组的长度。&lt;/p&gt;
&lt;p&gt;输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出: 3
解释: 长度最长的公共子数组是 [3, 2, 1]&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;状态转移方程 dp[i][j] = dp[i-1][j-1] + 1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;const findLength = (A, B) =&amp;gt; {
    const m = A.length;
    const n = B.length;
    const dp = Array.from(Array(n+1),()=&amp;gt;Array(n+1).fill(0))
    let res = 0;
    for (let i = 1; i &amp;lt;= m; i++) {
        for (let j = 1; j &amp;lt;= n; j++) {
            if (A[i - 1] == B[j - 1]) {
            dp[i][j] = dp[i - 1][j - 1] + 1;
            }
            res = Math.max(dp[i][j], res);
        }
    }
    return res;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3 交错字符串&lt;/h3&gt;
&lt;p&gt;给定三个字符串 s1, s2, s3, 验证 s3 是否是由 s1 和 s2 交错组成的。&lt;/p&gt;
&lt;p&gt;输入: s1 = &quot;aabcc&quot;, s2 = &quot;dbbca&quot;, s3 = &quot;aadbbcbcac&quot;
输出: true&lt;/p&gt;
&lt;p&gt;输入: s1 = &quot;aabcc&quot;, s2 = &quot;dbbca&quot;, s3 = &quot;aadbbbaccc&quot;
输出: false&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;f[i][j] |= f[i-1][j] &amp;amp;&amp;amp; s2[i-1] === s3[p]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;var isInterleave = function(s1, s2, s3) {
    let n1 = s1.length
    let n2 = s2.length
    let n3 = s3.length
    if(n1+n2 !== n3){
        return false
    }

    let f = new Array(n2+1)
    for(let i=0;i&amp;lt;n2+1;i++){
        f[i] = []
    }
    f[0][0] = true

    for(let i=0;i&amp;lt;n2+1;i++){
        for(let j=0;j&amp;lt;n1+1;j++){
            let p = i+j-1
            if(i&amp;gt;0){
                f[i][j] |= f[i-1][j] &amp;amp;&amp;amp; s2[i-1] === s3[p]
            }
            if(j&amp;gt;0){
                f[i][j] |= f[i][j-1] &amp;amp;&amp;amp; s1[j-1] === s3[p]
            }
        }
    }
    return f[n2][n1]
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>二叉树的反序列化</title><link>https://blog.cxyxiaoyu.top/posts/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/</guid><pubDate>Sun, 05 Jul 2020 21:37:43 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;遍历树的两个策略&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;DFS 分为    先序遍历    中序遍历    后序遍历
BFS 可以按照层次的顺序从上到下遍历所有的节点&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;准备 TreeNode 构造函数&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;function TreeNode(val){
  this.val = val
  this.left = this.right = null
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1. DFS 先序遍历&lt;/h2&gt;
&lt;h3&gt;反序列化&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 将先序遍历后的字符串 转为二叉树结构
var deserialize = (data) =&amp;gt; {
  const list = data.split(&apos;,&apos;)       // 转成list数组
  return buildTree(list)             // 构建树，dfs的入口
}

var buildTree = (list) =&amp;gt; {
  let nodeVal = list.shift()
  if(nodeVal === &apos;X&apos;) return null

  let node = new TreeNode(nodeVal)
  node.left = buildTree(list)
  node.right = buildTree(list)
  return node
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;序列化&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 将二叉树先序遍历 返回字符串
var serialize = (root) =&amp;gt; {
  if (root == null) return &apos;X,&apos; // 遇到null节点
  const leftSerialized = serialize(root.left)   //左子树的序列化字符串
  const rightSerialized = serialize(root.right) //右子树的序列化字符串
  return root.val + &apos;,&apos; + leftSerialized + rightSerialized // 根|左|右
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;中序遍历可以反序列化吗?&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;答案是否定的 请看下图&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果增加一个条件，即二叉树的高度平衡，是否可以唯一确定一个二叉树&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;答案仍然是否定的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;后序遍历可以反序列化吗?&lt;/h3&gt;
&lt;h2&gt;2 BFS&lt;/h2&gt;
&lt;h3&gt;序列化&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;var serialize = (root) =&amp;gt; {
  let queue = [root]
  let res = []

  while(queue.length){
    let node = queue.shift()
    if(node){
      res.push(node.val)
      queue.push(node.left)
      queue.push(node.right)
    }else{
      res.push(&apos;X&apos;)
    }
  }
  return res.join(&apos;,&apos;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;反序列化&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;var deserialize = (data) =&amp;gt; {
  if(data === &apos;X&apos;) return null
  let list = data.split(&apos;,&apos;)
  let root = new TreeNode(list[0])

  let queue = [root]
  let cursor = 1
  while(cursor &amp;lt; list.length){
    node = queue.shift()
    leftVal = list[cursor]
    rightVal = list[cursor+1]
    cursor += 2
    if(leftVal !== &apos;X&apos;){
      let leftNode = new TreeNode(leftVal)
      node.left = leftNode
      queue.push(leftNode)
    }
    if(rightVal !== &apos;X&apos;){
      let rightNode = new TreeNode(rightVal)
      node.right = rightNode
      queue.push(rightNode)
    }
  }
  return root
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>二叉树的序列化(遍历二叉树)</title><link>https://blog.cxyxiaoyu.top/posts/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E9%81%8D%E5%8E%86%E4%BA%8C%E5%8F%89%E6%A0%91/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E9%81%8D%E5%8E%86%E4%BA%8C%E5%8F%89%E6%A0%91/</guid><pubDate>Sun, 28 Jun 2020 23:20:30 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;遍历树的两个策略&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;DFS 分为    先序遍历    中序遍历    后序遍历
BFS 可以按照层次的顺序从上到下遍历所有的节点&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;构造一颗二叉树&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;const node = (value,left,right)=&amp;gt;{
  return {
    value,
    left,
    right
  }
}

const binaryTree =
  node(&quot;A&quot;,
    node(&quot;B&quot;,node(&quot;D&quot;)),
    node(&quot;C&quot;,
      node(&quot;E&quot;,undefined,node(&quot;G&quot;)),
      node(&quot;F&quot;,node(&quot;H&quot;),node(&quot;J&quot;))
    )
  )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./%E9%81%8D%E5%8E%86%E4%BA%8C%E5%8F%89%E6%A0%91.jpg&quot; alt=&quot;遍历二叉树&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;1. DFS 先序遍历&lt;/h2&gt;
&lt;h3&gt;利用递归先序遍历&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let resultFirst = []
const traverseRootFirst = (bTree) =&amp;gt; {
  if(!bTree) { return}
  resultFirst.push(bTree.value)
  traverseRootFirst(bTree.left)
  traverseRootFirst(bTree.right)
}
traverseRootFirst(binaryTree)
console.log(&apos;先序遍历&apos;)
console.log(resultFirst)  // [&quot;A&quot;, &quot;B&quot;, &quot;D&quot;, &quot;C&quot;, &quot;E&quot;, &quot;G&quot;, &quot;F&quot;, &quot;H&quot;, &quot;J&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;不利用递归先序遍历&lt;/h3&gt;
&lt;p&gt;自己维护一个栈 当前节点指向根节点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果当前节点不为空 就将当前节点入栈 并push到结果数组中 并让当前节点指向该节点的左节点&lt;/li&gt;
&lt;li&gt;如果当前节点为空 就从栈中pop一个节点 并让当前节点指向pop出节点的右节点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果栈不为空 或者 当前节点不为空 重复以上步骤&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let resultFirst2 = []
const traverseRootFirst2 = (bTree) =&amp;gt; {
  const stack = []
  let current = bTree
  while(stack.length || current){
    if(current){
      resultFirst2.push(current.value)
      stack.push(current)
      current = current.left
    }else{
      current = stack.pop()
      current = current.right
    }
  }
}
traverseRootFirst2(binaryTree)
console.log(&apos;\n 不利用递归先序遍历&apos;)
console.log(resultFirst2)  // [&quot;A&quot;, &quot;B&quot;, &quot;D&quot;, &quot;C&quot;, &quot;E&quot;, &quot;G&quot;, &quot;F&quot;, &quot;H&quot;, &quot;J&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2 DFS 中序遍历&lt;/h2&gt;
&lt;h3&gt;递归&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let resultMiddle = []
const traverseRootMiddle = (bTree) =&amp;gt; {
  if(!bTree) { return }
  traverseRootMiddle(bTree.left)
  resultMiddle.push(bTree.value)
  traverseRootMiddle(bTree.right)
}
traverseRootMiddle(binaryTree)
console.log(&apos;中序遍历&apos;)
console.log(resultMiddle)   // [&quot;D&quot;, &quot;B&quot;, &quot;A&quot;, &quot;E&quot;, &quot;G&quot;, &quot;C&quot;, &quot;H&quot;, &quot;F&quot;, &quot;J&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;不利用递归&lt;/h3&gt;
&lt;p&gt;自己维护一个栈 当前节点指向根节点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果当前节点不为空 就让当前节点指向该节点的左节点&lt;/li&gt;
&lt;li&gt;如果当前节点为空 就从栈中pop一个节点 并将该节点push到结果数组中  并将当前节点指向该节点的右节点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果栈不为空 或者 当前节点不为空 重复以上步骤&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;与先序遍历唯一不同的就是什么时候放入结果数组&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let resultMiddle2 = []
const traverSeRootMiddle2 = (bTree) =&amp;gt; {
  const stack = []
  let current = bTree
  while(stack.length || current){
    if(current){
      stack.push(current)
      current = current.left
    }else{
      current = stack.pop()
      resultMiddle2.push(current.value)
      current = current.right
    }
  }
}
traverSeRootMiddle2(binaryTree)
console.log(&apos;\n 不利用递归中序遍历&apos;)
console.log(resultMiddle2)  // [&quot;D&quot;, &quot;B&quot;, &quot;A&quot;, &quot;E&quot;, &quot;G&quot;, &quot;C&quot;, &quot;H&quot;, &quot;F&quot;, &quot;J&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3 DFS 后续遍历&lt;/h2&gt;
&lt;h3&gt;递归&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let resultLast = []
const traverseRootLast = (bTree) =&amp;gt; {
  if(!bTree) { return }
  traverseRootLast(bTree.left)
  traverseRootLast(bTree.right)
  resultLast.push(bTree.value)
}
traverseRootLast(binaryTree)
console.log(&apos;后序遍历&apos;)
console.log(resultLast)  // [&quot;D&quot;, &quot;B&quot;, &quot;G&quot;, &quot;E&quot;, &quot;H&quot;, &quot;J&quot;, &quot;F&quot;, &quot;C&quot;, &quot;A&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4 BFS&lt;/h2&gt;
&lt;p&gt;维护一个队列 将根节点放入队列中&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从队列中取出一个节点node&lt;/li&gt;
&lt;li&gt;如果node节点为空的话 就将当前的左右节点先后放入队列中&lt;/li&gt;
&lt;li&gt;如果当前节点为空 队列中push null&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果队列部位空 重复以上步骤 直到队列中的节点全部被取出 遍历完成&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var serialize = (root) =&amp;gt; {
  let queue = [root]
  let res = []

  while(queue.length){
    let node = queue.shift()
    if(node){
      res.push(node.val)
      queue.push(node.left)
      queue.push(node.right)
    }else{
      res.push(&apos;X&apos;)
    }
  }
  return res
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>JS零碎知识点整理</title><link>https://blog.cxyxiaoyu.top/posts/js%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86%E7%82%B9%E6%95%B4%E7%90%86/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/js%E9%9B%B6%E7%A2%8E%E7%9F%A5%E8%AF%86%E7%82%B9%E6%95%B4%E7%90%86/</guid><pubDate>Sun, 24 May 2020 21:09:56 GMT</pubDate><content:encoded>&lt;h1&gt;关于JS的零碎知识点整理&lt;/h1&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;/h3&gt;
&lt;h4&gt;1. 保留两位小数&lt;/h4&gt;
&lt;h6&gt;四舍五入&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;var num = 2.446242342
num = num.toFixed(2)     // 输出是字符串 需要用parseFloat或Number再转一下
&lt;/code&gt;&lt;/pre&gt;
&lt;h6&gt;不四舍五入&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;Math.floor(15.7784514000 * 100) / 100       // 15.77
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. 字符串反转&lt;/h4&gt;
&lt;p&gt;利用数组的reverse方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let str = &apos;abcdefg&apos;
let str_r1 = str.split(&apos;&apos;).reverse().join(&apos;&apos;)    // &apos;gfedcba&apos;
let str_r2 = [...str].reverse().join(&apos;&apos;)         // &apos;gfedcba&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. 一位数左边补0&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;(&apos;0&apos;+9).slice(-2)       // &apos;09&apos;
(&apos;0&apos;+12).slice(-2)      // &apos;12&apos;
(&apos;&apos;+9).padStart(2,0)    // &apos;09&apos;
(&apos;&apos;+12).padSart(2,0)    // &apos;12&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. Object.is()判断两个值是否相等&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;isNaN(NaN)           // true
Object.is(NaN,NaN)   // true
Object.is(1,1)       // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5. 字符与ASCII码互转&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;String.fromCharCode(97)  // &apos;a&apos;
String.fromCharCode(65)  // &apos;A&apos;
&apos;a&apos;.charCodeAt()         // 97
&apos;abc&apos;.charCodeAt(1)      // 98
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;6. Object.getOwnPropertyDescriptors()&lt;/h4&gt;
&lt;p&gt;ES5 的Object.getOwnPropertyDescriptor()方法会返回某个对象属性的描述（descriptor）。
ES8 引入了Object.getOwnPropertyDescriptors()方法，返回指定对象所有自身属性（非继承属性）的描述对象。&lt;/p&gt;
&lt;h4&gt;7.Object.getOwnPropertyDescriptors() 实现深拷贝&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;该方法的引入目的，主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;8. 右移一位可以实现除以2并向下取整&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;5 &amp;gt;&amp;gt; 1   // 2
4 &amp;gt;&amp;gt; 1   // 2
9 &amp;gt;&amp;gt; 1   // 4
10 &amp;gt;&amp;gt; 1  // 5
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;9. typeof&lt;/h4&gt;
&lt;p&gt;typeof 对初始化(未赋值)的变量返回undefined  对未声明的变量也会返回undefined ，而不是报错&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var message
typeof message    // undefined
typeof message2   // undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;10. isFinite() 判断一个数值是否有穷&lt;/h4&gt;
&lt;p&gt;正或负的infinity不能参与计算&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;isFinite(3)          // true
isFinite(Infinity)   // false
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;11. isNaN()的用法&lt;/h4&gt;
&lt;p&gt;接受一个参数，会先将这个值转为数值，如果不能转为数字，直接返回true&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;isNaN(true)       // false
isNaN(&apos;abc&apos;)      // true
isNaN(10)         // false
isNaN(&apos;10&apos;)       // false
isNaN(null)       // false
isNaN(undefined)  // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;12. (123).toString() 与 ‘123&apos;.toString()&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;(123.toString())&lt;/code&gt; 是 &lt;code&gt;Number.prototype.toString&lt;/code&gt; 可用于数字转为各种进制&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(45).toString(2)  // &quot;101101&quot;
(45).toString(8)  // &quot;55&quot;
(45).toString(16) // &quot;2d&quot;
(45).toString()   // &quot;45&quot;  为空则默认为10进制
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&apos;123&apos;.toString()&lt;/code&gt; 是 &lt;code&gt;String.prototype.toString&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;13. Array.from()生成值为0的二维数组&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 错误的写法
arr = new Array(5).fill(new Array(5).fill(0))
// 如果这时只改变arr[0][1] 那么其他项的第一列也会跟着变换
arr[0][1] = 3
console.log(arr[1][1])   // 3

// 正确的写法
arr = Array.from(Array(5),()=&amp;gt;Array(5).fill(0))
arr[0][1] = 3
console.log(arr[1][1]) // 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;14 码点与字符串互换&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;String.prototype.charCodeAt()  返回 0 到 65535 之间的整数(十进制)，表示给定索引处的 UTF-16 代码单元
String.fromCharCode()  返回由指定的UTF-16代码单元序列创建的字符串&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&apos;z&apos;.charCodeAt()              // 122
String.fromCharCode(&apos;122&apos;,&apos;0o172&apos;,&apos;0x7a&apos;)    // &quot;zzz&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是对于超过两个字节的字符ES5无法正确转换&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String.fromCharCode(0x20BB7)   // &quot;ஷ&quot;  乱码  因为0x20BB7超过两个字节,无法正确解读
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ES6的两个方法可以解决这一bug&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;String.codePointAt()  返回UTF-16的完整码点
String.fromCodePoint()  返回由指定的UTF-16代码单元序列创建的字符串&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&quot;🚀&quot;.codePointAt()              // 128640
String.fromCodePoint(128640)    // &quot;🚀&quot;
&quot;𠮷&quot;.codePointAt()              // 134071
String.fromCodePoint(0x20BB7,134071)   // &quot;𠮷𠮷&quot;

// 对于小于两个字节的也完全适用
&quot;a&quot;.codePointAt()               // 97
String.fromCodePoint(97)        // &quot;a&quot;
&quot;z&quot;.codePointAt()               // 122
String.fromCodePoint(&apos;122&apos;,&apos;0o172&apos;,&apos;0x7a&apos;)   // &quot;zzz&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外一些bug,请参考&lt;a href=&quot;http://www.alloyteam.com/2016/12/javascript-has-a-unicode-sinkhole/&quot;&gt;javascript有个unicode的天坑&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;15 生成指定范围的随机数 随机整数&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Math.random            //  [0,1)
Math.random()*10       //  [0,10)

Math.ceil(Math.random()*10)       //  [0,10] 为0的概率极小 向上取整
Math.floor(Math.random()*10)      //  [0,9]

Math.round(Math.random())         //  只能是0或者1  概率各一半
Math.round(Math.random()*10)      //  [0,10]，其中获取最小值0和最大值10的几率少一半

所以希望生成[min,max]的随机数，公式如下：
// parseInt() 和 Math.floor()结果都是向下取整
parseInt(Math.random()*(max - min + 1) + min,10)
Math.floor(Math.random()*(max - min +1) + min)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>正则入门</title><link>https://blog.cxyxiaoyu.top/posts/%E6%AD%A3%E5%88%99%E5%85%A5%E9%97%A8/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E6%AD%A3%E5%88%99%E5%85%A5%E9%97%A8/</guid><pubDate>Sun, 08 Dec 2019 19:56:56 GMT</pubDate><content:encoded>&lt;h2&gt;创建一个正则表达式&lt;/h2&gt;
&lt;p&gt;可以使用正则表达式字面量，也可以通过RegExp构造函数
var re = /abc/gi
var re = new regexp(&quot;abc&quot;,&apos;gi&apos;)&lt;/p&gt;
&lt;p&gt;其中&lt;code&gt;abc&lt;/code&gt;是&lt;code&gt;re.source&lt;/code&gt; &lt;code&gt;gi&lt;/code&gt;是&lt;code&gt;re.flags&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;lastIndex 改变&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;注意: 在全局匹配模式下，lastIndex的值在每次调用exec()或test()后都会增加，而在非全局模式下则始终保持不变。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;var regex = /foo/g;

// regex.lastIndex is at 0
regex.test(&apos;foo&apos;); // true

// regex.lastIndex is now at 3
regex.test(&apos;foo&apos;); // false
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;String.prototype.match()&lt;/h3&gt;
&lt;p&gt;返回值&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果使用g标志，则将返回与完整正则表达式匹配的所有结果，但不会返回捕获组。
如果未使用g标志，则仅返回第一个完整匹配及其相关的捕获组（Array）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&apos;hhabcdefxx&apos;.match(/abc(?&amp;lt;xxx&amp;gt;def)/g)  // [&apos;abcdef&apos;]
let res = &apos;hhabcdefxx&apos;.match(/abc(?&amp;lt;xxx&amp;gt;def)/)   //
// [&quot;abcdef&quot;, &quot;def&quot;, index: 2, input: &quot;hhabcdefxx&quot;, groups: {xxx: def}]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;res表示的内容&lt;/h4&gt;
&lt;p&gt;res[0] 匹配到的字符串
res[1,n] 分组匹配到的字符串
res[&apos;index&apos;] 匹配到的字符串位于原始字符串的索引
res[&apos;input&apos;] 原始字符串
res[&apos;groups&apos;] 各个分组匹配到的字符串 key值为分组名&lt;/p&gt;
</content:encoded></item><item><title>关于正则的灵活运用</title><link>https://blog.cxyxiaoyu.top/posts/%E5%85%B3%E4%BA%8E%E6%AD%A3%E5%88%99%E7%9A%84%E7%81%B5%E6%B4%BB%E8%BF%90%E7%94%A8/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%85%B3%E4%BA%8E%E6%AD%A3%E5%88%99%E7%9A%84%E7%81%B5%E6%B4%BB%E8%BF%90%E7%94%A8/</guid><pubDate>Sun, 08 Dec 2019 19:56:56 GMT</pubDate><content:encoded>&lt;h3&gt;1 替换字符串中最后出现的字符串&lt;/h3&gt;
&lt;p&gt;(?!exp)  零宽度负预测先行断言&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;abcxxyyabczioewf&apos;.replace(/abc(?!\w*abc)/,&apos;ABC&apos;)   // &quot;abcxxyyABCzioewf&quot;
&apos;abcabcxxxabcbxx&apos;.replace(/abc(?!\w*abc)/,&apos;ABC&apos;)    //  &quot;abcabcxxxABCbxx&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2 将数字转为金额表示&lt;/h3&gt;
&lt;h3&gt;3 计算字符串中重复出现的字符的个数&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// &apos;abc&apos; = &amp;gt; 0
// &apos;aabbb&apos; =&amp;gt; 2   // 因为 a和b重复出现了，是两个字符
function duplicateCount(text){
  return (text.toLowerCase().split(&apos;&apos;).sort().join(&apos;&apos;).match(/(.)\1+/g) || []).length
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4 交换字符串中的两个单词  (mdn)&lt;/h3&gt;
&lt;p&gt;使用$1 和 $2 代替替换文本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var re = /(\w+)\s(\w+)/;
var str = &quot;John Smith&quot;;
var newstr = str.replace(re, &quot;$2, $1&quot;); // Smith, John
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5 修改日期格式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;var reg = /(?&amp;lt;year&amp;gt;\d{4})-(?&amp;lt;month&amp;gt;\d{2})-(?&amp;lt;day&amp;gt;\d{2})/
&apos;2020-06-20&apos;.replace(reg,&apos;$&amp;lt;year&amp;gt;/$&amp;lt;month&amp;gt;/$&amp;lt;day&amp;gt;&apos;)      // &quot;2020/06/20&quot;

var reg = /(\d{4})-(\d{2})-(\d{2})/
&apos;2020-06-20&apos;.replace(reg,&apos;$1/$2/$3&apos;)       // &quot;2020/06/20&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6 将驼峰命名法改为 &quot;-&quot; 命名&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&apos;borderTop&apos;.replace(/[A-Z]/g,value=&amp;gt;&apos;-&apos;+value.toLowerCase())   // border-top
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7 将华氏温度转为摄氏温度  (mdn)&lt;/h3&gt;
&lt;h3&gt;8 leetCode 8 字符串转整数 (规则与parseInt相同)&lt;/h3&gt;
&lt;p&gt;即用正则实现parseInt&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var myAtoi = function(str) {
  var INT_MAX = 2**31-1
  var INT_MIN = (-2)**31
  var num = str.match(/^\s*([\+\-]?\d+)/)
  return Math.max(Math.min(num ? num[1]: 0,INT_MAX),INT_MIN)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;9 leetCode 16.18 模式匹配&lt;/h3&gt;
&lt;p&gt;你有两个字符串，即pattern和value。 pattern字符串由字母&quot;a&quot;和&quot;b&quot;组成，用于描述字符串中的模式。
例如，字符串&quot;catcatgocatgo&quot;匹配模式&quot;aabab&quot;（其中&quot;cat&quot;是&quot;a&quot;，&quot;go&quot;是&quot;b&quot;），
该字符串也匹配像&quot;a&quot;、&quot;ab&quot;和&quot;b&quot;这样的模式。但需注意&quot;a&quot;和&quot;b&quot;不能同时表示相同的字符串。编写一个方法判断value字符串是否匹配pattern字符串。&lt;/p&gt;
&lt;p&gt;输入： pattern = &quot;abba&quot;, value = &quot;dogcatcatdog&quot;
输出： true&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 思路 命名分组 反向引用
var patternMatching = function(pattern, value) {
  if(pattern === &apos;&apos;) return value === &apos;&apos;
  let reg = pattern.replace(&apos;a&apos;,&apos;(?&amp;lt;a&amp;gt;\\w*)&apos;).replace(&apos;b&apos;,&apos;(?&amp;lt;b&amp;gt;\\w*)&apos;)
  reg = reg.replace(/(?&amp;lt;!\&amp;lt;)a/g,&apos;\\k&amp;lt;a&amp;gt;&apos;).replace(/(?&amp;lt;!\&amp;lt;)b/g,&apos;\\k&amp;lt;b&amp;gt;&apos;)
  let match = new RegExp(&apos;^&apos;+reg+&apos;$&apos;).exec(value)
  return !!match &amp;amp;&amp;amp; match[1] !== match[2]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;10 计算字符串中重复出现的字符的个数&lt;/h3&gt;
&lt;p&gt;&apos;abc&apos; = &amp;gt; 0    // 没有重复的字符
&apos;aabbb&apos; =&amp;gt; 2   // 因为 a和b重复出现了，是两个字符&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// match 使用g标志，则将返回与完整正则表达式匹配的所有结果
function duplicateCount(text){
  return (text.toLowerCase().split(&apos;&apos;).sort().join(&apos;&apos;).match(/(.)\1+/g) || []).length
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;11 密码正则 6-20位 字母与数字的组合&lt;/h3&gt;
&lt;p&gt;要求字母与数字的组合 不能全为数字 也不能全为字母&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 1 排除法
/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9a-zA-Z]{6,20}$/
// 2 包含法
/^(?=.*[0-9])(?=.*[a-zA-Z])(.{6,20})$/      // 最后的一组括号可以不要
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;如果要求还要包含大写字母&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(.{6,20})$/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;我的问题&lt;/strong&gt;
为什么 (?=exp)可以判断 而 (?&amp;lt;=exp) 不可以&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%8C%B9%E9%85%8D%E5%AF%86%E7%A0%81.png&quot; alt=&quot;为什么(?&amp;lt;=exp)是false&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原来是因为 &lt;strong&gt;?&amp;lt;=不能匹配任意长度的东西&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>JS RSA AES 加解密</title><link>https://blog.cxyxiaoyu.top/posts/js-rsa-aes-%E5%8A%A0%E8%A7%A3%E5%AF%86/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/js-rsa-aes-%E5%8A%A0%E8%A7%A3%E5%AF%86/</guid><pubDate>Wed, 13 Nov 2019 10:16:41 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;需求:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对加密文本 textPlain 进行 AES 加密
AES 加密中的 key 通过 RSA 加密
并通过加解签对解密结果进行验证&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文为了统一, 随机数 key 为固定字符串&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let key = &quot;zCpdywHdFfPYQwYi&quot;;
let textPlain = &quot;{id:&apos;123&apos;,name:&apos;xiaoyu&apos;,address:&apos;address:&apos;上海市浦东新区&apos;}&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 key 随机生成,代码如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let key = randomString(16);

function randomString(len) {
  len = len || 32;
  // 默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1
  let $chars = &quot;ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678&quot;;
  let maxPos = $chars.length;
  let str = &quot;&quot;;
  for (i = 0; i &amp;lt; len; i++) {
    str += $chars.charAt(Math.floor(Math.random() * maxPos));
  }
  return str;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 RSA 加密中的公密钥可通过&lt;a href=&quot;https://travistidwell.com/jsencrypt/demo/&quot;&gt;此链接&lt;/a&gt;生成&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let privateKey = `-----BEGIN RSA PRIVATE KEY-----
    MIICWwIBAAKBgG+u7KERg6DWKDhK7NRUe63FPpxbzBAUWxULzjYCJexsvgzXjj/m
    UPvTR/wjFXf49sidHDcEwhzx18k8aJ0+Rt5AUtNExTeAIIGo/mytyQwlS0GuazdW
    Sqw+1IOEJe88f/I62prFuuVuY+cIxucXDVpxwv5mfkMjeiTIixWyju4tAgMBAAEC
    gYABkOtiitNUswwXVf2iMJkXY7ewC5198DsivWqj2T3c5d14YqFtBfbGwTy2LWvE
    peoYABQANYHfVAjZmo5A/jmSKWHaCHd+k8sMQeThdklGVGHBctZ46aJ32c4CRmZa
    pHAtgYVYBxOyn/55WlJb9cYEYGE1M9ouOmsEwW/sUruyZQJBANmmXVhte5LNCrmr
    c7GFbJQLNT4YTcGPXHyRdxjGBRfcgAK0IHREUrBGjCY+N4DYd3QxRYTAF+EYileF
    ou3C1bcCQQCDXKz9d3pibVtfi/qcO6bn3Bdqfv641sEO5++dD/SCAeUSTrd8KO/e
    ZoVIkYV8lcj5Ol6cRhXr9TGTGHUXzbs7AkACvxmwcvk7OgEnpKU6GT/sglOyq+XX
    SnLVU5SYKG+KBItM/W7naLFObiV4mhJjOtgPDEwfR7lFauTcA+CxdWLzAkBaOEVr
    lCqO/VKbTdVihim85tGCSBSEOKLBrRRJyBKDU+/TYhLtCGSuV7VEwQVwKqOsYKVo
    KU6XD4Dr6QlKvvbPAkEAkjlU5ua5BfGh601U8va1xWyxh0TqemBTsyoQ8bSwOpZH
    NgGjWqv5Z0QoK99pJv7VfReKbLyIg2MXhpigTogeWA==
    -----END RSA PRIVATE KEY-----`;

let publicKey = `-----BEGIN PUBLIC KEY-----
    MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgG+u7KERg6DWKDhK7NRUe63FPpxb
    zBAUWxULzjYCJexsvgzXjj/mUPvTR/wjFXf49sidHDcEwhzx18k8aJ0+Rt5AUtNE
    xTeAIIGo/mytyQwlS0GuazdWSqw+1IOEJe88f/I62prFuuVuY+cIxucXDVpxwv5m
    fkMjeiTIixWyju4tAgMBAAE=
    -----END PUBLIC KEY-----`;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外还需下载&lt;a href=&quot;http://react.file.alimmdn.com/aes.js&quot;&gt;aes.js&lt;/a&gt; &lt;a href=&quot;https://travistidwell.com/jsencrypt/&quot;&gt;jsencrypt.js&lt;/a&gt; &lt;a href=&quot;https://github.com/kjur/jsrsasign&quot;&gt;jsrsasign.js&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script src=&quot;./jsencrypt.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;./aes.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;./jsrsasign-all-min.js&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;RSA 加解密 key&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;let RSACrypt = new JSEncrypt();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;RSA 加密&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;RSACrypt.setKey(publicKey);
let keyEnc = RSACrypt.encrypt(key); // 每次加密结果不一致
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;RSA 解密&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;RSACrypt.setKey(privateKey);
let keyDec = RSACrypt.decrypt(keyEnc); // zCpdywHdFfPYQwYi
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;AES 加解密&lt;/h1&gt;
&lt;h2&gt;AES 加密&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let textEnc = AESEncrypt(textPlain, key); // 每次加密结果一致
// xoJ9fYIqyoAA3Qbhm/h8nGy4KjcmHVtusheMTd61mpUB8Lr6SDFQ1pWimi+WwlZyL5ncvZ9kQKjoRcRXUB7/D6uVkl724DzxBu/Ns1ivWnA=

// AES加密函数
function AESEncrypt(textPalin, key) {
  let key = CryptoJS.enc.Utf8.parse(key);
  let textEnc = CryptoJS.AES.encrypt(textPlain, key, {
    iv: CryptoJS.enc.Utf8.parse(&quot;1234567812345678&quot;),
    mode: CryptoJS.mode.CBC,
    //padding: CryptoJS.pad.Iso10126
  });
  return textEnc.toString();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;AES 解密&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// 利用解密后的keyDec解密textEnc
let textDec = AESDecrypt(textEnc, keyDec); //  {id:&apos;123&apos;,name:&apos;xiaoyu&apos;,address:&apos;address:&apos;上海市浦东新区&apos;}

// AES解密函数
function AESDecrypt(word, key) {
  let key = CryptoJS.enc.Utf8.parse(key);
  let decrypt = CryptoJS.AES.decrypt(word, key, {
    iv: CryptoJS.enc.Utf8.parse(&quot;1234567812345678&quot;),
    mode: CryptoJS.mode.CBC,
    //padding: CryptoJS.pad.Pkcs7
  });
  let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
  return decryptedStr.toString();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;RSA 加解签&lt;/h1&gt;
&lt;h2&gt;RSA 加签&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let sign = getSign(textPlain); // 每次加签结果一致
// 3b073ad8655293c8ec92dddeb6b986fbad190d642e2ca74f57f90aeedb1645be20449e0e3a7824f483d72de120221b0a29f2adf5c0cd0b913cee01d8d6a13eeb55c9f2b9a3868dc29181cddbd0fde15264d40a46f74ab7db23a09bc48d93a376eb8d50c84e67a7d08ea9810a76badfc4bb7f5e8cdbd045ef3551389eb1b9cef7

// 加签函数
function getSign(signData) {
  let signPrivateKey = privateKey;
  let sig = new KJUR.crypto.Signature({
    alg: &quot;SHA1withRSA&quot;,
    prvkeypem: signPrivateKey,
  });
  let hashAlg = &quot;sha1&quot;; // 设置sha1
  let sign = sig.signString(signData, hashAlg); // 加签
  return sign;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;RSA 验签&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let result = verifySign(textDec, sign); // true

// 验签函数
function verifySign(standData, signData) {
  try {
    let signPublicKey = publicKey;
    let sig = new KJUR.crypto.Signature({
      alg: &quot;SHA1withRSA&quot;,
      prvkeypem: signPublicKey,
    });
    sig.updateString(standData);
    let result = sig.verify(signData);
    return result;
  } catch (e) {
    console.error(e);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;在 vue 项目中使用加解密&lt;/h1&gt;
&lt;p&gt;安装各加密模块&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install crypto-js
npm install jsencrypt
npm install jsrsasign
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;import AESCrypto from &quot;crypto-js&quot;;
import JSEncrypt from &quot;jsencrypt&quot;;
import Jsrasign from &quot;jsrsasign&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;RSA 加解密对象需要初始化&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var RSACrypto = new JSEncrypt();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在加解签时需要修改&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 将 new KJUR.crypto.Signature 改为
new Jsrasign.Signature();
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;利用 Crypto 进行格式转换&lt;/h1&gt;
&lt;h2&gt;字符串转其他格式&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let str = &quot;abc&quot;;
let str_utf8 = CryptoJS.enc.Utf8.parse(str);

// 转为 hex
let str_hex = CryptoJS.enc.Hex.stringify(str_utf8); // &apos;616263&apos;

// 转为 base64
let str_base64 = CryptoJS.enc.Base64.stringify(str_utf8); // &quot;YWJj&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;hex 转为其他格式&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let hex = &quot;616263&quot;;
let hex_hex = CryptoJS.enc.Hex.parse(hex);

// 转为字符串
hex_str = CryptoJS.enc.Utf8.stringify(hex_hex); // &apos;abc&apos;

// 转为 base64
let hex_base64 = CryptoJS.enc.Base64.stringify(hex_hex); //  &quot;YWJj&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;我遇到的一个问题&lt;/h1&gt;
&lt;p&gt;以上所写的都是客户端加密客户端解密,但是通常工作都是客户端加密,服务端解密,服务端加密,客户端解密。&lt;/p&gt;
&lt;p&gt;当后端传回来的 keyEnc 为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6e1c9600284212f2425d9a490a1f4d9b2d0e2d8605691cc02debe2dcf7a4b6cc51f81534ed6df592cdb035e14d03b8276a90192b76ba51c240d8e26b55bb9ad195b1044fbe5ce444c3be2940f33b698df812020619c1e4b4071361732d8f0d6fe98c222467d64ce8f0a9f77a02eef22b57833e6474e5fc5cf4e00c97f2706fad
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将这个 16 进制字符串转为 base64,然后用&lt;code&gt;RSACrypt.decrypt&lt;/code&gt;对它进行解密,得到的结果却是&lt;code&gt;茁yˎѬ۽ܽ_뀅&lt;/code&gt;,然后代入 AES 解密得到的是乱码。
然后用 node 去解密得到是&lt;code&gt;&amp;lt;Buffer b8 0c c1 79 cb 4e d1 ec db bd dc 7d 5f ab 80 05&amp;gt;&lt;/code&gt;,通过&lt;code&gt;toString()&lt;/code&gt;得到的是,通过&lt;code&gt;toString(&apos;hex&apos;)&lt;/code&gt;得到&lt;code&gt;b80cc179cb4ed1ecdbbddc7d5fab8005&lt;/code&gt;,与&lt;code&gt;茁yˎѬ۽ܽ_뀅&lt;/code&gt;转为的 16 进制并不一样。
将这个 16 进制字符串带入 AES 解密,得到了正确的解密结果。
这样看来,node 解密的结果是正确的，难道 js 的解密方法不对吗？&lt;/p&gt;
&lt;p&gt;AES 解密 如果 key 是 16 进制的话&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 将 let key = CryptoJS.enc.Utf8.parse(key) 改为
let key = CryptoJS.enc.Hex.parse(key);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后查看源码的时候发现在&lt;code&gt;pkcs1unpad2&lt;/code&gt;函数里有 utf-8 decode,仔细研究发现这个
&lt;code&gt;b80cc179cb4ed1ecdbbddc7d5fab8005&lt;/code&gt;不是一个 utf-8 编码的字符串,因为根据&lt;a href=&quot;https://cxyxiaoyuyu.github.io/2019/11/01/%E6%88%91%E7%BB%88%E4%BA%8E%E6%87%82%E4%BA%86%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%98%AF%E5%A6%82%E4%BD%95%E5%AD%98%E5%82%A8%E6%95%B0%E6%8D%AE%E7%9A%84/&quot;&gt;utf-8 的编码规则&lt;/a&gt;,没有&lt;code&gt;10110110&lt;/code&gt;开头的,但是源码把它当作 utf-8 的 16 进制解析,所以与 node 结果不一致。&lt;/p&gt;
&lt;p&gt;那我能不能不让它 decode,在 decode 之前就直接得到它&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function pkcs1unpad2(d, n) {
  // ...
  var array = [];
  while (++i &amp;lt; b.length) {
    var c = b[i] &amp;amp; 255;
    array.push(c);
  }
  array = array
    .map((value) =&amp;gt;
      value.toString(16).length === 1
        ? 0 + value.toString(16)
        : value.toString(16)
    )
    .join(&quot;&quot;);

  return array;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后这里得到跟 node 解密一样的 16 进制字符串,问题已解决。&lt;/p&gt;
&lt;p&gt;因为后台进行 RSA 加密的时候,key 并不一定就是字符串,也有可能就是非 utf-8 编码的 16 进制的 Buffer。比如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var key_utf16 = &quot;b80cc179cb4ed1ecdbbddc7d5fab8005&quot;;
var key = Buffer.from(key_utf16, &quot;hex&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时我们再用 utf-8 解码的时候，会得到错误的结果。&lt;/p&gt;
</content:encoded></item><item><title>NodeJS AES RSA 加解密</title><link>https://blog.cxyxiaoyu.top/posts/js%E4%B8%8Enodejs-aes-rsa-%E5%8A%A0%E8%A7%A3%E5%AF%86/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/js%E4%B8%8Enodejs-aes-rsa-%E5%8A%A0%E8%A7%A3%E5%AF%86/</guid><pubDate>Wed, 06 Nov 2019 17:51:46 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;需求:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对加密文本textPlain进行AES加密,AES加密中的key通过RSA加密,并通过加解签对解密结果进行验证&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文为了统一, 随机数key为固定字符串&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let key = &apos;zCpdywHdFfPYQwYi&apos;
let textPlain = &quot;{id:&apos;123&apos;,name:&apos;xiaoyu&apos;,address:&apos;address:&apos;上海市浦东新区&apos;}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果key随机生成,代码如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let key = randomString(16)

function randomString(len) {
  len = len || 32
  // 默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1
  let $chars = &apos;ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678&apos;;
  let maxPos = $chars.length;
  let str = &apos;&apos;
  for (i = 0; i &amp;lt; len; i++) {
    str += $chars.charAt(Math.floor(Math.random() * maxPos));
  }
  return str;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中RSA加密中的公密钥可通过&lt;a href=&quot;https://travistidwell.com/jsencrypt/demo/&quot;&gt;此链接&lt;/a&gt;生成&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let privateKey = `-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgG+u7KERg6DWKDhK7NRUe63FPpxbzBAUWxULzjYCJexsvgzXjj/m
UPvTR/wjFXf49sidHDcEwhzx18k8aJ0+Rt5AUtNExTeAIIGo/mytyQwlS0GuazdW
Sqw+1IOEJe88f/I62prFuuVuY+cIxucXDVpxwv5mfkMjeiTIixWyju4tAgMBAAEC
gYABkOtiitNUswwXVf2iMJkXY7ewC5198DsivWqj2T3c5d14YqFtBfbGwTy2LWvE
peoYABQANYHfVAjZmo5A/jmSKWHaCHd+k8sMQeThdklGVGHBctZ46aJ32c4CRmZa
pHAtgYVYBxOyn/55WlJb9cYEYGE1M9ouOmsEwW/sUruyZQJBANmmXVhte5LNCrmr
c7GFbJQLNT4YTcGPXHyRdxjGBRfcgAK0IHREUrBGjCY+N4DYd3QxRYTAF+EYileF
ou3C1bcCQQCDXKz9d3pibVtfi/qcO6bn3Bdqfv641sEO5++dD/SCAeUSTrd8KO/e
ZoVIkYV8lcj5Ol6cRhXr9TGTGHUXzbs7AkACvxmwcvk7OgEnpKU6GT/sglOyq+XX
SnLVU5SYKG+KBItM/W7naLFObiV4mhJjOtgPDEwfR7lFauTcA+CxdWLzAkBaOEVr
lCqO/VKbTdVihim85tGCSBSEOKLBrRRJyBKDU+/TYhLtCGSuV7VEwQVwKqOsYKVo
KU6XD4Dr6QlKvvbPAkEAkjlU5ua5BfGh601U8va1xWyxh0TqemBTsyoQ8bSwOpZH
NgGjWqv5Z0QoK99pJv7VfReKbLyIg2MXhpigTogeWA==
-----END RSA PRIVATE KEY-----`

let publicKey = `-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgG+u7KERg6DWKDhK7NRUe63FPpxb
zBAUWxULzjYCJexsvgzXjj/mUPvTR/wjFXf49sidHDcEwhzx18k8aJ0+Rt5AUtNE
xTeAIIGo/mytyQwlS0GuazdWSqw+1IOEJe88f/I62prFuuVuY+cIxucXDVpxwv5m
fkMjeiTIixWyju4tAgMBAAE=
-----END PUBLIC KEY-----`
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;RSA 加解密&lt;/h1&gt;
&lt;h3&gt;RSA 公钥加密&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let keyEnc = publicEncrypt(key)          // RSA每次加密结果不一致

// RSA公钥加密函数 返回16进制字符串
function publicEncrypt(key){
  key = Buffer.from(key)
  let keyEnc = crypto.publicEncrypt({key:publicKey,padding:crypto.constants.RSA_PKCS1_PADDING}, key);
  return keyEnc.toString(&apos;hex&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;RSA 私钥解密&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let keyDec = privateDecrypt(keyEnc)       // zCpdywHdFfPYQwYi

// RSA私钥解密函数
function privateDecrypt(keyEnc){
  let keyDec = crypto.privateDecrypt(
    {
      key:privateKey,
      padding:crypto.constants.RSA_PKCS1_PADDING
    },
    // `keyEnc`是16进制第二个参数就是`hex` `keyEnc`是Base64第二个参数就是`base64`
    Buffer.from(keyEnc,&apos;hex&apos;)
  )
  return keyDec
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;AES 加解密&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;AES加密用到的iv写在函数里了,固定为&lt;code&gt;1234567812345678&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;AES 加密&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let textEnc = AESEncrypt(plainText,key)    // 每次AES加密结果一致
// xoJ9fYIqyoAA3Qbhm/h8nGy4KjcmHVtusheMTd61mpUB8Lr6SDFQ1pWimi+WwlZyL5ncvZ9kQKjoRcRXUB7/D6uVkl724DzxBu/Ns1ivWnA=

// AES 加密函数
function AESEncrypt(content,key){
  let iv = &apos;1234567812345678&apos;
  let cipher = crypto.createCipheriv(&apos;aes-128-cbc&apos;, key, iv);
  let encrypted = cipher.update(content,&apos;utf8&apos;,&apos;binary&apos;);
  encrypted += cipher.final(&apos;binary&apos;);
  encrypted = Buffer.from(encrypted,&apos;binary&apos;).toString(&apos;base64&apos;)
  return encrypted;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;AES 解密&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// 用解密后的keyDec通过AES解密textEnc
let textDec = AESDecrypt(textEnc,keyDec)
// {id:&apos;123&apos;,name:&apos;xiaoyu&apos;,address:&apos;address:&apos;上海市浦东新区&apos;}

function AESDecrypt(content,key){
  let iv = &quot;1234567812345678&quot;;
  content = Buffer.from(content,&apos;base64&apos;).toString(&apos;binary&apos;)
  let cipher = crypto.createDecipheriv(&apos;aes-128-cbc&apos;, key, iv);
  let decrypted = cipher.update(content,&apos;binary&apos;,&apos;utf8&apos;);
  decrypted += cipher.final(&apos;utf8&apos;);
  return decrypted;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;RSA 加解签&lt;/h1&gt;
&lt;h2&gt;RSA 加签&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let sign = getSign(plainText)       // 每次签名结果一致
// 3b073ad8655293c8ec92dddeb6b986fbad190d642e2ca74f57f90aeedb1645be20449e0e3a7824f483d72de120221b0a29f2adf5c0cd0b913cee01d8d6a13eeb55c9f2b9a3868dc29181cddbd0fde15264d40a46f74ab7db23a09bc48d93a376eb8d50c84e67a7d08ea9810a76badfc4bb7f5e8cdbd045ef3551389eb1b9cef7

//获取签名
function getSign(content){
  let sign = crypto.createSign(&apos;RSA-SHA1&apos;);
  sign.update(content);
  return sign.sign(privateKey,&apos;hex&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;RSA 验签&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;let result = verifySign(textDec,sign)      // true

// 验证签名
function verifySign(standard,sign){
  let verify = crypto.createVerify(&apos;RSA-SHA1&apos;);
  verify.update(standard,&apos;utf8&apos;);
  return verify.verify(publicKey,sign,&apos;hex&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>CSS-BFC与IFC</title><link>https://blog.cxyxiaoyu.top/posts/css-bfc%E4%B8%8Eifc/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/css-bfc%E4%B8%8Eifc/</guid><pubDate>Fri, 01 Nov 2019 16:53:50 GMT</pubDate><content:encoded>&lt;h1&gt;BFC (Block Formatting Context)&lt;/h1&gt;
&lt;p&gt;所以我们没有办法说明什么是BFC, 但我们知道如何创建一个BFC。&lt;/p&gt;
&lt;p&gt;比如 &lt;code&gt;overflow: hidden&lt;/code&gt; 的块级元素,但是我们只想让他变成BFC,发挥自己的作用,这个样式却会导致其他问题。 &lt;code&gt;display: flow-root&lt;/code&gt;,可以单纯将一个元素变成一个BFC。&lt;/p&gt;
&lt;p&gt;那BFC有什么作用呢&lt;/p&gt;
&lt;h2&gt;BFC作用1-父元素包住子元素&lt;/h2&gt;
&lt;p&gt;我们知道如果子元素是浮动元素,父元素不设置高度,就不会被子元素撑起来,就不会包住子元素,这会导致很多问题。我们通常利用伪元素清楚浮动，但这里我们可以通过将父元素变为BFC的方式包住子元素&lt;/p&gt;
&lt;h2&gt;BFC作用2-兄弟节点划清界限&lt;/h2&gt;
&lt;p&gt;两个块级元素一个浮动,浮动元素就会飘到另一个不浮动元素上面,会导致有重合部分,或挡住另一元素&lt;/p&gt;
&lt;p&gt;将不浮动的节点变为BFC,两个节点就会分开,就不会让兄弟元素挡住自己&lt;/p&gt;
&lt;p&gt;通俗来说,一个块级元素变为BFC后,里面的元素出不去,外面的元素进不来&lt;/p&gt;
&lt;h1&gt;IFC (Inline Formatting Context)&lt;/h1&gt;
</content:encoded></item><item><title>CSS-堆叠上下文</title><link>https://blog.cxyxiaoyu.top/posts/css-%E5%A0%86%E5%8F%A0%E4%B8%8A%E4%B8%8B%E6%96%87/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/css-%E5%A0%86%E5%8F%A0%E4%B8%8A%E4%B8%8B%E6%96%87/</guid><pubDate>Fri, 01 Nov 2019 15:27:34 GMT</pubDate><content:encoded/></item><item><title>计算机编码与js字符串</title><link>https://blog.cxyxiaoyu.top/posts/%E6%88%91%E7%BB%88%E4%BA%8E%E6%87%82%E4%BA%86%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%98%AF%E5%A6%82%E4%BD%95%E5%AD%98%E5%82%A8%E6%95%B0%E6%8D%AE%E7%9A%84/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E6%88%91%E7%BB%88%E4%BA%8E%E6%87%82%E4%BA%86%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%98%AF%E5%A6%82%E4%BD%95%E5%AD%98%E5%82%A8%E6%95%B0%E6%8D%AE%E7%9A%84/</guid><pubDate>Fri, 01 Nov 2019 09:25:27 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;世界上只有10种人, 认识二进制的和不认识的
计算机只能存储0和1, 所有信息在新计算内部最终都是以二进制的形式存储。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;每一个二进制位(bit)都只有0和1两种状态,八位二进制(一个字节byte)可以表示&lt;code&gt;2**8 === 256&lt;/code&gt;种状态,从00000000~11111111。&lt;/p&gt;
&lt;p&gt;既然计算机只能存储0和1,它是怎么存储数字以及各种字符呢？先从简单的数字说起吧&lt;/p&gt;
&lt;h1&gt;如何存储数字&lt;/h1&gt;
&lt;p&gt;存储数字很容易,直接转为二进制就好了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;15 =&amp;gt; 0b1111
-15 =&amp;gt; -0b1111
0.75 =&amp;gt; 0b0.11
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;计算机只能存储1和0
负数以&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E4%BA%8C%E8%A3%9C%E6%95%B8&quot;&gt;补码&lt;/a&gt;的形式存储
小数以&lt;a href=&quot;https://zh.wikipedia.org/wiki/%E6%B5%AE%E7%82%B9%E6%95%B0&quot;&gt;浮点数&lt;/a&gt;的形式存储&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;如何存储字符&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;上个世纪60年代，美国制定了一套字符编码，对英语字符与二进制位之间的关系，做了统一规定。这被称为 ASCII 码，一直沿用至今。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;比如 字符a的ascii码对应的十进制是97, 二进制是0110 0001; 字符5的ascii码对应的十进制是53, 二进制是0011 0101。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ASCII 码一共规定了128个字符的编码,只用了一个字节。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;如何存储中文字符&lt;/h1&gt;
&lt;p&gt;ASCII码只能存储128个英文字符, 不能存储中文字符,所以在ASCII的基础上,中国推出了&lt;a href=&quot;https://zh.wikipedia.org/wiki/GB_2312&quot;&gt;GB2321&lt;/a&gt;编码&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;GB2312 共收录 6763 个汉字，同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的 682 个字符。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;后来为了存储生僻字、繁体字、日语、朝鲜语等，微软推出了 GBK 字符集&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;如何存储所有字符&lt;/h1&gt;
&lt;p&gt;为了存储全球字符，将全球字符编号,有了unicode编码,包括中日韩文字、藏文、盲文、楔形文字、 颜文字:-)、 绘文字😂&lt;/p&gt;
&lt;p&gt;比如 汉字&lt;strong&gt;严&lt;/strong&gt;的 Unicode 是十六进制数4E25，转换成二进制数10011100 00100101 汉字&lt;strong&gt;你&lt;/strong&gt;的 Unicode 是十六进制数4f60, 转换成二进制数01001111 01100000，&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Unicode只规定了每个字符的码点，到底用什么样的字节序表示这个码点，就涉及到编码方法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;最直观的编码方法是，每个码点使用四个字节表示，字节内容一一对应码点。这种编码方法就叫做UTF-32。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;4个字节表示一个字符
完全对应unicode编码&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;比如 a的utf-32为0x00000061 即二进制 00000000 00000000 00000000 01100001&lt;/p&gt;
&lt;h1&gt;如何节省空间 (UTF-8编码)&lt;/h1&gt;
&lt;p&gt;虽然utf-32编码简单直观,查找起来很方便,但是浪费空间,比如字符&apos;a&apos;,ascii码只用了一个字节存储,utf-32却用了四个字节
所以我们需要一种节省空间的表示方法，这导致了utf-8的诞生&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;UTF-8是一种变长的编码方法，字符长度从1个字节到4个字节不等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;所以这里我们知道,unicode是一种字符集,而utf-8是为了节省存储空间的编码方法&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以下是utf-8编码规则&lt;/p&gt;
&lt;p&gt;00000000 00000000 00000000 01111111 即 0x0000007F 以下
0XXXXXXX&lt;/p&gt;
&lt;p&gt;00000000 00000000 00000111 11111111 即 0x000007FF 以下
110XXXXX 10XXXXXX&lt;/p&gt;
&lt;p&gt;00000000 00000000 11111111 11111111 即 0x0000FFFF 以下
1110XXXX 10XXXXXX 10XXXXXX&lt;/p&gt;
&lt;p&gt;00000000 00011111 11111111 11111111 即 0x001FFFFF 以下
11110XXX 10XXXXXX 10XXXXXX 10XXXXXX&lt;/p&gt;
&lt;p&gt;如果一个字节的第一位是0，则这个字节单独就是一个字符；如果第一位是1，则连续有多少个1，就表示当前字符占用多少个字节。&lt;/p&gt;
&lt;p&gt;比如 字符&apos;你&apos;的unicode编码是 &lt;code&gt;0100 1111 0110 0000&lt;/code&gt; 即 &lt;code&gt;0x4f60&lt;/code&gt; 在0x000007FF~0x0000FFFF之间,所以需要三个字节
格式是&lt;code&gt;1110XXXX 10XXXXXX 10XXXXXX&lt;/code&gt; ,从&apos;你&apos;的二进制最后一位开始,从右向左替换&apos;X&apos;,高位补0 会得到
&lt;code&gt;11100100 10111101 10100000&lt;/code&gt; 即 &lt;code&gt;E4BDA0&lt;/code&gt;,所以&apos;你&apos;的UTF-8编码是&lt;code&gt;E4BDA0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;还有更多的&lt;a href=&quot;http://www.ruanyifeng.com/blog/2014/12/unicode.html&quot;&gt;unicode编码方式&lt;/a&gt;,请参考&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;UTF-8 可变字节序列，用 1 到 4 个字节表示一个码点
UTF-16 可变字节序列，用 2 或 4 个字节表示一个码点
UTF-32 固定字节序列，用 4 个字节表示一个码点&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Javascript采用的编码方式 (USC-2编码)&lt;/h1&gt;
&lt;p&gt;由于js诞生的时候只有UCS-2编码, 所以js采用的编码是UCS-2编码&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;USC-2 1990年发布 使用2个字节表示已经有码点的字符
js是1995年诞生的
UTF-16 1996年7月发布&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;由于js采用的是USC-2编码,只能读取2个字节的字符(即范围在0x0000~0xFFFF),这会导致很多问题&lt;/p&gt;
&lt;h2&gt;字符的unicode表示法&lt;/h2&gt;
&lt;p&gt;这里请参考&lt;a href=&quot;http://es6.ruanyifeng.com/#docs/string&quot;&gt;字符的unicode表示法&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ES6 加强了对 Unicode 的支持, 允许采用\uxxxx形式表示一个字符，其中xxxx表示字符的 Unicode 码点。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&apos;\u4f60&apos; // &apos;你&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是在读取超过两个字节的字符时,就会只读取前两个字节&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;\u20bb7&apos;  // &quot;₻7&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;而大于两字节的字符的length也会显示错误&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;𠮷&quot;.length  // 2
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;ES6 对这一点做出了改进，只要将码点放入大括号，就能正确解读该字符,以UTF-16的编码方式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&quot;\u{20BB7}&quot;    // &quot;𠮷&quot;

&quot;\u{41}\u{42}\u{43}&quot;     // &quot;ABC&quot;

let hello = 123;
hell\u{6F}  // 123   因为&apos;\u{6f}&apos;表示字符&apos;o&apos;

&apos;\u{1F680}&apos; === &apos;\uD83D\uDE80&apos; // true  都表示字符&quot;🚀&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;javaScript 共有 6 种方法可以表示一个字符。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;\z&apos; === &apos;z&apos;  // true
&apos;\172&apos; === &apos;z&apos; // true     这里是unicode码的八进制
&apos;\x7A&apos; === &apos;z&apos; // true
&apos;\u007A&apos; === &apos;z&apos; // true
&apos;\u{7A}&apos; === &apos;z&apos; // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;将字符串转为数组&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;(&apos;𠮷𠮷𠮷&apos;).split(&apos;&apos;)  //  [&quot;�&quot;, &quot;�&quot;, &quot;�&quot;, &quot;�&quot;, &quot;�&quot;, &quot;�&quot;] 超过两个字节的字符无法正确识别
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解决办法: ES6推出了&lt;code&gt;Array.from&lt;/code&gt;方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Array.from(&apos;𠮷𠮷𠮷&apos;)  //  [&quot;𠮷&quot;, &quot;𠮷&quot;, &quot;𠮷&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;码点与字符互换&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;String.prototype.charCodeAt()  返回 0 到 65535 之间的整数(十进制)，表示给定索引处的 UTF-16 代码单元
String.fromCharCode()  返回由指定的UTF-16代码单元序列创建的字符串&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&apos;z&apos;.charCodeAt()              // 122
String.fromCharCode(&apos;122&apos;,&apos;0o172&apos;,&apos;0x7a&apos;)    // &quot;zzz&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是对于超过两个字节的字符ES5无法正确转换&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String.fromCharCode(0x20BB7)   // &quot;ஷ&quot;  乱码  因为0x20BB7超过两个字节,无法正确解读
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ES6的两个方法可以解决这一bug&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;String.codePointAt()  返回UTF-16的完整码点
String.fromCodePoint()  返回由指定的UTF-16代码单元序列创建的字符串&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&quot;🚀&quot;.codePointAt()              // 128640
String.fromCodePoint(128640)    // &quot;🚀&quot;
&quot;𠮷&quot;.codePointAt()              // 134071
String.fromCodePoint(0x20BB7,134071)   // &quot;𠮷𠮷&quot;

// 对于小于两个字节的也完全适用
&quot;a&quot;.codePointAt()               // 97
String.fromCodePoint(97)        // &quot;a&quot;
&quot;z&quot;.codePointAt()               // 122
String.fromCodePoint(&apos;122&apos;,&apos;0o172&apos;,&apos;0x7a&apos;)   // &quot;zzz&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外一些bug,请参考&lt;a href=&quot;http://www.alloyteam.com/2016/12/javascript-has-a-unicode-sinkhole/&quot;&gt;javascript有个unicode的天坑&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>JS模拟继承</title><link>https://blog.cxyxiaoyu.top/posts/js%E6%A8%A1%E6%8B%9F%E7%BB%A7%E6%89%BF/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/js%E6%A8%A1%E6%8B%9F%E7%BB%A7%E6%89%BF/</guid><pubDate>Wed, 30 Oct 2019 15:00:52 GMT</pubDate><content:encoded>&lt;h1&gt;什么是继承&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;继承（英语：inheritance）是面向对象软件技术当中的一个概念。如果一个类别B“继承自”另一个类别A，就把这个B称为“A的子类”，而把A称为“B的父类别”也可以称“A是B的超类”。继承可以使得子类具有父类别的各种属性和方法，而不需要再次编写相同的代码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;而javascript中没有类,虽然es6引入了class,但那只是一个语法糖。所以javascript不能继承,只能通过原型模拟继承&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;每个实例对象( object )都有一个私有属性(称之为 &lt;strong&gt;proto&lt;/strong&gt; )指向它的构造函数的原型对象( prototype )。该原型对象也有一个自己的原型对象( &lt;strong&gt;proto&lt;/strong&gt; ) ，层层向上直到一个对象的原型对象为 null。根据定义，null 没有原型，并作为这个原型链中的最后一个环节。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;比如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;num = new Number(123)
num.__proto__ === Number.prototype // true
Number.prototype.__proto__ === Object.prototype  // true
Object.prototype.__proto__ === null    // true

num.toString()  // &apos;123&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的&lt;code&gt;num&lt;/code&gt;为什么会有&lt;code&gt;toString&lt;/code&gt;方法呢,打印一下num看看&lt;/p&gt;
&lt;p&gt;区域1是&lt;code&gt;Number&lt;/code&gt;的原型对象&lt;code&gt;Number.prototype&lt;/code&gt;
区域2是&lt;code&gt;Object&lt;/code&gt;的原型对象&lt;code&gt;Object.prototype&lt;/code&gt;
javascript为了节省空间，num在调用toString方法时会先在自身的属性上找，如果没有则继续向&lt;code&gt;__proto__&lt;/code&gt;属性中找，找到的话就调用该属性，如果没找到则继续向下一级&lt;code&gt;__proto__&lt;/code&gt;中找，找到&lt;code&gt;Object.prototype&lt;/code&gt;都还没找到，就返回&lt;code&gt;undefined&lt;/code&gt;。而Object的原型对象有&lt;code&gt;toString&lt;/code&gt;属性,所以num可以调用该方法&lt;/p&gt;
&lt;p&gt;那我们说Number继承了Object,所以Number实例对象具有&lt;code&gt;Object.prototype&lt;/code&gt;的各种属性和方法&lt;/p&gt;
&lt;h1&gt;怎样继承&lt;/h1&gt;
&lt;p&gt;正是因为&lt;code&gt;Number.prototype.__ptoto__&lt;/code&gt; 指向了 &lt;code&gt;Object.prototype&lt;/code&gt;对象, num才能具有&lt;code&gt;Object.prototype&lt;/code&gt;的属性和方法
这里的Number继承Object,是js自身实现,如果我们需要实现一个&apos;类&apos;继承另一个&apos;类&apos;呢？&lt;/p&gt;
&lt;h2&gt;继承属性&lt;/h2&gt;
&lt;p&gt;比如父类Animal 子类Cat Animal具有方法run Cat具有方法say 我们想让Cat也有run方法通过继承Animal&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function Animal(name){
  this.name = name
}
Animal.prototype.run = function(){
  console.log(&apos;I can run&apos;)
}
function Cat(){
  this.age = 1
}
let animal = new Animal()
let cat = new Cat()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个时候我们看下animal和cat&lt;/p&gt;
&lt;p&gt;如果我们想让cat也具有name属性,可以在定义Cat时调用一下Animal方法,用&lt;strong&gt;call&lt;/strong&gt;绑定this,这样cat的name就是&apos;cat&apos;了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function Animal(name){
  this.name = name
}
Animal.prototype.run = function(){
  console.log(&apos;I can run&apos;)
}
function Cat(){
  Animal.call(this,&apos;cat&apos;)
  this.age = 1
}
let animal = new Animal()
let cat = new Cat()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;继承方法&lt;/h2&gt;
&lt;p&gt;继承属性还不够,我们还需要继承共有方法呢！根据上文,我们要让&lt;code&gt;Cat&lt;/code&gt;继承&lt;code&gt;Animal&lt;/code&gt;,让&lt;code&gt;Cat.prototype&lt;/code&gt; 的 &lt;code&gt;__proto__&lt;/code&gt; 指向 &lt;code&gt;Animal.prototype&lt;/code&gt; 不就可以了吗？那我们来试试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Cat.prototype.__proto__ = Animal.prototype
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;真的实现了呢，现在我们的小猫也可以 &lt;code&gt;run&lt;/code&gt;了,但是这种方法在IE不兼容,有什么解决办法呢？
总结一下我们就是让一个对象的__proto__ 只想另一个对象的prototype,好熟悉,有木有？在&lt;code&gt;num = new Number()&lt;/code&gt; 的时候 &lt;code&gt;num.__proto ==== Number.prototype&lt;/code&gt;
那就套一下呗&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Cat.prototype = new Animal(&apos;cat&apos;)
let cat2 = new Cat()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是这样Cat.prototype却会多出一个name属性,我们并不想要这个name属性
我们不应该直接new这个Animal,不然它就会给实例对象添加name属性。
通过一个空函数,间接改变指向&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var f = function(){}
f.prototype = Animal.prototype
Cat.prototype = new f()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个时候&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Cat.prototype.__proto__ === Animal.prototype  // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实上面的代码就是&lt;code&gt;Object.create()&lt;/code&gt;的&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create&quot;&gt;Polyfill&lt;/a&gt;
所以我们也可以直接这么写&lt;/p&gt;
&lt;p&gt;Cat.prototype = Object.create(Animal.prototype)&lt;/p&gt;
&lt;h2&gt;改变constructor指向&lt;/h2&gt;
&lt;p&gt;虽然我们已经实现了&lt;code&gt;Cat.prototype.__proto__ === Animal.prototype &lt;/code&gt;&lt;/p&gt;
&lt;p&gt;但是&lt;code&gt;Cat.prototype.constructor&lt;/code&gt;方法缺丢失了,打印一下看看&lt;/p&gt;
&lt;p&gt;可以看到&lt;code&gt;Cat.prototype&lt;/code&gt;只有一个&lt;code&gt;__proto__&lt;/code&gt;属性,&lt;/p&gt;
&lt;p&gt;但是当我们打印&lt;code&gt;Cat.prototype.constructor&lt;/code&gt;是有输出的，输出的是Animal函数&lt;/p&gt;
&lt;p&gt;那为什么会这样呢, 因为&lt;/p&gt;
&lt;p&gt;Cat.prototype.constructor  === Cat.prototype.&lt;strong&gt;proto&lt;/strong&gt;.constructor        // true
Cat.prototype.&lt;strong&gt;proto&lt;/strong&gt;.constructor ===  Animal.prototype.constructor               // true
Animal.prototype.constructor === Animal    // true&lt;/p&gt;
&lt;p&gt;所以我们调用的是Animal.prototype的constructor,当然是Animal函数啦&lt;/p&gt;
&lt;p&gt;所以我们需要手动加上&lt;code&gt;Cat.prototype&lt;/code&gt;的&lt;code&gt;constructor&lt;/code&gt;属性&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Cat.prototype.constructor = Cat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时候再看&lt;code&gt;Cat.prototype&lt;/code&gt;就会多了一个constructor&lt;/p&gt;
&lt;h1&gt;ES5 写法&lt;/h1&gt;
&lt;p&gt;总结一下ES5 的写法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function Animal(name){
  this.name = name
}
Animal.prototype.run = function(){
  console.log(&apos;I can run&apos;)
}
function Cat(){
    Animal.call(this,&apos;cat&apos;)
    this.age = 1
}
var f = function(){}
f.prototype = Animal.prototype
Cat.prototype = new f()
Cat.prototype.constructor = Cat

// 后面再给Cat添加公有属性
Cat.prototype.say = function(){
  console.log(&apos;miao miao miao&apos;)
}

也叫作寄生组合继承
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;ES6 写法&lt;/h1&gt;
&lt;p&gt;ES6引入了class语法糖,跟java类似&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Animal{
    constructor(name){
        this.name = name
    }
    run(){
        console.log(&quot;I can run&quot;)
        return undefined
    }
}
class Cat extends Animal{
    constructor(name){
        super(name)
        this.age = 1
    }
    say(){
      console.log(&apos;miao miao miao&apos;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>cookie、session与登录注册(基于Node.js)</title><link>https://blog.cxyxiaoyu.top/posts/cookiesession%E4%B8%8E%E7%99%BB%E5%BD%95%E6%B3%A8%E5%86%8C/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/cookiesession%E4%B8%8E%E7%99%BB%E5%BD%95%E6%B3%A8%E5%86%8C/</guid><pubDate>Mon, 28 Oct 2019 14:56:36 GMT</pubDate><content:encoded>&lt;p&gt;本文代码基于Node.js,参考&lt;a href=&quot;https://github.com/cxyxiaoyuyu/login_register&quot;&gt;完整代码&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;登录与注册流程&lt;/h3&gt;
&lt;h4&gt;注册流程&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;注册时传入用户名,密码,确认密码&lt;/li&gt;
&lt;li&gt;注册成功将用户名与密码存入数据库&lt;/li&gt;
&lt;li&gt;密码与确认密码不同，或已经注册的用户，返回400&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;注册具体实现&lt;/h4&gt;
&lt;p&gt;客户端传递注册时需要的数据&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let value = [&apos;username&apos;,&apos;password&apos;,&apos;password_confirm&apos;]
  value.forEach(name=&amp;gt;{
      data[name] = $(`input[name=${name}]`).val()
  })
  $.post({
      url: &apos;./sign_up&apos;,
      type: &apos;post&apos;,
      data
  }).then( response =&amp;gt; {
      window.location.href = &apos;./sign_in&apos;
  }).fail( error =&amp;gt; {
      alert(error.responseText)
  })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务端获取数据readBodyData&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 因为数据是一个一个上传的
function readBodyData(request){
  return new Promise(function(resolve,reject){
    let data = &apos;&apos;
    request.on(&apos;data&apos;,chunk =&amp;gt; {
      data += chunk.toString()
    })
    request.on(&apos;end&apos;, ()=&amp;gt;{
      let params = data.split(&apos;&amp;amp;&apos;)
      let hash = {}
      params.forEach(value =&amp;gt; {
        let temp = value.split(&apos;=&apos;)
        hash[temp[0]] = temp[1]
      })
      resolve(hash)
    })
  })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;读取数据库文件,判断用户是否已经注册&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// else if(path === &apos;/sign_up&apos; &amp;amp;&amp;amp; method === &apos;POST&apos;)

readBodyData(request).then(data =&amp;gt; {
   let { username,password,password_confirm } = data

  let users = JSON.parse(fs.readFileSync(&apos;./database.json&apos;,&apos;utf8&apos;) || &quot;[]&quot;)
  let userExist = false
  for(i=0;i&amp;lt;users.length;i++){
    if(users[i].username === username){
      userExist = true
    }
  }

  // 已经注册返回400,提示已经注册,注册成功将用户名密码写入数据库

  if(userExist){
      response.statusCode = 400
      response.setHeader(&apos;Content-Type&apos;, &apos;application/json;charset=utf-8&apos;)
      response.write(&apos;该用户已注册,请勿重复注册&apos;)
    }else{
      users.push({username,password})
      fs.writeFileSync(&apos;./database.json&apos;,JSON.stringify(users))
      response.statusCode = 200
    }
    response.end()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;登录流程&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;登录时传入用户名密码&lt;/li&gt;
&lt;li&gt;如果与数据库匹配，则登录成功，跳转到主页面&lt;/li&gt;
&lt;li&gt;如果用户不存在或密码错误，返回404&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;登录具体实现&lt;/h4&gt;
&lt;p&gt;客户端传递数据与注册类似&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let value = [&apos;username&apos;,&apos;password&apos;]
value.forEach(name=&amp;gt;{
    data[name] = $(`input[name=${name}]`).val()
})
$.post({
    url: &apos;./sign_in&apos;,
    type: &apos;post&apos;,
    data
}).then( response =&amp;gt; {
    // 登录成功 前往主页
    window.location.href = &apos;./&apos;
}).fail( error =&amp;gt; {
    console.log(error,&apos;error&apos;)
    alert(error.responseText)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;匹配数据库,用户名密码是否匹配&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;readBodyData(request).then(data=&amp;gt;{
  let { username,password } = data

  // 判断数据库是否有该用户
  let users = JSON.parse(fs.readFileSync(&apos;./database.json&apos;,&apos;utf8&apos;) || &quot;[]&quot;)
  console.log(users)
  let userMatch = false
  for(i=0;i&amp;lt;users.length;i++){
    if(users[i].username === username &amp;amp;&amp;amp; users[i].password === password){
      userMatch = true
    }
  }
  if(userMatch){
    response.statusCode = 200
  }else{
    response.setHeader(&apos;Content-Type&apos;, &apos;application/json;charset=utf-8&apos;)
    response.write(&apos;用户名或密码错误&apos;)
  }
  response.end()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上已经实现的基本的登录与注册
但是我们也可以直接输入主页的url访问主页，并不要经过登录啊，有没有什么方法可以判断,用户访问主页，如果登录了就返回主页面，如果没有登录就跳转到登录页面呢？如果我们还想要在主页显示用户的用户名呢？&lt;/p&gt;
&lt;p&gt;由于Http是无状态协议,也就是说，服务器单从网络连接上无从知道客户身份。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;HTTP无状态协议，是指协议对于事务处理没有记忆能力。
缺少状态意味着如果后续处理需要前面的信息，则它必须重传，这样可能导致每次连接传送的数据量增大。
另一方面，在服务器不需要先前信息时它的应答就较快。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果每次请求的时候,都带上相关标志呢, 让我们来看看cookie是如何工作的吧&lt;/p&gt;
&lt;h3&gt;cookie判断是否登录&lt;/h3&gt;
&lt;p&gt;在登录成功的时候, 设置响应头,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;response.setHeader(&apos;Set-Cookie&apos;,`username=${username}`)

// 设置多个cookie
response.setHeader(&apos;Set-Cookie&apos;,[`username=${username}`,&apos;a=123&apos;,&apos;b=456&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样,客户端在请求同一域名的情况下，都会带上这个cookie
比如在登录成功跳转到主页面的时候发送请求,就会带上刚才拿到的cookie&lt;/p&gt;
&lt;p&gt;我们也可以在控制台中看到cookie,并且可以随意修改cookie&lt;/p&gt;
&lt;p&gt;这时,我们就可以通过请求头的cookie判断用户是否登录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if(request.headers.cookie){
  let cookies = request.headers.cookie.split(&apos;; &apos;)
  let cookieObj = {}
  cookies.forEach(value =&amp;gt; {
    let temp = value.split(&apos;=&apos;)
    cookieObj[temp[0]] = temp[1]
  })
  let username = cookieObj.username

  // 登录成功替换页面用户名
  body = body.replace(&apos;__username__&apos;,username)
}else(
  response.writeHead(301, {&apos;Location&apos;: &apos;./sign_in&apos;})
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;好啦,通过cookie判断用户是否登录,并且通过cookie可以获取到用户相关信息,当然这个值也是服务器自己设置的&lt;/p&gt;
&lt;p&gt;但是刚才说过cookie是不安全的,它是可以手动修改的,如果我们主页显示的是用户的隐私信息呢,比如余额,薪资什么的,只要将cookie的username改为其他人的,不就想看谁的就看谁的了嘛,那不就太危险了,有什么解决办法呢?&lt;/p&gt;
&lt;h3&gt;session存储信息&lt;/h3&gt;
&lt;p&gt;如果我们在服务器设立一小块内存,存储用户的隐私信息,并给每个用户信息设置一个Id标志,每次登录成功,生成一个随机数Id传给客户端,用户每次通过cookie将这个随机数传到服务器,那这个内存就存储了所有的session, 这个随机数标志就是sessionId&lt;/p&gt;
&lt;p&gt;因为随机数范围很大,所以用户根本无法篡改
那么现在我们登录成功的需要做的事情是这样,假设&lt;code&gt;sessions&lt;/code&gt;在内存中是这样的&lt;code&gt;sessions = {}&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 设置sessionId
let sessionId = Math.random() * 100000
response.setHeader(&apos;Set-Cookie&apos;,`sessionId=${sessionId}`)
// 用session存储用户隐私数据
sessions[sessionId] = { username,password }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主页面从sessions中获取username&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let username = sessions[cookieObj.sessionId].username
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;一般来说，Session 基于 Cookie 来实现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;cookie&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;服务器通过 Set-Cookie 头给客户端一串字符串&lt;/li&gt;
&lt;li&gt;客户端每次访问相同域名的网页时，必须带上这段字符串&lt;/li&gt;
&lt;li&gt;客户端要在一段时间内保存这个Cookie&lt;/li&gt;
&lt;li&gt;Cookie 默认在用户关闭页面后就失效，后台代码可以任意设置 Cookie 的过期时间&lt;/li&gt;
&lt;li&gt;大小大概在 4kb 以内&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;session&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;将 SessionID（随机数）通过 Cookie 发给客户端&lt;/li&gt;
&lt;li&gt;客户端访问服务器时，服务器读取 SessionID&lt;/li&gt;
&lt;li&gt;服务器有一块内存（哈希表）保存了所有 session&lt;/li&gt;
&lt;li&gt;通过 SessionID 我们可以得到对应用户的隐私信息，如 id、email&lt;/li&gt;
&lt;li&gt;这块内存（哈希表）就是服务器上的所有 session&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>npm安装、更新与卸载</title><link>https://blog.cxyxiaoyu.top/posts/npm%E5%AE%89%E8%A3%85%E6%9B%B4%E6%96%B0%E4%B8%8E%E5%8D%B8%E8%BD%BD/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/npm%E5%AE%89%E8%A3%85%E6%9B%B4%E6%96%B0%E4%B8%8E%E5%8D%B8%E8%BD%BD/</guid><pubDate>Thu, 24 Oct 2019 16:47:53 GMT</pubDate><content:encoded>&lt;h1&gt;npm 安装、更新与卸载&lt;/h1&gt;
&lt;h2&gt;本地安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm install &amp;lt;package-name&amp;gt;
npm install &amp;lt;package-name&amp;gt; --save
npm install &amp;lt;package-name&amp;gt; --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;安装完安装包会出现在当前目录的node_modules下&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;--save 与 --save--dev的区别&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;--save 保存在package.json 的 dependencies 字段
--save-dev 保存在package.json 的 devDependencies 字段&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;生产环境依赖包&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;dependencies&quot;: These packages are required by your application in production.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;测试环境依赖包&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;devDependencies&quot;: These packages are only needed for development and testing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;更新本地安装包&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更新完成后 执行 &lt;code&gt;npm outdate&lt;/code&gt; 没有任何输出&lt;/p&gt;
&lt;h2&gt;本地卸载&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm uninstall &amp;lt;package-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不加参数可自动卸载，也可添加参数指定卸载&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm uninstall &amp;lt;package-name&amp;gt; --save
npm uninstall &amp;lt;package-name&amp;gt; --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;全局安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm install -g &amp;lt;package-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;全局安装路径可通过 &lt;code&gt;npm config get prefix&lt;/code&gt; 查看&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;更新全局安装包&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm update -g &amp;lt;package-name&amp;gt;   // 指定更新某个包
npm outdated -g --depth=0      // 更新需要更新的
npm update -g                  // 更新全局所有的
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;卸载全局安装的包&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm uninstall -g &amp;lt;package-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;更新npm&lt;/h2&gt;
&lt;h3&gt;查看npm版本&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm -v
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;更新成为最新的&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;npm install npm@latest -g
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;将要发布的下一版本&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm install npm@next -g
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Promise解决回调地狱问题</title><link>https://blog.cxyxiaoyu.top/posts/promise%E8%A7%A3%E5%86%B3%E5%9B%9E%E8%B0%83%E5%9C%B0%E7%8B%B1%E9%97%AE%E9%A2%98/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/promise%E8%A7%A3%E5%86%B3%E5%9B%9E%E8%B0%83%E5%9C%B0%E7%8B%B1%E9%97%AE%E9%A2%98/</guid><pubDate>Thu, 24 Oct 2019 14:19:53 GMT</pubDate><content:encoded>&lt;h2&gt;回调地狱问题的由来&lt;/h2&gt;
&lt;p&gt;需求： 封装一个方法，给定文件路径，可以读取文件内容，并把文件内容返回，如果错误返回错误结果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  const fs = require(&apos;fs&apos;)
  const path = require(&apos;path&apos;)

  // 普通读取文件的方式
  function getFileByPath(fpath){
    fs.readFile(fpath,&apos;utf-8&apos;,(err,data) =&amp;gt; {
      if(err) throw err
      console.log(data)
      return data
    })
  }

  let result = getFileByPath(path.join(__dirname,&apos;.files/1.txt&apos;))
  console.log(result)
```

这样确实可以读到文件内容，但是`result`却是`undefined`，因为`readFiles`是异步读取的

于是想到在调用`getFileByPath`时添加回调函数

```js
  function getFileByPath(fpath，callback){
    fs.readFile(fpath,&apos;utf-8&apos;,(err,data) =&amp;gt; {
      if(err) throw err
      callback(data)
    })
  }

  getFileByPath(path.join(__dirname,&apos;.files/1.txt&apos;),(data)=&amp;gt;{
    console.log(data)
  })
```

但是如果error了，`readFile`里的`callback`就不执行了，
需要将成功的结果和失败的结果都返回给用户，所以添加两个回调函数，一个成功调用`succCb`，一个失败调用`errCb`

```js
  function getFileByPath(fpath，succCb, errCb){
    fs.readFile(fpath,&apos;utf-8&apos;,(err,data) =&amp;gt; {
      if(err) return errCb
      succCb
    })
  }

  getFileByPath(path.join(__dirname,&apos;.files/1.txt&apos;),(data)=&amp;gt;{
    console.log(data)
  },(err)=&amp;gt;{
    console.log(err)
  })
```

看起来很完美呢，如果这时候再加个需求呢，读取文件1成功后，再读取文件2，读取文件2成功后再读取文件3，那么代码将会是下面的样子

    getFileByPath(path.join(__dirname,&apos;.files/1.txt&apos;),(data1)=&amp;gt;{
      console.log(data1)

      // 读取文件2
      getFileByPath(path.join(__dirname,&apos;files/2.txt&apos;),(data2)=&amp;gt;{
        console.log(data2)

        // 读取文件3
        getFileByPath(path.join(__dirname,&apos;files/3.txt&apos;),(data3)=&amp;gt;{
          console.log(data3)
        })
      })
    })

哈哈，层层缩进，如果后面还要读取很多文件呢，那么就陷入了回调地狱，看看下面这张图，很形象有木有

![回调地狱](./callback_hell.jpg)

## 用Promise解决回调地狱问题

如果不想让代码变成酱紫，我们就开始使用Promise，其实Promise的本质就是单纯为了解决回调地狱问题，它会让代码变的优雅，并不会减少代码量

    &amp;gt; console.log(Promise)
      ƒ Promise() { [native code] }

可以看出来Promise是一个构造函数，我们通过new Promise可以生成一个Promise的实例

关于Promise更详细的说明 [Promise](http://es6.ruanyifeng.com/?search=fetch&amp;amp;x=0&amp;amp;y=0#docs/promise)

如果用Promise来实现读取三个文件的需求呢

首先构建一个方法，返回一个promise

    function getFileByPath(fpath){
      var promise = new Promise(resolve,reject){
        fs.readFile(fpath,&apos;utf-8&apos;,(err,data)=&amp;gt;{
          if(err) return reject(err)
          resolve(data)
        })
      }
      return promise
    }

开始读取文件

    getFileByPath(&apos;files1.txt&apos;).then((data1)=&amp;gt;{
      console.log(data1)

      getFileByPath(&apos;files2.txt&apos;).then((data2)=&amp;gt;{
        console.log(data2)

        getFileByPath(&apos;files3.txt&apos;).then((data3)=&amp;gt;{
          console.log(data3)
        })
      })
    })

咦，写完了，这好像还是回调地狱啊，纳尼？

    getFileByPath(&apos;files1.txt&apos;).then( data1 =&amp;gt; {
      console.log(data1)
      return getFileByPath(&apos;files2.txt&apos;)
    }).then( data2 =&amp;gt; {
      console.log(data2)
      return getFileByPath(&apos;file3.txt&apos;)
    }).then( data3 =&amp;gt; {
      console.log(data3)
    })

emmmmm...，正确打开方式是这样，是不是很优雅

但是为什么第二个`then`会获取到`getFileByPath(&apos;files2.txt&apos;)`的结果呢？

&amp;gt; 上面代码中，第一个then方法指定的回调函数，返回的是另一个Promise对象。这时，第二个then方法指定的回调函数，就会等待这个新的Promise对象状态发生变化。如果变为resolved，就调用第一个回调函数，如果状态变为rejected，就调用第二个回调函数。

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>什么是JSONP</title><link>https://blog.cxyxiaoyu.top/posts/%E4%BB%80%E4%B9%88%E6%98%AFjsonp/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E4%BB%80%E4%B9%88%E6%98%AFjsonp/</guid><pubDate>Tue, 19 Mar 2019 23:36:26 GMT</pubDate><content:encoded>&lt;h2&gt;先简单说下跨域问题&lt;/h2&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;协议相同&lt;/li&gt;
&lt;li&gt;域名相同&lt;/li&gt;
&lt;li&gt;端口相同&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;满足以上三个就是同源&lt;/p&gt;
&lt;h3&gt;可以发请求的 HTML 标签&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;form 表单
a 标签
link 标签
img 标签
script 标签&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;基本需求&lt;/h2&gt;
&lt;p&gt;比如我有 100 元，我每次点击付钱的按钮就会支付一元，后台将这 100 元存于数据库(本文中存于 txt)
假设页面有一个 id 为 button 的 button 按钮&lt;/p&gt;
&lt;p&gt;在最开始的前端&lt;/p&gt;
&lt;h3&gt;form 表单发请求&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;我们都知道form表单可以发post或get请求到后台,但是会刷新页面，为了更好的用户体验我们怎么能每次变更数据都刷新页面呢，所以得想办法局部更新呀。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;创建 img 标签发请求(get)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;    前端代码
    button.addEventListener(&apos;click&apos;, (e)=&amp;gt;{
        let image = document.createElement(&apos;img&apos;)
        image.src = &apos;/pay&apos;
        image.onload = function(){ // 状态码是 200~299 则表示成功
            remain.innerText = remain - 1
        }
        image.onerror = function(){ // 状态码大于等于 400 则表示失败
            alert(&apos;失败&apos;)
        }
    })

    后端代码

这种方法虽然可以实现，但是每此都要请求一个图片，当你返回不是图片类型的时候，浏览器也不是那么傻，它就报错啦，我们每一次都返回一个图片，总感觉多次一举。emmmmmm....有没有什么更好的办法呢,试试创建script标签发请求吧。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;创建 script 标签发请求&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;    前端代码
    button.addEventListener(&apos;click&apos;, (e)=&amp;gt;{
        let script = document.createElement(&apos;script&apos;)
        script.src = &apos;/pay&apos;
        document.body.appendChild(script)
        script.onload = function(e){ // 状态码是 200~299 则表示成功
            e.currentTarget.remove() // 发一次请求就会多一个script标签，需要删除
            remain.innerText = remain - 1
        }
        script.onerror = function(e){ // 状态码大于等于 400 则表示失败
            e.currentTarget.remove()
        }
   })

    后端代码
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;为什么不直接返回 script 脚本呢&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;既然后端返回给前端的是js,嵌入到页面时脚本就会执行，我何必多写一个onload函数，好像有点道理哎~
那就后端来写局部刷新代码吧

写完后，后端不开心了,作为后端，我只想做后端的事，这样前后端发生了耦合，如果后端程序员不熟悉前端，那不就写不下去了嘛，怎么办呢，解耦合呀。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;解决前后端耦合 =&amp;gt; Jsonp 技术的由来&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;怎么解耦合呢，那前端想了，说我们封装一个函数吧，你们成功后就调用一下这个回调函数，把要返回的值放在回调函数的第一个参数里。这样的话，后端说好呀，那你们封装好函数，函数名传给我，我成功就调用一下。

以上，这个技术就叫jsonp啦，总结一下

请求方：frank.com 的前端程序员（浏览器）
响应方：jack.com 的后端程序员（服务器）

请求方创建 script，src 指向响应方，同时传一个查询参数 ?callbackName=yyy
响应方根据查询参数callbackName，构造形如
yyy.call(undefined, &apos;你要的数据&apos;)
yyy(&apos;你要的数据&apos;)
这样的响应
浏览器接收到响应，就会执行 yyy.call(undefined, &apos;你要的数据&apos;)
那么请求方就知道了他要的数据
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;约定&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;哎呦,还不错，就是我们能不能做些规定,前端传过来回调函数的key值能不能统一,如下:

callbackName -&amp;gt; callback
yyy -&amp;gt; 随机数 function12312312312321325() (为了不重复)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;jquery 封装的 API&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$.ajax({
    url: &quot;http://jack.com:8002/pay&quot;,
    dataType: &quot;jsonp&quot;,
    success: function( response ) {
        if(response === &apos;success&apos;){
        amount.innerText = amount.innerText - 1
        }
    }
})
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>linux 常用命令使用</title><link>https://blog.cxyxiaoyu.top/posts/%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E4%BD%BF%E7%94%A8/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E4%BD%BF%E7%94%A8/</guid><pubDate>Tue, 12 Mar 2019 21:30:22 GMT</pubDate><content:encoded>&lt;h2&gt;1. ls ==&amp;gt; list segment 列出文件&lt;/h2&gt;
&lt;p&gt;ls          #列出当前目录下的文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ls
_config.yml  node_modules/  package-lock.json  scaffolds/  themes/
db.json      package.json   public/            source/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ls -lh      #-l以长格式显示文件 -h以人性化显示文件大小&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls -l
total 237K
-rw-r--r-- 1 CXY 197121 1.8K 10月  4 19:37 _config.yml
-rw-r--r-- 1 CXY 197121  174 10月  4 19:38 db.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ls -a       #显示隐藏文件(以&quot;.&quot;开头的文件)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls -a
./   .deploy_git/  _config.yml  node_modules/  package-lock.json  scaffolds/  themes/
../  .gitignore    db.json      package.json   public/            source/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ls 目录名    #列出该目录下的文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls ~/local
demo/  demo.js  demo.txt  jsdemo.js*  kk/  shell/  xxx/  yyy/  zzz/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. cat  查看文件内容&lt;/h2&gt;
&lt;p&gt;cat 文件名    #显示整个文件内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat demo.js
var fs = require(&apos;fs&apos;);
var dirName = process.argv[2]
if(fs.existsSync(&apos;./&apos; + dirName)){
console.log(&apos;file exists&apos;)
}
else{
    fs.mkdirSync(dirName)
    process.chdir(dirName)
    fs.mkdirSync(&apos;css&apos;)
    fs.mkdirSync(&apos;js&apos;)

    fs.writeFileSync(&apos;./index.html&apos;,&apos;&amp;lt;!DOCTYPE html&amp;gt;\n\t&amp;lt;title&amp;gt;Hello&amp;lt;/title&amp;gt;\n\t&amp;lt;h1&amp;gt;Hi&amp;lt;/h1&amp;gt;&apos;)
    fs.writeFileSync(&apos;css/style.css&apos;,&apos;h1 {color: red;}&apos;)
    fs.writeFileSync(&apos;js/main.js&apos;,&apos;var string = &quot;Hello World&quot;\n\talert(string)&apos;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;cat -n 文件名    #显示行号&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat -n demo.js
1  var fs = require(&apos;fs&apos;);
2  var dirName = process.argv[2]
3  if(fs.existsSync(&apos;./&apos; + dirName)){
4      console.log(&apos;file exists&apos;)
5  }
6  else{
7      fs.mkdirSync(dirName)
8      process.chdir(dirName)
9      fs.mkdirSync(&apos;css&apos;)
10      fs.mkdirSync(&apos;js&apos;)
11
12      fs.writeFileSync(&apos;./index.html&apos;,&apos;&amp;lt;!DOCTYPE html&amp;gt;\n\t&amp;lt;title&amp;gt;Hello&amp;lt;/title&amp;gt;\n\t&amp;lt;h1&amp;gt;Hi&amp;lt;/h1&amp;gt;&apos; )
13      fs.writeFileSync(&apos;css/style.css&apos;,&apos;h1 {color: red;}&apos;)
14      fs.writeFileSync(&apos;js/main.js&apos;,&apos;var string = &quot;Hello World&quot;\n\talert(string)&apos;)
15  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;cat file1 file2 &amp;gt; file3     #将文件1和文件2的内容合并到文件3中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat file1
hello file1

$ cat file2
hello file2

$ cat file1 file2 &amp;gt; file3

$ cat file3
hello file1
hello file2
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. mv ==&amp;gt; move   #移动文件或重命名文件&lt;/h2&gt;
&lt;p&gt;mv 文件名 文件名        #将源文件名改为目标文件名
mv 文件名 目录名        #将文件移动到目标目录
mv 目录名 目录名        #目标目录已存在，将源目录移动到目标目录；目标目录不存在则改名&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mv test.log test1.txt   # 将test.log 改名为 test1.txt
$ mv test1.txt test3      #将test1.txt 移动到test3目录
$ mv log1.txt log2.txt log3.txt test3   # 将log1.txt log2.txt log3.txt 都移动到test目录
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;常用选项&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;-i 询问是否覆盖&lt;/li&gt;
&lt;li&gt;-f 强制覆盖&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. touch   创建文件&lt;/h2&gt;
&lt;p&gt;touch new_file     #创建新文件
touch -t 201811142234.50 old_file    #设定文件的时间戳&lt;/p&gt;
&lt;p&gt;关于linux中不懂的命令，可以将完整命令复制到&lt;a href=&quot;http://explanshell.com&quot;&gt;explanshell.com&lt;/a&gt; 中，查看命令详细解释。&lt;/p&gt;
</content:encoded></item><item><title>初步理解jQuery</title><link>https://blog.cxyxiaoyu.top/posts/%E5%88%9D%E6%AD%A5%E7%90%86%E8%A7%A3jquery/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%88%9D%E6%AD%A5%E7%90%86%E8%A7%A3jquery/</guid><pubDate>Wed, 05 Dec 2018 21:28:30 GMT</pubDate><content:encoded>&lt;h2&gt;封装addClass函数&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;    function addClass(node,classes) {
        classes.forEach( (value) =&amp;gt; node.classList.add(value) )
    }

    let divNode = document.getElementById(&apos;xxx&apos;)
    addClass(divNode,[&apos;a&apos;,&apos;b&apos;,&apos;c&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;命名空间&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; 如果别人也写了addClass这个函数，就会重复，所以需要命名空间

    window.xiaoyu = {}
    xiaoyu.addClass = function(node,classes) {
        classes.forEach( (value) =&amp;gt; node.classList.add(value) )
    }

    let divNode = document.getElementById(&apos;xxx&apos;)
    xiaoyu.addClass(divNode,[&apos;a&apos;,&apos;b&apos;,&apos;c&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;能不能把node放在前面&lt;/h2&gt;
&lt;h4&gt;方法一 扩展node接口&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    Node.prototype.addClass = function(classes){
        classes.forEach( (value) =&amp;gt; this.classList.add(value) )
    }

    let divNode = document.getElementById(&apos;xxx&apos;)
    divNode.addClass([&apos;a&apos;,&apos;b&apos;,&apos;c&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法二  返回一个新的node对象&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    function newNode(node){
        newNode = {
            0: node,
            length: 1,
        }
        newNode.addClass = function(classes){
            classes.forEach( (value) =&amp;gt; this.classList.add(value) )
        }
        return newNode
    }

    let divNode = document.getElementById(&apos;xxx&apos;)
    let newnode = newNode(divNode)
    newnode.addClass([&apos;a&apos;,&apos;b&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;给生成newNode对象的函数改个名字吧&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;    function jQuery(node){
        newNode = {
            0: node,
            length: 1,
        }
        newNode.addClass = function(classes){
            classes.forEach( (value) =&amp;gt; this.classList.add(value) )
        }
        return newNode
    }

    window.$ = jQuery
    let divNode = document.getElementById(&apos;xxx&apos;)
    let $newnode = $divNode)
    $newnode.addClass([&apos;a&apos;,&apos;b&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;让这个参数可以接收selector，以及元素结点&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;    window.jQuery = function(nodeOrSelector){
        if(typeof nodeOrSelector === &apos;string&apos;){
            temp = document.querySelectorAll(nodeOrSelector)
                var nodes = {}
            for(var i=0;i&amp;lt;temp.length;i++){
                nodes[i] = temp[i]
            }
            nodes.length = temp.length
        }else{
            console.log(2)
                nodes = {
                    0: nodeOrSelector,
                    length: 1
                }
        }

        // 参数可以不为数组
        nodes.addClass = function(){
            let classes = []
            for(let i=0;i&amp;lt;arguments.length;i++){
                classes.push(arguments[i])
            }
            classes.forEach((value)=&amp;gt; {
                for(let i=0;i&amp;lt;nodes.length;i++){
                    nodes[i].classList.add(value)
                }
            })
        }

        // 获取文本或设置文本内容
        nodes.text = function(text){
            if(typeof text === &apos;undefined&apos;){
                let texts = []
                for(let i=0;i&amp;lt;nodes.length;i++){
                    texts.push(nodes[i].textContent)
                }
                return texts
            }
            else{
                for(let i=0;i&amp;lt;nodes.length;i++){
                    nodes[i].textContent = text
                }
            }
        }

        return nodes
    }

    window.$ = jQuery
    let $div = $(&apos;div&apos;)

    $div.addClass(&apos;a&apos;,&apos;b&apos;)
    $div.text(&apos;hello jquery&apos;)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>原型与原型链</title><link>https://blog.cxyxiaoyu.top/posts/%E5%8E%9F%E5%9E%8B%E4%B8%8E%E5%8E%9F%E5%9E%8B%E9%93%BE/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%8E%9F%E5%9E%8B%E4%B8%8E%E5%8E%9F%E5%9E%8B%E9%93%BE/</guid><pubDate>Tue, 06 Nov 2018 21:34:41 GMT</pubDate><content:encoded>&lt;h2&gt;普通类型与对象的区别&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;在javascript中，一切东西都可以被当做对象&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    var n1 = 1
    var n2 = new Number(1)
    n1.toString() // &quot;1&quot;
    n2.toString() // &quot;1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这两种方法都是定义一个数字1，但n1是基本类型，n2是对象
但为什么n1也有toString()方法呢，因为在调用&lt;code&gt;n1.toString()&lt;/code&gt;时，内存中会临时生成一个对象&lt;code&gt;temp=new Number&lt;/code&gt;,&lt;code&gt;temp.toString()&lt;/code&gt; 的结果给&lt;code&gt;n1.toString()&lt;/code&gt;
同理,字符串也有两种声明的方法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var s1 = &apos;123&apos;
    var s2 = new String(&apos;123&apos;)
    s1.toString() // &apos;123&apos;
    s2.toString() // &apos;123&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;共有属性(原型对象)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;    var n1 = new Number(1)
    var n2 = new Number(2)
    var s1 = new String(&apos;123&apos;)
    var s2 = new String(&apos;abc&apos;)
    n1.valueOf()  // 1
    n2.valueOf()  // 2
    s1.charAt(0)  // &quot;1&quot;
    s2.charAt(0)  // &quot;a&quot;
    s1.valueOf()  // &quot;123&quot;
    s2.valueOf()  // &quot;abc&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为什么s1和s2都有&lt;code&gt;charAt()&lt;/code&gt;属性,n1和n2都有&lt;code&gt;valueOf()&lt;/code&gt;属性,n1,n2,s1,s2都有&lt;code&gt;valueOf()&lt;/code&gt;属性？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;javascript为了节省空间，将n1和n2的公用属性封装在了&lt;code&gt;Number.prototype&lt;/code&gt;，Number的原型对象中，在n1调用属性时，会先在自身的属性上找，如果没有则继续向&lt;code&gt;__proto__&lt;/code&gt;属性中找，找到的话就调用该属性，如果没找到则继续向下一级&lt;code&gt;__proto__&lt;/code&gt;中找，找到&lt;code&gt;Object.prototype&lt;/code&gt;都还没找到，就返回&lt;code&gt;undefined&lt;/code&gt;。而Number的原型对象有&lt;code&gt;valueOf()&lt;/code&gt;属性,所以n1和n2都可以调用此方法。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;javascript将所有对象共有的属性封装在了Object的原型对象中(Object.prototype),但是因为Number，String，Boolean类型又有不同的属性，所以分别又有&lt;code&gt;Number.prototye&lt;/code&gt;，&lt;code&gt;Boolean.prototype&lt;/code&gt;,&lt;code&gt;String.prototype&lt;/code&gt;分别存储了Number，String，Boolean类型的共有属性,不同类型的实例对象可以通过&lt;code&gt;__proto__&lt;/code&gt;访问该类型的原型对象,Number,String,Boolean的实例对象的&lt;code&gt;__proto__&lt;/code&gt;又指向&lt;code&gt;Object.prototype&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;prototype 与 &lt;strong&gt;proto&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;拿Number举例,&lt;code&gt;var n = new Number&lt;/code&gt;,n是对象，n有__proto__属性,指向Number的原型对象(Number共有属性)==&amp;gt; Number.prototype
而原型对象也有__proto__属性，Number原型对象的__proto__指向Object的原型对象==&amp;gt;Object.prototype&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var number = new Number(1)
    number.__proto__ === Number.prototype  // true
    Number.prototype.__proto__ === Object.prototype // true
    Object.prototype.__proto__ === null
    Number.__proto__ === Function.prototype // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;number的原型链
number ==&amp;gt; Number.prototype ==&amp;gt; Object.prototype ==&amp;gt; null&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var string = new String(&apos;123&apos;)
    string.__proto__ === Number.prototype // true
    String.prototype.__proto__ === Object.prototype // true
    Object.prototype.__proto__ === null
    String.__proto__ === Function.prototype // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;string的原型链
string ==&amp;gt; String.prototype ==&amp;gt; Object.prototype ==&amp;gt; null&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var bool = new Boolean(true)
    bool.__proto__ === Boolean.prototype // true
    Boolean.prototype.__proto__ === Object.prototype // true
    Object.prototype.__proto__ === null
    Boolean.__proto__ === Function.prototype // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;bool的原型链
bool ==&amp;gt; Boolean.prototype ==&amp;gt; Object.prototype ==&amp;gt; null&lt;/p&gt;
&lt;p&gt;以上总结 一个公式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var 对象 = new 函数()
    对象.__proto__ === 函数.prototype
    函数.prototype.__proto__ === Object.prototype
    Object.prototype.__proto__ === null
    函数.__proto__ === Function.prototype
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对象类型的原型链最短,直接指向&lt;code&gt;Object.prototype&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var obj = new Object()
    obj.__proto__ === Object.prototype  // true
    Object.prototype.__proto__ === null  // true
    Object.__proto__ === Function.prototype  // true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;obj的原型链
obj ==&amp;gt; Object.prototype ==&amp;gt; null&lt;/p&gt;
&lt;p&gt;Function函数比较特殊&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Function._proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>JS中的类型转换</title><link>https://blog.cxyxiaoyu.top/posts/js%E4%B8%AD%E7%9A%84%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/js%E4%B8%AD%E7%9A%84%E7%B1%BB%E5%9E%8B%E8%BD%AC%E6%8D%A2/</guid><pubDate>Sat, 03 Nov 2018 20:00:17 GMT</pubDate><content:encoded>&lt;h2&gt;==&amp;gt; String&lt;/h2&gt;
&lt;h4&gt;方法1  &lt;code&gt;toString()&lt;/code&gt;方法&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;null&lt;/code&gt; 和 &lt;code&gt;undefined&lt;/code&gt; 没有&lt;code&gt;toString()&lt;/code&gt; API
&lt;code&gt;object&lt;/code&gt; 的 &lt;code&gt;toString()&lt;/code&gt; 不能得到想要的结果
&lt;code&gt;console.log()&lt;/code&gt;  默认调用&lt;code&gt;toString()&lt;/code&gt;方法&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    123.toString() // &apos;123&apos;
    true.toString() // &apos;true&apos;
    null.toString()  // Cannot read property &apos;toString&apos; of null
    undefined.toString()  //Cannot read property &apos;toString&apos; of undefined
    var a = {a:1}
    a.toString()  // &quot;[object Object]&quot;
    ({a:1}).toString() // &quot;[object Object]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法2  &lt;code&gt;+ &apos;&apos;&lt;/code&gt; 加空字符串&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    1 + &apos;1&apos; // &apos;11&apos;  ==&amp;gt; &apos;+&apos; 会自动进行类型转换,优先变为字符串
    123 + &apos;&apos; // &apos;123&apos;
    true + &apos;&apos;  // &apos;true&apos;
    null + &apos;&apos; // &apos;null&apos;
    undefined + &apos;&apos; // &apos;undefined&apos;
    ({a:1}) + &apos;&apos; // &quot;[object Object]&quot;
    &apos;&apos; + {a:1}  // &quot;[object Object]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法3  全局函数&lt;code&gt;String()&lt;/code&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    String(1)  // &apos;1&apos;
    String(true) // &apos;true&apos;
    String(null)  // &apos;null&apos;
    String(undefined) // undefined
    String({a:1})  // &quot;[object Object]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;==&amp;gt; Boolean&lt;/h2&gt;
&lt;h4&gt;方法1  全局方法&lt;code&gt;Boolean()&lt;/code&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    Boolean(1) // true
    Boolean(0) // false
    Boolean(NaN) // false
    Boolean(&apos;&apos;) // false
    Boolean(&apos; &apos;) // true
    Boolean(&apos;123&apos;) // true
    Boolean(null)  // false
    Boolean(undefined) //false
    Boolean({})  // true
    Boolean({a:1}) //true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法2 双重取反 ==&amp;gt; &lt;code&gt;!!&lt;/code&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    !! 1  // true
    !! NaN // false
    !! 0 // false
    !! &apos;&apos; // false
    !! &apos; &apos; // true
    !! &apos;123&apos; // true
    !! null // false
    !! undefined //false
    !! {}  // true
    !! {a:1} // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;五个&lt;strong&gt;falsy&lt;/strong&gt;值&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;0
NaN
null
undefined
&apos;&apos;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;==&amp;gt; Number&lt;/h2&gt;
&lt;h4&gt;方法1  &lt;code&gt;Number()&lt;/code&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    Number(&apos;1&apos;) // 1
    Number(&apos;1.123&apos;) // 1.123
    Number(&apos;1.abc&apos;) // NaN
    Number(&apos;&apos;)    // 0
    Number(true)  // 1
    Number(false) // 0
    Number(null) // 0
    Number(undefined) // NaN
    Number({a:1})  // NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法2  &lt;code&gt;parseInt()&lt;/code&gt;&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;parseInt()&lt;/code&gt; 主要是将字符串转为整数
它会从第一个字符开始，直到不是数字结束&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    parseInt(&apos;1&apos;) //1
    parseInt(&apos;1.23&apos;) // 1
    parseInt(&apos;  123&apos;) // 123
    parseInt(&apos;1234abc&apos;)  // 1234
    parseInt(&apos;abc&apos;) // NaN
    parseInt(&apos;a567bc&apos;) // NaN
    parseInt(true) // NaN
    parseInt(null) // NaN
    parseInt(undefined) // NaN
    parseInt({a:1}) // NaN
    parseInt([1,2,3]) // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法3  &lt;code&gt;parseFloat()&lt;/code&gt;&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;parseFloat() 主要是将字符串转为浮点数&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    parseFloat(&apos;3.14&apos;) // 3.14
    parseFloat(&apos;314e-2&apos;) // 3.14
    parseFloat(&apos;0.0314E+2&apos;) // 3.14
    // 其他与parseInt()结果相同
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法4  &lt;code&gt;- 0&lt;/code&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    &apos;1&apos; - 0 // 1
    &apos;1.23&apos; - 0  // 1.23
    &apos;1.abc&apos; -0 // NaN
    &apos;&apos; - 0  // 0
    true -0 // 1
    false - 0  // 0
    null - 0 // 0
    undefined - 0 // NaN
    ({a:1}) - 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法5  取正&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    + &apos;1&apos; // 1
    + &apos;1.23&apos; // 1.23
    + &apos;1.abc&apos; // NaN
    + &apos;&apos; // 0
    + true // 1
    + false // 0
    + null // 0
    + undefined // NaN
    + {a:1} // NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;JS中普通类型与对象的区别&lt;/h2&gt;
&lt;h4&gt;普通类型  Number Bool String Symbol Null Undefined&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;数据直接存在栈内存中&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    var a = 1
    var b = a
    a = 2
    b // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将a中的数据直接赋值给b，所以改变a，不会影响b ==&amp;gt; 深复制
所以普通类型之间的复制都是深复制&lt;/p&gt;
&lt;h4&gt;引用类型  Object&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;栈内存中保存的是对象在堆内存中的引用地址
通过这个引用地址可以快速查找到保存中堆内存中的对象&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    var a = {name:1}
    var b = a
    a.name = 2
    b // {name:2}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将a赋值给b的时候，实际上是b也指向了与a地址相同的对象，
改变a会影响b == &amp;gt; 浅复制&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    var a = {name:1}
    var b = a
    a = {name:2}
    b // {name:1}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;a = {name:2}&lt;/code&gt; 时，a指向了另一个对象的地址,而b还指向原来地址的对象,所以给a重新赋值不会影响b&lt;/p&gt;
</content:encoded></item><item><title>CSS-REM与动态REM</title><link>https://blog.cxyxiaoyu.top/posts/css-rem%E4%B8%8E%E5%8A%A8%E6%80%81rem/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/css-rem%E4%B8%8E%E5%8A%A8%E6%80%81rem/</guid><pubDate>Thu, 01 Nov 2018 14:01:13 GMT</pubDate><content:encoded>&lt;h1&gt;什么是em&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;相对长度单位，这个单位表示元素的font-size的计算值。如果用在font-size 属性本身，它会继承父元素的font-size。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;什么是rem&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;这个单位代表根元素的 font-size 大小（例如 &amp;lt;html&amp;gt; 元素的font-size）。当用在根元素的font-size上面时 ，它代表了它的初始值&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;所以em与rem没有任何关系&lt;/p&gt;
&lt;h1&gt;动态rem&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;动态rem是通过js动态改变html的font-size,也就是改变rem，一般是改为页面宽度的十分之一，确保最小字号不会小于12px。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;动态rem可以用于移动端页面布局&lt;/p&gt;
&lt;p&gt;如果页面宽度是1000px,将rem设为 1000px / 10 =&amp;gt; 100px 所以1em就是100px, 将一个div元素设为3em,他就会占页面的十分之三&lt;/p&gt;
&lt;p&gt;这样元素的宽高都以屏幕宽度为基准,对于不同的移动设备,屏幕宽度不同,页面元素都会等比例缩放&lt;/p&gt;
&lt;p&gt;代码如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;scrtip&amp;gt;
  document.write(`
      &amp;lt;style&amp;gt;
          html {
              font-size: ${window.innerWidth/10}px
          }
      &amp;lt;/style&amp;gt;
  `)
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样就可以让rem为页面宽度的十分之一&lt;/p&gt;
&lt;h1&gt;利用scss 函数设置元素rem值&lt;/h1&gt;
&lt;p&gt;一般ui设计稿都是给定宽度的,我们不能一一计算每个元素的比例,这时候就可以通过scss里的函数自动计算比例.
假设设计稿的页面宽度为640px,xxx元素的宽度为320px(二分之一), 高度为160px(四分之一)。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$pageWidth = 640
function px2rem(px){
  return px/$pageWidth * 10 + rem
}

.xxx {
  width: px2rem(320);  // 5em
  height: px2rem(160); // 2.5em
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>JS中的数据类型</title><link>https://blog.cxyxiaoyu.top/posts/js%E4%B8%AD%E7%9A%84%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/js%E4%B8%AD%E7%9A%84%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/</guid><pubDate>Mon, 29 Oct 2018 18:46:30 GMT</pubDate><content:encoded>&lt;h1&gt;JS中一共有七大数据类型&lt;/h1&gt;
&lt;h2&gt;2. 字符串(string)&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;字符串也可以叫做字符数组&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;字符串就是零个或多个排在一起的字符，放在单引号或双引号之中。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;空字符串与空格字符串&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;空字符串    没有字符,长度为0
空格字符串  只有一个空格的字符串，空格也是一个字符，所以长度为1&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;多行字符串(3种) ,ES6新语法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; var s=&apos;1234\    //使用&quot;\&quot;换行
 5677&apos;
 s
 &quot;12345677&quot;     //输出还是一行

 var s=&apos;1234&apos;+   //使用&quot;+&quot;连接字符串
 &apos;5677&apos;
 s
 &quot;12345677&quot;    //输出还是一行

 //ES6新语法
 var s=`1234    //使用&quot;`&quot; 换行
 5678`
 s
 &quot;1234        //输出为两行,length为9，换行符也为一个字符
 5678&quot;

 //使用转义符
 var s=&apos;1234\n5678&apos;   //&quot;\n&quot;表示换行符
 s
 &quot;1234        //输出为两行
 5678&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;转义&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;反斜杠（\）在字符串内有特殊含义，用来表示一些特殊字符，所以又称为转义符。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;\0 : null&lt;/li&gt;
&lt;li&gt;\b : 后退键&lt;/li&gt;
&lt;li&gt;\f : 换页符&lt;/li&gt;
&lt;li&gt;\n : 换行符&lt;/li&gt;
&lt;li&gt;\r : 回车键&lt;/li&gt;
&lt;li&gt;\t : 制表符&lt;/li&gt;
&lt;li&gt;\v : 垂直制表符&lt;/li&gt;
&lt;li&gt;&apos; : 单引号&lt;/li&gt;
&lt;li&gt;\ : 反斜杠&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;字符串与数组&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;字符串可以被视为字符数组，因此可以使用数组的方括号运算符，用来返回某个位置的字符（位置编号从0开始）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    var s = &quot;hello&quot;
    s[0] // &apos;h&apos;
    s[1] // &apos;e&apos;
    s[4] // &apos;o&apos;
    s[-1] // undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;字符串是不可变的数据类型，不可以通过此方法改变字符串&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    var s = &quot;hello&quot;
    delete s[0]
    s // &apos;hello&apos;
    s[0] = &quot;G&quot;
    s// &apos;hello&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 布尔(boolean)&lt;/h2&gt;
&lt;p&gt;只有true 和 false 两个值,用于条件判断&lt;/p&gt;
&lt;h2&gt;4. 符号(symbol)  ES6新增&lt;/h2&gt;
&lt;h2&gt;5. unidefine  只有一个值==&amp;gt; undefined&lt;/h2&gt;
&lt;h2&gt;6. null       只有一个值==&amp;gt; null&lt;/h2&gt;
&lt;p&gt;undefined 与 null 都表示什么也没有&lt;/p&gt;
&lt;h3&gt;undefined 与 null 的区别&lt;/h3&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;一个变量被声明而没有赋值，则返回&lt;code&gt;undefined&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;一个非空对象，现在不想给值，&lt;code&gt;var xxx=undefined&lt;/code&gt; ==&amp;gt; 空非对象&lt;/li&gt;
&lt;li&gt;一个对象，不想给值， &lt;code&gt;var obj=null&lt;/code&gt; ==&amp;gt; 空对象&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2&gt;7. 对象(object)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;简单说，对象就是一组“键值对”（key-value）的集合，是一种无序的复合数据集合。
数组和方法也是对象&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    var obj = {a:1,b:2}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;如果键名是数字，会自动转为字符串，如果不加引号，会按照标识符规则，不符合就报错&lt;/li&gt;
&lt;li&gt;键名又称为属性，值可以为任意数据类型&lt;/li&gt;
&lt;li&gt;对象的引用&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;如果不同的变量名指向同一个对象，那么它们都是这个对象的引用，也就是说指向同一个内存地址。修改其中一个变量，会影响到其他所有变量。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    var o1 = {}
    var o2 = o1

    o1.a = 1
    o2.a //1 o1改变，也会影响到o2

    o2.b = 2
    o1.b //2 o2改变，也会影响到o1

    o1 = 1    //取消o1对原对象的引用
    o2 // {a:1,b:2}  //o2不变
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;对属性的读取&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; var obj = {&apos;name&apos;:&apos;xiaoyu&apos;}
 obj[&apos;name&apos;] // &apos;xiaoyu&apos;
 obj.name    // &apos;xiaoyu&apos;  &amp;lt;==&amp;gt; obj[&apos;name&apos;]

 var key =  &apos;name&apos;
 obj.key  //error!  obj[&apos;key&apos;],这个对象没有key这个属性
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对象有哪些属性&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; var obj = {&apos;name&apos;:&apos;xiaoyu&apos;,&apos;age&apos;:&apos;18&apos;}
 Object.keys(obj) //[&apos;name&apos;,&apos;age&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对象的遍历&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;for...in&lt;/code&gt; 循环用来遍历一个对象的全部属性。
它遍历的是对象所有可遍历（enumerable）的属性，会跳过不可遍历的属性。
它不仅遍历对象自身的属性，还遍历继承的属性。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    var obj = {&apos;name&apos;:&apos;xiaoyu&apos;,&apos;age&apos;:&apos;18&apos;}
    for(var attr in obj){
        console.log(attr+&apos;==&amp;gt;&apos;+obj[attr]) // name==&amp;gt;xiaoyu age==&amp;gt;18
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;其他&lt;/h2&gt;
&lt;p&gt;typeof 查看数据类型，两个bug，一个特殊的name&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var num = 1
typeof num  //number

//bug1
function f(){]
typeof f //function  js没function这个数据类型

//bug2
var a = null
typeof a //object

//特殊的name  ==&amp;gt;  window.name 会将值通过toString()方法转为字符串
var name = [1,2,4]
typeof name // string
name  // &apos;1,2,4&apos;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>CSS布局</title><link>https://blog.cxyxiaoyu.top/posts/css%E5%B8%83%E5%B1%80/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/css%E5%B8%83%E5%B1%80/</guid><pubDate>Sat, 13 Oct 2018 00:50:02 GMT</pubDate><content:encoded>&lt;ul&gt;
&lt;li&gt;左右布局&lt;/li&gt;
&lt;li&gt;左中右布局&lt;/li&gt;
&lt;li&gt;水平居中&lt;/li&gt;
&lt;li&gt;垂直居中&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;左右布局&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;clearfix&quot;&amp;gt;
    &amp;lt;div class=&quot;a&quot;&amp;gt;左&amp;lt;/a&amp;gt;
    &amp;lt;div class=&quot;b&quot;&amp;gt;右&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;

.clearfix {
    content: &apos;&apos;;
    display: block;
    clear: both;
}
.a,
.b {
        width: 50%;
        height: 200px;
        float: left;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;父元素的class设为clearfix
子元素全部设为左浮动,可设置width的百分比决定每个子元素占据的宽度&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;左中右布局(三列布局)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;left&quot;&amp;gt;左&amp;lt;/div&amp;gt;
&amp;lt;div class=&quot;middle&quot;&amp;gt;中&amp;lt;/div&amp;gt;
&amp;lt;div class=&quot;right&quot;&amp;gt;右&amp;lt;/div&amp;gt;

.left{
    width: 200px;height: 300px; background: yellow; float: left;
}
.right{
    width: 170px; height: 300px; background: green; float: right;
}
.middle{
    height: 300px; background: red; margin-left: 220px; margin-right: 180px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;通过给左右两边设定宽度,左右元素分别向左右浮动
中间元素设置左右边距&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;水平居中&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;内联元素
给该内联元素所在的块父级元素添加&lt;code&gt;text-align: center;&lt;/code&gt; 样式&lt;/li&gt;
&lt;li&gt;块级元素
给该元素添加&lt;code&gt;margin: 0 auto;&lt;/code&gt; 样式&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;垂直居中&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;居中文字
将该元素的&lt;code&gt;line-height&lt;/code&gt;设为与&lt;code&gt;height&lt;/code&gt; 一样的值&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;块级元素&lt;/p&gt;
&lt;p&gt;.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
transform: translateY(-50%);
}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>几个关于CSS的套路</title><link>https://blog.cxyxiaoyu.top/posts/%E5%87%A0%E4%B8%AA%E5%85%B3%E4%BA%8Ecss%E7%9A%84%E5%A5%97%E8%B7%AF/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/%E5%87%A0%E4%B8%AA%E5%85%B3%E4%BA%8Ecss%E7%9A%84%E5%A5%97%E8%B7%AF/</guid><pubDate>Mon, 08 Oct 2018 22:28:21 GMT</pubDate><content:encoded>&lt;h2&gt;横向布局&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;将子元素全部浮动起来&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;父元素添加 clearfix 属性&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;给clearfix类添加三行样式,清除浮动带来的影响&lt;/p&gt;
&lt;p&gt;&amp;lt;head&amp;gt;
...
&amp;lt;style&amp;gt;
clearfix::after {
content: &apos;&apos;;
display: block;
clear: both;
nav&amp;gt;ul&amp;gt;li {
float: left;
}
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;nav&amp;gt;
&amp;lt;ul class=&quot;clearfix&quot;&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/nav&amp;gt;
&amp;lt;/body&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;如果是两列显示在一行可以将li的高度设为50%;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;文档流&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;文档流就是文档内元素的流动方向，内联:左-&amp;gt;右,块级:上-&amp;gt;下
脱离文档流会影响body的高度
脱离文档流会导致宽度收缩，需设置&lt;code&gt;width: 100%;&lt;/code&gt;解决&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;元素的高度&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;块级元素的高度由其内部文档流元素高度的总和&lt;strong&gt;决定&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;内联元素的高度&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;position 定位&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;sticky      粘性定位&lt;/li&gt;
&lt;li&gt;static      当前位置&lt;/li&gt;
&lt;li&gt;relative    相对定位(参照自身原来的位置)&lt;/li&gt;
&lt;li&gt;absolute    绝对定位(参照父级)&lt;/li&gt;
&lt;li&gt;fixed       固定定位(参照浏览器)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;通常将父级元素设置为相对定位，子元素设置为决定定位，子元素参照父级设定位置&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;背景图片居中，自适应&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;background-position: center center;
background-size: cover;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;引用SVG图标&lt;/h2&gt;
&lt;p&gt;常用的网站：&lt;a href=&quot;http://www.iconfont.cn&quot;&gt;iconfont.cn&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;其他很实用的小技巧&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;max-height,max-weight&lt;/li&gt;
&lt;li&gt;一般不给元素设定固定的height和width,容易出bug&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;通常从内向外加pading，将元素撑到指定宽高&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;内联元素不接受宽高度, 设置margin-top和margin-bottom不生效&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;display: block / inline-block;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;hr 一条横线,样式设置&lt;/p&gt;
&lt;p&gt;hr {
height: 0;
border: none;
border-top: 1px solid red;
}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;work-break: break all&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;对于non-CJK (CJK 指中文/日文/韩文) 文本，可在任意字符间断行。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>HTML常用标签</title><link>https://blog.cxyxiaoyu.top/posts/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/html%E5%B8%B8%E7%94%A8%E6%A0%87%E7%AD%BE/</guid><pubDate>Sun, 07 Oct 2018 11:49:03 GMT</pubDate><content:encoded>&lt;h2&gt;iframe 和 a&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;iframe 标签通常用于&lt;strong&gt;嵌套页面&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;framborder 属性设置为0用于消除边框&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;name&lt;/strong&gt; 属性与a标签&lt;strong&gt;结合&lt;/strong&gt;起来&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;target=&quot;xxx&quot;         #跳转到name为xxx的窗口或iframe
target=&quot;_blank&quot;     #在新的窗口打开
target=&quot;_self&quot;      #在当前窗口打开
target=&quot;_parent&quot;    #在父级窗口打开
target=&quot;_top&quot;       #在顶层窗口打开&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;iframe src=&quot;https://www.baidu.com&quot; name=&quot;xxx&quot;&amp;gt;&amp;lt;/iframe&amp;gt;
    &amp;lt;a target=&quot;xxx&quot; href=&quot;http://qq.com&quot;&amp;gt;QQ&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;a标签的download属性提供下载功能&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a herf=&quot;./test.png&quot; download&amp;gt;test&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a标签的href属性可以发送http请求(只有&lt;strong&gt;锚点&lt;/strong&gt;不发送请求，其他全部发送&lt;strong&gt;get&lt;/strong&gt;       &amp;lt;请求)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;href=&quot;#&quot;     #跳到页面顶部，锚点变为&quot;#&quot;
href=&quot;&quot;      #刷新页面，跳转自身
href=&quot;//qq.com&quot;  #以当前协议访问qq.com
href=&quot;?q=xxx&quot;    #发送get请求,在地址栏显示
href=&quot;javascript:;&quot;  #鼠标点击什么也不做,javascript&lt;strong&gt;伪协议&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;form 和 table&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h3&gt;form&lt;/h3&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;除了a标签可以发送请求外，form标签也可以发送请求，通常是post请求
action 指定请求路径，请求路径中也可加查询参数，method指定请求动词
form 中提交信息的标签，必须添加name属性
要想发送请求，form标签里必须有提交按钮&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;form 中有&lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;,没写type属性，自动升级为提交按钮
form 中如果有&lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;,只有type为&lt;strong&gt;submit&lt;/strong&gt;时,才为提交按钮&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    &amp;lt;form action=&apos;query&apos; method=&quot;post&quot;&amp;gt;
        &amp;lt;label&amp;gt;Username&amp;lt;input type=&quot;text&quot; name=&quot;name&quot;&amp;gt;&amp;lt;/label&amp;gt;
        &amp;lt;label&amp;gt;Password&amp;lt;input type=&quot;password&quot; name=&quot;pwd&quot;&amp;gt;&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;submit&quot; value=&quot;submit&quot;&amp;gt;
    &amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;input与文字关联&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;通常写法,id与for关联&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;input type=&quot;checkbox&quot; id=&quot;xxx&quot;&amp;gt;&amp;lt;label for=&quot;xxx&quot;&amp;gt;Something&amp;lt;/label&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;常用套路,label将input包起来&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;label&amp;gt;&amp;lt;input type=&quot;checkbox&quot;&amp;gt;Something&amp;lt;/label&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h3&gt;table&lt;/h3&gt;
table的全部用法见下图
&lt;img src=&quot;/img/table.png&quot; alt=&quot;table&quot; title=&quot;table的全部用法&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;其他标签&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;nav,navigation 用来写导航条&lt;/li&gt;
&lt;li&gt;ul,un-ordered list 无序列表&lt;/li&gt;
&lt;li&gt;ol,ordered list    有序列表&lt;/li&gt;
&lt;li&gt;除了span标签和div标签，其他标签都有默认样式&lt;/li&gt;
&lt;li&gt;body默认margin为8px&lt;/li&gt;
&lt;li&gt;strong和b都是加粗，strong表示语气上的强调，b只是单纯加粗&lt;/li&gt;
&lt;li&gt;空元素   &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;hr&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;col&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;colgroup&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;可替代元素  &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>curl常用用法以及http协议</title><link>https://blog.cxyxiaoyu.top/posts/curl%E5%B8%B8%E7%94%A8%E7%94%A8%E6%B3%95%E4%BB%A5%E5%8F%8Ahttp%E5%8D%8F%E8%AE%AE/</link><guid isPermaLink="true">https://blog.cxyxiaoyu.top/posts/curl%E5%B8%B8%E7%94%A8%E7%94%A8%E6%B3%95%E4%BB%A5%E5%8F%8Ahttp%E5%8D%8F%E8%AE%AE/</guid><pubDate>Sat, 06 Oct 2018 22:38:26 GMT</pubDate><content:encoded>&lt;h2&gt;curl命令常用参数&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;h4&gt;最简单的用法，直接加网址&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;curl http://www.baidu.com       #返回下载进度，及网页源代码
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4&gt;-X 指定请求方式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GET 请求&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X GET http://www.baidu.com  # -X GET 是可选的
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;POST 请求&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST -d &apos;123&apos; http://www.baidu.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;-d 后是向服务端发送的数据，写-d 选项后可省略 -X POST&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4&gt;-H 增加头部信息&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;   curl -H &quot;Content-Type:application/json&quot; -d &apos;{&quot;data&quot;:&quot;123&quot;,&quot;key&quot;:&quot;456&quot;}&apos; http://www.baidu.com
   #即指定发送数据的格式为json格式
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4&gt;-s silent  静静的下载，不输出进度条&lt;/h4&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4&gt;-v 显示请求详细信息,HTTP协议的请求内容和响应&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    $ curl -s -v http://www.baidu.com
    ...
    &amp;gt; GET / HTTP/1.1
    &amp;gt; Host: www.baidu.com
    &amp;gt; User-Agent: curl/7.55.0
    &amp;gt; Accept: */*
    &amp;gt;
    &amp;lt; HTTP/1.1 200 OK
    &amp;lt; Accept-Ranges: bytes
    &amp;lt; Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
    &amp;lt; Connection: Keep-Alive
    &amp;lt; Content-Length: 2381
    &amp;lt; Content-Type: text/html
    &amp;lt; Date: Sun, 07 Oct 2018 02:59:01 GMT
    &amp;lt; Etag: &quot;588604c4-94d&quot;
    &amp;lt; Last-Modified: Mon, 23 Jan 2017 13:27:32 GMT
    &amp;lt; Pragma: no-cache
    &amp;lt; Server: bfe/1.0.8.18
    &amp;lt; Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
    &amp;lt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;http协议(hyper text translate protocol)  超文本传输协议&lt;/h2&gt;
&lt;h3&gt;通过curl命令查看http请求和响应&lt;/h3&gt;
&lt;p&gt;参考curl命令-v参数，&lt;code&gt;&amp;gt;&lt;/code&gt;开头的为http请求，&lt;code&gt;&amp;lt;&lt;/code&gt;开头的为响应
请求的内容为&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;gt; GET / HTTP/1.1
&amp;gt; Host: www.baidu.com
&amp;gt; User-Agent: curl/7.55.0
&amp;gt; Accept: &lt;em&gt;/&lt;/em&gt;
&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;响应的内容为&lt;/p&gt;
&lt;blockquote&gt;
&lt;pre&gt;&lt;code&gt;       &amp;lt; HTTP/1.1 200 OK
       &amp;lt; Accept-Ranges: bytes
       &amp;lt; Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
       &amp;lt; Connection: Keep-Alive
       &amp;lt; Content-Length: 2381
       &amp;lt; Content-Type: text/html
       &amp;lt; Date: Sun, 07 Oct 2018 02:59:01 GMT
       &amp;lt; Etag: &quot;588604c4-94d&quot;
       &amp;lt; Last-Modified: Mon, 23 Jan 2017 13:27:32 GMT
       &amp;lt; Pragma: no-cache
       &amp;lt; Server: bfe/1.0.8.18
       &amp;lt; Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
       &amp;lt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;另一个请求示例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST -d &quot;data&quot; -s -v -H &quot;Xiaoyu: xxx&quot; -- &quot;https://www.baidu.com&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;请求的内容为&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;gt; POST / HTTP/1.1
&amp;gt; Host: www.baidu.com
&amp;gt; User-Agent: curl/7.55.0
&amp;gt; Accept: &lt;em&gt;/&lt;/em&gt;
&amp;gt; Xiaoyu: xxx
&amp;gt; Content-Length: 4
&amp;gt; Content-Type: application/x-www-form-urlencoded&lt;/p&gt;
&lt;p&gt;data&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;请求的格式为&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1 动词 路径 协议/版本   #动词有 GET POST PUT PATCH DELETE HEAD OPTIONS 等
2 Key1: value1
2 Key2: value2
2 Key3: value3
2 Content-Type: application/x-www-form-urlencoded
2 Host: www.baidu.com
2 User-Agent: curl/7.54.0
3                          #空格
4 要上传的数据             #可以为空&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;响应的内容为&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt; HTTP/1.1 302 Found
&amp;lt; Connection: Keep-Alive
&amp;lt; Content-Length: 17931
&amp;lt; Content-Type: text/html
&amp;lt; Date: Sun, 07 Oct 2018 03:08:53 GMT
&amp;lt; Etag: &quot;54d97485-460b&quot;
&amp;lt; Server: bfe/1.0.8.18
&amp;lt;
data                    #下载的数据，数据格式由content-type决定&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;响应的格式为&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1 协议/版本号 状态码 状态解释
2 Key1: value1
2 Key2: value2
2 Content-Length: 17931
2 Content-Type: text/html
3
4 要下载的内容&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;通过Chrome浏览器查看http请求和响应&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;打开 Network&lt;/li&gt;
&lt;li&gt;地址栏输入网址&lt;/li&gt;
&lt;li&gt;在 Network 点击，查看 request/response，点击「view source」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;点击「view source」&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;如果有请求的第四部分，那么在 FormData 或 Payload 里面可以看到&lt;/li&gt;
&lt;li&gt;如果是响应，可以点击Response查看响应的数据&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item></channel></rss>