YuanWing

YuanWing 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

知易行难~埋头赶路~自己选择的路~爬也得爬完~

个人动态

YuanWing 赞了文章 · 5月25日

记一次vue3.0技术分享会

记一次vue3.0技术分享会

记录了我在组内的技术分享, 有同样需求的同学可以参考一下
分享全程下来时间大约1小时

一. 版本

当前还处于 beta版本, 想要正式使用在项目里还需要一段的时间, 但是结构与api变化应该不大了.

这里列出的并不全, 但是够用了
1. alpha 内测版本
2. beta  公测版本
3. Gamma 正式发布的候选版本
4. Final 正式版
5. plus  增强版
6. full  完全版 
7. Trial 试用版(一般有时间或者功能限制)

二. 介绍

  1. 学一门新鲜的技术,就像练习王者荣耀新出的英雄一样, 探索他的好玩之处可以给开发者带来快乐, 使用新的好的技术会让工作更愉悦

  2. 这个版本的vue 类似"我的世界", 全部都是一个个方块组成, 不要小看方块, 方块多了甚至可以组成圆形(量变引起质变), 新鲜的玩法才能激发我们的兴趣

三. vue3.0的环境搭建

准备一套搭建好的环境防治到时候出现意外, 现场再按照步骤搭建一版, 每一步都不要落下能让大家更好的理解.
  1. npm install -g @vue/cli  cli升级到4版本

  2. 随便创建个项目, vue create next-test

  3. 选择配置最好把router与vuex一起选上, 方便后续升级

  4. vue add vue-next   cli提供的进化到下一版本的命令, 执行后自动将router, vuex 升级到alpha版.

四. vue3.0重要的优化

  1. 模板编译速度的提升, 对静态数据的跳过处理.
  2. 对数组的监控
  3. 对ts有了很好的支持
  4. 对2.x版本的完全兼容
  5. 可以有多个根节点 (也有bug, 比如外层开了display:flex 那么里面会收到影响, 也就是说布局更灵活但也更要小心, 总之请对自己与他人的代码负责)
  6. 支持Source map, 虽然没演示但是这点真的重要

五. vuex, router, vue 初始化写法的变化

vue:
import { createApp } from 'vue';
import App from './App.vue'
import router from './router'
import store from './store'

// 方法一. 创建实例变成了链式, 直接写下去感觉语义与结构有点模糊, 但是我们要理解vue这样做的良苦用心, 前端趋近于函数化.
// createApp(App).use(router).use(store).mount('#app')

// 方法二.
const app = createApp(App);
app.use(router);
app.use(store);
app.mount('#app');
vuex:
import { createStore } from 'vuex'
// 专门创建实例的一个方法
export default createStore({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
});
router
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  }
]

const router = createRouter({
// 专门创建history的函数
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

六. 变量的定义

1: ref

import { ref } from "vue";
export default {
  // 1: 这个版本基本逻辑都在setup里面完成了, 有人说把他当成2.x的data.
  setup() {
    // 2: 定义一个追踪的变量,也就是双向绑定.
    const n = ref(1); // 生成的n是一个对象, 这样方便vue去监控它
    function addN() {
      n.value++; // 注意这里要使用.value的形式, 因为n是对象↑, value才是他的值
    }
    return {
      n,   // 返回出去页面的template才可以使用它, {{n}} 不需要.value
      addN
    }
  }
 }

2: reactive

import { reactive, toRefs } from "vue";
export default {
  setup() {
    // 注意事项: reactive的对象不可以结构返回或导入, 会导致失去响应式
    const obj = reactive({
      name: "金毛",
      age: 4
    });
    function addObj() {
      obj.age++;
    }
    return {
      ...obj, // 这样写不好, 里面会失去响应式
      obj, // 这样写那么外面就要都基于obj来调取, 类型{{obj.age}}
      ...toRefs(obj) // 必须是reactive生成的对象, 普通对象不可以, 他把每一项都拿出来包了一下, 我们可以这样用了 {{age}}, 放心咱们多深的obj也可以响应
    }
  }
 }

7. 之前的ref何去何从

这个老兄被别人抢了关键词, 也只能自己改改写法了
  <div>
    <div ref="content">第一步, 在dom上面定义, 他会有一个回调</div>
  </div>
  <ul>
    <li>v-for 出来的ref</li>
    <li>可以写为表达式的形式, 可以推导出vue是如何实现的</li>
    <li>vue2.x的时候v-for不用这么麻烦, 直接写上去会被组装成数组</li>
    <li :ref="el => { items[index] = el }" v-for="(item,index) in 6" :key="item">{{item}}</li>
  </ul>
import { ref, onMounted, onBeforeUpdate } from "vue";
export default {
  setup() {
    // 2: 定义一个变量接收dom, 名字无所谓, 但是与dom统一的话会有很好的语义化
    const content = ref(null);
    const items = ref([]);

    // 4: 在生命周期下, 这个值已经完成了变化, 所以当然第一时间就拿到
    onMounted(() => {
      console.log(content.value);
      console.log("li标签组", items.value);
    });

    // 5: 确保在每次变更之前重置引用
    onBeforeUpdate(() => {
      items.value = [];
    });

    // 3: 返出去的名称要与dom的ref相同, 这样就可以接收到dom的回调
    return {
      content,
      items
    };
  }
};

8. 生命周期

<template>
  <div>
    <button @click="add">点击增加, 触发updata</button>
    <p>{{obj.count}}</p>
  </div>
  <p>
    2.x与 3.0的对照
    beforeCreate -> 使用 setup()
    created -> 使用 setup()
    beforeMount -> onBeforeMount
    mounted -> onMounted
    beforeUpdate -> onBeforeUpdate
    updated -> onUpdated
    beforeDestroy -> onBeforeUnmount
    destroyed -> onUnmounted
    errorCaptured -> onErrorCaptured
  </p>
</template>

<script>

//这些生命周期注册方法只能用在 setup 钩子中
import { onMounted, onUpdated, onBeforeUnmount, reactive } from "vue";
export default {
  // 1: setup显得冗长, 可以自己动手做一些插件来优化
  // 2: 本身更加明确意图
  // 3: 需要树立工程师的正确代码意识
  // 4: 能力不足可能会写出更差的代码
  // 作者说: 提升上界的收益远远大于降低下界的损失。值得深思, 前端需要提高门槛
  // 5: 调用时机: 创建组件实例,然后初始化 props ,紧接着就调用setup 函数。从生命周期钩子的视角来看,它会在 beforeCreate 钩子之前被调用
  // 6: 这些生命周期钩子注册函数只能在 setup() 期间同步使用, 因为它们依赖于内部的全局状态来定位当前组件实例(正在调用 setup() 的组件实例), 不在当前组件下调用这些函数会抛出一个错误。
  // 7: 原则上生命周期里面不会放具体的逻辑,哪怕只有一句赋值一个三元都不可放, 这也正好符合当前的工程模式

  // 讨论: 有多少种方式, 可以判断出某个函数 当前处于哪个函数?
  //       比如有多层嵌套的组件是否有影响
  setup() {
    onMounted(() => {
      console.log("is mounted!");
    });
    onUpdated(() => {
      console.log("is onUpdated!");
    });
    onBeforeUnmount(() => {
      console.log("is onBeforeUnmount!");
    });
    const obj = reactive({
      count: 1
    });
    function add() {
      obj.count++;
    }
    return {
      obj,
      add
    };
  }
};
</script>

9. 路由

<template>
  <div>
    {{id}}
  </div>
</template>

<script>
import { getCurrentInstance, ref } from 'vue';
export default {
  setup(){
    const { ctx } = getCurrentInstance()
    // 1. 这样也是为了去掉this
    // 2. 方便类型推导
    console.log(ctx.$router); // push等方法
    console.log(ctx.$router.currentRoute.value); // 路由实例
    // 这个其实没有必要变成ref因为这个值没必要动态
    // 但是他太长了, 这个真的不能忍
    const id = ref(ctx.$router.currentRoute.value.query.id)

    // 4: 页面拦截器
    ctx.$router.beforeEach((to, from,next)=>{
      console.log('路由的生命周期')
      next()
    })
    return {
      id
    }
  }
}

</script>

10. vuex

import { createStore } from 'vuex'

// 难道前端趋势只有函数这一种吗
export default createStore({
  state: {
    name:'牛逼, 你拿到我了',
    age: 24,
    a:'白',
    b:'黑'
  },
  mutations: {
    updateName(state, n){
      state.name += n
    }
  },
  actions: {
    deferName(store) {
     setTimeout(()=>{
       // 必须只有commit可以修改值, 这个设定我比较反对, 可以讨论
       // vuex本身结构就很拖沓, 定义域使用个人都不喜欢
      store.state.name = '牛逼, 你改回来了'
     },1000)
    }
  },
  getters: {
    fullName(state){ return `${state.a} - + -${state.b}` }
  },
  modules: {
  }
});
<template>
  <div>
    <p>{{name}}</p>
    <button @click="updateName('+')">点击改变名字</button>
    <br />
    <button @click="deferName('+')">改回来</button>

    <p>{{fullName}}</p>
  </div>
</template>

<script>
import { useStore } from "vuex";
import { computed } from "vue";
export default {
  setup() {
    const store = useStore();
    // 1: 单个引入
    const name = computed(() => store.state.name);
    // 2: 引入整个state
    const state = computed(() => store.state);
    console.log("vuex的实例", state.value); // 别忘了.value

    // 3: 方法其实就直接从本体上取下来了
    const updateName = newName => store.commit("updateName", newName);

    // 4: action一个意思
    const deferName = () => store.dispatch("deferName");

    // 5: getter 没变化
    const fullName = computed(() => store.getters.fullName);
    return {
      name,
      fullName,
      deferName,
      updateName,
    };
  }
};
</script>

11. composition(这个可能是最重要的改革了)

前端算是面向函数编程, 各种规范也都趋近于函数化
composition使得前端工程师的编程规范, 更接近于原生js, 三十年河东三十年河西, 几年前前端需要模板来进行'规则化', 现在前端又想要更多的自由.
开发工程而不是插件的话, 还是不要使用mixin了, 这东西无法追溯来源, 搞得语义太差了, 我们要对它说'no'.
举例子的变量命名有点low, 抱歉~~
<template>
  <div>
    <button @click="addN1">上面的增加</button>---> {{n1}}
  </div>
   <div>
    <button @click="addN2">下面的增加</button>---> {{n2}}
    <button @click="addN210">每次n2+10</button>
  </div>
  <div>
    <p>组件里面也可以如此引用, 这就可以代替mixin一部分功能了</p>
    <button @click="addN3">n3的增加</button>---> {{n3.value}}
  </div>
  <div>
    <com></com>
  </div>
</template>

<script>
import { ref} from 'vue';
import n3Change from './mixin';
import com from '../components/composition.vue';

export default {
  components:{
    com
  },
  setup(){
     // 1: setup只是一个整合函数
     // 2: 甚至整个函数里面可能会没有具体的逻辑
     // 3: 以此推断, ref等方式定义的变量, 会自动识别在哪个setup内部, 从而达到逻辑的复用
     // 4: 由此方法可以很好的代替mixin了
     // 5: 当然, 这里也可以截获数据,来做一些事情
     const {n2, addN2} = n2Change();
     function addN210(){
       n2.value += 10
     }
     return {
       ...n1Change(),
       ...n3Change(),
       n2,
       addN2,
       addN210
     }
  }
}
// 甚至已经可以写在任何地方了, 响应式自由度大大提高
function n1Change(){
   const n1 = ref(1);
   let addN1 = ()=>{
     n1.value++
   }
   return {
     n1,
     addN1
   }
}

function n2Change(){
   const n2 = ref(1);
   let addN2 = ()=>{
     n2.value++
   }
   return {
     n2,
     addN2
   }
}
</script>

写在任何地方, 然后导入就成了mixin


import { reactive } from 'vue';


export default ()=> {
  const n3 = reactive({
    name: 'mixin',
    value: 1
  })
  const addN3=()=>{
    n3.value++
  }
  return {
    n3,
    addN3
  }
}

12. 插件的新思路

// 开发插件并不一定要挂载到vue的原型上
// 导致vue原型臃肿, 命名冲突等等(比如两个ui都叫 message)
// 原理就是 provide 和 inject, 依赖注入.

import {provide, inject} from 'vue';

// 这里使用symbol就不会造成变量名的冲突了, 这个命名权交给用户才是真正合理的架构设计
const StoreSymbol = Symbol()

export function provideString(store){
  provide(StoreSymbol, store)
}

export function useString() {

  const store = inject(StoreSymbol)

  return store
}

app.vue页面统一的初始化一下

export default {
  setup(){
    // 一些初始化'配置/操作'可以在这里进行
    // 需要放在对应的根节点, 因为依赖provide 和 inject
     provideString({
       a:'可能我是axios',
       b:'可能我是一个message弹框'
     })
  }
}

在需要使用的组件里面接收

<template>
  <div>
    插件的演示
  </div>
</template>

<script>
import { useString } from '../插件';

export default {
  setup(){
    const store = useString();
    // 不光是拿到值, 可以由app定义什么可以被拿到
    console.log('拿到值了',store)
  }
}

</script>

13. 新观察者

<template>
  <div>
    <button @click="addn1">n1增加--{{n1}}</button>
    <button @click="addn2">n2增加--{{n2}}</button>
    <button @click="addn3">n3增加--{{n3}}</button>
  </div>
</template>

<script>
import { watch, ref } from "vue";
export default {
  setup() {
    const n1 = ref(1);
    const n2 = ref(1);
    const n3 = ref(1);
    // 1: 监听一个
    // 第一个参数是函数返回值, 当然也可以 直接写n1 
    // 如果监听的是一个对象里面的某个属性, 那就需要这种函数的写法了, 比2.x的字符串写法高明很多

    watch(
      () => n1.value,
      (val, oldVal) => {
        console.log("新值", val);
        console.log("老值", oldVal);
      }
    );
    // 2: 监听多个
    // 数组的形式定义多个, 这就出现问题了吧, 如果我观察的对象就是个数组, 并且每一项都是一个返回值的函数, 岂不是会被他误认为是多监控的结构, 苦恼
    watch(
      [() => n2.value, ()=>n3.value],
      ([val, val3],[val2, val4]) => {
        // val 是 n2的新值   val2是 n2的老值
        // val3 是 n3的新值  val4是 n3的老值
        console.log("新值 与 老值 是这种对应关系", val, val2);
        console.log("新值 与 老值 是这种对应关系", val3, val4);
      }
    );

    function addn1() {
      n1.value++;
    }
    function addn2() {
      n2.value++;
    }
     function addn3() {
      n3.value++;
    }
    return {
      addn1,
      addn2,
      addn3,
      n1,
      n2,
      n3
    };
  }
};
</script>

13. 新计算属性

别看watchEffect带个'watch',但是他的功能可以归为计算属性里面
<template>
  <div>
    <button @click="addCount">点击计算</button>
    <button @click="setCount(1)">点击出发set</button>
    <p>count--{{count}}</p>
    <p>count2--{{count2}}</p>
    <p>count3--{{count3}}</p>
  </div>
</template>

<script>
// 弄得类似react了
import { computed, ref, watchEffect } from "vue";
export default {
  setup() {
    const count = ref(1);
    // 1. 默认的定义方式
    const count2 = computed(() => count.value * 2);
    console.log(count2.value); // 也要value因为可能是简单类型
    // 2. getter与setter当然可以定义
    const count3 = computed({
      get: () => count.value * 3,
      set: val => {
        // 这里重置了count
        count.value = val;
      }
    });
    // 3. watchEffect 更像是计算函数
    // 立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数
    // 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
    // Vue 的响应式系统会缓存副作用函数,并异步地刷新它, 比如同时改变了count与conut4此时watchEffect只是执行一次
    // 初始化运行是在组件 mounted 之前执行的。因此,如果你希望在编写副作用函数时访问 DOM(或模板 ref),请在 onMounted 钩子中进行
    // 并不是返回值, 而是监听里面所有的值, 任何有变化都会重新执行, 他应该可以玩出点东西。
    const count4 = ref(1);
    const stop = watchEffect(() => {
      if (count4.value) {
        console.log("作为判断条件也可以根据count4的变化而重新执行");
      }
      console.log(count.value);
    });
    setTimeout(() => {
      stop(); // 停止监听
    }, 10000);
    function addCount() {
      count.value++;
      setTimeout(() => {
        count4.value++;
      }, 1000);
    }
    // 触发setter
    function setCount() {
      count3.value = 2;
    }
    return {
      count,
      count2,
      addCount,
      count3,
      setCount
    };
  }
};
</script>

14. customRef防抖

当然这里说的'防抖'不是重点, 重点是这种代码的思维
<template>
  <div>
    <input type="text" v-model="text" />
  </div>
</template>

<script>
import { customRef, onUnmounted } from "vue";
export default {
  setup() {
    let timeout = null; // 并不需要响应式
    const text = useDebouncedRef("hello", (time) => {
      // 毕竟是延时的不用担心获取不到这个值
      console.log("延时执行回调", text.value);
      console.log('时间实例', time)
      timeout = time;
    });
    // 好习惯也是成为合格工程师的必要条件
    onUnmounted(()=>{
        clearTimeout(timeout);
    })
    return {
      text
    };
  }
};

// 并不用纯粹的js编写, 可以利用customRef来监控这个值的一举一动
// 写法一般, 但是思路又多了一条, 感谢
function useDebouncedRef(value, callback, delay = 200) {
  let timeout;
  // 两个参数分别是用于追踪的 track 与用于触发响应的 trigger
  // 这两个参数对 值的追踪 在当前并没有用,比如watchEffect的出发机制
  // 不调用这两个值没问题, 但是如果写成插件的话还是要调用的, 因为别人没准在追踪这个值,

  // 注意:  这个函数不可以有太大的delay, 如果超过500的话就需要考虑在组件销毁时候的清除定时器, 反而逻辑加深了, 此时我们可以每次把演示器的实例拿到
  return customRef((track,trigger) => {
    return {
      get() {
        track()
        return value;
      },
      set(newValue) {
        clearTimeout(timeout);
        // callback接受的太晚了, 可以在这里用另一个函数或对象接收
        timeout = setTimeout(() => {
          value = newValue;
          trigger()
          callback(timeout);
        }, delay);
      }
    };
  });
}
</script>

15. 组件与注入

父级

<template>
  <div>
    组件:
    <zj :type="type" @ok="wancheng"></zj>
  </div>
</template>

<script>
import zj from "../components/子组件.vue";
import { ref } from 'vue';
import { provide } from 'vue'

export default {
  components: { 
    zj
  },
  setup() {
    provide('name','向下传值'); // 基础值
    provide('name2', ref('向下传值')); // 监控值
    const type = ref('大多数');

    function wancheng(msg){
      console.log('子组件-->',msg)
      setTimeout(()=>{
        type.value = 'xxxxxxx'
      },2000)
    }
    return {
      type,
      wancheng
    }
  }
};
</script>

子组件

<template>
  <div>props的属性不用setup去return --- {{type}}</div>
</template>

<script>
import { inject, ref } from 'vue'
// 为了让 TypeScript 正确的推导类型,我们必须使用 createComponent 来定义组件:
export default {
  props: {
    type: String
  },
  // 1: props也是不可以解构的, 会失去响应式
  // 2: context是上下文, 我们可以获取到slots emit 等方法
  // 3: props, context 分开也是为了ts更明确的类型推导
  // setup({type}){
  setup(props, context) {
    // 1: props
    console.log("props", props.type);
    console.log("上下文", context);
    context.emit('ok','传递完成')

    // 2: 注入
    console.log('inject',inject('name'));
    console.log('inject',inject('xxxx','我是默认值'))
    inject('name1', ref('默认值')) // 接收方也可以这样
  }
};
</script>

16. 总结

每次看到新技术都会感觉挺好玩的, 一成不变的生活会太无趣了, 在某些方面讲vue失去了一些本来的优势, 但是人家可以兼容vue2.x那就没的说了, 作为分享会的稿子的话时间差不多一个小时, 最好每个点都现场手敲, 光让大家看已经写好的代码会走神的, 我在学习视频的时候最不喜欢的就是老师说"这个我就不演示了".
这次就这么多, 希望和你一起进步.

查看原文

赞 30 收藏 13 评论 0

YuanWing 收藏了文章 · 2019-01-05

js数据结构-二叉树(二叉堆)

二叉树

二叉树(Binary Tree)是一种树形结构,它的特点是每个节点最多只有两个分支节点,一棵二叉树通常由根节点,分支节点,叶子节点组成。而每个分支节点也常常被称作为一棵子树。

图片描述

  • 根节点:二叉树最顶层的节点
  • 分支节点:除了根节点以外且拥有叶子节点
  • 叶子节点:除了自身,没有其他子节点

常用术语
在二叉树中,我们常常还会用父节点和子节点来描述,比如图中2为6和3的父节点,反之6和3是2子节点

二叉树的三个性质

  1. 在二叉树的第i层上,至多有2^i-1个节点

    • i=1时,只有一个根节点,2^(i-1) = 2^0 = 1
  2. 深度为k的二叉树至多有2^k-1个节点

    • i=2时,2^k-1 = 2^2 - 1 = 3个节点
  3. 对任何一棵二叉树T,如果总结点数为n0,度为2(子树数目为2)的节点数为n2,则n0=n2+1

树和二叉树的三个主要差别

  • 树的节点个数至少为1,而二叉树的节点个数可以为0
  • 树中节点的最大度数(节点数量)没有限制,而二叉树的节点的最大度数为2
  • 树的节点没有左右之分,而二叉树的节点有左右之分

二叉树分类

二叉树分为完全二叉树(complete binary tree)和满二叉树(full binary tree)

  • 满二叉树:一棵深度为k且有2^k - 1个节点的二叉树称为满二叉树
  • 完全二叉树:完全二叉树是指最后一层左边是满的,右边可能满也可能不满,然后其余层都是满的二叉树称为完全二叉树(满二叉树也是一种完全二叉树)

图片描述

二叉树的数组表示

用一个数组来表示二叉树的结构,将一组数组从根节点开始从上到下,从左到右依次填入到一棵完全二叉树中,如下图所示

图片描述

通过上图我们可以分析得到数组表示的完全二叉树拥有以下几个性质:

  • left = index * 2 + 1,例如:根节点的下标为0,则左节点的值为下标array[0*2+1]=1
  • right = index * 2 + 2,例如:根节点的下标为0,则右节点的值为下标array[0*2+2]=2
  • 序数 >= floor(N/2)都是叶子节点,例如:floor(9/2) = 4,则从下标4开始的值都为叶子节点

二叉堆

二叉堆由一棵完全二叉树来表示其结构,用一个数组来表示,但一个二叉堆需要满足如下性质:

  • 二叉堆的父节点的键值总是大于或等于(小于或等于)任何一个子节点的键值
  • 当父节点的键值大于或等于(小于或等于)它的每一个子节点的键值时,称为最大堆(最小堆)

图片描述
从上图可以看出:

  • 左图:父节点总是大于或等于其子节点,所以满足了二叉堆的性质,
  • 右图:分支节点7作为2和12的父节点并没有满足其性质(大于或等于子节点)。

二叉堆的主要操作

  • insert:插入节点
  • delete:删除节点
  • max-hepify:调整分支节点堆性质
  • rebuildHeap:重新构建整个二叉堆
  • sort:排序

初始化一个二叉堆

从上面简单的介绍,我们可以知道,一个二叉堆的初始化非常的简单,它就是一个数组

  • 初始化一个数组结构
  • 保存数组长度
    class Heap{
        constructor(arr){
            this.data = [...arr];
            this.size = this.data.length;
        }
    }

max-heapify最大堆操作

max-heapify是把每一个不满足最大堆性质的分支节点进行调整的一个操作。

图片描述

如上图:

  1. 调整分支节点2(分支节点2不满足最大堆的性质)

    • 默认该分支节点为最大值
  2. 将2与左右分支比较,从2,12,5中找出最大值,然后和2交换位置

    • 根据上面所将的二叉堆性质,分别得到分支节点2的左节点和右节点
    • 比较三个节点,得到最大值的下标max
    • 如果该节点本身就是最大值,则停止操作
    • 将max节点与父节点进行交换
  3. 重复step2的操作,从2,4,7中找出最大值与2做交换

    • 递归
    maxHeapify(i) {
        let max = i;
        
        if(i >= this.size){
            return;
        }
        // 当前序号的左节点
        const l = i * 2 + 1;
        // 当前需要的右节点
        const r = i * 2 + 2;
        
        // 求当前节点与其左右节点三者中的最大值
        if(l < this.size && this.data[l] > this.data[max]){
            max = l;
        }
        if(r < this.size && this.data[r] > this.data[max]){
            max = r;
        }
        
        // 最终max节点是其本身,则已经满足最大堆性质,停止操作
        if(max === i) {
            return;
        }
        
        // 父节点与最大值节点做交换
        const t = this.data[i];
        this.data[i] = this.data[max];
        this.data[max] = t;
        
        // 递归向下继续执行
        return this.maxHeapify(max);
    }

重构堆

我们可以看到,刚初始化的堆由数组表示,这个时候它可能并不满足一个最大堆或最小堆的性质,这个时候我们可能需要去将整个堆构建成我们想要的。
上面我们做了max-heapify操作,而max-heapify只是将某一个分支节点进行调整,而要将整个堆构建成最大堆,则需要将所有的分支节点都进行一次max-heapify操作,如下图,我们需要依次对12,3,2,15这4个分支节点进行max-hepify操作

图片描述

具体步骤:

  • 找到所有分支节点:上面堆的性质提到过叶子节点的序号>=Math.floor(n/2),因此小于Math.floor(n/2)序号的都是我们需要调整的节点。

    • 例如途中所示数组为[15,2,3,12,5,2,8,4,7] => Math.floor(9/2)=4 => index小于4的分别是15,2,3,12(需要调整的节点),而5,2,8,4,7为叶子节点。
  • 将找到的节点都进行maxHeapify操作
    rebuildHeap(){
        // 叶子节点
        const L = Math.floor(this.size / 2);
        for(let i = L - 1; i>=0; i--){
            this,maxHeapify(i);
        }
    }

最大堆排序

图片描述

最大堆的排序,如上图所示:

  • 交换首尾位置
  • 将最后个元素从堆中拿出,相当于堆的size-1
  • 然后在堆根节点进行一次max-heapify操作
  • 重复以上三个步骤,知道size=0 (这个边界条件我们在max-heapify函数里已经做了)
    sort() {
        for(let i = this.size - 1; i > 0; i--){
            swap(this.data, 0, i);
            this.size--;
            this.maxHeapify(0);
        }
    }

插入和删除

这个的插入和删除就相对比较简单了,就是对一个数组进行插入和删除的操作

  • 往末尾插入
  • 堆长度+1
  • 判断插入后是否还是一个最大堆
  • 不是则进行重构堆
  insert(key) {
    this.data[this.size] = key;
    this.size++
    if (this.isHeap()) {
      return;
    }
    this.rebuildHeap();
  }
  • 删除数组中的某个元素
  • 堆长度-1
  • 判断是否是一个堆
  • 不是则重构堆
  delete(index) {
    if (index >= this.size) {
      return;
    }
    this.data.splice(index, 1);
    this.size--;
    if (this.isHeap()) {
      return;
    }
    this.rebuildHeap();
  }

完整代码

/**
 * 最大堆
 */

function left(i) {
  return i * 2 + 1;
}

function right(i) {
  return i * 2 + 2;
}

function swap(A, i, j) {
  const t = A[i];
  A[i] = A[j];
  A[j] = t;
}

class Heap {
  constructor(arr) {
    this.data = [...arr];
    this.size = this.data.length;
  }

  /**
   * 重构堆
   */
  rebuildHeap() {
    const L = Math.floor(this.size / 2);
    for (let i = L - 1; i >= 0; i--) {
      this.maxHeapify(i);
    }
  }

  isHeap() {
    const L = Math.floor(this.size / 2);
    for (let i = L - 1; i >= 0; i++) {
      const l = this.data[left(i)] || Number.MIN_SAFE_INTEGER;
      const r = this.data[right(i)] || Number.MIN_SAFE_INTEGER;

      const max = Math.max(this.data[i], l, r);

      if (max !== this.data[i]) {
        return false;
      }
      return true;
    }
  }

  sort() {
    for (let i = this.size - 1; i > 0; i--) {
      swap(this.data, 0, i);
      this.size--;
      this.maxHeapify(0);
    }
  }

  insert(key) {
    this.data[this.size++] = key;
    if (this.isHeap()) {
      return;
    }
    this.rebuildHeap();
  }

  delete(index) {
    if (index >= this.size) {
      return;
    }
    this.data.splice(index, 1);
    this.size--;
    if (this.isHeap()) {
      return;
    }
    this.rebuildHeap();
  }

  /**
   * 堆的其他地方都满足性质
   * 唯独跟节点,重构堆性质
   * @param {*} i
   */
  maxHeapify(i) {
    let max = i;

    if (i >= this.size) {
      return;
    }

    // 求左右节点中较大的序号
    const l = left(i);
    const r = right(i);
    if (l < this.size && this.data[l] > this.data[max]) {
      max = l;
    }

    if (r < this.size && this.data[r] > this.data[max]) {
      max = r;
    }

    // 如果当前节点最大,已经是最大堆
    if (max === i) {
      return;
    }

    swap(this.data, i, max);

    // 递归向下继续执行
    return this.maxHeapify(max);
  }
}

module.exports = Heap;

总结

堆讲到这里就结束了,堆在二叉树里相对会比较简单,常常被用来做排序和优先级队列等。堆中比较核心的还是max-heapify这个操作,以及堆的三个性质。

后续

下一篇应该会介绍二叉搜索树。欢迎大家指出文章的错误,如果有什么写作建议也可以提出。我会持续的去写关于前端的一些技术文章,如果大家喜欢的话可以关注一和点个赞,你的赞是我写作的动力。
顺便再提一下,我在等第一个粉丝哈哈

以下个人公众号,欢迎大家关注,用户量达到一定的量,我会推出一些前端教学视频
图片描述

查看原文

YuanWing 赞了文章 · 2019-01-05

js数据结构-二叉树(二叉堆)

二叉树

二叉树(Binary Tree)是一种树形结构,它的特点是每个节点最多只有两个分支节点,一棵二叉树通常由根节点,分支节点,叶子节点组成。而每个分支节点也常常被称作为一棵子树。

图片描述

  • 根节点:二叉树最顶层的节点
  • 分支节点:除了根节点以外且拥有叶子节点
  • 叶子节点:除了自身,没有其他子节点

常用术语
在二叉树中,我们常常还会用父节点和子节点来描述,比如图中2为6和3的父节点,反之6和3是2子节点

二叉树的三个性质

  1. 在二叉树的第i层上,至多有2^i-1个节点

    • i=1时,只有一个根节点,2^(i-1) = 2^0 = 1
  2. 深度为k的二叉树至多有2^k-1个节点

    • i=2时,2^k-1 = 2^2 - 1 = 3个节点
  3. 对任何一棵二叉树T,如果总结点数为n0,度为2(子树数目为2)的节点数为n2,则n0=n2+1

树和二叉树的三个主要差别

  • 树的节点个数至少为1,而二叉树的节点个数可以为0
  • 树中节点的最大度数(节点数量)没有限制,而二叉树的节点的最大度数为2
  • 树的节点没有左右之分,而二叉树的节点有左右之分

二叉树分类

二叉树分为完全二叉树(complete binary tree)和满二叉树(full binary tree)

  • 满二叉树:一棵深度为k且有2^k - 1个节点的二叉树称为满二叉树
  • 完全二叉树:完全二叉树是指最后一层左边是满的,右边可能满也可能不满,然后其余层都是满的二叉树称为完全二叉树(满二叉树也是一种完全二叉树)

图片描述

二叉树的数组表示

用一个数组来表示二叉树的结构,将一组数组从根节点开始从上到下,从左到右依次填入到一棵完全二叉树中,如下图所示

图片描述

通过上图我们可以分析得到数组表示的完全二叉树拥有以下几个性质:

  • left = index * 2 + 1,例如:根节点的下标为0,则左节点的值为下标array[0*2+1]=1
  • right = index * 2 + 2,例如:根节点的下标为0,则右节点的值为下标array[0*2+2]=2
  • 序数 >= floor(N/2)都是叶子节点,例如:floor(9/2) = 4,则从下标4开始的值都为叶子节点

二叉堆

二叉堆由一棵完全二叉树来表示其结构,用一个数组来表示,但一个二叉堆需要满足如下性质:

  • 二叉堆的父节点的键值总是大于或等于(小于或等于)任何一个子节点的键值
  • 当父节点的键值大于或等于(小于或等于)它的每一个子节点的键值时,称为最大堆(最小堆)

图片描述
从上图可以看出:

  • 左图:父节点总是大于或等于其子节点,所以满足了二叉堆的性质,
  • 右图:分支节点7作为2和12的父节点并没有满足其性质(大于或等于子节点)。

二叉堆的主要操作

  • insert:插入节点
  • delete:删除节点
  • max-hepify:调整分支节点堆性质
  • rebuildHeap:重新构建整个二叉堆
  • sort:排序

初始化一个二叉堆

从上面简单的介绍,我们可以知道,一个二叉堆的初始化非常的简单,它就是一个数组

  • 初始化一个数组结构
  • 保存数组长度
    class Heap{
        constructor(arr){
            this.data = [...arr];
            this.size = this.data.length;
        }
    }

max-heapify最大堆操作

max-heapify是把每一个不满足最大堆性质的分支节点进行调整的一个操作。

图片描述

如上图:

  1. 调整分支节点2(分支节点2不满足最大堆的性质)

    • 默认该分支节点为最大值
  2. 将2与左右分支比较,从2,12,5中找出最大值,然后和2交换位置

    • 根据上面所将的二叉堆性质,分别得到分支节点2的左节点和右节点
    • 比较三个节点,得到最大值的下标max
    • 如果该节点本身就是最大值,则停止操作
    • 将max节点与父节点进行交换
  3. 重复step2的操作,从2,4,7中找出最大值与2做交换

    • 递归
    maxHeapify(i) {
        let max = i;
        
        if(i >= this.size){
            return;
        }
        // 当前序号的左节点
        const l = i * 2 + 1;
        // 当前需要的右节点
        const r = i * 2 + 2;
        
        // 求当前节点与其左右节点三者中的最大值
        if(l < this.size && this.data[l] > this.data[max]){
            max = l;
        }
        if(r < this.size && this.data[r] > this.data[max]){
            max = r;
        }
        
        // 最终max节点是其本身,则已经满足最大堆性质,停止操作
        if(max === i) {
            return;
        }
        
        // 父节点与最大值节点做交换
        const t = this.data[i];
        this.data[i] = this.data[max];
        this.data[max] = t;
        
        // 递归向下继续执行
        return this.maxHeapify(max);
    }

重构堆

我们可以看到,刚初始化的堆由数组表示,这个时候它可能并不满足一个最大堆或最小堆的性质,这个时候我们可能需要去将整个堆构建成我们想要的。
上面我们做了max-heapify操作,而max-heapify只是将某一个分支节点进行调整,而要将整个堆构建成最大堆,则需要将所有的分支节点都进行一次max-heapify操作,如下图,我们需要依次对12,3,2,15这4个分支节点进行max-hepify操作

图片描述

具体步骤:

  • 找到所有分支节点:上面堆的性质提到过叶子节点的序号>=Math.floor(n/2),因此小于Math.floor(n/2)序号的都是我们需要调整的节点。

    • 例如途中所示数组为[15,2,3,12,5,2,8,4,7] => Math.floor(9/2)=4 => index小于4的分别是15,2,3,12(需要调整的节点),而5,2,8,4,7为叶子节点。
  • 将找到的节点都进行maxHeapify操作
    rebuildHeap(){
        // 叶子节点
        const L = Math.floor(this.size / 2);
        for(let i = L - 1; i>=0; i--){
            this,maxHeapify(i);
        }
    }

最大堆排序

图片描述

最大堆的排序,如上图所示:

  • 交换首尾位置
  • 将最后个元素从堆中拿出,相当于堆的size-1
  • 然后在堆根节点进行一次max-heapify操作
  • 重复以上三个步骤,知道size=0 (这个边界条件我们在max-heapify函数里已经做了)
    sort() {
        for(let i = this.size - 1; i > 0; i--){
            swap(this.data, 0, i);
            this.size--;
            this.maxHeapify(0);
        }
    }

插入和删除

这个的插入和删除就相对比较简单了,就是对一个数组进行插入和删除的操作

  • 往末尾插入
  • 堆长度+1
  • 判断插入后是否还是一个最大堆
  • 不是则进行重构堆
  insert(key) {
    this.data[this.size] = key;
    this.size++
    if (this.isHeap()) {
      return;
    }
    this.rebuildHeap();
  }
  • 删除数组中的某个元素
  • 堆长度-1
  • 判断是否是一个堆
  • 不是则重构堆
  delete(index) {
    if (index >= this.size) {
      return;
    }
    this.data.splice(index, 1);
    this.size--;
    if (this.isHeap()) {
      return;
    }
    this.rebuildHeap();
  }

完整代码

/**
 * 最大堆
 */

function left(i) {
  return i * 2 + 1;
}

function right(i) {
  return i * 2 + 2;
}

function swap(A, i, j) {
  const t = A[i];
  A[i] = A[j];
  A[j] = t;
}

class Heap {
  constructor(arr) {
    this.data = [...arr];
    this.size = this.data.length;
  }

  /**
   * 重构堆
   */
  rebuildHeap() {
    const L = Math.floor(this.size / 2);
    for (let i = L - 1; i >= 0; i--) {
      this.maxHeapify(i);
    }
  }

  isHeap() {
    const L = Math.floor(this.size / 2);
    for (let i = L - 1; i >= 0; i++) {
      const l = this.data[left(i)] || Number.MIN_SAFE_INTEGER;
      const r = this.data[right(i)] || Number.MIN_SAFE_INTEGER;

      const max = Math.max(this.data[i], l, r);

      if (max !== this.data[i]) {
        return false;
      }
      return true;
    }
  }

  sort() {
    for (let i = this.size - 1; i > 0; i--) {
      swap(this.data, 0, i);
      this.size--;
      this.maxHeapify(0);
    }
  }

  insert(key) {
    this.data[this.size++] = key;
    if (this.isHeap()) {
      return;
    }
    this.rebuildHeap();
  }

  delete(index) {
    if (index >= this.size) {
      return;
    }
    this.data.splice(index, 1);
    this.size--;
    if (this.isHeap()) {
      return;
    }
    this.rebuildHeap();
  }

  /**
   * 堆的其他地方都满足性质
   * 唯独跟节点,重构堆性质
   * @param {*} i
   */
  maxHeapify(i) {
    let max = i;

    if (i >= this.size) {
      return;
    }

    // 求左右节点中较大的序号
    const l = left(i);
    const r = right(i);
    if (l < this.size && this.data[l] > this.data[max]) {
      max = l;
    }

    if (r < this.size && this.data[r] > this.data[max]) {
      max = r;
    }

    // 如果当前节点最大,已经是最大堆
    if (max === i) {
      return;
    }

    swap(this.data, i, max);

    // 递归向下继续执行
    return this.maxHeapify(max);
  }
}

module.exports = Heap;

总结

堆讲到这里就结束了,堆在二叉树里相对会比较简单,常常被用来做排序和优先级队列等。堆中比较核心的还是max-heapify这个操作,以及堆的三个性质。

后续

下一篇应该会介绍二叉搜索树。欢迎大家指出文章的错误,如果有什么写作建议也可以提出。我会持续的去写关于前端的一些技术文章,如果大家喜欢的话可以关注一和点个赞,你的赞是我写作的动力。
顺便再提一下,我在等第一个粉丝哈哈

以下个人公众号,欢迎大家关注,用户量达到一定的量,我会推出一些前端教学视频
图片描述

查看原文

赞 52 收藏 40 评论 7

YuanWing 发布了文章 · 2018-12-11

iOS11.3以下modal中input光标错位

先看一下错位的效果:
图片描述

这本是iOS系统的一个BUG,不过在iOS11.3以后的系统中修复了;

解决办法:
body标签中添加样式:

body {
  position: fixed;
  width: 100%;
}

或者

body {
  overflow: hidden;
  height: 100%;
}

图片来源于:https://hackernoon.com/how-to...

查看原文

赞 3 收藏 2 评论 1

YuanWing 评论了文章 · 2018-11-28

使用crypto-js进行128位AES/ECB/PKCS7Padding加密/解密

crypto-js支持多种加/解密方案, 这里主要记录一下使用 crypto-js 进行 AES 128位 的加/解密;

前端加密是不安全的, 不安全的, 不安全的;

// 初始化一个 package.json 文件, 直接全部回车就行啦;
$ yarn init

// 安装 crypto-js;
$ yarn add crypto-js

package.json 同级目录下新建一个 crypto.js 文件, 写入以下内容:

// 导入 crypto-js 包
const CryptoJS = require('crypto-js');
// 定义加/解密的 key(key都放这里了, 加密还有啥意义!^_^)
const initKey = '123!@#';
// 设置数据块长度
const keySize = 128;

/**
 * 生成密钥字节数组, 原始密钥字符串不足128位, 补填0.
 * @param {string} key - 原始 key 值
 * @return Buffer
 */
const fillKey = (key) => {
  const filledKey = Buffer.alloc(keySize / 8);
  const keys = Buffer.from(key);
  if (keys.length < filledKey.length) {
    filledKey.map((b, i) => filledKey[i] = keys[i]);
  }

  return filledKey;
}

/**
 * 定义加密函数
 * @param {string} data - 需要加密的数据, 传过来前先进行 JSON.stringify(data);
 * @param {string} key - 加密使用的 key
 */
const aesEncrypt = (data, key) => {
  /**
   * CipherOption, 加密的一些选项:
   *   mode: 加密模式, 可取值(CBC, CFB, CTR, CTRGladman, OFB, ECB), 都在 CryptoJS.mode 对象下
   *   padding: 填充方式, 可取值(Pkcs7, AnsiX923, Iso10126, Iso97971, ZeroPadding, NoPadding), 都在 CryptoJS.pad 对象下
   *   iv: 偏移量, mode === ECB 时, 不需要 iv
   * 返回的是一个加密对象
   */
  const cipher = CryptoJS.AES.encrypt(data, key, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7,
    iv: '',
  });
  // 将加密后的数据转换成 Base64
  const base64Cipher = cipher.ciphertext.toString(CryptoJS.enc.Base64);
  // 处理 Android 某些低版的BUG
  const resultCipher = base64Cipher.replace(/\+/g,'-').replace(/\//g,'_');
  // 返回加密后的经过处理的 Base64
  return resultCipher;
}

/**
 * 定义解密函数
 * @param {string} encrypted - 加密的数据;
 * @param {string} key - 加密使用的 key
 */
const aesDecrypt = (encrypted, key) => {
  // 先将 Base64 还原一下, 因为加密的时候做了一些字符的替换
  const restoreBase64 = encrypted.replace(/\-/g,'+').replace(/_/g,'/');
  // 这里 mode, padding, iv 一定要跟加密的时候完全一样
  // 返回的是一个解密后的对象
  const decipher = CryptoJS.AES.decrypt(restoreBase64, key, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7,
    iv: '',
  });
  // 将解密对象转换成 UTF8 的字符串
  const resultDecipher = CryptoJS.enc.Utf8.stringify(decipher);
  // 返回解密结果
  return resultDecipher;
}
// 获取填充后的key
const key = CryptoJS.enc.Utf8.parse(fillKey(initKey));

// 定义需要加密的数据
const data = {"password":"qwe123!@#","userName":"wing@email.com"};
// 调用加密函数
const encrypted = aesEncrypt(JSON.stringify(data), key);
// 调用解密函数
const decrypted = aesDecrypt(encrypted, key);
// 控制台输出查看结果
console.log('加密结果: ', encrypted);
console.log('解密结果: ', decrypted);

最后可以在 node 环境下运行查看一下结果:

$ node crypto.js
加密结果:  GFkA5wmbOgi47TX8lfhAsACwLbFnhUByAfB2Xe3iuOl0DN6pk-EOM9mqHPoU9oo-mIEzQDhCr0_jPtnhKCPRfg==
解密结果:  {"password":"qwe123!@#","userName":"wing@email.com"}

为了验证结果是否正确, 可以去网上找一个AES加/解密的工具对比一下;
以下是在网上找的工具加密的结果:
图片描述

查看原文

YuanWing 收藏了文章 · 2018-07-19

Vue2 利用 v-model 实现组件props双向绑定的优美解决方案

在项目中开始使用vue2来构建项目了,跟 vue1 很大的一处不同在于2 取消了props 的双向绑定,改成只能从父级传到子级的单向数据流,初衷当然是好的,为了避免双向绑定在项目中容易造成的数据混乱。

解决方案一

然后开始参考网上和github上的方案等等,发现很多解决方案是这样的

  1. 用data对象中创建一个props属性的副本

  2. watch props属性 赋予data副本 来同步组件外对props的修改

  3. watch data副本,emit一个函数 通知到组件外

这里以最常见的 modal为例子:
modal挺合适双向绑定的,外部可以控制组件的 显示或者隐藏,组件内部的关闭可以控制 visible属性隐藏,同时visible 属性同步传输到外部


///modal.vue  组件
<template>
  <div class="modal" v-show="visible">
      <div class="close" @click="cancel">X</div>
  </div>
</template>

<script>
export default {
    name:'modal',
    props: {
      value: {
        type: Boolean,
        default:false
      }
    },

  data () {
    return {
      visible:false
    }
  },
  watch:{
      value(val) {
        console.log(val);
        this.visible = val;
      },
      visible(val) {
        this.$emit("visible-change",val);
      }
  },
  methods:{
    cancel(){
      this.visible = false;
    }
  },
  mounted() {
    if (this.value) {
      this.visible = true;
    }
  }
}
</script>


///调用modal组件
<modal :value="isShow" @visible-change="modalVisibleChange"></modal>

export default {
  name: 'app',
  data () {
    return {
      isShow:true,
    }
  },
  methods:{
     modalVisibleChange(val){
       this.isShow = val;
     }
  }
}

这样就解决了 组件props 双向绑定的问题。 但是这样有一个不是太美观的现象就是 在父级调用 modal组件的时候,还需要写一个 modalVisibleChange 的methods. 总是显得这部分代码是多余的。 特别是写一个让别人用的公共组件,这样调用太麻烦了。
能不能不写method来实现props的双向绑定呢,答案是可以的。

优美解决方案

那就是利用 v-model, 然后使用value来保存v-model的值,进行双向绑定

改成如下代码:

<template>
  <div class="modal" :value="value" v-show="visible">
      <div class="close" @click="cancel">X</div>
  </div>
</template>

<script>
export default {
    props: {
      value: {
        type: Boolean,
        default:false
      }
    },

  data () {
    return {
      visible:false
    }
  },
  watch:{
      value(val) {
        console.log(val);
        this.visible = val;
      },
      visible(val) {
        this.$emit('input', val);
      }
  },
  methods:{
    cancel(){
      this.visible = false;
    }
  },
  mounted() {
    if (this.value) {
      this.visible = true;
    }
  }
}
</script>


///调用modal组件

  <modal v-model="isShow"></modal>

export default {
  name: 'app',
  data () {
    return {
      isShow:false
    }
  }
}
</script>

这样调用组件的代码是不是很简洁,其他人员要调用的话,会很轻松,只要设置 isShow 就可以控制 modal 组件的显示或者隐藏,同时 如果是modal 组件内部关闭按钮关闭的,状态也会传到 isShow

查看原文

YuanWing 赞了文章 · 2018-07-19

Vue2 利用 v-model 实现组件props双向绑定的优美解决方案

在项目中开始使用vue2来构建项目了,跟 vue1 很大的一处不同在于2 取消了props 的双向绑定,改成只能从父级传到子级的单向数据流,初衷当然是好的,为了避免双向绑定在项目中容易造成的数据混乱。

解决方案一

然后开始参考网上和github上的方案等等,发现很多解决方案是这样的

  1. 用data对象中创建一个props属性的副本

  2. watch props属性 赋予data副本 来同步组件外对props的修改

  3. watch data副本,emit一个函数 通知到组件外

这里以最常见的 modal为例子:
modal挺合适双向绑定的,外部可以控制组件的 显示或者隐藏,组件内部的关闭可以控制 visible属性隐藏,同时visible 属性同步传输到外部


///modal.vue  组件
<template>
  <div class="modal" v-show="visible">
      <div class="close" @click="cancel">X</div>
  </div>
</template>

<script>
export default {
    name:'modal',
    props: {
      value: {
        type: Boolean,
        default:false
      }
    },

  data () {
    return {
      visible:false
    }
  },
  watch:{
      value(val) {
        console.log(val);
        this.visible = val;
      },
      visible(val) {
        this.$emit("visible-change",val);
      }
  },
  methods:{
    cancel(){
      this.visible = false;
    }
  },
  mounted() {
    if (this.value) {
      this.visible = true;
    }
  }
}
</script>


///调用modal组件
<modal :value="isShow" @visible-change="modalVisibleChange"></modal>

export default {
  name: 'app',
  data () {
    return {
      isShow:true,
    }
  },
  methods:{
     modalVisibleChange(val){
       this.isShow = val;
     }
  }
}

这样就解决了 组件props 双向绑定的问题。 但是这样有一个不是太美观的现象就是 在父级调用 modal组件的时候,还需要写一个 modalVisibleChange 的methods. 总是显得这部分代码是多余的。 特别是写一个让别人用的公共组件,这样调用太麻烦了。
能不能不写method来实现props的双向绑定呢,答案是可以的。

优美解决方案

那就是利用 v-model, 然后使用value来保存v-model的值,进行双向绑定

改成如下代码:

<template>
  <div class="modal" :value="value" v-show="visible">
      <div class="close" @click="cancel">X</div>
  </div>
</template>

<script>
export default {
    props: {
      value: {
        type: Boolean,
        default:false
      }
    },

  data () {
    return {
      visible:false
    }
  },
  watch:{
      value(val) {
        console.log(val);
        this.visible = val;
      },
      visible(val) {
        this.$emit('input', val);
      }
  },
  methods:{
    cancel(){
      this.visible = false;
    }
  },
  mounted() {
    if (this.value) {
      this.visible = true;
    }
  }
}
</script>


///调用modal组件

  <modal v-model="isShow"></modal>

export default {
  name: 'app',
  data () {
    return {
      isShow:false
    }
  }
}
</script>

这样调用组件的代码是不是很简洁,其他人员要调用的话,会很轻松,只要设置 isShow 就可以控制 modal 组件的显示或者隐藏,同时 如果是modal 组件内部关闭按钮关闭的,状态也会传到 isShow

查看原文

赞 25 收藏 39 评论 17

YuanWing 收藏了文章 · 2018-07-12

插件 html-webpack-plugin 的详解

最近在学习webpack,接触到的第一个插件就是html-webpack-plugin,那么今天就来详解一下它的用法吧。

  • 先来上一个例子:
var htmlWebpackPlugin = require('html-webpack-plugin')

const path = require('path')
module.exports = {
    entry: './src/script/main.js',
    output: {
        filename: 'js/bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new htmlWebpackPlugin({
            filename: 'index.html',
            template: 'index.html',
            inject: 'head'
        })
    ]
}

配置属性

title

生成html文件的标题

filename

就是html文件的文件名,默认是index.html

template

指定你生成的文件所依赖哪一个html文件模板,模板类型可以是html、jade、ejs等。但是要注意的是,如果想使用自定义的模板文件的时候,你需要安装对应的loader哦。

举例子:

$ npm install jade-loader --save-dev
// webpack.config.js
...
loaders: {
    ...
    {
        test: /\.jade$/,
        loader: 'jade'
    }
}
plugins: [
    new HtmlWebpackPlugin({
        ...
        jade: 'path/to/yourfile.jade'
    })
]

如果你设置的 titlefilename于模板中发生了冲突,那么以你的titlefilename 的配置值为准。

inject

inject有四个值: truebodyheadfalse

true 默认值,script标签位于html文件的 body 底部
body script标签位于html文件的 body 底部
head script标签位于html文件的 head中
false 不插入生成的js文件,这个几乎不会用到的

favicon

给你生成的html文件生成一个 favicon ,值是一个路径

plugins: [
    new HtmlWebpackPlugin({
        ...
        favicon: 'path/to/my_favicon.ico'
    }) 

然后再生成的html中就有了一个 link 标签

<link rel="shortcut icon" href="example.ico">

minify

使用minify会对生成的html文件进行压缩。默认是false。html-webpack-plugin内部集成了 html-minifier,因此,还可以对minify进行配置:(注意,虽然minify支持BooleanObject,但是不能直接这样写:minify: true , 这样会报错 ERROR in TypeError: Cannot use 'in' operator to search for 'html5' in true , 使用时候必须给定一个 { } 对象 )

...
plugins: [
    new HtmlWebpackPlugin({
        ...
        minify: {
            removeAttributeQuotes: true // 移除属性的引号
        }
    })
]

cache

默认是true的,表示内容变化的时候生成一个新的文件。

showErrors

当webpack报错的时候,会把错误信息包裹再一个pre中,默认是true。

chunks

chunks主要用于多入口文件,当你有多个入口文件,那就回编译后生成多个打包后的文件,那么chunks 就能选择你要使用那些js文件

entry: {
    index: path.resolve(__dirname, './src/index.js'),
    devor: path.resolve(__dirname, './src/devor.js'),
    main: path.resolve(__dirname, './src/main.js')
}

plugins: [
    new httpWebpackPlugin({
        chunks: ['index','main']
    })
]

那么编译后:

<script type=text/javascript data-original="index.js"></script>
<script type=text/javascript data-original="main.js"></script>
  • 如果你没有设置chunks选项,那么默认是全部显示

excludeChunks

排除掉一些js

excludeChunks: ['devor.js']
// 等价于上面的

xhtml

一个布尔值,默认值是 false ,如果为 true ,则以兼容 xhtml 的模式引用文件。

chunksSortMode

script的顺序,默认四个选项: noneautodependency{function}

'dependency' 不用说,按照不同文件的依赖关系来排序。

'auto' 默认值,插件的内置的排序方式,具体顺序....

'none' 无序?

{function} 提供一个函数?

查看原文

YuanWing 赞了文章 · 2018-07-12

插件 html-webpack-plugin 的详解

最近在学习webpack,接触到的第一个插件就是html-webpack-plugin,那么今天就来详解一下它的用法吧。

  • 先来上一个例子:
var htmlWebpackPlugin = require('html-webpack-plugin')

const path = require('path')
module.exports = {
    entry: './src/script/main.js',
    output: {
        filename: 'js/bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    plugins: [
        new htmlWebpackPlugin({
            filename: 'index.html',
            template: 'index.html',
            inject: 'head'
        })
    ]
}

配置属性

title

生成html文件的标题

filename

就是html文件的文件名,默认是index.html

template

指定你生成的文件所依赖哪一个html文件模板,模板类型可以是html、jade、ejs等。但是要注意的是,如果想使用自定义的模板文件的时候,你需要安装对应的loader哦。

举例子:

$ npm install jade-loader --save-dev
// webpack.config.js
...
loaders: {
    ...
    {
        test: /\.jade$/,
        loader: 'jade'
    }
}
plugins: [
    new HtmlWebpackPlugin({
        ...
        jade: 'path/to/yourfile.jade'
    })
]

如果你设置的 titlefilename于模板中发生了冲突,那么以你的titlefilename 的配置值为准。

inject

inject有四个值: truebodyheadfalse

true 默认值,script标签位于html文件的 body 底部
body script标签位于html文件的 body 底部
head script标签位于html文件的 head中
false 不插入生成的js文件,这个几乎不会用到的

favicon

给你生成的html文件生成一个 favicon ,值是一个路径

plugins: [
    new HtmlWebpackPlugin({
        ...
        favicon: 'path/to/my_favicon.ico'
    }) 

然后再生成的html中就有了一个 link 标签

<link rel="shortcut icon" href="example.ico">

minify

使用minify会对生成的html文件进行压缩。默认是false。html-webpack-plugin内部集成了 html-minifier,因此,还可以对minify进行配置:(注意,虽然minify支持BooleanObject,但是不能直接这样写:minify: true , 这样会报错 ERROR in TypeError: Cannot use 'in' operator to search for 'html5' in true , 使用时候必须给定一个 { } 对象 )

...
plugins: [
    new HtmlWebpackPlugin({
        ...
        minify: {
            removeAttributeQuotes: true // 移除属性的引号
        }
    })
]

cache

默认是true的,表示内容变化的时候生成一个新的文件。

showErrors

当webpack报错的时候,会把错误信息包裹再一个pre中,默认是true。

chunks

chunks主要用于多入口文件,当你有多个入口文件,那就回编译后生成多个打包后的文件,那么chunks 就能选择你要使用那些js文件

entry: {
    index: path.resolve(__dirname, './src/index.js'),
    devor: path.resolve(__dirname, './src/devor.js'),
    main: path.resolve(__dirname, './src/main.js')
}

plugins: [
    new httpWebpackPlugin({
        chunks: ['index','main']
    })
]

那么编译后:

<script type=text/javascript data-original="index.js"></script>
<script type=text/javascript data-original="main.js"></script>
  • 如果你没有设置chunks选项,那么默认是全部显示

excludeChunks

排除掉一些js

excludeChunks: ['devor.js']
// 等价于上面的

xhtml

一个布尔值,默认值是 false ,如果为 true ,则以兼容 xhtml 的模式引用文件。

chunksSortMode

script的顺序,默认四个选项: noneautodependency{function}

'dependency' 不用说,按照不同文件的依赖关系来排序。

'auto' 默认值,插件的内置的排序方式,具体顺序....

'none' 无序?

{function} 提供一个函数?

查看原文

赞 81 收藏 75 评论 3

YuanWing 回答了问题 · 2018-06-28

nuxt generate 打包生成相对路径

这个我解决了,自己用node写了个中间件,然后把dist目录的执行了一下替换操作。

今天干了个npm包,可以去试一下,可以把nuxt generate生成的静态文件里面的静态资源替换成相对文件nuxt-relative-dir

关注 2 回答 2

认证与成就

  • 获得 229 次点赞
  • 获得 96 枚徽章 获得 9 枚金徽章, 获得 36 枚银徽章, 获得 51 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-07-25
个人主页被 1.6k 人浏览