vue2.5.0学习预备知识
注意attention: 转载学习来源–Vue.js 技术揭秘
MVVM之监测一个普通对象的变化
注意attention: 转载学习来源-JavaScript实现MVVM之我就是想监测一个普通对象的变化
Object.defineProperty {#define_property}
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。
var o = {}; // 创建一个新对象
// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(o, "a", {
value : 37, // 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined
writable : true, // 当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。
enumerable : true, //当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
configurable : true //当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
});
// 对象o拥有了属性a,值为37
// 在对象中添加一个属性与存取描述符的示例
var bValue;
Object.defineProperty(o, "b", {
get : function(){ // 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)
return bValue;
},
set : function(newValue){ // 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。
bValue = newValue;
},
enumerable : true,
configurable : true
});
o.b = 38; // 对象o拥有了属性b,值为38
// o.b的值现在总是与bValue相同,除非重新定义o.b
// 数据描述符和存取描述符不能混合使用
Object.defineProperty(o, "conflict", {
value: 0x9f91102,
get: function() {
return 0xdeadbeef;
}
});
// throws a TypeError: value appears only in data descriptors, get appears only in accessor descriptors
// 对于对象的监控
var oldVal = obj[key];
Object.defineProperty(obj, key, {
get: function(){
return oldVal;
},
set: (function(newVal){
if(oldVal !== newVal){
if(OP.toString.call(newVal) === '[object Object]'){
this.observe(newVal);
}
this.$callback(newVal, oldVal);
oldVal = newVal;
}
}).bind(this)
});
// 对于数组的监控
overrideArrayProto(array){
// 保存原始 Array 原型
var originalProto = Array.prototype,
// 通过 Object.create 方法创建一个对象,该对象的原型就是Array.prototype
overrideProto = Object.create(Array.prototype),
self = this,
result;
// 遍历要重写的数组方法
Object.keys(OAM).forEach(function(key, index, array){
var method = OAM[index],
oldArray = [];
// 使用 Object.defineProperty 给 overrideProto 添加属性,属性的名称是对应的数组函数名,值是函数
Object.defineProperty(overrideProto, method, {
value: function(){
oldArray = this.slice(0);
var arg = [].slice.apply(arguments);
// 调用原始 原型 的数组方法
result = originalProto[method].apply(this, arg);
// 对新的数组进行监测
self.observe(this);
// 执行回调
self.$callback(this, oldArray);
return result;
},
writable: true,
enumerable: false,
configurable: true
});
}, this);
// 最后 让该数组实例的 __proto__ 属性指向 假的原型 overrideProto
array.__proto__ = overrideProto;
}
rollup
// src/main.js
import foo from './foo.js';
export default function () {
console.log(foo);
}
// src/foo.js
export default 'hello world!';
// rollup.config.js
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
}
};
// 命令行 rollup main.js -f cjs -o cjs_bundle.js
nextTick事件机制
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在”任务队列“的尾部添加一个事件,因此要等到同步任务和”任务队列”现有的事件都处理完,才会得到执行。
除了setTimeout和setInterval这两个方法,Node.js还提供了另外两个与”任务队列”有关的方法:process.nextTick 和 setImmediate。它们可以帮助我们加深对”任务队列”的理解。 process.nextTick 方法可以在当前”执行栈”的尾部—-下一次Event Loop(主线程读取”任务队列”)之前—-触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。setImmediate方法则是在当前”任务队列”的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像.
- MessageChannel– MDN MessageChannel
//在以下代码块中,您可以看到使用MessageChannel构造函数实例化了一个channel对象。当iframe加载完毕,我们使用MessagePort.postMessage方法把一条消息和MessageChannel.port2传递给iframe。handleMessage处理程序将会从iframe中(使用MessagePort.onmessage监听事件)接收到信息,将数据其放入innerHTML中。
var channel = new MessageChannel();
var para = document.querySelector('p');
var ifr = document.querySelector('iframe');
var otherWindow = ifr.contentWindow;
ifr.addEventListener("load", iframeLoaded, false);
function iframeLoaded() {
// otherWindow其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames
// transfer 可选 是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
otherWindow.postMessage('Hello from the main page!', '*', [channel.port2]);
}
channel.port1.onmessage = handleMessage;
function handleMessage(e) {
para.innerHTML = e.data;
}
// 跨域读写
// https://a.com
var iframe = document.createElement('iframe');
iframe.style.css = 'position:absolute;width:1px;height:1px;left:-9999px;';
document.body.appendChild(iframe);
iframe.src = 'https://b.com';
iframe.addEventListener('load',function(){
// otherWindow其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames
// 通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。
iframe.contentWindow.postMessage('aaa', 'https://b.com');
}, false);
window.addEventListener('message', function(event) {
// 处理接收回来的事件
// event.origin调用 postMessage 时消息发送方窗口的 origin . 这个字符串由 协议、“://“、域名、“ : 端口号”拼接而成。
console.log("收到" + event.origin + "消息:" + event.data);
})
// https://b.com
window.addEventListener("message", function(event) {
// 设置或者读取cookie啥的
// event.source对发送消息的窗口对象的引用; 可以使用此来在具有不同origin的两个窗口之间建立双向通信。
event.source.postMessage('我来自b.com', 'https://a.com');
}, false);
Macrotask 和 microtask 都是属于上述的异步任务中的一种,我们先看一下他们分别是哪些 API :
1)macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
2)microtasks: process.nextTick, Promises, Object.observe(废弃), MutationObserver
- vue2 源码实现
// /src/core/util/env.js
// 闭包实现 返回函数queueNextTick,callbacks对象保存需要回调的,timerFunc设置异步执行方式
export const nextTick = (function () {
const callbacks = []
let pending = false
let timerFunc
function nextTickHandler () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
// An defer asynchronousring mechanism.
// In pre 2.4, we used to use microtasks (Promise/MutationObserver)
// but microtasks actually has too high a priority and fires in between
// supposedly sequential events (e.g. #4521, #6690) or even between
// bubbling of the same event (#6566). Technically setImmediate should be
// the ideal choice, but it's not available everywhere; and the only polyfill
// that consistently queues the callback after all DOM events triggered in the
// same loop is by using MessageChannel.
/* istanbul ignore if */
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(nextTickHandler)
}
} else if (typeof MessageChannel !== 'undefined' && (
isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
const channel = new MessageChannel() // 使用MessageChannel
const port = channel.port2
channel.port1.onmessage = nextTickHandler
timerFunc = () => {
port.postMessage(1)
}
} else
/* istanbul ignore next */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
// use microtask in non-DOM environments, e.g. Weex
const p = Promise.resolve()
timerFunc = () => {
p.then(nextTickHandler)
}
} else {
// fallback to setTimeout
timerFunc = () => {
setTimeout(nextTickHandler, 0)
}
}
return function queueNextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
_resolve = resolve
})
}
}
})()
performance
// Start with one mark.
performance.mark("mySetTimeout-start");
// Wait some time.
setTimeout(function() {
// Mark the end of the time.
performance.mark("mySetTimeout-end");
// Measure between the two different markers.
performance.measure(
"mySetTimeout",
"mySetTimeout-start",
"mySetTimeout-end"
);
// Get all of the measures out.
// In this case there is only one.
var measures = performance.getEntriesByName("mySetTimeout");
var measure = measures[0];
console.log("setTimeout milliseconds:", measure.duration)
// Clean up the stored markers.
performance.clearMarks();
performance.clearMeasures();
}, 1000);
// vue2源码 /src/core/util/perf.js
if (process.env.NODE_ENV !== 'production') {
const perf = inBrowser && window.performance
/* istanbul ignore if */
if (
perf &&
perf.mark &&
perf.measure &&
perf.clearMarks &&
perf.clearMeasures
) {
mark = tag => perf.mark(tag)
measure = (name, startTag, endTag) => {
perf.measure(name, startTag, endTag)
perf.clearMarks(startTag)
perf.clearMarks(endTag)
perf.clearMeasures(name)
}
}
}
cache缓存
/**
* Create a cached version of a pure function.
*/
export function cached(fn){
const cache = Object.create(null)
return (function cachedFn (str) {
const hit = cache[str]
return hit || (cache[str] = fn(str))
})
}
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
let aaDom = idToTemplate('aa')
// 比如节流函数
function throttle(fn, wait) {
let last = 0;
return (function(){
let now = new Date();
if(now - last < wait) {
return;
}
last = now;
return fn.apply(this, arguments);
});
}
this.orderBtnEveThro = throttle(this.orderBtnEve, 1000);
passive
由于浏览器无法预先知道一个事件处理函数中会不会调用 preventDefault(),它需要等到事件处理函数执行完后,才能去执行默认行为,然而事件处理函数执行是要耗时的,这样一来就会导致页面卡顿;
我们可以通过传递 passive 为 true 来明确告诉浏览器,事件处理程序不会调用 preventDefault 来阻止默认滑动行为。
export let supportsPassive = false
if (inBrowser) {
try {
const opts = {}
// passive: Boolean,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
Object.defineProperty(opts, 'passive', ({
get () {
/* istanbul ignore next */
supportsPassive = true
}
}: Object)) // https://github.com/facebook/flow/issues/285
window.addEventListener('test-passive', null, opts)
} catch (e) {}
}
compositionstart/compositionend
注意attention: input 的 compositionstart 和 compositionend 事件(禁止非直接输入)
在 web 开发中,我们通常需要对输入的内容进行校验。这段代码虽然执行起来没有什么问题,但是会产生非直接输入,比方说我们输入“树莓派”,中间过程会输入拼音,每次输入字母都会触发input事件,然而当中文输入完成之前,都属于非直接输入。
- compositionstart 是指中文输入法开始输入触发,每次输入开始仅执行一次,执行的开始是 end 事件结束了才会触发;
- compositonupdate 是指中文输入法在输入时触发,也就是可能得到 shu’mei 这种内容,这里返回的内容是实时的,仅在 start 事件触发后触发,输入时实时触发;
- compositionend 是指中文输入法输入完成时触发,这是得到的结果就是最终输入完成的结果,此事件仅执行一次。 需要特别注意的是:该事件触发顺序在 input 事件之后,故而需要在此事件的处理逻辑里调用一次 input 里边的业务逻辑;
// 添加标记位 lock ,当用户未输入完时,lock 为 true
var lock = false;
var inputEle = document.getElementById('inputEle');
// input 事件中的处理逻辑, 这里仅仅打印文本
var todo = function (text) {
console.log(text)
};
inputEle.addEventListener('compositionstart', function () {
lock = true;
});
inputEle.addEventListener('compositionend', function (event) {
lock = false;
// compositionend 事件发生在 input 之后,故此需要此处调用 input 中逻辑
todo(event.target.value);
});
inputEle.addEventListener('input', function (event) {
// 忽略一切非直接输入,不做逻辑处理
if (!lock) todo(event.target.value);
});
Snabbdom
FLIP
注意attention: 学习参考文章-使用 FLIP 来提高 Web 动画的性能
LIP 是将一些开销高昂的动画,如针对 width,height,left 或 top 的动画,映射为 transform 动画。通过记录元素的两个快照,一个是元素的初始位置(First – F),另一个是元素的最终位置(Last – L),然后对元素使用一个 transform 变换来反转(Invert – I),让元素看起来还在初始位置,最后移除元素上的 transform 使元素由初始位置运动(Play – P)到最终位置;
// Get the first position.
var first = el.getBoundingClientRect();
// Now set the element to the last position.
el.classList.add('totes-at-the-end');
// Read again. This forces a sync
// layout, so be careful.
var last = el.getBoundingClientRect();
// You can do this for other computed
// styles as well, if needed. Just be
// sure to stick to compositor-only
// props like transform and opacity
// where possible.
var invert = first.top - last.top;
// Invert.
el.style.transform =
`translateY(${invert}px)`;
// Wait for the next frame so we
// know all the style changes have
// taken hold.
requestAnimationFrame(function() {
// Switch on animations.
el.classList.add('animate-on-transforms');
// GO GO GOOOOOO!
el.style.transform = '';
});
// Capture the end with transitionend
el.addEventListener('transitionend',
tidyUpAnimations);
// 或者利用 Web Animations API https://github.com/web-animations/web-animations-js
// Get the first position.
var first = el.getBoundingClientRect();
// Move it to the end.
el.classList.add('totes-at-the-end');
// Get the last position.
var last = el.getBoundingClientRect();
// Invert.
var invert = first.top - last.top;
// Go from the inverted position to last.
var player = el.animate([
{ transform: `translateY(${invert}px)` },
{ transform: 'translateY(0)' }
], {
duration: 300,
easing: 'cubic-bezier(0,0,0.32,1)',
});
// Do any tidy up at the end
// of the animation.
player.addEventListener('finish',
tidyUpAnimations);
// requestAnimationFrame
var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';
function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.left = Math.min(progress / 10, 200) + 'px';
if (progress < 2000) {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);