Coder Social home page Coder Social logo

note's Introduction

note's People

Contributors

xuejianrong avatar

Watchers

James Cloos avatar  avatar

note's Issues

new Vue(options)做了哪些事情

Vue是个构造函数,函数里就做了一个事情,那就是调用了this._init()方法,这个方法定义在了initMixin里,在initMixin里又做了合并选项、initDatainitEvent等事情。
说一下这个initData,这里面遍历data,对象通过Object.defineProperty进行数据的监听,对象里包含对象的话也会进行递归,数组通过重新定义会改变数组的7个方法进行监听,如果是添加项的话也会监听这个添加的内容,还有数组里的对象也会进行监听。还有一个就是定义了proxy方法对this._data代理到this下。最后判断了一下options里是否有el,有的话就调用$mount()进行挂载

题目:两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

function ListNode (val) {
    this.val = val;
    this.next = null;
}
var addTwoNumbers = function(l1, l2) {
    var node = new ListNode('head'); // 结果的链表,但是最后需要去掉第一个'head'
    var temp = node; // 哑结点
    var add = 0; // 是否需要进一
    var sum = 0; // 当前的和
    while (l1 || l2) { // 遍历l1、l2,直到都为null
        sum = (l1 ? l1.val : 0) + (l2 ? l2.val : 0) + add;
        temp.next = new ListNode(sum % 10); // 对10取余就是链表的值
        temp = temp.next; // 把哑结点指向链表的下一个节点
        add = sum >= 10 ? 1 : 0;
        l1 && (l1 = l1.next); // l1、l2指向该链表的下一个节点
        l2 && (l2 = l2.next);
    }
    add && (temp.next = new ListNode(1)); // 如果add在最高位还有进一的话,给链表添加最后一个值为1的节点
    return node.next; // 除去第一个节点
}

// 示例
var [l1_1, l1_2, l1_3] = [new ListNode(4), new ListNode(2), new ListNode(3)];
var [l2_1, l2_2, l2_3] = [new ListNode(7), new ListNode(5), new ListNode(4)];
l1_2.next = l1_3;
l1_1.next = l1_2;
l2_2.next = l2_3;
l2_1.next = l2_2;
// 期望值:1 > 8 > 7

var res = addTwoNumbers(l1_1, l2_1);
console.log(res);

image

一次表单验证不生效引发的思考

使用elementUI的表单验证,输入框本身不可编辑,需要点击选择按钮,选择完了之后,给表单赋值。如图:
image
选择成功之后,点击确定,表单验证不通过。我通常就是通过试错的方式去修复问题,即使改好了,也不知道为什么,但是这一次我决定一探究竟!

首先我找到了eleUI的form组件的源码,找到validate函数,关键代码:

this.fields.forEach(field => {
  field.validate('', (message, field) => {
    if (message) {
      valid = false;
    }
    invalidFields = objectAssign({}, invalidFields, field);
    if (typeof callback === 'function' && ++count === this.fields.length) {
      callback(valid, invalidFields);
    }
  });
});

fields是啥,我们使用DevTool看看
image
原来是el-form-item的实例,然后再打开form-item组件的源码,找到validate函数,其中使用到了async-validator这个库,这个库可以验证prop是否符合我们定义的规则,具体可以自行了解。另外还有一行关键代码model[this.prop] = this.fieldValue;,这说明prop的值用的是fieldValue的值,在组件里搜了一下fieldValue可以知道,fieldValue是一个计算属性,其中涉及到一个方法getPropByPath,这个我们后面在看,先在DevTool查看fieldValue的值,我们发现,fieldValue为undefined
image
并且我查看了外层表单绑定的字段中,是给上了值了的。于是我在fieldValue的计算属性的函数中打了断点。发现在赋值之后,记这个计算属性的方法根本就没有执行。这个方案代码:

fieldValue() {
  const model = this.form.model;
  if (!model || !this.prop) { return; }

  let path = this.prop;
  if (path.indexOf(':') !== -1) {
    path = path.replace(/:/, '.');
  }

  return getPropByPath(model, path, true).v;
}

很明显它的值是依赖this.form.model的,this.form.model就是form表单绑定的对象也就是this.form.model的值变了,但是没有触发计算属性函数的执行。来到这里,心里感觉还得去看vue的源码,看watcher之类的。。。有点放弃的冲动了,但还是先看看getPropByPath是啥吧......

getPropByPath是在util.js里,其实也只是拿到path对应的值而已。换一个思路,计算数据没有触发,有可能是这个动态prop是没有添加对应的watcher,所以我在chrome里给form-item的validate函数打了个断点(可以从控制台的warning进来)
image
再触发一下表单验证,然后切换到console,输入_this.form.model
image
果然是的!!!所以确定最终解决方案:给emergencyUuid 这个prop赋值时使用$set设置

另外提一下为什么不初始化的时候就给emergencyUuid这个key赋一个初始值,因为这个表单可能是写成一个组件,初始值可能是在父组件中初始化的,那么多次引用子组件就需要每次都增加一个初始值了,所以在子组件内统一处理更合理

js中链表

js中的链表可以理解为是一个类的多个实例组嵌套,这个类里面有一个属性表示它的值,还有一个属性指向链表中的下一个实例或者是null

function ListNode (val) {
  this.val = val;
  this.next = null
}

vue自定义指令的使用

简介和使用场景

官方的说法是:有的情况下,你仍然需要对普通 DOM 元素进行底层操作。
所以当我们不熟悉自定义指令的使用,不知道什么时候需要去使用它的时候,就可以在我们想要操作dom的时候去使用它,例如:输入框在打开页面就自动聚焦

// 注册全局指令 v-focus
Vue.directive('focus', {
  inserted (el, bindings, vnode) {
    el.focus()
  }
})
// 使用
<input v-focus />

考虑到,如果是第一次学习自定义指令这个知识点,很可能不知道这个东西有什么好处。概念性的东西也枯燥,这里先实现一个比较常用的功能之后,再去说明一些概念性的东西

click-outside: 点击其他元素时触发

应用场景:日期选择组件、下拉选择组件等,点击组件外的内容隐藏选择弹窗。

<style>
  .comp {
    width: 200px;
  }
  .dialog {
    width: 200px;
    height: 200px;
    background: #666;
  }
</style>
<body>
  <div id="app">
    <div class="comp" v-click-outside>
      <input type="text"><br>
      <div class="dialog" v-show="isShow"></div>
    </div>
  </div>
</body>
// 组件内
new Vue({
  el: '#app',
  data: {
    isShow: false
  },
  directives: {
    clickOutside: {
      // 绑定指令时触发
      bind (el, bindings, vnode) {
        // 给el设置fn方法,为了在元素销毁时解绑
        el.fn = function (e) {
          // 判断点击的是组件内还是组件外
          if (el.contains(e.target)) {
            // vnode.context可以放回当前的vue实例
            vnode.context.show()
          } else {
            vnode.context.hide()
          }
        }
        document.addEventListener('click', el.fn)
      },
      // 指令与元素解绑时调用(可以大概理解为元素销毁时触发)
      unbind (el) {
        // 解除事件绑定,如果不解除,bind中的el.fn、el.contains就会出错,控制台一片红
        document.removeEventListener('click', el.fn)
      }
    }
  },
  methods: {
    show () {
      this.isShow = true
    },
    hide () {
      this.isShow = false
    }
  }
})

钩子函数

  • bind: 只调用一次,指令第一次绑定到元素时调用。
  • inserted: 被绑定元素插入父节点时调用
  • update: 所在组件的 VNode 更新时调用
  • unbind: 只调用一次,指令与元素解绑时调用

参数

  • el: 指令绑定的元素,可以用来直接操作dom
  • bindings: 一个包含很多有用信息的对象
  • vnode: 虚拟节点,可以通过vnode.context来获取当前的Vue实例

自定义指令的组成部分

这里只讨论常用部分,例如: v-myDirective:arg.a.b.c="value"

  • myDirective: 自定义指令的名称,使用的时候也可以写成:v-my-directive
  • arg: 参数,可通过bindings.arg获取
  • a: 同b、c,修饰符,可通过bindings.modifiers获取
  • value: 指令绑定的值,可通过bindings.value获取,顾名思义也可以传入方法直接调用

vue Observer的实现

Observer需要实现的功能

例如vue里面的data,修改data中的数据的时候,能够触发视图更新

先实现最简单的值为常量的情况

例如

const data = {
  name: '魁拔',
  title: '十万火急'
}

定义observe函数:遍历data的所有属性,并对属性进行处理

function observe (data) {
  Object.keys.forEach(key => {
    defineReactive(data, key, data[key])
  })
}

实现defineReactive:利用Object.defineProperty()对对象属性进行重写

function defineReactive (data, key, val) {
  Object.defineProperty(data, key, {
    get () {
      // 这里不能return data[key],会一直触发get,造成死循环
      return val
    },
    set (newVal) {
      // 同理这里使用data[key]去判断也会出现死循环
      if (val === newVal) reutrn
      val = newVal
      console.log('更新视图')
    }
  })
}

注意:这里的val,为常量的时候,可以看做是一个中间变量。为引用类型时是对data[key]的值的一个引用,此时调用val并不会执行data[key]的get方法
此时,执行以下代码就会打印“更新视图”

observe(data)
data.title = '战神崛起'

深度监听data

data下的属性值也可能是一个对象,需要做以下修改

function observe (data) {
  if (typeof data !== 'object' || data === null) return
  Object.keys(data).forEach(key => {
    defineReactive(data, key, data[key])
  })
}

function defineReactive (data, key, val) {
  observe(val)
  Object.defineProperty(data, key, {
    get () {
      return val
    },
    set (newVal) {
      if (val === newVal) reutrn
      val = newVal
      console.log('更新视图')
    }
  })
}

这个例子也就能触发“视图更新”

const data = {
  name: '魁拔',
  title: '十万火急',
  exp: {
    boxOffice: 100
  }
}
observe(data)
data.exp.boxOffice = 200

传给observe函数的是一个数组

需要遍历数组,把数组的每一项再传给observe

function observe (data) {
  if (typeof data !== 'object' || data === null) return
  if (Array.isArray(data)) {
    for (let i = 0; i < data.length; i += 1) {
      observe(data[i])
    }
  } else {
    Object.keys(data).forEach(key => {
      defineReactive(data, key, data[key])
    })
  }
}

这个例子也就能触发“视图更新”

const data = {
  name: '魁拔',
  title: '十万火急',
  version: [1, 2, { n: 100 }]
}
observe(data)
data.version[2].n = 200

push,pop,shift,unshift,splice,sort,reverse也能触发视图更新

修改代码

const arrayProto = Array.prototype
// 复制一份新的原型,避免对数组的原型造成污染
const proto = Object.create(arrayProto)
// 改写数组的7个方法
;['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
  proto[method] = function (...args) {
    // Array.prototype[method]
    const result = arrayProto[method].call(this, ...args)
    console.log('更新视图')
    return result
  }
})
function observe (data) {
  if (typeof data !== 'object' || data === null) return
  if (Array.isArray(data)) {
    // 给数组设置新的原型
    // data.__proto__ = proto
    Object.setPrototypeOf(data, proto)
    for (let i = 0; i < data.length; i += 1) {
      observe(data[i])
    }
  } else {
    Object.keys(data).forEach(key => {
      defineReactive(data, key, data[key])
    })
  }
}
function defineReactive (data, key, val) {
  observe(val)
  Object.defineProperty(data, key, {
    get () {
      return val
    },
    set (newVal) {
      if (val === newVal) reutrn
      val = newVal
      console.log('更新视图')
    }
  })
}

这个例子也就能触发“视图更新”

const data = {
  name: '魁拔',
  title: '十万火急',
  version: [1, 2]
}
observe(data)
data.version.push(3)

数组方法中如果增加了元素也是对象的话,也需要进行监听

增加元素的方法有push、unshift、splice,所以最后的代码为:

const arrayProto = Array.prototype
const proto = Object.create(arrayProto)
// 改写数组的7个方法
;['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
  proto[method] = function (...args) {
    // Array.prototype[method]
    const result = arrayProto[method].call(this, ...args)
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    observeArray(inserted)
    console.log('更新视图')
    return result
  }
})

function observe (data) {
  if (typeof data !== 'object' || data === null) return
  if (Array.isArray(data)) {
    // data.__proto__ = proto
    Object.setPrototypeOf(data, proto)
    observeArray(data)
  } else {
    Object.keys(data).forEach(key => {
      defineReactive(data, key, data[key])
    })
  }
}

function observeArray (data) {
  for (let i = 0; i < data.length; i += 1) {
    observe(data[i])
  }
}

function defineReactive (data, key, val) {
  observe(val)
  Object.defineProperty(data, key, {
    get () {
      return val
    },
    set (newVal) {
      if (val === newVal) reutrn
      val = newVal
      console.log('更新视图')
    }
  })
}
const data = {
  name: '魁拔',
  title: '十万火急',
  version: [1, 2]
}
observe(data)
data.version.push({ n: 100 }) // 添加对象,对象也需要监听
data.version[2].n = 200 // 更改添加对象的属性

品代码系列——高阶函数和call配合使用

class Table {
    list = []
    addItem(key, value) {
        this.list.push({ key, value })
    }
    forEach(fn, thisArr) {
        this.list.forEach(item => {
            fn.call(thisArr, item.key, item.value)
        })
    }
}

var table1 = new Table()
var table2 = new Table()
table1.list = [{ key: 'a', value: 1 }, { key: 'b', value: 2 }]

// 可方便的将table1的list复制到table2的list
table1.forEach(table2.addItem, table2)

BFC总结

作用

  • 自适应两列布局
  • 计算高度时,浮动的子元素的高度也会算上
  • 防止浮动子元素覆盖其他元素
  • 解决上下margin值重叠的问题(两个BFC之间的margin值不会重叠)

产生条件

  • 根元素(html元素)
  • position值为absolute或fixed
  • overflow值不为visible的块元素
  • display值为inline-block、table-cell、table-caption、float-root、grid、flex

call、apply、bind

call

描述

  • 允许为不同的对象分配和调用属于一个对象的函数/方法
  • 提供新的this值给当前调用的函数/方法

使用语法

fun.call(thisArg, arg1, arg2, ...)

apply

使用语法

fun.apply(thisArg, [argArray]) // [argArray]为数组或者类数组对象

bind

描述

创建一个新的函数(也就是返回值是一个函数,通常使用变量或者对象中键值对的方式去接收),函数调用时,this指向bind的第一个参数,其余参数作为新函数调用时使用,新函数调用时的所有参数也将接在bind的其余参数后面调用
例如:

function fn(a, b) {} // 原函数
var obj = {};
var newFn = fn.bind(obj, 'a'); // 新函数,'a'作为参数a调用
newFn('b'); // 'b'作为参数b调用

使用语法

fun.bind(thisArg[, arg1[, arg2[, ...]]])

例子

call

指定函数上下文(最简单的使用,apply也是)

调用父级构造函数

function Property(faction, property) {
  this.faction = faction;
  this.property = property;
}

function Hero(faction, property, name) {
  Property.call(this, faction, property); // 能用call的时候不用apply
  this.name = name;
}

const morphling = new Hero('dire', 'power', 'morphling');

类数组对象使用数组的方法(直接使用会有兼容性问题,例如nodeList直接调用数组的方法)

let nodeList = document.querySelectorAll('div');
[].forEach.call(nodeList, (node, i) => { ... });

apply

把一个数据添加到另一个数组

直接使用push需要循环一个个push,使用concat需要使用另一个用一个新数组接收返回值

let arr1 = [1, 2];
let arr2 = [3, 4];
[].push.apply(arr1, arr2); // arr1 变成了[1, 2, 3, 4],但是push的返回值依然是内部的this.length

与内置函数结合使用

let arr = [8, 5, 3, 9, 6];
Math.max.apply(null, arr); // 求arr中的最大值
Math.min.apply(null, arr); // 求arr中的最小值

浏览器会有参数数量上的限制,如果arr长度超过了限制,浏览器可能会报错,也可能会忽略掉超过的部分,所以可以通过以下方法优化:

// 例如求最大值
let arr = [8, 5, 3, 9, 6];
let min = Infinity
const QUANTUM = 32768;
for (let i = 0; i < arr.length; i += QUANTUM) {
  let subMin = Math.min.apply(null, arr.slice(i, i + QUANTUM)); // arr.slice(i, i + QUANTUM) 改成 arr.slice(i, Math.min(i + QUANTUM, arr.length)) 会更严谨,但slice的第二个参数大于数组长度也是会截取到数组末尾
  min = Math.min(min, subMin);
}

bind

指定函数上下文

与call类似的作用,区别在于call是在调用时改变指定this的指向。而bind是在声明的时候,例如

let obj = { a: 1 };

// call
let fn1 = function() { console.log(this.a) };
fn1.call(obj);

// bind
let fn2 = function() { console.log(this.a) }.bind(obj)
fn2()

另外,如果先使用了bind,再使用callcall是不起作用的,例如上面的: fn2.call(obj2),fn2中的this并不会指向obj2

偏函数:预设初始参数

实现bind

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    var _this = this;
    var args = [].slice.call(arguments, 1);
    var fn = function() {
      args = args.concat([].slice.call(arguments))
      // 如果是使用new关键字生成的话,this的指向不是oThis
      _this.apply(this instanceof fn ? this : oThis, args);
    }
    if (this.prototype) {
      fn.prototype = this.prototype
    }
    return fn
  }
}

// 使用例子:
function fn(str, str2) {
    this.name = 'fnName';
    console.log(this.name + ' ' + str + ' ' + str2)
}
var a = { name: 'test' }
var fn2 = fn.bind(a, '初始化参数')

fn2('动态参数') // test 初始化参数 动态参数
// 这里的this就不会被bind影响
new fn2('动态参数') // fnName 初始化参数 动态参数

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

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

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.