# 2018年终总结-年底了，你总结了吗？我先来

## 1、小白的常见概念混淆与常用技巧

**1）字面量与构建函数**

字面量分为： 对象字面、数组字面量、字符串字面量

new Object()则是构造函数\
new Array()构造数组

先说下优缺点

1. 对象字面量的声明比构建函数更加方便
2. 空的对象实质上只是满足以下条件的数据结构：
   * **proto**属性指向 Object.prototype
   * 其成员列表指向一个空壳

```js
var obj3 = new Object();
var num = 0;
for (var i in obj3) {
  num++;
}
console.log(num); //0
```

对象的成员的存取遵循两个规则：1、在读取时，该成员表上的属性和方法将会被优先访问到；\
2、如果成员表中没有指定的属性，那么会查询对象的整个原型链，直到找到该属性或者原型链的顶部。\
所以存取实例中的属性比存取原型中的属性效率要高。我们应该采用字面量语法创建对象

```js
var Student = function () {
  this.name = "default";
  this.say = function () {
    console.log("say hello!");
  };
};
var s1 = new Student();
console.log(window.name);
```

new 的话，对于 student 内部的 this 便指向了全局对象,没有定义的话.

所以在这里简单总结下构造函数、原型、隐式原型和实例的关系：每个构造函数都有一个原型属性(prototype)，该属性指向构造函数的原型对象；而实例对象有一个隐式原型属性(**proto**)，其指向构造函数的原型对象(obj.**proto**==Object.prototype)；同时实例对象的原型对象中有一个 constructor 属性，其指向构造函数。

**2）this 在 call 与 apply 与 bind 中**

fn.call(asThis, p1, p2)是函数的正常调用方式，当你不确定参数的个数时，就使用 apply\
fn.apply(asThis, params)

bind\
call 和 apply 是直接调用函数，而 bind 则是返回一个新函数(并没用调用原来的函数)，这个新函数会 call 原来的函数，call 的参数由你指定。

**3）map 的使用技巧**

```js
// 使用 Map 来找到对应颜色的水果
const fruitColor = newMap()
  .set("red", ["apple", "strawberry"])
  .set("yellow", ["banana", "pineapple"])
  .set("purple", ["grape", "plum"]);
function test(color) {
  return fruitColor.get(color) || [];
}
```

**4）typeof 数组或者 null 判断出来的类型值是 object**

**5）svg 的书写方式**

```javascript
<svg class="icon"><use :xlink:href="`#i-${icon}`"></use></svg>
```

**debounce 与 throttle**

* debounce 定义：当调用动作 n 毫秒之后，才会执行该动作，若在这 n 秒之后又调用此动作则将重新计算执行时间
* throttle 定义：节流无视后来产生的回调请求。

```js
function createDebounce(callback, time) {
  var timer;
  time = time || 300; //默认值

  return function () {
    if (timer) {
      clearTimeout(timer);
    }

    timer = setTimeout(() => {
      callback();
    }, time);
  };
}
```

```js
// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function throttle(fn, interval) {
  // last为上一次触发回调的时间
  let last = 0;

  // 将throttle处理结果当作函数返回
  return function () {
    let context = this;
    // 保留调用时传入的参数
    let args = arguments;
    // 记录本次触发回调的时间
    let now = +new Date();

    //时间差
    if (now - last >= interval) {
      last = now;
      fn.apply(context, args);
    }
  };
}
```

**7）if 判断的一些坑**

诸如  if  语句之类的条件语句使用  ToBoolean  抽象方法来强制求值它们的表达式，并始终遵循以下简单规则：

* Objects  求值为  true
* Undefined  求值为  false
* Null  求值为  false
* Booleans  求值为   布尔值
* Numbers  如果是  +0、-0、或 NaN  求值为  false ， 否则为  true
* Strings  如果是空字符串  ''  求值为  false ， 否则为  true

```js
//解释一下
if ([0] && []) {
  // true
  // 一个数组 (即使是一个空数组) 是一个 object, objects 被求值为 true
}
```

**8）深拷贝与浅拷贝**

1. 浅拷贝

* 展开操作符...
* Object.assign

```js
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };

var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1); // { a: 1, b: 2, c: 3 }， 目标改变了
```

1. 不会拷贝对象的继承属性
2. 不会拷贝对象的不可枚举属性
3. 可以拷贝 Symbol 类型的属性

* slice\
  适用于基本类型值的数组
* concat\
  适用与基本类型值的数组

2. 深拷贝的

一. JSON.stringify 与 JSON.parse![](https://user-gold-cdn.xitu.io/2018/12/17/167bbdde634aa605?w=898\&h=264\&f=png\&s=100215) 2.浅拷贝

从以上结果我们可知 JSON.stringfy() 存在以下一些问题：

* 执行会报错：存在 BigInt 类型、循环引用。
* 拷贝 Date 引用类型会变成字符串。
* 键值会消失：对象的值中为 Function、Undefined、Symbol 这几种类型，。
* 键值变成空对象：对象的值中为 Map、Set、RegExp 这几种类型。
* 无法拷贝：不可枚举属性、对象的原型链。

二. 自定义深拷贝\
存在的问题 改进方案

1. 不能处理循环引用 | 使用 WeakMap 作为一个 Hash 表来进行查询
2. 只考虑了 Object 对象 | 当参数为 Date、RegExp 、Function、Map、Set，则直接生成一个新的实例返回
3. 属性名为 Symbol 的属性
4. 丢失了不可枚举的属性 | 针对能够遍历对象的不可枚举属性以及 Symbol 类型，我们可以使用 Reflect.ownKeys()\
   注：Reflect.ownKeys(obj)相当于\[...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)]
5. 原型上的属性 | Object.getOwnPropertyDescriptors()设置属性描述对象，以及 Object.create()方式继承原型链\
   ————————————————

```js
function deepClone(target) {
  // WeakMap作为记录对象Hash表（用于防止循环引用）
  const map = new WeakMap();

  // 判断是否为object类型的辅助函数，减少重复代码
  function isObject(target) {
    return (
      (typeof target === "object" && target) || typeof target === "function"
    );
  }

  function clone(data) {
    // 基础类型直接返回值
    if (!isObject(data)) {
      return data;
    }

    // 日期或者正则对象则直接构造一个新的对象返回
    if ([Date, RegExp].includes(data.constructor)) {
      return new data.constructor(data);
    }

    // 处理函数对象
    if (typeof data === "function") {
      return new Function("return " + data.toString())();
    }

    // 如果该对象已存在，则直接返回该对象
    const exist = map.get(data);
    if (exist) {
      return exist;
    }

    // 处理Map对象
    if (data instanceof Map) {
      const result = new Map();
      map.set(data, result);
      data.forEach((val, key) => {
        // 注意：map中的值为object的话也得深拷贝
        if (isObject(val)) {
          result.set(key, clone(val));
        } else {
          result.set(key, val);
        }
      });
      return result;
    }

    // 处理Set对象
    if (data instanceof Set) {
      const result = new Set();
      map.set(data, result);
      data.forEach((val) => {
        // 注意：set中的值为object的话也得深拷贝
        if (isObject(val)) {
          result.add(clone(val));
        } else {
          result.add(val);
        }
      });
      return result;
    }

    // 收集键名（考虑了以Symbol作为key以及不可枚举的属性）
    const keys = Reflect.ownKeys(data);
    // 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性以及对应的属性描述
    const allDesc = Object.getOwnPropertyDescriptors(data);
    // 结合 Object 的 create 方法创建一个新对象，并继承传入原对象的原型链， 这里得到的result是对data的浅拷贝
    const result = Object.create(Object.getPrototypeOf(data), allDesc);

    // 新对象加入到map中，进行记录
    map.set(data, result);

    // Object.create()是浅拷贝，所以要判断并递归执行深拷贝
    keys.forEach((key) => {
      const val = data[key];
      if (isObject(val)) {
        // 属性值为 对象类型 或 函数对象 的话也需要进行深拷贝
        result[key] = clone(val);
      } else {
        result[key] = val;
      }
    });
    return result;
  }

  return clone(target);
}

// 测试
const clonedObj = deepClone(obj);
clonedObj === obj; // false，返回的是一个新对象
clonedObj.arr === obj.arr; // false，说明拷贝的不是引用
clonedObj.func === obj.func; // false，说明function也复制了一份
clonedObj.proto; // proto，可以取到原型的属性
```

**9）Array.some 的用法**

```js
(function s() {
  const value = "0";
  const arrNo = ["", null, "0", 0, "undefined", undefined];
  if (arrNo.some((d) => d === value)) return console.log("不是想要的值");
})();
```

**10）Array.forEach 用法**

```js
var arry1 = [1, 2, 3];
arry1.forEach((item, index, arr) => {
  console.log(arr[index]); //1、2、3
});
```

**11）Array.from**

```js
let hours = [];
for (let i = 0; i < 24; i++) {
  hours.push(i + "时");
}
//我们可以写成以下形式
let hours = Array.from({ length: 24 }, (value, index) => index + "时");
```

**12）for-in、for-of**

```js
for(let i of nums){
of 里面的值是VALUE
in 里面的值是key，
for...of 语句遍历可迭代对象定义要迭代的数据。
for...in 语句以原始插入顺序迭代对象的可枚举属性。
}
```

**13）扩展运算符简写**

```js
const odd = [1, 3, 4];
const nums = [2, 5, 6, ...odd];
console.log(nums); //[2,5,6,1,3,4]
```

**14）乐观与悲观锁（这是不知道哪位大神取的名）**

悲观锁\
定义：提交表单时，防止多次提交，我们会在请求前加上标志位

```js
var block = false;
function refesh() {
  if (block) {
    return;
  }
  block = true;

  return fetch("api")
    .then((res) => {
      block = false;
    })
    .catch((err) => {
      block = false;
    });
}
```

乐观锁\
定义：用户在资源下载完之前，在点击其他组件，我们就会去下载其他资源，之前的请求照常发起，只是无论如何都会被丢弃。

```js
var id = 0;
function loading(resources) {
  var load_id = ++id;
  PromiseAll(resources)
    .then(() => {
      if (load_id === id) {
        //渲染
      } else {
        //丢弃
      }
    })
    .catch(() => {
      if (load_id === id) {
        //提示错误
      } else {
        //丢弃
      }
    });
}
```

**15）Proxy**

对象接受两个参数，第一个参数是需要操作的对象，第二个参数是设置对应拦截的属性，这里的属性同样也支持 get，set 等等，也就是劫持了对应元素的读和写，能够在其中进行一些操作，最终返回一个 Proxy 对象。

```js
const proxy = new Proxy({}, {
  get(target, key) {
    console.log('proxy get key', key)
  },
  set(target, key, value) {
    console.log('value', value)
  }
})
proxy.info     // 'proxy get key info'
proxy.info = 1 // 'value 1'

案例：
            const me = {
                name: "小明", like: "小红 ", food: "香菇", musicPlaying: true
            };

            // const meWithProxy = new Proxy(me, {
            //     get(target, prop) {
            //         if (prop === "like") {
            //             return "学习";
            //         }
            //         return target[prop];
            //     }
            // });
            const meWithProxy = new Proxy(me, {
                set(target, prop, value) {
                    if (prop === "musicPlaying" && value !== true) {
                        throw Error("任何妄图停止音乐的行为都是耍流氓！");
                    }
                    target[prop] = value;
                }
            });

            //console.log("sale_check mounted", meWithProxy.like);
            console.log.log("sale_check mounted", meWithProxy.food);
            console.log("sale_check mounted", meWithProxy.musicPlaying = false);
```

## 小白的 vue 入坑之路

**1.vue 中 v-if 中用 key 管理可复用的元素**

我们在不要复用的 input 框加入了相对应的 key，而 label 元素仍然会被高效的复用。

```js
        <template v-if="loginType === 'username'">
            <label>Username</label>
            <input placeholder="Enter your username" key="username-input">
        </template>
        <template v-else>
            <label>Email</label>
            <input placeholder="Enter your email address" key="emial-input">
        </template>
```

**2.动态插入相对应 script**

![script](https://user-gold-cdn.xitu.io/2018/12/17/167bb4fcdf31f11d?w=738\&h=172\&f=png\&s=44110)

**3. computed 与 watch**

计算属性顾名思义就是通过其他变量计算得来的另一个属性，计算属性具有缓存。计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。

侦听器 watch 是侦听一个特定的值，当该值变化时执行特定的函数。例如分页组件中，我们可以监听当前页码，当页码变化时执行对应的获取数据的函数

1. 从属性名上，computed 是计算属性，也就是依赖其它的属性计算所得出最后的值。watch 是去监听一个值的变化，然后执行相对应的函数。
2. 从实现上，computed 的值在 get 执行后是会缓存的，只有在它依赖的属性值改变之后，下一次获取 computed 的值时才会重新调用对应的 getter 来计算。watch 在每次监听的值变化时，都会执行回调。\
   其实从这一点来看，都是在依赖的值变化之后，去执行回调。很多功能本来就很多属性都可以用，只不过有更适合的。

   > 如果一个值依赖多个属性（多对一），用 computed 肯定是更加方便的。\
   > 如果一个值变化后会引起一系列操作，或者一个值变化会引起一系列值的变化（一对多），用 watch 更加方便一些。
3. watch 的回调里面会传入监听属性的新旧值，通过这两个值可以做一些特定的操作。computed 通常就是简单的计算。\
   下面是两者之间的互换

```js
    <input >

        watch: {
            word() {
                this.$emit("update:keyword", this.word);
            }
        }

上面这个data里面定义word
computed: {
        word: {
            get() {
                return this.keyword;
            },
            set(val) {
                this.$emit("update:keyword", val);
            }
        }
}
```

**4.refs 的一些使用注意**

ref  被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的  $refs 对象上。

关于 ref 注册时间的重要说明：因为 ref 本身是作为渲染结果被创建的，在初始渲染的时候你不能访问它们 - 它们还不存在！$ refs  也不是响应式的，因此你不应该试图用它在模板中做数据绑定。\
你应该避免在模板或计算属性中访问  $refs。\
还有考虑到他们 refs 的唯一性，例如：两个表单取同样的话，只会引用到后面那个。

**5.父子组件通信**

```js
//父
<Child-one :parentMessage="parentMessage" @childEvent='childtMethod'></Child-one>

methods:{
    childMethod(){
        alert('组件的消息')
    }
}

//子
<button @click="childMethod">子组件</button>
props:{
    parentMessage:{
        type: String,
        default: '子组件'
    }
}
methods:{
    childMethod(){
        this.$emit('childEvent', '组件的信息')
    }
}

```

**6.非父子组件的数据传递可以采用**

![](https://user-gold-cdn.xitu.io/2018/12/17/167bb5ec5e0e3782?w=1210\&h=566\&f=png\&s=86034)

**prop 是单向数据流，所以 prop 接受的数据无法双向绑定，我们可以采用**

子组件中通过 $emit(eventName)来触发事件\
父组件通过$on(eventName)来监听事件，

* 定义一个局部变量，并用 prop 的值初始化它:

```js
props:['initialCounter'],
data:function(){
    return {counter: this.initialCounter}
}
```

* 定义一个计算属性，处理 prop 的值并返回。

```js
props:['size'],
computed:{
    normalizedSize:function(){
         return this.size.trim().toLowerCase()
    }
}
```

**7.vue.extend**

1.使用 vue 构造器,创建一个子类,参数是包含组件选项的对象. 2.是全局的.\
结论: Vue.extend 实际是创建一个扩展实例构造器,对应的初始化构造器,并将其挂载到标签上

![](https://user-gold-cdn.xitu.io/2018/12/17/167bb61c33503465?w=1260\&h=852\&f=png\&s=301194)

**8.vue.mixins**

结论:

* 1.mixins 执行的顺序为 mixins>mixinTwo>created(vue 实例的生命周期钩子);
* 2.选项中数据属性如 data,methods,后面执行的回覆盖前面的,而生命周期钩子都会执行\
  后面执行 created，而我们 created 里面放着 getSum(){console.log(‘这是 vue 实例的 getSum’)};\
  -3.extends 用法和 mixins 很相似,只不过接收的参数是简单的选项对象或构造函数,所以 extends 只能单次扩展一个组件

结论: 1.extends 执行顺序为:extends>mixins>mixinTwo>created 2.定义的属性覆盖规则和 mixins 一致

**9.vue.install**

1.install 是开发 vue 的插件,这个方法的第一个参数是 Vue 构造器，第二个参数是一个可选的选项对象(可选) 2.vue.use 方法可以调用 install 方法,会自动阻止多次注册相同插件.

![](https://user-gold-cdn.xitu.io/2018/12/17/167bb6b55a0ebe46?w=824\&h=410\&f=png\&s=74626)\
上面的也可以简写为

```js
let install = function (Vue) {
  components.map((component) => {
    Vue.component(Vue.component.name, Vue.component);
  });
};
```

总结下这几个的区别

* vue.extend 和 vue.use 的使用，可以构建自己的 UI 库
* Vue.extend 和 Vue.component 是为了创建构造器和组件;
* mixins 和 extends 是为了拓展组件;
* install 是开发插件;

总的顺序关系: Vue.extend>Vue.component>extends>mixins

**10.组件 name 的作用**

1. 当项目使用 keep-alive 时，可搭配组件 name 进行缓存过滤
2. DOM 做递归组件时\
   比如说 detail.vue 组件里有个 list.vue 子组件，递归迭代时需要调用自身 name
3. 当你用 vue-tools 时\
   vue-devtools 调试工具里显示的组见名称是由 vue 中组件 name 决定的

**11.watcher 有两个配置**

（1）深度 watcher\
deep: true\
（2）该回调将会在侦听开始之后被立即调用\
immediate: true

**12.beforeDestroy**

vue 的 beforeDestroy（）生命周期钩子是一个解决基于 Vue Router 的应用中的这类问题的好地方。\
beforeDestroy: function () {\
this.choicesSelect.destroy()\
}\
可以配合 deactivated\
使用，可以去除 keep-alive 的钩子

**13.keep-alive**

当你用 keep-alive 包裹一个组件后，它的状态就会保留，因此就留在了内存里。

## 其他

**1.diff 算法**

vue 中 key 的作用主要是为了高效的更新虚拟 DOM，其主要的逻辑大概下面这样![](https://user-gold-cdn.xitu.io/2018/12/17/167bb4c8b802b925?w=1660\&h=1056\&f=png\&s=349490)

**2.get 和 post 在缓存方面的区别**

get 请求类似于查找的过程，用户获取数据，可以不用每次都与数据库连接，所以可以使用缓存。\
post 不同，post 做的一般是修改和删除的工作，所以必须与数据库交互，所以不能使用缓存。\
因此 get 请求适合于请求缓存。

**3.setTimeout、process.nextTick、promise 执行顺序**

Event Loop 队列任务有宏任务、微任务

常见的 macro-task 比如： setTimeout、setInterval、 setImmediate、script（整体代码）、 I/O 操作、UI 渲染等。

常见的 micro-task 比如: process.nextTick、Promise、MutationObserver 等。\
macro-task 出队时，任务是一个一个执行的，micro-task 一队一队执行的。

它们的执行优先顺序是 process.nextTick>promise>setTimeout

**4.css 的 min-width 和 max-width**

min-width 会继承父元素,min-height 不会继承父元素，若无父元素，也就是最外层的 body，则默认 100%

***

* 以上是一些工作时候的遇到的问题、可以使用到的技巧或者容易混淆的概念，希望能帮助到一些人。由于能力有限，可能理解有误，欢迎指出。
* 不喜勿喷，多谢合作
* 求职启示：深圳有坑的可以联系我 微信:hoel\_shen,或者在评论区留下您的联系方式。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://shenjunhong.gitbook.io/blog/chang-jian-zong-jie/2018-nian-zhong-zong-jie-nian-di-le-ni-zong-jie-le-ma-wo-xian-lai.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
