vue 笔记备份
Vue2实现数据双向绑定的原理:Object.defineProperty()
vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <body> <div id="app"> <input type="text" id="txt"> <p id="show"></p> </div> </body> <script type="text/javascript"> var obj = {} Object.defineProperty(obj, 'txt', { get: function () { return obj }, set: function (newValue) { document.getElementById('txt').value = newValue document.getElementById('show').innerHTML = newValue } }) document.addEventListener('keyup', function (e) { obj.txt = e.target.value }) </script> </body> </html>
简易版的watch
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<style>
* {
margin: 0;
padding: 0;
}
div {
padding: 20px;
border: 1px solid red;
margin: 20px auto;
}
</style>
<div id="btn1">A</div>
<div id="btn2">B</div>
<div id="btn3">C</div>
<div id="btn4">D</div>
<script>
function watcher(obj2, fn) {
var hasProxy = typeof Proxy === 'function' ? 1 : 0;
hasProxy = 0;
console.log("代理类型:", hasProxy ? "Proxy" : "defineProperty")
//ie typeof Proxy === "undefined" true // typeof Proxy === 'function' false
if (hasProxy) {
function myProxy(obj2, fn) {
this._callback = fn;
//console.log("myProxy 参数", obj)
return this.ProxyCreate(obj2);
}
myProxy.prototype = {
constructor: myProxy,
ProxyCreate: function(obj) {
var self = this;;
var handle = {
get: function(target, prop, receiver) {
//console.log("get prop", prop, typeof target[prop]);
if (typeof target[prop] === "object" && target[prop] !== null) {
console.log("Proxy 深层次递归")
return self.ProxyCreate(target[prop]);
}
return target[prop];
},
set: function(target, prop, value, receiver) {
//target:被代理的目标对象
//prop:想要获取的属性名
//receiver:Proxy 或者继承 Proxy 的对象
if (value === target[prop]) return;
//console.log("Proxy 修改了prop", prop);
//console.log("新值:", value, "旧值:", target[prop]);
self._callback.call(this, value, target[prop], prop)
target[prop] = value;
}
}
var proxy = new Proxy(obj, handle);
return proxy;
}
}
var obj1 = new myProxy(obj2, fn);
return obj1;
} else {
function myProxy(obj2, fn) {
this._callback = fn;
//console.log("myProxy 参数", obj)
this.ProxyCreate(obj2);
return obj2
}
myProxy.prototype = {
constructor: myProxy,
ProxyCreate: function(obj) {
var self = this;
var keyArr1 = [];
for (var prop in obj) {
keyArr1.push(prop)
}
//console.log("keyArr1", keyArr1) // ['prop1', 'prop2', 'prop3']
// var keyArr2 = Object.keys(obj);
keyArr1.forEach(function(prop) {
var oldVal = obj[prop];
Object.defineProperty(obj, prop, {
get: function() {
return oldVal;
},
set: function(newVal) {
if (newVal === oldVal) return;
//console.log("defineProperty 修改了prop", prop);
//console.log("新值:", newVal, "旧值:", oldVal);
if (Object.prototype.toString.call(newVal) ==
'[object Object]') {
self.ProxyCreate(newVal);
}
self._callback.call(this, newVal, oldVal, prop)
oldVal = newVal
}
})
if (Object.prototype.toString.call(oldVal) === '[object Object]') {
console.log("defineProperty 深层次递归")
self.ProxyCreate(oldVal)
}
// if (Object.prototype.toString.call(oldVal) === '[object Array]') {
// self.ProxyCreate(oldVal)
// }
})
return this
}
}
var obj1 = new myProxy(obj2, fn);
return obj1;
}
}
</script>
<script>
var obj1 = {
old: 0,
a: 1,
b: 2,
c: 3,
d: {
age: 22
}
}
//直接 new watcher(obj2,fn) 不支持
var obj2 = new watcher(obj1, function(newVal, oldVal, prop) {
console.log("watcher最后", "newVal", newVal, "oldVal", oldVal, "修改的属性", prop)
//console.log("原始obj1", obj1)
})
var btn1 = document.getElementById("btn1")
var btn2 = document.getElementById("btn2")
var btn3 = document.getElementById("btn3")
var btn4 = document.getElementById("btn4")
btn1.onclick = function() {
console.log("1 obj2 oldVal", obj2.a)
obj2.a = 'a11' + (+new Date);
}
btn2.onclick = function() {
console.log("2 obj2 oldVal", obj2.b)
obj2.b = 'b22' + (+new Date);
}
btn3.onclick = function() {
console.log("3 obj2 oldVal", obj2.c)
obj2.c = 'c33' + (+new Date);
}
btn4.onclick = function() {
console.log("4 obj2.d.age oldVal", obj2.d.age)
obj2.d.age = 'd.age:' + (+new Date);
}
</script>
</body>
</html>
高阶版的watch
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
div {
margin: 10px;
padding: 10px;
border: 1px solid red;
}
</style>
<body>
<div id="btn0">相同</div>
<div id="btn1">A</div>
<div id="btn2">B</div>
<div id="btn3">C</div>
<div id="btn4">添加属性</div>
<div id="btn5">删除属性</div>
<div id="btn6">查询属性</div>
<div>------------------------------</div>
<div id="dsd00">0设置observeObj x.name</div>
<div id="dsd11">11设置observeObj x.leve_1.name= "leve_1_" + (new Date)</div>
<div id="dsd12">11设置observeObj x.leve_1.leve_2.name= "leve_2_" + (new Date)</div>
<div id="dsd22">22设置observeObj x.leve_1.leve_2.leve_3.name = "leve_3_" + (new Date)</div>
<div id="dsd33">33设置observeObj x.leve_1.leve_2.leve_3.leve_4.name = "leve_4_" + (new Date)</div>
<div id="dsd34">混个1与2</div>
<div id="dsd35">混个2与3</div>
<div id="dsd36">混个1与2与3</div>
<div>------------------------------</div>
<div style="margin-bottom: 500px;;"></div>
<!-- <script src="watch.js"></script> -->
<script>
function watcher(obj2, fn, isClone) {
//ie typeof Proxy === "undefined" true // typeof Proxy === 'function' false
var hasProxy = typeof Proxy === 'function' ? 1 : 0;
hasProxy = 1;
console.log("代理类型:", hasProxy ? "Proxy" : "defineProperty")
// var toClone = 0
// if (isClone&&!toClone) {
// obj2 = deepClone&&deepClone(obj2)
//toClone=1
// }
// function deepClone(obj) { //
// if (obj === null) return obj
// //if ([null, undefined, NaN, false].includes(obj)) return obj;
// if (typeof obj == 'function') return new Function('return ' + obj.toString())()
// if (typeof obj != 'object') return obj
// if (obj instanceof RegExp) return new RegExp(obj)
// if (obj instanceof Date) return new Date(obj)
// // 运行到这里,基本上只存在数组和对象两种类型了
// let newObj = Object.prototype.toString.call(obj) === '[object Array]' ? [] : {};
// for (let index in obj) {
// newObj[index] = deepClone(obj[index]); // 对子项进行递归复制
// }
// return newObj;
// }
function $set(data, prop, val, type) { //set 通知根新属性的值 重置最初的值
//if (Array.isArray(target) && isValidArrayIndex(val)) {//isArray 判断是不是数组,并且key的值是有效的数组索引。
// }
var text = ''
if (type == "add" || type == 1) {
text = '增加'
data[prop] = val;
//return initWatch(data, fn)
}
if (type == "delete" || type == 2) {
text = '删除'
for (var key in data) {
if (key == prop) {
delete data[key]
}
}
//return initWatch(data, fn)
}
if (type == "search" || type == 3) {
text = '查询'
}
if (type == "edit" || type == 4) {
text = '修改'
}
//console.log("set 通知变化:" + text + "了 " + prop + "属性", data, val)
}
//typeof Proxy === 'function'
if (hasProxy) {
// obj2 = new Proxy(data, {
//console.log("2 new Proxy 对象", obj2)
function myProxy(obj2, fn) {
this._callback = fn;
this._isSET = 0;
this._isGET = 0;
//console.log("Proxy 参数", obj)
return this.ProxyCreate(obj2);
}
myProxy.prototype = {
constructor: myProxy,
ProxyCreate: function(obj, pathArray) {
var self = this;
var firstPath = [];
// for (let prop in obj) {
// if (typeof obj[prop] === 'object') {
// obj[prop] = self.ProxyCreate(target[prop], pathArray);
// }
// }
// 这边把handle拿出来,看着更清晰
var handle = {
get: function(target, prop, receiver) {
self._isGET = 1;
if (!firstPath.length) {
firstPath = [prop];
}
//console.log("get- prop:", prop);
if (!pathArray || self._isSET) {
pathArray = [];
self._isSET = 0;
//console.log("get 置空 pathArray", pathArray);
}
//debugger;
pathArray.push(prop)
//console.log("get pathArray", pathArray, "self._isSET", self._isSET);
// 如果是对象的话就递归
if (typeof target[prop] === "object" && target[prop] !== null) {
self._isGET = 2;
return self.ProxyCreate(target[prop], pathArray);
}
return target[prop];
},
set: function(target, prop, value, receiver) {
if (value === target[prop]) return;
//console.log("set 拿到 第一个", get1)
var path = [];
//console.log("set pathArray", pathArray, "_isGET", self._isGET)
if (pathArray && self._isGET == 2) {
//path = [...firstPath, ...pathArray, prop]
path = [].concat(firstPath, pathArray, [prop])
}
if (!self._isGET || self._isGET == 1) { //单层 的设置,不走get,直接set
//console.log("set 里面" + (self._isGET == 1 ? "单层 的设置,走了get,没递归" :"单层 的设置,不走get,直接set "))
path = [prop];
pathArray = [];
}
self._isSET = 1;
self._isGET = 0;
//console.log("set _isGET-----", self._isGET);
//console.log("set path-----", path, "firstPath", firstPath);
//console.log("set",prop,"arr",arr);
self._callback.call(this, value, target[prop], prop, path)
target[prop] = value;
}
};
var proxy = new Proxy(obj, handle);
return proxy;
}
}
var obj1 = new myProxy(obj2, fn);
obj1.$set = $set;
//console.log("new Proxy 对象 ", obj2)
return obj1
} else {
function myProxy(obj2, fn) {
this._callback = fn;
//console.log("myProxy 参数", obj)
this.ProxyCreate(obj2);
return obj2
//return ProxyCreate(target, handler);
}
myProxy.prototype = {
constructor: myProxy,
ProxyCreate: function(obj, path) {
var self = this;
var keyArr1 = [];
for (var prop in obj) {
keyArr1.push(prop)
}
//console.log("keyArr1", keyArr1) // ['prop1', 'prop2', 'prop3']
// var keyArr2 = Object.keys(obj);
// console.log("keyarr2", keyArr2)
if (Object.prototype.toString.call(obj) === '[object Array]') {
var prop = path && path.slice(-1)[0] || ""
self.overrideArrayProto(obj, prop, path);
}
keyArr1.forEach(function(prop) {
var oldVal = obj[prop];
let pathArray = path && path.slice();
//console.log("path", path, "pathArray", pathArray)
if (pathArray) {
pathArray.push(prop);
} else {
pathArray = [prop];
}
Object.defineProperty(obj, prop, {
get: function() {
return oldVal;
},
set: function(newVal) {
if (newVal === oldVal) return;
//console.log("defineProperty 修改了prop", prop);
//console.log("新值:", newVal, "旧值:", oldVal);
if (Object.prototype.toString.call(newVal) ==
'[object Object]') {
self.ProxyCreate(newVal, pathArray);
}
self._callback.call(self, newVal, oldVal, prop, pathArray)
oldVal = newVal
}
})
if (Object.prototype.toString.call(oldVal) === '[object Object]') {
self.ProxyCreate(oldVal, pathArray)
}
if (Object.prototype.toString.call(oldVal) === '[object Array]') {
self.ProxyCreate(oldVal, pathArray)
}
}) //keyArr1.forEach end
return obj
},
overrideArrayProto(array, prop, path) {
// 保存原始 Array 原型
var originalProto = Array.prototype, // 通过 Object.create 方法创建一个对象,该对象的原型是Array.prototype
overrideProto = Object.create(Array.prototype),
OAM = ['push', 'pop', 'shift', 'unshift', 'short', 'reverse', 'splice'],
self = this,
result;
// 遍历要重写的数组方法
OAM.forEach((method) => {
Object.defineProperty(overrideProto, method, {
value: function() {
var oldVal = this.slice();
//调用原始原型上的方法
result = originalProto[method].apply(this, arguments);
//继续监听新数组
//console.log("method", method, "oldVal", oldVal,prop, path)
self.ProxyCreate(this, path);
self._callback(this, oldVal, prop, path);
return result;
}
})
});
// 最后 让该数组实例的 __proto__ 属性指向 假的原型 overrideProto
array.__proto__ = overrideProto;
}
}
var obj1 = new myProxy(obj2, fn);
obj1.$set = $set;
//console.log("new myProxy 对象 ", obj2)
return obj1
}
}
</script>
<script>
let obj = {
name: "leve_1_颜1酱",
age: 18,
leve_1: {
like: "猫",
name: "leve_1_name",
leve_2: {
name: "leve_2_name",
leve_3: {
name: "leve_3_name",
age: 23,
leve_4: {
name: "leve_4_name",
age: 23,
}
}
}
}
};
//let x = observe2Obj(obj, function(newVal, oldVal, prop, path) {
let x = new watcher(obj, function(newVal, oldVal, prop, path) {
console.log("第1个 watcher", "newVal", newVal, "oldVal", oldVal, "修改的属性", prop, "path", path)
//console.log("原始obj1", obj1)
})
document.getElementById("dsd00").onclick = function() {
x.name = "leve_1_" + (+new Date);
}
document.getElementById("dsd11").onclick = function() {
x.leve_1.name = "leve_1_" + (+new Date);
}
document.getElementById("dsd12").onclick = function() {
x.leve_1.leve_2.name = "leve_2_" + (+new Date);
}
document.getElementById("dsd22").onclick = function() {
x.leve_1.leve_2.leve_3.name = "leve_3_" + (+new Date);
}
document.getElementById("dsd33").onclick = function() {
//console.log(x.son.boyFriend.name);
x.leve_1.leve_2.leve_3.leve_4.name = "leve_4_" + (+new Date);
}
document.getElementById("dsd34").onclick = function() {
//console.log(x.son.boyFriend.name);
x.name = "leve_1_" + (+new Date);
x.leve_1.leve_2.name = "leve_2_" + (+new Date);
}
document.getElementById("dsd35").onclick = function() {
//console.log(x.son.boyFriend.name);
x.leve_1.leve_2.name = "leve_2_" + (+new Date);
x.leve_1.leve_2.leve_3.name = "leve_3_" + (+new Date);
}
document.getElementById("dsd36").onclick = function() {
//console.log(x.son.boyFriend.name);
x.name = "leve_1_" + (+new Date);
x.leve_1.leve_2.name = "leve_2_" + (+new Date);
x.leve_1.leve_2.leve_3.name = "leve_3_" + (+new Date);
}
//console.log(x, obj);
// --------------------------------------------------------
var obj1 = {
old: 0,
a: 1,
b: 2,
c: 3,
}
//直接 new watcher(obj2,fn) 不支持
var obj2 = new watcher(obj1, function(newVal, oldVal, prop, path) {
console.log("第2个 watcher 最后", "newVal", newVal, "oldVal", oldVal, "修改的属性", prop, "path", path)
//console.log("原始obj1", obj1)
})
//console.log("obj2", obj2)
var btn0 = document.getElementById("btn0")
var btn1 = document.getElementById("btn1")
var btn2 = document.getElementById("btn2")
var btn3 = document.getElementById("btn3")
var btn4 = document.getElementById("btn4")
var btn5 = document.getElementById("btn5")
var btn6 = document.getElementById("btn6")
var sum = 0;
var ss = 0;
btn0.onclick = function() {
sum++
ss++;
obj2.old = sum;
if (sum == 2) {
sum = 1
}
if (ss == 4) {
sum = -1
}
//obj2.b = 'b11';
//console.log("1 obj2", obj2.a)
//document.getElementById('id2').innerHTML = a;
}
btn1.onclick = function() {
console.log("1 obj2 oldVal", obj2.a)
obj2.a = 'a11' + (+new Date);
//obj2.b = 'b11';
//document.getElementById('id2').innerHTML = a;
}
btn2.onclick = function() {
console.log("2 obj2 oldVal", obj2.b)
obj2.b = 'b22' + (+new Date);
//document.getElementById('id2').innerHTML = a;
}
btn3.onclick = function() {
console.log("3 obj2 oldVal", obj2.c)
obj2.c = 'c33' + (+new Date);
//document.getElementById('id2').innerHTML = a;
}
btn4.onclick = function() {
console.log("添加新属性 newprop")
var asdad = 'newprop' + (+new Date)
obj2.newprop = asdad;
obj2.$set(obj2, "newprop", asdad, "add")
//document.getElementById('id2').innerHTML = a;
}
btn5.onclick = function() {
console.log("删除新属性 newprop")
delete obj2.newprop;
obj2.$set(obj2, "newprop", "", 'delete')
//document.getElementById('id2').innerHTML = a;
}
btn6.onclick = function() {
console.log("查询属性 newprop", obj2.newprop)
obj2.$set(obj2, "newprop", "", 'search')
//document.getElementById('id2').innerHTML = a;
}
</script>
</body>
</html>
Object.defineProperty实现对数组的监听
const orginalProto = Array.prototype;
const arrayProto = Object.create(orginalProto); // 先克隆一份Array的原型出来
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(method => {
arrayProto[method] = function () {
// 执行原始操作
orginalProto[method].apply(this, arguments)
console.log('监听赋值成功', method)
}
})
ResizeObserver polyfill
// index.js
(async () => {
if (!ResizeObserver in window) {
const module = await import('resize-observer-polyfill');
window.ResizeObserver = module.ResizeObserver
}
}();
nextTick
//vue3 nextTick-------------------------------------------------------------------------
const resolvedPromise = Promise.resolve();
let currentFlushPromise = null;
function nextTick(fn) {
const p = currentFlushPromise || resolvedPromise;
return fn ? p.then(this ? fn.bind(this) : fn) : p;
}
function nextTick2(fn){
var p = Promise.resolve();
return fn ? p.then(fn) : p;
}
// 回调模式
nextTick2(() => console.log('DOM 更新后执行'));
// Promise 模式
nextTick2().then(() => console.log('Promise 触发'));
// 混合模式
setTimeout(() => console.log('宏任务'));
nextTick2(() => console.log('微任务1'));
nextTick2(() => console.log('微任务2'));
/* 输出顺序:
1. 微任务1
2. 微任务2
3. Promise 触发
4. 宏任务
*/
const callbacks = []
let pending = false
function flushCallbacks(){ //执行回调
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0 //清空回调队列
for(let i = 0; i < copies.length; i++) {
copies[i]()
}
}
let microTimerFunc
const p = Promise.resolve()
microTimerFunc = () => { //注册微任务
p.then(flushCallbacks)
}
export function nextTick(cb,ctx){
callbacks.push(()=>{
if(cb){
cb.call(ctx)
}
})
if(!pending){
pending = true //将pending设置为true,保证任务在依次事件循环中不会重复添加
microTimerFunc()
}
}
延迟执行
https://github.com/AlloyTeam/PhyTouch/blob/master/transformjs/asset/tick.js
; (function () { if ( !window.requestAnimationFrame || !window.cancelAnimationFrame) { var lastTime = 0; window.requestAnimationFrame = function (callback) { var now =(+new Date); var nextTime = Math.max(lastTime + 16, now); return setTimeout(function () { callback(lastTime = nextTime); },nextTime - now); }; window.cancelAnimationFrame = clearTimeout; } var tickArr = []; var tick = function (fn) { tickArr.push(fn); }; var execTick = function () { var i = 0, len = tickArr.length; for (; i < len; i++) { tickArr[i](); } requestAnimationFrame(execTick); }; execTick(); window.tick = tick; })();
var element1 = document.querySelector("#test1");
Transform(element1);
element1.rotateZ = 45;
var element2 = document.querySelector("#test2");
Transform(element2);
tick(function () {
element2.rotateY++;
});
var element3 = document.querySelector("#test3");
Transform(element3, true);
tick(function () {
element3.rotateX++;
});
vue diff算法
function diff(oldTree, newTree) {
let diffs = [];
function compare(node1, node2) {
// 如果节点类型不同,直接添加到差异队列
if (node1.type !== node2.type) {
diffs.push({
type: 'replace',
oldNode: node1,
newNode: node2,
});
return;
}
// 比较属性
let attrs1 = node1.attrs;
let attrs2 = node2.attrs;
let attrDiffs = [];
for (let key in attrs1) {
if (attrs1[key] !== attrs2[key]) {
attrDiffs.push({
name: key,
oldValue: attrs1[key],
newValue: attrs2[key],
});
}
}
for (let key in attrs2) {
if (!attrs1.hasOwnProperty(key)) {
attrDiffs.push({
name: key,
oldValue: null,
newValue: attrs2[key],
});
}
}
if (attrDiffs.length > 0) {
diffs.push({
type: 'update',
node: node1,
attrs: attrDiffs,
});
}
// 比较子节点
let children1 = node1.children;
let children2 = node2.children;
if (children1.length !== children2.length) {
diffs.push({
type: 'replace',
oldNode: node1,
newNode: node2,
});
return;
}
for (let i = 0; i < children1.length; i++) {
compare(children1[i], children2[i]);
}
}
compare(oldTree, newTree);
return diffs;
}
js发布订阅模式简单实现(vue中,$on, $emit, $off)
function EventBus() {
this._events = {};
}
EventBus.prototype.on = function(type, fn) {
if (!this._events[type]) {
this._events[type] = []
}
this._events[type].push(fn);
}
EventBus.prototype.emit = function(type) {
if (!this._events[type]) {
return;
}
//var args = Array.prototype.slice.call(arguments, 1);
var args = [];
for (var i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
}
var listeners = this._events[type] || [];
for (var i = 0; i < listeners.length; i++) {
listeners[i].apply(this, args); // 执行回调
}
}
EventBus.prototype.once = function(type, fn) {
var self = this;
var onceCallback = function() {
fn.apply(self, arguments); // 正确传递事件参数
self.off(type, onceCallback); // 精确移除当前监听器
};
this.on(type, onceCallback);
}
EventBus.prototype.off = function(type, fn) {
if (!this._events[type]) {
return;
}
if (!fn) {
this._events[type] = undefined;
return;
}
var index = this._events[type].indexOf(fn);
this._events[type].splice(index, 1);
}
EventBus.prototype.offAll = function(type) {
if (this._events[type]) {
delete this._events[type];
} else {
this._events = {};
}
}
使用方法
// 创建实例
var emitter = new EventEmitter();
// 绑定事件
var loginHandler = function(user) {
console.log(user + ' 登录成功');
};
emitter.on('login', loginHandler);
// 触发事件
emitter.emit('login', 'Admin'); // 输出:"Admin 登录成功"
// 移除事件
emitter.off('login', loginHandler);
emitter.emit('login', 'Guest'); // 无输出
使用
var eventEmitter = new EventBus();
function readyFh(val) {
console.log('readyFh', val);
}
function listener1(val) {
console.log('Listener 1 called', val);
}
function listener2(val) {
console.log('Listener 2 called', val);
}
function listener3(val) {
console.log('Listener 3 called', val);
}
function onec5(val) {
console.log(' onec5 事件', val);
}
function onec6(val) {
console.log(' onec6 事件', val);
}
eventEmitter.on('ready', readyFh);
eventEmitter.on('event1', listener1);
eventEmitter.on('event1', listener2);
eventEmitter.on('event2', listener3);
eventEmitter.once('event5', onec5); // 输出: Listener 1 called, Listener 2 called
eventEmitter.once('event6', onec6); // 输出: Listener 1 called, Listener 2 called
eventEmitter.emit('ready', 22); // 输出: Listener 1 called, Listener 2 called
var k222 = document.querySelector("#k222");
k222.onclick = function(e) {
console.log("点击 emit event1,event2")
eventEmitter.emit('event1', 1); // 输出: Listener 1 called, Listener 2 called
eventEmitter.emit('event2', 2); // 输出: Listener 3 called
}
var k223 = document.querySelector("#k223");
k223.onclick = function(e) {
console.log("点击 off event1 有fn ")
eventEmitter.off('event1', listener1); // 输出: Listener 1 called, Listener 2 called
}
var k2123 = document.querySelector("#k2123");
k2123.onclick = function(e) {
console.log("点击 off event1 无fn")
eventEmitter.off('event1'); // 输出: Listener 1 called, Listener 2 called
}
var k224 = document.querySelector("#k224");
k224.onclick = function(e) {
console.log("点击 offAll")
eventEmitter.offAll(); // 输出: Listener 1 called, Listener 2 called
}
var k225 = document.querySelector("#k225");
k225.onclick = function(e) {
console.log("点击 onec event5 event6")
eventEmitter.emit('event5', 5); // 输出: Listener 1 called, Listener 2 called
eventEmitter.emit('event6', 6); // 输出: Listener 1 called, Listener 2 called
}
ie 浏览器刷新去除缓存
方法1
旧窗口刷新无效,换一个新的浏览器窗口页面
document.getElementById("jsds").onclick =function(){
var preWin = window;
window.open(window.location.href, '_blank');
setTimeout(function() {
preWin.open('', '_self').close();
}, 100)
}
方法2
触发beforeDestroy。刷新页面的时候,vue的组件生命周期beforeDestroy和destroyed都没有被触发,所以清除定时器的api也就没有被执行,相关依赖的变量、内存也就没有被释放。
app.vue里面执行了v-if操作,当监听到用户执行刷新的操作时,将v-if的值改为false,组件就会触发beforeDestroy。
<template>
<div class="body">
<div v-if="isShow">
<router-view
/>
</div>
</div>
</template>
data () {
return {
isShow:false,
};
},
mounted () {
window.addEventListener("beforeunload",(e)=> {
console.log('刷新');
this.isShow=false;
window.removeEventListener('beforeunload', ()=>{})//移除这个监听
});
},
vue filter
<div class="flexbox "><span> 截至时间</span> <span v-if="pivot">{{service_end_time|timeTypeFilter("Y-m-d H:i:s")}}</span></div>
filters: {
timeTypeFilter(timestamp,formats) {
if(!timestamp){
return "";
}
function dateFormat (timestamp, formats) {
// formats格式包括
// 1. Y-m-d
// 2. Y-m-d H:i:s
// 3. Y年m月d日
// 4. Y年m月d日 H时i分
formats = formats || 'Y-m-d H:i:s';
var zero = function (value) {
if (value < 10) {
return '0' + value;
}
return value;
};
var arrTimestamp = (timestamp + '').split('');
for (var start = 0; start < 13; start++) {
if (!arrTimestamp[start]) {
arrTimestamp[start] = '0';
}
}
timestamp = arrTimestamp.join('') * 1;
var myDate = timestamp? new Date(timestamp): new Date();
var year = myDate.getFullYear();
var month = zero(myDate.getMonth() + 1);
var day = zero(myDate.getDate());
var hour = zero(myDate.getHours());
var minite = zero(myDate.getMinutes());
var second = zero(myDate.getSeconds());
return formats.replace(/Y|m|d|H|i|s/ig, function (matches) {
return ({
Y: year,
m: month,
d: day,
H: hour,
i: minite,
s: second
})[matches];
});
};
return dateFormat(timestamp,formats);
},
},
axios formData
var params = new FormData();
params.append('name', self.addnewmsg.staffName);
params.append('identNo', self.addnewmsg.identNo); //身份证
let config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'token': sessionStorage.getItem('token')
}
}
axios.post("/manage/project/staff/add", params, config).then(res => {
console.log('addRes',res)
}).catch(res => {
console.log(res)
})
elementui tree checkChange 多次触发
@check-change="checkChange" @check="checkEvent"
checkEvent(item,checkedObj){
this.checkedKeys=checkedObj.checkedKeys;
this.checkedNodes=checkedObj.checkedNodes;
console.log("3里面checkEvent",item,'checkedObj---',checkedObj)
this.$emit("checkChange",{
item:item,
checkedKeys:this.checkedKeys,
checkedNodes:this.checkedNodes
})
},
checkChange(item,check,aa){
console.log("1里面checkChange 多次触发",item,check,aa)
item.myCheck=check;
},
Vue的生命周期
beforeCreate(创建前) 在数据观测和初始化事件还未开始
created(创建后) 完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来
beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
mounted(载入后) 在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。
destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
父子 组件的加载顺序
同步:
import Page from '@/components/page' // 同步方式引入
父子组件的执行顺序为,
父组件beforeCreated
父组件created
父组件beforeMounted
子组件beforeCreated
子组件created
子组件beforeMounted
孙组件beforeCreated
孙组件created
孙组件beforeMounted
孙组件mounted
子组件mounted
父组件mounted
异步:
const Page = () => import ('@/components/page') // 异步引入
父组件beforeCreated
父组件created
父组件beforeMounted
父组件mounted
子组件beforeCreated
子组件created
子组件beforeMounted
子组件mounted
孙组件beforeCreated
孙组件created
孙组件beforeMounted
孙组件mounted
父子 组件的销毁顺序
beforeDestroy钩子函数在实例销毁之前调用。在这一步,实例仍然完全可用。
destroyed钩子函数在Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁(也就是说子组件也会触发相应的函数)。这里的销毁并不指代'抹去',而是表示'解绑'。
销毁时beforeDestory函数的传递顺序为由父到子,destory的传递顺序为由子到父。
.第一次页面加载会触发哪几个钩子?
答:会触发 下面这几个beforeCreate, created, beforeMount, mounted 。
.vue.js的两个核心是什么?
答:数据驱动、组件系统
vue3 proxy的优缺点
性能优化 内存减少
兼容低,没错,IE11都不支持Proxy
vue3的整个数据监听系统都进行了重构,由es5的Object.defineProperty改为了es6的proxy。尤大说,这个新的数据监听系统带来了初始化速度加倍同时内存占用减半的效果。
Vue 2.x里,是通过 递归 + 遍历 data对象来实现对数据的监控的,
Vue 3.x 使用Proxy。
这里面有两点需要强调下:
1、Object.defineProperty需要遍历所有的属性,这就造成了如果vue对象的data/computed/props中的数据规模庞大,那么遍历起来就会慢很多。
2、同样,如果vue对象的data/computed/props中的数据规模庞大,那么Object.defineProperty需要监听所有的属性的变化,那么占用内存就会很大。
Object.defineProperty VS Proxy
Object.definePropety的缺点
除了上面讲,在数据量庞大的情况下Object.defineProperty的两个缺点外,Object.defineProperty还有以下缺点。
1、无法监听es6的Set、WeakSet、Map、WeakMap的变化;
2、无法监听Class类型的数据;
3、属性的新加或者删除也无法监听;
4、数组元素的增加和删除也无法监听 (性能考虑,所以无法监听 )
Proxy应运而生
针对Object.defineProperty的缺点,Proxy都能够完美得解决,它唯一的缺点就是,对IE不友好,所以vue3在检测到如果是使用IE的情况下(没错,IE11都不支持Proxy),会自动降级为Object.defineProperty的数据监听系统。所以如果是IE用户,那么就享受不到速度加倍,内存减半的体验了。
Object.defineProperty初始化
const data = {}
for(let i = 0; i <= 100000; i++) {
data['pro' + i] = i
}
function defineReactive(data, property) {
let value = data[property]
Object.defineProperty(data, property, {
get() {
// console.log(`读取${property}的值为${value}`)
return value
},
set(newVal) {
// console.log(`更新${property}的值为${newVal}`)
}
})
}
for(let property in data) {
defineReactive(data, property)
}
Proxy初始化
const data = {} for(let i = 0; i <= 100000; i++) { data['pro' + i] = i } var proxyData = new Proxy(data, { get(target, property, receiver) { // console.log(`读取${property}的值为${target[property]}`) return Reflect.get(target, property, receiver); }, set(target, property, value, receiver) { // console.log(`更新${property}的值为${value}`) return Reflect.set(target, property, value, receiver); } })
ES6 原生提供的 Proxy 语法很简单,用法如下:
let proxy = new Proxy(target, handler);
let obj = { a : 1 } let proxyObj = new Proxy(obj,{ get : function (target,prop) { return prop in target ? target[prop] : 0 }, set : function (target,prop,value) { target[prop] = 888; } }) console.log(proxyObj.a); // 1 console.log(proxyObj.b); // 0 proxyObj.a = 666; console.log(proxyObj.a) // 888
下面的例子 给予proxy的vue demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>简单版mvvm</title>
</head>
<body>
<div id="app">
<h1>开发语言:{{language}}</h1>
<h2>组成部分:</h2>
<ul>
<li>{{makeUp.one}}</li>
<li>{{makeUp.two}}</li>
<li>{{makeUp.three}}</li>
</ul>
<h2>描述:</h2>
<p>{{describe}}</p>
<p>计算属性:{{sum}}</p>
<input placeholder="123" v-module="language" />
</div>
<script>
function Mvvm(options = {}) {
this.$options = options
let data = this._data = this.$options.data
let vm = initVm.call(this)
initObserve.call(this, data)
initComputed.call(this)
new Compile(this.$options.el, vm)
mounted.call(this._vm)
return this._vm
}
function initVm () {
this._vm = new Proxy(this, {
get: (target, key, receiver) => {
return this[key] || this._data[key] || this._computed[key]
},
set: (target, key, value) => {
return Reflect.set(this._data, key, value)
}
})
return this._vm
}
function initObserve(data) {
this._data = observe(data)
}
function observe(data) {
if (!data || typeof data !== 'object') return data
return new Observe(data)
}
class Observe {
constructor(data) {
this.dep = new Dep()
for (let key in data) {
data[key] = observe(data[key])
}
return this.proxy(data)
}
proxy(data) {
let dep = this.dep
return new Proxy(data, {
get: (target, prop, receiver) => {
if (Dep.target) {
dep.addSub(Dep.target)
}
return Reflect.get(target, prop, receiver)
},
set: (target, prop, value) => {
const result = Reflect.set(target, prop, observe(value))
dep.notify()
return result
}
})
}
}
class Compile{
constructor (el, vm) {
this.vm = vm
this.element = document.querySelector(el)
this.fragment = document.createDocumentFragment()
this.init()
}
init() {
let element = this.element
this.fragment.append(element)
this.replace(this.fragment)
document.body.appendChild(this.fragment)
}
replace(frag) {
let vm = this.vm
Array.from(frag.childNodes).forEach(node => {
let txt = node.textContent
let reg = /\{\{(.*?)\}\}/g
if (node.nodeType === 1) {
let nodeAttr = node.attributes;
Array.from(nodeAttr).forEach(attr => {
let name = attr.name
let exp = attr.value
if (name.includes('v-')){
node.value = vm[exp]
node.addEventListener('input', e => {
let newVal = e.target.value
// 相当于给this.a赋了一个新值
// 而值的改变会调用set,set中又会调用notify,notify中调用watcher的update方法实现了更新
vm[exp] = newVal
})
}
});
} else if (node.nodeType === 3 && reg.test(txt)) {
replaceTxt()
function replaceTxt() {
node.textContent = txt.replace(reg, (matched, placeholder) => {
new Watcher(vm, placeholder, replaceTxt); // 监听变化,进行匹配替换内容
return placeholder.split('.').reduce((val, key) => {
return val[key]
}, vm)
})
}
}
if (node.childNodes && node.childNodes.length) {
this.replace(node)
}
})
}
}
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.filter(item => typeof item !== 'string').forEach(sub => sub.update())
}
}
class Watcher {
constructor (vm, exp, fn) {
this.fn = fn
this.vm = vm
this.exp = exp
Dep.exp = exp
Dep.target = this
let arr = exp.split('.')
let val = vm
arr.forEach(key => {
val = val[key]
})
Dep.target = null
}
update() {
let exp = this.exp
let arr = exp.split('.')
let val = this.vm
arr.forEach(key => {
val = val[key]
})
this.fn(val)
}
}
function initComputed() {
let vm = this
let computed = this.$options.computed
vm._computed = {}
if (!computed) return
Object.keys(computed).forEach(key => {
this._computed[key] = computed[key].call(this._vm)
new Watcher(this._vm, key, val => {
this._computed[key] = computed[key].call(this._vm)
})
})
}
function mounted() {
let mounted = this.$options.mounted
mounted && mounted.call(this)
}
// 写法和Vue一样
let mvvm = new Mvvm({
el: '#app',
data: {
language: 'Javascript',
makeUp: {
one: 'ECMAScript',
two: '文档对象模型(DOM)',
three: '浏览器对象模型(BOM)'
},
describe: '没什么产品是写不了的',
a: 1,
b: 2
},
computed: {
sum() {
return this.a + this.b
}
},
mounted() {
console.log('i am mounted', this.a)
}
})
</script>
</body>
</html>
参考 https://segmentfault.com/a/1190000015460479
清楚销毁事件 一般 beforeDestroy ;实际情况可能 mounted 事件和 beforeDestroy 事件之间有很多其他的代码,这样,很容易忘记自己究竟有没有清除 resize 事件的监听,为了解决这个问题,我们可以在监听 resize 事件之后,紧跟着使用 $on 或者 $once 来监听生命周期如 created 、mounted 等等。
<template>
<div class="echarts"></div>
</template>
<script>
export default {
created() {
// 干了一堆活
},
mounted() {
this.chart = echarts.init(this.$el)
// 请求数据,赋值数据 等等一系列操作...
// 监听窗口发生变化,resize组件
window.addEventListener('resize', this.handleResizeChart)
},
updated() {
// 干了一堆活
},
beforeDestroy() {
// 组件销毁时,销毁监听事件
window.removeEventListener('resize', this.handleResizeChart)
},
methods: {
handleResizeChart() {
this.chart.resize()
},
// 其他一堆方法
}
}
</script>
为了解决这个问题,我们可以在监听 resize 事件之后,紧跟着使用 $on 或者 $once 来监听生命周期钩子函数,如 created 、mounted 等等。
export default {
mounted() {
this.chart = echarts.init(this.$el)
// 请求数据,赋值数据 等等一系列操作...
// 监听窗口发生变化,resize组件
window.addEventListener('resize', this.handleResizeChart)
// 通过hook监听组件销毁钩子函数,并取消监听事件
this.$once('hook:beforeDestroy', () => {
window.removeEventListener('resize', this.handleResizeChart)
})
},
updated() {},
created() {},
methods: {
handleResizeChart() {
// this.chart.resize()
}
}
}
这样全局
Vue的路由实现:hash模式 和 history模式
hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;
特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。
hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
history模式:history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。
history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”
Vuex和全局变量的概念区别
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:1、Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
2、Vuex和全局变量的概念区别
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
1、Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
2、你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。、你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

vue常用的修饰符?
答:.prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发;.capture: 事件侦听,事件发生的时候会调用
vue等单页面应用及其优缺点
答:优点:Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、快速、模块友好。
缺点:不支持低版本的浏览器,最低只支持到IE9;不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一些;不可以使用浏览器的导航按钮需要自行实现前进、后退。
vue props 参数类型
props: {
// 基础类型检测 (`null` 意思是任何类型都可以)
propA: Number,
// 多种类型
propB: [String, Number],
// 必传且是字符串
propC: {
type: String,
required: true
},
// 数字,有默认值
propD: {
type: Number,
default: 100
},
// 数组/对象的默认值应当由一个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
}
refAge: {
type: Number,
default: 0
},
refName: {
type: String,
default: ''
},
hotDataLoading: {
type: Boolean,
default: false
},
hotData: {
type: Array,
default: () => {
return []
}
},
getParams: {
type: Function,
default: () => () => {}
},
meta: {
type: Object,
default: () => ({})
}
vue router
组件内的导航钩子主要有这三种:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。他们是直接在路由组件内部直接进行定义的
const File = {
template: `<div>This is file</div>`,
beforeRouteEnter(to, from, next) {
// do someting
// 在渲染该组件的对应路由被 confirm 前调用
},
beforeRouteUpdate(to, from, next) {
// do someting
// 在当前路由改变,但是依然渲染该组件是调用
},
beforeRouteLeave(to, from ,next) {
// do someting
// 导航离开该组件的对应路由时被调用
}
}
beforeRouteEnter 不能获取组件实例 this,因为当守卫执行前,组件实例被没有被创建出来,剩下两个钩子则可以正常获取组件实例 this
vue父组件如何动态注册子组件
少数情况,我们不知道要 加载那些组件,随着业务动态添加,比如 接口动态加载,根据需要加载组件
<button @click="asdfn">切换组件</button>
挂载的元素地方
<div ref="xxx"></div>
注册组件脚本
registerComponent(templateName,yourNeedEl){
var self=this;
return import(`@/components/${templateName}.vue`).then((component) => {
console.log("component",component)
const cpt = Vue.extend(component.default);
new cpt({
el: yourNeedEl
});
});
},
组件调用
asdfn(){ console.log("tabContent2"); var ele=this.$refs.xxx; this.registerComponent("tabContent2",ele) },
记得引入
import Vue from 'vue'
vue 参数传递
sync
//子组件
<template>
<label class="child">
输入框:
<input :value=val @input="$emit('update',$event.target.value)"/>
</label>
</template>
//父组件
<template>
<div class="parent">
<p>父组件传入子组件的值:{{name}}</p>
<fieldset>
<legend>子组件</legend>
<child :val.sync="name">
</child>
</fieldset>
</div>
</template>
$attrs
想象一下,你打算封装一个自定义input组件——MyInput,需要从父组件传入type,placeholder,title等多个html元素的原生属性。此时你的MyInput组件props如下:
props:['type','placeholder','title',...]
$attrs专门为了解决这种问题而诞生,这个属性允许你在使用自定义组件时更像是使用原生html元素。比如:
//父组件 <my-input placeholder="请输入你的姓名" type="text" title="姓名" v-model="name"/>
my-input的使用方式就像原生的input一样。而MyInput并没有设置props,如下
<template>
<div>
<label>输入框:</label><input v-bind="$attrsAll" @input="$emit('input',$event.target.value)"/>
</div>
</template>
<script>
export default {
inheritAttrs:false,
computed: {
$attrsAll() {
return {
value: this.$vnode.data.model.value,
...this.$attrs
}
}
}
}
</script>
比较迷惑的一点是给子组件设置:value="name"相当于给子组件设置props:['value'],所以在MyInput中直接从$attrs获取不到value,需要重新包装$attrsAll,添加value属性。所以子组件还有下面写法,我倾向于这种写法,因为它更优雅
<template>
<div>
<label>输入框:</label><input v-bind="$attrs" :value="value" @input="$emit('input',$event.target.value)"/>
</div>
</template>
<script>
export default {
inheritAttrs:false,
props:['value']
}
</script>
$listener
同上面$attrs属性一样,这个属性也是为了在自定义组件中使用原生事件而产生的。比如要让前面的MyInput组件实现focus事件,直接这么写是没用的
<my-input @focus="focus" placeholder="请输入你的姓名" type="text" title="姓名" v-model="name"/>
必须要让focus事件作用于MyInput组件的input元素上,最终的MyInput源码如下:
<template>
<div>
<label>输入框:</label><input v-bind="$attrsAll" v-on="$listenserAll"/>
</div>
</template>
<script>
export default {
inheritAttrs:false,
props:['value'],
computed:{
$attrsAll() {
return {
value: this.value,
...this.$attrs
}
},
$listenserAll(){
return Object.assign(
{},
this.$listeners,
{input:(event) => this.$emit('input',event.target.value)})
}
}
}
</script>
组件 v-model
参考 https://juejin.cn/post/6987958410276782094


vue beforeEach
</script>
<script>
console.log('22 click2');
const obj3 = {
name: 'QuintionTang',
routerList: ['页面1的url', '页面2的url', '页面3的url'],
beforeEach: function(fn) {
//fn=f(to,form next){}
console.log('beforeEach 里面调用 to');
this.registerHook(this.routerList, fn)
},
registerHook: function(arrList, fn) {
var to = arrList[0];
var form = arrList[1];
var nextNoop = function(to) {
console.log("里面 next 接收参数 拦截跳转", to)
if (to === false) {
// next(false) -> abort navigation, ensure current URL
} else if (typeof to === 'string' || (typeof to === 'object' && (typeof to.path ===
'string' || typeof to.name === 'string'))) {
// next('/') or next({ path: '/' }) -> redirect
} else {
//next() confirm transition and pass on the value
}
return to
};
//console.log('beforeEach 里面2 ', to, form);
return fn && fn.call(this, to, form, nextNoop)
}
};
obj3.beforeEach((to, from, next) => {
console.log('beforeEach 外面调用 to', to, "from", from);
next(123)
});
</script>
vue中methods、watch、computed之间的差别对比以及适用场景
一、computer
当页面中有某些数据依赖其他数据进行变动的时候,可以使用计算属性。
<p id="app"> {{fullName}} </p> <script> var vm = new Vue({ el: '#app', data: { firstName: 'Foo', lastName: 'Bar', }, computed: { fullName: function () { return this.firstName + ' ' + this.lastName } } }) </script>
需要注意的是,就算在data中没有直接声明出要计算的变量,也可以直接在computed中写入。
计算属性默认只有getter,可以在需要的时候自己设定setter:
// ...
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
这个时候在控制台直接运行vm.fullName = ‘bibi wang’,相应的firstName和lastName也会改变。
适用场景:

二、watch
watch和computed很相似,watch用于观察和监听页面上的vue实例,当然在大部分情况下我们都会使用computed,但如果要在数据变化的同时进行异步操作或者是比较大的开销,那么watch为最佳选择。watch为一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。直接引用文档例子
var vm = new Vue({
el: '#app',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
如果在data中没有相应的属性的话,是不能watch的,这点和computed不一样。
适用场景

三、methods
方法,跟前面的都不一样,我们通常在这里面写入方法,只要调用就会重新执行一次,相应的有一些触发条件,在某些时候methods和computed看不出来具体的差别,但是一旦在运算量比较复杂的页面中,就会体现出不一样。
需要注意的是,computed是具有缓存的,这就意味着只要计算属性的依赖没有进行相应的数据更新,那么computed会直接从缓存中获取值,多次访问都会返回之前的计算结果。
vuex --> computed-->watch-->回调方法
vue 页面插入了非虚拟dom的处理,例如地图的气泡 点击事件的绑定
var MyComponent = Vue.extend({
data: function() {
return {
message: '快速录入信息',
taskUrl: '',
message2: '查看详情>>',
telPhone:'拨打电话',
}
},
// props:['data'],
props:['myname'],
template: '<a @click="show(message)">{{message }}</a>',
methods: {
show(message) {
var self=this;
// console.log('地图气泡 哈哈哈',message,this.message2,this.myProps,this.myType);
// this.$router.push({path: '/detail', query:defaults });
//快速录入信息点
// this.myRouter.push({ path: '/PositioningAndPublishing', query: { 'testId': this.props.id } })
if(this.myType=="go_addEvent"){
var highwayId=this.myProps.highwayId;//K1289+820
var highwayDirectionId=this.myProps.highwayDirectionId;//
var positionCode=this.myProps.positionCode;//
var cache=+new Date;
self.bus.$emit('upPositionNumber', positionCode) // 触发事件sh
//this.$emit("upDataPoint",this.myProps)
var path='/PositioningAndPublishing';
var query={
"testId": self.myProps.id ,
"highwayId": highwayId ,
"highwayDirectionId": highwayDirectionId ,
"positionCode": positionCode,
"cache": cache
}
console.log(path,"0myProps",query);
self.myRouter.push({ path: path, replace: true, query:query })
}
//查看详情
// this.myRouter.push({ path: '/evnetWork', query: { 'testId': this.props.id } })
// evnetWork?testId='+id+'"
if(this.myType=="go_Detail"){
var cache=+new Date;
this.myRouter.push({ path: '/evnetWork',replace: true, query: { 'testId': this.myProps.id ,'cache': cache }})
}
},
},
created(){
//this.message=this.message2;
if(this.myType=="go_addEvent"){
this.message="快速录入信息"
}
//查看详情
// this.myRouter.push({ path: '/evnetWork', query: { 'testId': this.props.id } })
// evnetWork?testId='+id+'"
if(this.myType=="go_Detail"){
this.message="查看详情>>"
}
//console.log("cre33ated",this,this.message);
},
mounted() {
//console.log("TextIconOverlay",new BMapLib.TextIconOverlay());
},
beforeDestroy () {
this.bus.$off('upPositionNumber');
this.bus.$off('upTestId'); // 触发事件sh
},
});
调用
var component = new MyComponent().$mount();
infoBox =new BMapLib.InfoBox(map,content,opts);
infoBox.enableAutoPan();
infoBox.addEventListener('open',function(type, target, point){ //窗口打开是,隐藏自带的关闭按钮
//console.log(type,target,point);
document.querySelectorAll('.boxClass')[0].clientLeft;
document.querySelectorAll('.boxClass')[0].classList.add("myps1sss");
setTimeout(()=>{
// console.log("component go_addEvent",component)
console.log("dat222a2",data2.positionCode);
component.myProps=data2;
component.myRouter=self.$router;
component.myType="go_addEvent";
component.message="快速录入信息";
document.querySelectorAll('.luxx')[0].appendChild(component.$el);
},100)
})
或者直接dom 绑定
self.lookTimeer=setTimeout(()=>{ if(document.querySelector('.jshows')){ document.querySelector('.jshows').removeEventListener("click",looksss); document.querySelector('.jshows').addEventListener("click",looksss) } },200)
vue 指令 directives
export default {
bind (el, binding, vnode) {
function documentHandler (e) {
if (el.contains(e.target)) {
return false;
}
if (binding.expression) {
binding.value(e);
}
}
el.__vueClickOutside__ = documentHandler;
document.addEventListener('click', documentHandler);
},
update () {
},
unbind (el, binding) {
document.removeEventListener('click', el.__vueClickOutside__);
delete el.__vueClickOutside__;
}
/**
* v-clickoutside
* @desc 点击元素外面才会触发的事件
* @example
* ```vue
* <div v-element-clickoutside="handleClose">
* ```
*/
export default {
bind(el, binding, vnode) {
nodeList.push(el);
const id = seed++;
el[ctx] = {
id,
documentHandler: createDocumentHandler(el, binding, vnode),
methodName: binding.expression,
bindingFn: binding.value
};
},
update(el, binding, vnode) {
el[ctx].documentHandler = createDocumentHandler(el, binding, vnode);
el[ctx].methodName = binding.expression;
el[ctx].bindingFn = binding.value;
},
unbind(el) {
let len = nodeList.length;
for (let i = 0; i < len; i++) {
if (nodeList[i][ctx].id === el[ctx].id) {
nodeList.splice(i, 1);
break;
}
}
delete el[ctx];
}
};
on-change
$ npm install on-change
use strict';
module.exports = (object, onChange) => {
const handler = {
get(target, property, receiver) {
try {
return new Proxy(target[property], handler);
} catch (err) {
return Reflect.get(target, property, receiver);
}
},
defineProperty(target, property, descriptor) {
onChange();
return Reflect.defineProperty(target, property, descriptor);
},
deleteProperty(target, property) {
onChange();
return Reflect.deleteProperty(target, property);
}
};
return new Proxy(object, handler);
};
使用方法
const object = {
foo: false,
a: {
b: [
{
c: false
}
]
}
};
let index = 0;
const watchedObject = onChange(object, function (path, value, previousValue, applyData) {
console.log('Object changed:', ++index);
console.log('this:', this);
console.log('path:', path);
console.log('value:', value);
console.log('previousValue:', previousValue);
console.log('applyData:', applyData);
});
watchedObject.foo = true;
树节点
deal(nodes, predicate) {
// 如果已经没有节点了,结束递归
if (!(nodes && nodes.length)) {
return;
}
const newChildren = [];
for (const node of nodes) {
if (predicate(node)) {
// 如果自己(节点)符合条件,直接加入到新的节点集
newChildren.push(node);
// 并接着处理其 children
node.children = this.deal(node.children, predicate);
} else {
// 如果自己不符合条件,需要根据子集来判断它是否将其加入新节点集
// 根据递归调用 deal() 的返回值来判断
const subs = this.deal(node.children, predicate);
// 以下两个条件任何一个成立,当前节点都应该加入到新子节点集中
// 1. 子孙节点中存在符合条件的,即 subs 数组中有值
// 2. 自己本身符合条件
if ((subs && subs.length) || predicate(node)) {
node.children = subs;
newChildren.push(node);
}
/* if (subs && subs.length) {
// 1. 如果子孙集中有符合要求的节点(返回 [...]),加入
node.children = subs;
newChildren.push(node);
} */
// 2. 否则,不加入(因为整个子集都没有符合条件的)
}
}
return newChildren.length ? newChildren : void 0;
},
ztree 向上递归
function getTreeList(node){
var menuIds=[];
function getTreeId(node) {
if (node.level > 0) {
var one=node.getParentNode()
menuIds.push(one);
getTreeId(one);
return menuIds;
}
}
return getTreeId(node);
}
ztree展开节点
function expandLevel(treeObj,node,level)
{
var childrenNodes = node.children||[];
console.log("childrenNodes",childrenNodes)
for(var i=0;i<childrenNodes.length;i++)
{
treeObj.expandNode(childrenNodes[i], true, true, false);
level=level-1;
if(level>0)
{
expandLevel(treeObj,childrenNodes[i],level);
}
}
}
dom 检测
// 判断是否在浏览器环境中,还是在node中
const isServer = typeof window === 'undefined';
/* istanbul ignore next */
const resizeHandler = function(entries) {
for (let entry of entries) {
// entry.target通过这个访问监听的DOM对象,然后,这个对象上有__resizeListeners__属性。遍历存储的监听回调
const listeners = entry.target.__resizeListeners__ || [];
if (listeners.length) {
listeners.forEach(fn => {
fn();
});
}
}
};
/* istanbul ignore next */
// 接受DOM元素和方法
export const addResizeListener = function(element, fn) {
if (isServer) return;
if (!element.__resizeListeners__) {
// 在DOM元素对象上,设置__resizeListeners__属性,存储监听器(回调函数)。
element.__resizeListeners__ = [];
// ResizeObserver:MDN上解释可以监听DOM元素的变化(什么变化呢?位置变化和大小变化)
element.__ro__ = new ResizeObserver(resizeHandler);
element.__ro__.observe(element);// 这里观察主体是DOM对象。(本质就是观察一个对象)
}
element.__resizeListeners__.push(fn);
};
/* istanbul ignore next */
export const removeResizeListener = function(element, fn) {
if (!element || !element.__resizeListeners__) return; // 先判断下
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
if (!element.__resizeListeners__.length) {
// 取消element.__ro__观察者身上所有的元素的观察
element.__ro__.disconnect();
}
};
post man
<template>
<view class="post-man" :style="{'height':height +'px' }" :class="[myClass]">
<view class="title">接口测试<text v-if="nowTime" style="padding-left: 5px;;">请求时间:{{nowTime||""}}</text></view>
<view class="content" >
<view class="one-list" >
<view class="left"><text class="title2" :class="{'titleTlue':isHidenMethod}" @click="hideTxtHandle('Method')">请求Method</text> </view>
<view class="myradio" :class="{'isHidenHeader':isHidenMethod}">
<text class=" myradio-one" :class="{'myradio-active':method_1=='GET'}" @click="selectRadio('GET')">GET</text>
<text class=" myradio-one" :class="{'myradio-active':method_1=='POST'}" @click="selectRadio('POST')">POST</text>
</view>
</view>
<view>
</view>
<view class="one-list" >
<view class="left"><text class="title2" :class="{'titleTlue':isHidenUrl}" @click="hideTxtHandle('Url')">请求Url</text> =>http://a.test.cn/b/api/list <text class="blue1 titleTlue" v-if="url_1&&url_1.length>4" @click="clearTxt('Url')">清除</text><text class="blue3 titleTlue" v-if="url_1&&url_1.length>4" @click="copyToClip('Url')" style="right: 40px;">复制</text></view>
<view class="right" :class="{'isHidenHeader':isHidenUrl}" >
<textarea ref="Url" :maxlength="-1" v-model="url_1" class="right-textarea" placeholder-class="textarea1" auto-height :placeholder="placeholder_url" />
</view>
</view>
<!-- <view>https://www.baidu.com/h5/api/v1/city_list</view> -->
<view class="one-list" >
<view class="left"><text :data-length="header_1.length" class="title2" :class="{'titleTlue':isHidenHeader}" @click="hideTxtHandle('Header')">请求Header</text> <text @click="formatParms('Header',-1)">=></text><text class="red5">{"</text>key<text class="red5">"</text>:<text class="red5">"</text>val<text class="red5">"}</text><text class="blue2 titleTlue" v-if="header_1&&header_1.length>4" @click="formatParms('Header')">美化</text><text class="blue1 titleTlue" v-if="header_1&&header_1.length>4" @click="clearTxt('Header')">清除</text><text class="blue3 titleTlue" v-if="header_1&&header_1.length>4" @click="copyToClip('Header')">复制</text></view>
<view class="right" :class="{'isHidenHeader':isHidenHeader}" style="max-height:142px;overflow-x: hidden;overflow-y: scroll;">
<textarea ref="Header" :maxlength="-1" v-model="header_1" class="right-textarea" placeholder-class="textarea1" auto-height :placeholder="placeholder_header" />
</view>
</view>
<view class="one-list" >
<view class="left"><text :data-length="query_1.length" class="title2" :class="{'titleTlue':isHidenQuery}" @click="hideTxtHandle('Query')">请求Query</text><text @click="formatParms('Query',-1)">=></text><text class="red5">{"</text>key<text class="red5">"</text>:<text class="red5">"</text>val<text class="red5">"}</text>|| <text class="red5">[]</text><text class="blue2 titleTlue" v-if="query_1&&query_1.length>4" @click="formatParms('Query')">美化</text><text class="blue1 titleTlue" v-if="query_1&&query_1.length>4" @click="clearTxt('Query')">清除</text><text class="blue3 titleTlue" v-if="query_1&&query_1.length>4" @click="copyToClip('Query')">复制</text></view>
<view class="right" :class="{'isHidenHeader':isHidenQuery}" >
<textarea ref="Query" :maxlength="-1" v-model="query_1" class="right-textarea" placeholder-class="textarea1" style="min-height:120px" auto-height :placeholder="placeholder_query" />
</view>
</view>
<!-- <view>--query_1 长度:{{query_1.length}}--query_1 结果:{{query_1}}</view> -->
<view class="one-list" >
<view class="send" @click="sendAjax">发送请求</view>
</view>
<view class="one-list" >
<view class="clearbtn" @click="clearTxt('all')">清空表单</view>
</view>
<view class="one-list" style="margin-bottom: 5px;">
<view class="left"><text class="title2">请求结果</text> <text class="titleTlue" @click="copyToClip('Result')" style="padding-left: 10px;;">复制</text> </view>
<view class="right result" >
<text ref="Result" :maxlength="-1" class="resultInner" :class="{'red':statusCode!=200}">{{jsonToStr(result_1)}}</text>
</view>
</view>
<view class="one-list" >
<view class="dixian" ref="line">--我是底线--</view>
</view>
</view>
</view>
</template>
<script>
const Base64 = require('@/utils/js-base64/base64').Base64;
import { clientId, clientSecret, tokenName,urlConfig } from '@/js/----';
function trimStrBoth(str){//去除首尾空格
return str.replace(/(^\s*)|(\s*$)/g,"");
}
function trimStrAll(str){//去除全部空格
return str.replace(/\s*/g,"")
}
function isJson(obj){
return typeof(obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && !obj.length;
}
function isArray(value) {
if (typeof Array.isArray === "function") {
return Array.isArray(value);
} else {
return Object.prototype.toString.call(value) === "[object Array]";
}
}
function toJson2(data,arr){
try {
return JSON.parse(data)
}
catch(error) {
// console.log('toJson error----------------',error)
return arr?[]:{};
}
}
function serialize(obj) {
var ary = [];
for (var p in obj)
if (obj.hasOwnProperty(p) && obj[p]) {
ary.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
}
return ary.join('&');
}
export default {
name:"post-man",
props: {
myClass:{
type: String,
default: ""
},
height:{
type: [Number,String],
default: "auto"
},
showBack:{
type: Boolean,
default: true
}
},
data() {
return {
placeholder_url:'http://a.test.cn/b/api/list',
placeholder_header:'{"key":"value"}',
placeholder_query:'{"key":"value"}',
nowTime:"",
statusCode:200,
isHidenMethod:false,
isHidenUrl:false,
isHidenHeader:false,
isHidenQuery:false,
url_1:'',
method_1:"GET",
header_1:'',
query_1:'',
result_1:"",
};
},
computed: {
},
onLoad(option){
},
onShow() {
},
created() {
// console.log("11 拿到缓存的 created 1");
this.$nextTick(()=>{
var url_1=uni.getStorageSync('url_1')||"";
var method_1=uni.getStorageSync('method_1')||"GET";
var query_1=uni.getStorageSync('query_1')||"";
// console.log("22 拿到缓存的 created",url_1,method_1,query_1);
this.url_1=url_1;
this.method_1=method_1;
this.query_1=this.jsonToStr(query_1);
if(this.query_1=="{}"){
this.query_1="";
}
})
},
methods: {
strToObject (str,prop) {
if(typeof str != 'string'){
return str;
}
if(!str||!str.length){
return {}
}
var data=trimStrBoth(str)
if(!data||!data.length){
return {}
}
if(typeof data == 'string'){
var res=data;
var firstTxt=res.charAt(0);
var lastTxt=res.charAt(res.length - 1);;
var isArraryStr=false;
if (/(\')/.test(res)) {
// console.log("json 存在单引号")
res=res.replace(/'/g, '"');//单引号替换成双引号
}
if(firstTxt=='['&&lastTxt==']'){//是数组字符串
// console.log("arr 字符串")
isArraryStr=true;
res=res.replace(/,]/g, ']'); //将arr字符串中最后一个元素","逗号去掉
res=res.replace(/,}/g, '}');//将json字符串中最后一个元素","逗号去掉
res= toJson2(res,'arr')
//console.log("arr 字符串",res)
return res
}
if(!isArraryStr&&(firstTxt!='{'&&lastTxt!='}')){
res='{'+res+'}';
res=res.replace(/,]/g, ']'); //将arr字符串中最后一个元素","逗号去掉
res=res.replace(/,}/g, '}');//将json字符串中最后一个元素","逗号去掉
//console.log("json 字符串1",res)
res= toJson2(res)
return res
}
res=res.replace(/,]/g, ']'); //将arr字符串中最后一个元素","逗号去掉
res=res.replace(/,}/g, '}');//将json字符串中最后一个元素","逗号去掉
//console.log("json 字符串2",res);
res= toJson2(res);
// var res0=' {"id":"1","name":"张珊","i2d":"21",} ';
// var res0=' {"id":"1","name":"张珊","i2d":"21","sa":[1,2,3,],"i4d":"14",} ';
// var res0=' [{"id":"1","name":"张珊1",},{"id":"2","name":"张珊2"}] ';
return res
}
},
//
sendAjax(){
var timestamp=(+new Date)
this.nowTime=this.formatTime(timestamp);
var method=this.method_1||"get";
var url=this.url_1;
var data_str=this.query_1;
var header_str=this.header_1||{};
var data=this.strToObject(data_str,'Query')
var header_Old=this.strToObject(header_str,'Header')
var header={};
var token=uni.getStorageSync("accessToken");
if (token) {
if(!header[tokenName]){
header[tokenName] = 'Bearer ' + token;
header['Authorization'] = `Basic ${Base64.encode(`${clientId}:${clientSecret}`)}`;
}
} else {
header['Authorization'] = `Basic ${Base64.encode(`${clientId}:${clientSecret}`)}`;
}
//console.log("最后header_Old",typeof header_Old,header_Old)
var lastHeader={...header_Old,...header}
//console.log("最后Header",lastHeader)
//请求的参数美化显示到表单
this.header_1= this.jsonToStr(this.objClone(lastHeader));
this.query_1 = this.jsonToStr(this.objClone(data));
this.statusCode=200;
this.result_1="";
method=method.toUpperCase();
uni.setStorageSync('method_1', method);
uni.setStorageSync('url_1', this.url_1);
uni.setStorageSync('header_1', this.header_1);
uni.setStorageSync('query_1', this.query_1);
// console.log("最后 this.header_1", this.header_1)
//console.log(data,"缓存 url",url,'缓存 query_1',this.query_1)
// console.log("-----------最后请求 1---------------------------")
// console.log("请求地址url",url)
// console.log("请求方式method",method)
// console.log("请求header",header)
// console.log("请求参数data",data)
// console.log("----------最后请求 2---------------------------")
//处理 get 请求 data为JSON;
if(method=="GET"&&isJson(data)){
//console.log("GET 请求 data isJson",isJson(data))
var separator =url.lastIndexOf('?') != -1 ? "&" : "?";
var part_search=serialize(data);
var part_url=url+separator ;
// http://aa.test.com/search?&id=1&age=22 => http://aa.test.com/search?id=1&age=22
var part_new_url=part_url.replace("?&","?");
var part_href=part_new_url + part_search;
url= part_href
// data={} //根据需要 这边设置
//console.log("2处理 get 处理 序列换 url--",url)
//console.log("2处理 get 处理 序列换 part_href--",part_href)
}
console.log("最后data",data)
this.ajaxSend(url,method,data,lastHeader);
},
ajaxSend(url,method,data,header,dataType){
uni.request({
url: url,
method:method|| "GET",
data: data,
dataType:dataType|| 'json',
header:header || {
"content-type": "application/json"
},
success: (res) => {
console.log("request success res",res)
this.statusCode=res.statusCode;
if(res.statusCode==200){
this.result_1=res.data
}else{
this.result_1="请求状态:"+res.statusCode+" ===> " + res.data
}
},
fail: (err) => {
//console.log("request fail res",err)
this.statusCode="";
this.result_1=err.errMsg||"接口请求异常"
}
})
},
jsonToStr(data,prop){
if(!data){
return "";
}
if(typeof data == 'string'){
if(data=="{}"){
return "";
}
return data;
}
return JSON.stringify(data , null, 4);
},
objClone(obj) {
return JSON.parse(JSON.stringify(obj));
},
selectRadio(val){
this.method_1=val||"GET";
this.method_1=this.method_1.toUpperCase();
uni.setStorageSync('method_1', this.method_1);
},
hideTxtHandle(val){
if(val=="Method"){
this.isHidenMethod=!this.isHidenMethod;
}
if(val=="Url"){
this.isHidenUrl=!this.isHidenUrl;
}
if(val=="Header"){
this.isHidenHeader=!this.isHidenHeader;
}
if(val=="Query"){
this.isHidenQuery=!this.isHidenQuery;
}
},
copyToClip(elemRef){
//console.log("1复制 elemRef",elemRef,)
// this.$nextTick(()=>{
var prop=elemRef+'_1';
prop=prop.toLowerCase();
var text = this[prop];
if(prop=="result_1"){
text=this.jsonToStr(this.result_1);
//console.log("2 getValue", prop)
}
setTimeout(()=>{
console.log('复制的信息:',text );
uni.setClipboardData({
data: text ,
showToast:true,
success: function (res) {
// uni.showToast({
// title: '复制成功',
// });
}
});
},30)
},
formatParms(val,trimAll){
if(val=="Header"){
if(trimAll==-1){
this.header_1= trimStrAll(this.header_1);
return false;
}
var header=this.strToObject(this.header_1,'Header')
this.header_1= this.jsonToStr(this.objClone(header));
}
if(val=="Query"){
if(trimAll==-1){
this.query_1 = trimStrAll(this.query_1);
return false;
}
var data=this.strToObject(this.query_1,'Query')
this.query_1 = this.jsonToStr(this.objClone(data));
}
},
clearTxt(val){
if(val=="all"){
this.url_1="";
this.header_1='';
this.query_1='';
this.result_1="";
this.nowTime="";
this.statusCode="";
}else{
var prop=val+"_1";
prop=prop.toLowerCase();
//console.log("clearTxt prop",prop)
this[prop]="";
}
},
formatTime(timestamp, nos = 1, bindf = '-') {
if (!timestamp) {
return
}
var date = new Date(timestamp)
var strLen = timestamp.toString().length
// 判断时间戳是否不足13位,不足时低位补0,即乘以10的所差位数次方
if (strLen < 13) {
var sub = 13 - strLen
sub = Math.pow(10, sub) // 计算10的n次方
date = new Date(timestamp * sub)
}
var y = date.getFullYear()
var M = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
var d = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var h = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
if (nos === 1) { //2022-08-12 12:10:22
return y + bindf + M + bindf + d + ' ' + h + ':' + m + ':' + s
} else if (nos === 9) {//08月12日12:10
return M + '月' + d + '日' + h + ':' + m
} else if (nos === 10) {//08月12日 12:10:22
return M + '月' + d + '日' + ' ' + h + ':' + m
}
}
}
}
</script>
<style>
.post-man{ border: 2px solid red; min-height:400px; position: relative; z-index:36; background-color: #fff;;}
.post-man .title{ text-align: center;; padding: 12px; background-color:#eee; font-size: 14px;;}
.post-man .content{ padding:0 10px; min-height: 500px;;}
.post-man .content .left{ height:44px; line-height:44px;position: relative;-webkit-user-select:text;user-select:text;}
.post-man .content .right{width: 100%; position: relative; padding:10px;border: 1px solid #CCC;background-color: #F9F9F9; }
.post-man .content .myradio{width: 100%; position: relative;border: 0px solid #CCC; height: 42px ; line-height:42px; display: flex; padding-top:0 ;}
.post-man .content .myradio .myradio-one{ flex:1; border:1px solid #409eff;background-color: #fff; text-align: center;}
.post-man .content .myradio .myradio-one.myradio-active{ color: #fff;background-color: #409eff;}
.post-man .title2{color: #333; font-weight: 600;}
.post-man .blue1{position: absolute; right: 0; top:0;;}
.post-man .blue2{ position: absolute; right: 40px; top:0;}
.post-man .blue3{ position: absolute; right: 80px; top:0;}
.post-man .titleTlue{color: #409eff; ;}
.post-man .red5{color: red;}
.post-man .content .radio{ margin-right:20px;}
.post-man .content .one-list{ min-height:44px; margin-bottom:5px;}
.post-man .content .right-input{ border: 1px solid #999; height:32px; line-height:32px; width: 100%;;}
.post-man .content .right-textarea{-webkit-user-select:text;user-select:text; min-height:18px;height:auto; line-height:18px; width: 100%; box-sizing: border-box;background-color: #F9F9F9; overflow: hidden;white-space:pre-wrap;word-wrap:break-word;word-break:break-all;}
.post-man .clearbtn{text-align: center; padding: 12px; color: #409eff;background-color: #fff;border:1px solid #409eff; }
.post-man .send{ text-align: center; padding: 12px; color: #fff;background-color: #409eff;border-color: #409eff}
.post-man .content .result{ min-height:44px; width: 100%; box-sizing: border-box; border:1px solid #CCC; }
.post-man .content .resultInner{white-space:pre-wrap;word-wrap:break-word;word-break:break-all;}
.post-man .content .result .red{ color: red;;}
.post-man .content .dixian{height:44px; line-height:44px; color: #eee;text-align: center;margin-bottom:5px;}
.post-man .textarea1{color: #ddd; font-size:14px; }
.post-man .isHidenHeader{ height: 0!important; padding:0 !important; border: 0!important; overflow:hidden;;}
</style>
时间选择
<template>
<view>
<uni-popup ref="popupTime" type="bottom" :safeArea="false" :mask-click="true" @maskClick="change2">
<view class="coupon-time_wrap">
<view class="fsize28 fweight5 _title"> 选择预计送达时间 </view>
<view class="flexbox" style="overflow: hidden;height:40vh">
<view class="day1" style="" v-if="dayList">
<view class="day1-one" v-for="(item,index) in dayList":key='index' :class="show1Index == index ? 'day1-one-on' : ''" @click="chooseDAY(item,index)">
{{item.text}}
</view>
</view>
<view class="day2 flex1">
<scroll-view scroll-y="true" style="height:40vh" :scroll-top="scrolltop">
<view class="day2-one_box">
<view @click="chooseTime(item,index)" class="day2-one flexbox" v-for="(item,index) in couponList":key='index' :class="index == show2Index ? 'day2-one-on' : ''" >
{{item}} <!-- {{item.startTime}}-{{item.endTime}} -->
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
function format0Time(time) { //时间为个位数则补零
return time < 10 ? '0' + time : time;
}
//import { formatTime } from '@/js/unti.js'
function formatTime(timestamp, nos = 1, bindf = '-') {
if (!timestamp) {
return
}
var date = new Date(timestamp)
var strLen = timestamp.toString().length
// 判断时间戳是否不足13位,不足时低位补0,即乘以10的所差位数次方
if (strLen < 13) {
var sub = 13 - strLen
sub = Math.pow(10, sub) // 计算10的n次方
date = new Date(timestamp * sub)
}
var y = date.getFullYear()
var M = format0Time(date.getMonth() + 1);
var d =format0Time(date.getDate());
var h =format0Time(date.getHours()) ;
var m =format0Time(date.getMinutes());
var s =format0Time(date.getSeconds());
if (nos === -1) {
return{y:y,M:M,d:d,h:h,m:m,s:s,}
}
if (nos === 1) { //2022-08-12 12:10:22
return y + bindf + M + bindf + d + ' ' + h + ':' + m + ':' + s
} else if (nos === 2) { //2022-08-12
return y + bindf + M + bindf + d
}
}
export default{
components:{ },
props:{
list:{
type: Array,
default: ()=> []
},
minStartTime:{
type:Number,
default: 0
},
delayTime:{
type:Number,
default: 2
},
},
data() {
return {
formatTime,
scrolltop:0,
show1Index:0,
show2Index:0,
dayList:[],
couponList:[],
show1Item:{},
show2Item:{},
};
},
computed:{
},
watch:{
list: {
handler(newName, oldName) {
//console.log("watch---shop_id--- --",newName,"productid",this.productid)
if(newName!=oldName&&newName.length){
this.dayList=newName;
this.reservable_time(newName);
}
},
immediate: true,
deep:true,
},
minStartTime: {
handler(newName,oldName) {
if(newName!=oldName) {
//console.log("watch--- shopId -- minStartTime -- dayList --- ",newName)
if(this.dayList.length){
this.chageTime2(this.dayList)
}
}
},
immediate: true,
deep:true,
},
},
created(){
},
onShow(){
this.scrolltop=0;
},
methods:{
reservable_time(dayList){
var dayList=dayList||[];
dayList.forEach((item)=>{
item.origintime=item.time
})
//console.log("内部dayList",dayList)
var date =new Date();
var day0 = +new Date;
var day1 = day0+24*60*60*1000;
var week0 = date.getDay();
var arr_week = new Array("日", "一", "二", "三", "四", "五", "六");
//拿到今天
var today=dayList[week0];
var str0=arr_week[week0];
today.text="今天(周"+str0+")";
today.dateTimeStamp=day0;
today.date=this.formatTime(day0,2)
//拿到明天
var week1=(week0+1)>6?0:(week0+1)
var tomorrow=dayList[week1];
var str1=arr_week[week1];
tomorrow.text="明天(周"+str1+")";
tomorrow.dateTimeStamp=day1;
tomorrow.date=this.formatTime(day1,2)
dayList=[today,tomorrow] ;
//this.$emit("businessStatus",business_status)
this.chageTime2(dayList)
},
chageTime2(dayList){
var minStartTime=this.minStartTime||"";
//var minStartTime="";
var date7 =minStartTime?new Date(minStartTime):new Date();
var nowHours=date7.getHours();
var nowMinutes=date7.getMinutes();
nowHours=parseInt(nowHours);
nowMinutes=parseInt(nowMinutes);
if(!minStartTime){
nowHours=nowHours+parseInt(this.delayTime);
}
//分割时间段
dayList.forEach((item,index)=>{
var time=item.origintime;
var satrt_time=time.split("-")[0];
var end_time=time.split("-")[1];
var satrt_hh=satrt_time.split(":")[0];
var satrt_ss=satrt_time.split(":")[1];
var end_hh=end_time.split(":")[0];
var end_ss=end_time.split(":")[1];
if(index==0){//当前时间比较
satrt_hh=parseInt(satrt_hh);
if(nowHours>=satrt_hh){
satrt_hh=nowHours;
}
if(nowMinutes>0&&nowMinutes<=30){
satrt_ss=30;
}else{
satrt_hh=parseInt(satrt_hh)+1;
satrt_ss=0;
}
}
if(parseInt(satrt_ss)==0){ //
satrt_hh=parseInt(satrt_hh);
satrt_ss=0;
}
else if(parseInt(satrt_ss)>0&&parseInt(satrt_ss)<=30){ //
satrt_hh=parseInt(satrt_hh);
satrt_ss=30;
}
else{//
satrt_hh=parseInt(satrt_hh)+1;
satrt_hh=satrt_hh>24?24:satrt_hh;
satrt_ss=0;
}
if(parseInt(end_ss)==0){ //
end_hh=parseInt(end_hh);
end_ss=0;
}
if(parseInt(end_ss)>0&&parseInt(end_ss)<30){ //
end_hh=parseInt(end_hh);
end_ss=0;
}
if(parseInt(end_ss)>=30){//
end_hh=parseInt(end_hh);
end_ss=30;
}
item.time=format0Time(satrt_hh)+":"+format0Time(satrt_ss)+"-"+ format0Time(end_hh)+":"+format0Time(end_ss) //'11:00 - 12:00'
if(index==0){
if(nowHours>=parseInt(end_hh)){
item.time="";
}
}
item.list=[];
if(item.time){
let openTime = [item.time] // ['11:00 - 12:00']; => 11:00-11:30 12:30:12:00
let y = new Date().getFullYear();
let m = new Date().getMonth()+1;
let d = new Date().getDate();
let start = [], end = []; //
for(let i=0,len=openTime.length;i<len;i++) {
let [s, e] = openTime[i].split('-');
start.push(new Date(y+'/'+m+'/'+d+' '+s));
end.push(new Date(y+'/'+m+'/'+d+' '+e));
}
let list = [];
for(let i=0,len=start.length;i<len;i++) {
let len2 = (end[i].getTime() - start[i].getTime())/(30*60*1000);
for(let j=0;j<len2;j++) {
if(start[i].getTime()+30*60*1000<=end[i].getTime()) {
let ss = new Date(start[i].getTime()+30*60*1000*j), ee = new Date(start[i].getTime()+30*60*1000*(j+1));
list.push([format0Time(ss.getHours())+':'+format0Time(ss.getMinutes()), format0Time(ee.getHours())+':'+format0Time(ee.getMinutes())]);
}
}
}
list = list.map(item => {
return item.join('-');
})
item.list=list
}
//console.log(";list", item.list)
})
this.dayList=dayList ;
this.chooseDAY(dayList[0],0)
},
chooseTime(item,index){
this.show2Index=index;
this.show2Item=item;
//console.log("chooseTime",item,'index',index)
this.sendCoupon({
day:this.dayList[this.show1Index],
timeIndex:this.show2Index,
time:this.show2Item,
},1)
},
chooseDAY(item,index){
this.show1Index=index;
this.couponList=this.dayList[index].list;
if(index==0){
var arr0=['立即送出']
this.couponList=[...arr0,...this.couponList]
}
this.show2Index=0;
this.show2Item=this.couponList[0];
this.scrolltop=-1;
this.$nextTick(()=>{
this.scrolltop=0;
})
// console.log("chooseDAY",item,'index',index)
},
change2(e){
//console.log("change2",e)
this.sendCoupon({
day:this.dayList[this.show1Index],
timeIndex:this.show2Index,
time:this.show2Item,
},0)
},
showPopup(){
this.$refs.popupTime.open()
},
sendCoupon(item,flag){
this.$emit("timeSelect",item)
if(flag){
this.$refs.popupTime.close()
}
},
}
}
</script>
<style scoped>
.coupon-time_wrap{height: 55vh;padding: 45rpx 0 0;border-radius: 16rpx 16rpx 0 0;background-color: #fff;
padding-bottom: constant(safe-area-inset-bottom);padding-bottom: env(safe-area-inset-bottom);
display: flex;flex-direction: column; z-index:555;
}
.coupon-time_wrap ._title{margin-bottom: 40rpx;text-align: center;}
.day1{width:240rpx; background-color:#f6f6f6; height:40vh}
.day1 .day1-one{padding-left:25rpx; height: 44px; line-height: 44px;;}
.day1 .day1-one-on{ background-color:#fff !important}
.day2{}
.day2-one_box{ padding: 0 25rpx;}
.day2 .day2-one{; height: 44px; line-height: 44px; border-bottom:1rpx solid #eee;}
.day2 .day2-one-on{ color:#bb944f !important;}
.day2 .day2-one:last-child{border-bottom:0}
</style>
uni-app getRect
function getRect (id,vue,useUni,delay){ //ID选择器:#the-id #可以不要 紧紧支持id
var that=vue;//Vue实例才拥有_isVue 属性,在此是避免观测Vue实例对象。
var id =id;
if(!vue||!vue._isVue){
console.log("请传入vue 对象")
//Promise.resolve(false);
return false;
}
var useUni=useUni||false;
var platform="APP-PLUS";
//#ifdef APP-PLUS-NVUE || APP-NVUE
platform = "APP-NVUE";///**App nvue*/ //APP-PLUS-NVUE或APP-NVUE App nvue 页面
//#endif
if(useUni){
platform="APP-PLUS";
}
//console.log("getRect id",id)
//console.log("getRect this",JSON.stringify(that))
if(platform=="APP-PLUS"){////nvue不支持 uni.createSelectorQuery,
return new Promise(resolve => {
setTimeout(() => {
var selector=id.indexOf("#")!=-1?id:("#"+id);
//console.log("selector ",selector)
var query = uni.createSelectorQuery().in(that);
query.select(selector).boundingClientRect(res => {
res.methodType='uni';
resolve(res)
//console.log("uni getRect 1得到布局位置信息" , res);
//res={"id":"as2ss","dataset":{"ref":"as2ss"},"left":12,"right":312,"top":286,"bottom":336,"width":300,"height":50}
}).exec();
}, delay||100);
})
}else{
////#ifdef APP-NVUE
const dom = weex.requireModule('dom');
var id2=id.replace("#",'')
//console.log("id2 ",id2)
return new Promise(resolve => {
setTimeout(() => {
var talkcontent=that.$refs[id2]
var result = dom.getComponentRect(talkcontent, res => {
res.size.methodType='weex';
resolve(res.size)
// console.log("weex getRect 2得到布局位置信息" , res);
//res.size= {"right":300,"left":0,"width":300,"bottom":71,"top":21,"height":50}
})
}, delay||100);
})
//// #endif
}
}


浙公网安备 33010602011771号