# 每日分享

# 重置页面copy事件

每日tips 平日逛思否,掘金,简书等网站要复制文本的时候,可能会出现不让复制,或者复制内容后面跟着著作权归作者所有,等字样 原因是网站监听copy事件,改写了copy事件逻辑。 解决方案有 1.用IE打开复制,copy事件中的clipboardData属性 IE浏览器不兼容 2.控制台打开设置将JavaScript禁掉 3.写油猴脚本,匹配到该网站,替换到原先的copy监听事件

脚本如下

document.addEventListener('copy', function (event) {
    var clipboardData = event.clipboardData || window.clipboardData;
    if (!clipboardData) { return; }
    var text = window.getSelection().toString();
    if (text) {
        event.preventDefault();
        clipboardData.setData('text/plain', text);
    }
});
1
2
3
4
5
6
7
8
9

提醒

男哥测试mac不生效

# 面试 createFlow

题目是

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

let log=console.log
const subFlow = createFlow([() => delay(1000).then(() => log("c"))]);

createFlow([
    () => log("a"),
    () => log("b"),
    subFlow,
    [() => delay(1000).then(() => log("d")), () => log("e")],
]).run(() => {
    console.log("done");
});
1
2
3
4
5
6
7
8
9
10
11
12
13

实现打印a,b 过一秒c 再过一秒d 再打印done

#####方案1

function createFlow(effects) {
    return {
      async run(callback) {
        effects = effects.flat(Infinity);
        for (const effect of effects) {
          if (effect(effect.run) === 'Function') {
            await effect.run();
          } else {
            await effect();
          }
        }
        callback&&callback()
      }
    };
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# js 如果两个, 会取最后一个

console.log((1,2,3))// 3
console.log([1,2,3][1,2])=console.log([1,2,3][2])=3
1
2

# 表单label 自定义的话

<el-form-item label-width="160px">
    <span slot="label">
      <el-tooltip
        class="item"
        effect="dark"
        content="批量升级需要验证新Chart,当实例升级后状态为升级成功时,验证状态为已验证"
        placement="top"
      >
        <i class="el-icon-question"></i> </el-tooltip
      >新Chart验证状态:</span
    >
    {{ verifyStatus('text') }}
</el-form-item>
1
2
3
4
5
6
7
8
9
10
11
12
13

如果不加 label-width="160px" 会被后面的定位元素覆盖 原因是 如果两个盒子 左面是浮动 右面是定位 浮动的层级不如定位的高被挡住了 z-index 只能在定位的时候生效 非static

# ES6 标签模板与模板字符串

var a = 5;
var b = 10;
tag`Hello ${a + b} world ${a * b}`;
//这个标识名tag,它是一个函数。整个表达式的返回值,就是tag函数处理模板字符串之后的返回值。函数tag会依次接收到多个参数。
function tag(a,b,c){
    console.log(a,b,c)//[ 'Hello ', ' world ', '' ] 15 50
}
1
2
3
4
5
6
7

# ES6 - 链判断运算符

编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取message.body.user.firstName,安全的写法是写成下面这样

const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';
1
2
3
4

或者使用三元运算符?:,判断一个对象是否存在。

const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined
1
2

这样的层层判断非常麻烦,因此 ES2020 引入了“链判断运算符”(optional chaining operator)?.,简化上面的写法。

const firstName = message?.body?.user?.firstName || 'default';
const fooValue = myForm.querySelector('input[name=foo]')?.value
1
2

上面代码使用了?.运算符,直接在链式调用的时候判断,左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined。

链判断运算符有三种用法。

obj?.prop // 对象属性
obj?.[expr] // 同上
func?.(...args) // 函数或对象方法的调用
1
2
3

# 管道操作符

const double = (n) => n * 2;
const increment = (n) => n + 1;

// 没有用管道操作符
double(increment(double(5))); // 22

// 用上管道操作符之后
5 |> double |> increment |> double; // 22
1
2
3
4
5
6
7
8

# 简版MVVM v-model {{}}原理

class Observer {
  constructor(data) {
    // 如果不是对象,则返回
    if (!data || typeof data !== 'object') {
      return
    }
    this.data = data
    this.walk()
  }

  // 对传入的数据进行数据劫持
  walk() {
    for (let key in this.data) {
      this.defineReactive(this.data, key, this.data[key])
    }
  }
  // 创建当前属性的一个发布实例,使用Object.defineProperty来对当前属性进行数据劫持。
  defineReactive(obj, key, val) {
    // 创建当前属性的发布者
    const dep = new Dep()
    /*
     * 递归对子属性的值进行数据劫持,比如说对以下数据
     * let data = {
     *  name: 'cjg',
     *  obj: {
     *   name: 'zht',
     *   age: 22,
     *   obj: {
     *    name: 'cjg',
     *    age: 22,
     *   }
     *  },
     * };
     * 我们先对data最外层的name和obj进行数据劫持,之后再对obj对象的子属性obj.name,obj.age, obj.obj进行数据劫持,层层递归下去,直到所有的数据都完成了数据劫持工作。
     */
    new Observer(val)
    Object.defineProperty(obj, key, {
      get() {
        // 若当前有对该属性的依赖项,则将其加入到发布者的订阅者队列里
        if (Dep.target) {
          dep.addSub(Dep.target)
        }
        return val
      },
      set(newVal) {
        if (val === newVal) {
          return
        }
        val = newVal
        new Observer(newVal)
        dep.notify()
      }
    })
  }
}

// 发布者,将依赖该属性的watcher都加入subs数组,当该属性改变的时候,则调用所有依赖该属性的watcher的更新函数,触发更新。
class Dep {
  constructor() {
    this.subs = []
  }

  addSub(sub) {
    if (this.subs.indexOf(sub) < 0) {
      this.subs.push(sub)
    }
  }

  notify() {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}

Dep.target = null

// 观察者
class Watcher {
  /**
   *Creates an instance of Watcher.
   * @param {*} vm
   * @param {*} keys
   * @param {*} updateCb
   * @memberof Watcher
   */
  constructor(vm, keys, updateCb) {
    this.vm = vm
    this.keys = keys
    this.updateCb = updateCb
    this.value = null
    this.get()
  }

  // 根据vm和keys获取到最新的观察值
  get() {
    // 将Dep的依赖项设置为当前的watcher,并且根据传入的keys遍历获取到最新值。
    // 在这个过程中,由于会调用observer对象属性的getter方法,因此在遍历过程中这些对象属性的发布者就将watcher添加到订阅者队列里。
    // 因此,当这一过程中的某一对象属性发生变化的时候,则会触发watcher的update方法
    Dep.target = this
    this.value = CompileUtils.parse(this.vm, this.keys)
    Dep.target = null
    return this.value
  }

  update() {
    const oldValue = this.value
    const newValue = this.get()
    if (oldValue !== newValue) {
      this.updateCb(oldValue, newValue)
    }
  }
}

class MVVM {
  constructor({ data, el }) {
    this.data = data
    this.el = el
    this.init()
    this.initDom()
  }

  // 初始化
  init() {
    // 对this.data进行数据劫持
    new Observer(this.data)
    // 传入的el可以是selector,也可以是元素,因此我们要在这里做一层处理,保证this.$el的值是一个元素节点
    this.$el = this.isElementNode(this.el)
      ? this.el
      : document.querySelector(this.el)
    // 将this.data的属性都绑定到this上,这样用户就可以直接通过this.xxx来访问this.data.xxx的值
    for (let key in this.data) {
      this.defineReactive(key)
    }
  }

  initDom() {
    const fragment = this.node2Fragment()
    this.compile(fragment)
    document.body.appendChild(fragment)
  }
  // 将节点转为fragment,通过fragment来操作DOM,可以获得更高的效率
  // 因为如果直接操作DOM节点的话,每次修改DOM都会导致DOM的回流或重绘,而将其放在fragment里,修改fragment不会导致DOM回流和重绘
  // 当在fragment一次性修改完后,在直接放回到DOM节点中
  node2Fragment() {
    const fragment = document.createDocumentFragment()
    let firstChild
    while ((firstChild = this.$el.firstChild)) {
      fragment.appendChild(firstChild)
    }
    return fragment
  }

  defineReactive(key) {
    Object.defineProperty(this, key, {
      get() {
        return this.data[key]
      },
      set(newVal) {
        this.data[key] = newVal
      }
    })
  }

  compile(node) {
    const textReg = /\{\{\s*\w+\s*\}\}/gi // 检测{{name}}语法
    if (this.isElementNode(node)) {
      // 若是元素节点,则遍历它的属性,编译其中的指令
      const attrs = node.attributes
      Array.prototype.forEach.call(attrs, attr => {
        if (this.isDirective(attr)) {
          CompileUtils.compileModelAttr(this.data, node, attr)
        }
      })
    } else if (this.isTextNode(node)) {
      // 若是文本节点,则判断是否有{{}}语法,如果有的话,则编译{{}}语法
      let textContent = node.textContent
      if (textReg.test(textContent)) {
        // 对于 "test{{test}} {{name}}"这种文本,可能在一个文本节点会出现多个匹配符,因此得对他们统一进行处理
        // 使用 textReg来对文本节点进行匹配,可以得到["{{test}}", "{{name}}"]两个匹配值
        const matchs = textContent.match(textReg)
        CompileUtils.compileTextNode(this.data, node, matchs)
      }
    }
    // 若节点有子节点的话,则对子节点进行编译。
    if (node.childNodes && node.childNodes.length > 0) {
      Array.prototype.forEach.call(node.childNodes, child => {
        this.compile(child)
      })
    }
  }

  // 是否是属性节点
  isElementNode(node) {
    return node.nodeType === 1
  }
  // 是否是文本节点
  isTextNode(node) {
    return node.nodeType === 3
  }

  isAttrs(node) {
    return node.nodeType === 2
  }
  // 检测属性是否是指令(vue的指令是v-开头)
  isDirective(attr) {
    return attr.nodeName.indexOf('v-') >= 0
  }
}

const CompileUtils = {
  reg: /\{\{\s*(\w+)\s*\}\}/, // 匹配 {{ key }}中的key
  // 编译文本节点,并注册Watcher函数,当文本节点依赖的属性发生变化的时候,更新文本节点
  compileTextNode(vm, node, matchs) {
    // 原始文本信息
    const rawTextContent = node.textContent
    matchs.forEach(match => {
      const keys = match.match(this.reg)[1]
      console.log(rawTextContent)
      new Watcher(vm, keys, () =>
        this.updateTextNode(vm, node, matchs, rawTextContent)
      )
    })
    this.updateTextNode(vm, node, matchs, rawTextContent)
  },
  // 更新文本节点信息
  updateTextNode(vm, node, matchs, rawTextContent) {
    let newTextContent = rawTextContent
    matchs.forEach(match => {
      const keys = match.match(this.reg)[1]
      const val = this.getModelValue(vm, keys)
      newTextContent = newTextContent.replace(match, val)
    })
    node.textContent = newTextContent
  },
  // 编译v-model属性,为元素节点注册input事件,在input事件触发的时候,更新vm对应的值。
  // 同时也注册一个Watcher函数,当所依赖的值发生变化的时候,更新节点的值
  compileModelAttr(vm, node, attr) {
    const { value: keys, nodeName } = attr
    node.value = this.getModelValue(vm, keys)
    // 将v-model属性值从元素节点上去掉
    node.removeAttribute(nodeName)
    new Watcher(vm, keys, (oldVal, newVal) => {
      node.value = newVal
    })
    node.addEventListener('input', e => {
      this.setModelValue(vm, keys, e.target.value)
    })
  },
  /* 解析keys,比如,用户可以传入
   * let data = {
   *  name: 'cjg',
   *  obj: {
   *   name: 'zht',
   *  },
   * };
   * new Watcher(data, 'obj.name', (oldValue, newValue) => {
   *  console.log(oldValue, newValue);
   * })
   * 这个时候,我们需要将keys解析为data[obj][name]的形式来获取目标值
   */
  parse(vm, keys) {
    keys = keys.split('.')
    let value = vm
    keys.forEach(_key => {
      value = value[_key]
    })
    return value
  },
  // 根据vm和keys,返回v-model对应属性的值
  getModelValue(vm, keys) {
    return this.parse(vm, keys)
  },
  // 修改v-model对应属性的值
  setModelValue(vm, keys, val) {
    keys = keys.split('.')
    let value = vm
    for (let i = 0; i < keys.length - 1; i++) {
      value = value[keys[i]]
    }
    value[keys[keys.length - 1]] = val
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284