xuejianrong / note Goto Github PK
View Code? Open in Web Editor NEW记录平时的学习或者遇到的问题
记录平时的学习或者遇到的问题
Vue是个构造函数,函数里就做了一个事情,那就是调用了this._init()
方法,这个方法定义在了initMixin
里,在initMixin
里又做了合并选项、initData
、initEvent
等事情。
说一下这个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);
使用elementUI的表单验证,输入框本身不可编辑,需要点击选择按钮,选择完了之后,给表单赋值。如图:
选择成功之后,点击确定,表单验证不通过。我通常就是通过试错的方式去修复问题,即使改好了,也不知道为什么,但是这一次我决定一探究竟!
首先我找到了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看看
原来是el-form-item
的实例,然后再打开form-item组件的源码,找到validate
函数,其中使用到了async-validator
这个库,这个库可以验证prop是否符合我们定义的规则,具体可以自行了解。另外还有一行关键代码model[this.prop] = this.fieldValue;
,这说明prop的值用的是fieldValue
的值,在组件里搜了一下fieldValue
可以知道,fieldValue
是一个计算属性,其中涉及到一个方法getPropByPath
,这个我们后面在看,先在DevTool查看fieldValue
的值,我们发现,fieldValue为undefined
并且我查看了外层表单绑定的字段中,是给上了值了的。于是我在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进来)
再触发一下表单验证,然后切换到console,输入_this.form.model
果然是的!!!所以确定最终解决方案:给emergencyUuid
这个prop赋值时使用$set
设置
另外提一下为什么不初始化的时候就给emergencyUuid这个key赋一个初始值,因为这个表单可能是写成一个组件,初始值可能是在父组件中初始化的,那么多次引用子组件就需要每次都增加一个初始值了,所以在子组件内统一处理更合理
js中的链表可以理解为是一个类的多个实例组嵌套,这个类里面有一个属性表示它的值,还有一个属性指向链表中的下一个实例或者是null
function ListNode (val) {
this.val = val;
this.next = null
}
官方的说法是:有的情况下,你仍然需要对普通 DOM 元素进行底层操作。
所以当我们不熟悉自定义指令的使用,不知道什么时候需要去使用它的时候,就可以在我们想要操作dom的时候去使用它,例如:输入框在打开页面就自动聚焦
// 注册全局指令 v-focus
Vue.directive('focus', {
inserted (el, bindings, vnode) {
el.focus()
}
})
// 使用
<input v-focus />
考虑到,如果是第一次学习自定义指令这个知识点,很可能不知道这个东西有什么好处。概念性的东西也枯燥,这里先实现一个比较常用的功能之后,再去说明一些概念性的东西
应用场景:日期选择组件、下拉选择组件等,点击组件外的内容隐藏选择弹窗。
<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
}
}
})
vnode.context
来获取当前的Vue实例这里只讨论常用部分,例如: v-myDirective:arg.a.b.c="value"
v-my-directive
bindings.arg
获取bindings.modifiers
获取bindings.value
获取,顾名思义也可以传入方法直接调用例如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下的属性值也可能是一个对象,需要做以下修改
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
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
修改代码
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 // 更改添加对象的属性
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)
fun.call(thisArg, arg1, arg2, ...)
fun.apply(thisArg, [argArray]) // [argArray]为数组或者类数组对象
创建一个新的函数(也就是返回值是一个函数,通常使用变量或者对象中键值对的方式去接收),函数调用时,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[, ...]]])
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');
let nodeList = document.querySelectorAll('div');
[].forEach.call(nodeList, (node, i) => { ... });
直接使用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);
}
与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,再使用call,call是不起作用的,例如上面的:
fn2.call(obj2)
,fn2中的this并不会指向obj2
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 初始化参数 动态参数
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.