提到vue,大家肯定会想到双向数据绑定,数据驱动视图,虚拟dom,diff算法等等这些概念。在使用vue的时候,会感觉到它的数据双向绑定真的很爽啊。会不会在你用了很长时间后,会好奇到,这个是如何实现的?或者在遇到问题的时候,会不会想到,为啥这个数据并没有响应式的发生改变,视图怎么没有变化...当你抱着这些疑问的时候你肯定会想了解其中的原理了。那么我也是..现在把之前学习和理解的内容整理一下,如果有什么问题,请多多指教~
众所周知,是个vue的使用者都知道其响应式数据是结合Object.defineProperty()这个方法实现,那么关于这个方法的使用和作用,请自行了解..一个合格的jser应该都知道的。
核心是观察者模式,数据是我们的被观察者,发生改变的时候,会通知我们所有的观察者。
关于上面这个图,请仔细的看下,从vue官网拔过来的...。主要包括数据变化更新视图,视图变化更新数据。其中主要涉及Observe,Watcher,Dep这三个类,要了解vue的响应式原理,弄清楚这三个类是如何运作的,这样就能够大概了解了。
Observe是数据监听器,其实现方法就是Object.defineProperty。
Watcher是我们所说的观察者。
Dep是可以容纳观察者的一个订阅器,主要收集订阅者,然后在发生变化的时候通知每个订阅者。
observe的主要工作是针对vue中的响应式数据属性进行监听,所以通过递归的方法遍历属性。
function observe(data) {
if(!data || typeof data !== 'object') {
return
}
Object.keys(data).map((k) => {
definereactive(data, k, data[k])
})
}
function defineReactive(data, k, val) {
observe(val)
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
return val
},
set: function(newval) {
val = newval
}
})
}
订阅器负责收集观察者,然后在属性变化的时候通知观察者进行更新。
function Dep() {
this.subs = []
}
Dep.prototype.addSub = function(sub) {
this.subs.push(sub)
}
Dep.prototype.notify = function() {
this.subs.forEach(v => {
v.update()
})
}
那么什么时候将一个观察者添加到Dep里面呢?设计上是将观察者的添加放在getter里面。
上面说到Watcher是在初始化的时候在getter里面放进订阅器Dep中。那么在Watcher初始化的时候触发getter。那么还有个问题,在getter中是如何获取到Watcher的?这个我们可以暂时缓存的放到Dep的静态属性Dep.target上面。
function Watcher(vm, exp, cb) {
this.vm = vm
this.exp = exp
this.cb = cb
}
Watcher.prototype.update = function() {}
Watcher.prototype.run = function() {
var value = this.vm.data[this.exp];
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
}
Watcher.prototype.get = function() {
Dep.target = this // 在target上进行缓存
var value = this.vm.data[exp] //触发getter
Dep.target = null //清空null
return value
}
这个时候我们还需要修改下Observe:
function defineReactive(data, k, val) {
observe(val)
var dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
if(Dep.target) {
dep.addSub(Dep.target)
}
return val
},
set: function(newval) {
if(newval === val) {
return
}
val = newval
dep.notify()
}
})
}
看到这里,大致应该都明白如何把Observe,Dep,Watcher如何运作到一起了吧。
function MyVue(data, el, exp) {
this.data = data
observe(this.data)
el.innerhtml = this.data[exp]
new Watcher(this, exp, function(value) {
el.innerHTML = value
})
}
function MyVue(data, el, exp) {
this.data = data
Object.key(data).forEach(function(k) => {
this.proxyKeys(k)
})
observe(data);
el.innerHTML = this.data[exp]; // 初始化模板数据的值
new Watcher(this, exp, function (value) {
el.innerHTML = value;
});
return this;
}
MyVue.prototype.proxyKeys = function(k) {
Object.defineProperty(this, k, {
enumerable: false,
configurable: true,
get: function proxyGetter() {
return this.data[key];
},
set: function proxySetter(newVal) {
this.data[key] = newVal;
}
})
}
上面的例子中没有解析DOM节点的操作,只是针对指定dom节点进行内容的替换。那么compile要进行哪写操作呢?
function nodeToFragment(el) {
var fragment = document.createDocumentFragment()
var child = el.firstChild
while(child) {
fragment.appendChild(child)
child = el.firstChild
}
return fragment
}
接下来需要遍历各个节点,对含有相关指定的节点进行特殊处理。
function compileElement(el) {
var childNodes = el.childNodes;
var self = this;
[].slice.call(childNodes).forEach(function(node) {
var reg = /\{\{(.*)\}\}/;
var text = node.textContent;
if (self.isTextNode(node) && reg.test(text)) { // 判断是否是符合这种形式{{}}的指令
self.compileText(node, reg.exec(text)[1]);
}
if (node.childNodes && node.childNodes.length) {
self.compileElement(node); // 继续递归遍历子节点
}
});
}
function compileText (node, exp) {
var self = this;
var initText = this.vm[exp];
updateText(node, initText); // 将初始化的数据初始化到视图中
new Watcher(this.vm, exp, function (value) { // 生成订阅器并绑定更新函数
self.updateText(node, value);
});
},
function updateText (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
}
获取到最外层节点后,调用compileElement函数,对所有子节点进行判断,如果节点是文本节点且匹配{{}}这种形式指令的节点就开始进行编译处理,编译处理首先需要初始化视图数据,对应上面所说的步骤1,接下去需要生成一个并绑定更新函数的订阅器,对应上面所说的步骤2。这样就完成指令的解析、初始化、编译三个过程,一个解析器Compile也就可以正常的工作了。为了将解析器Compile与监听器Observer和订阅者Watcher关联起来,我们需要再修改一下类MySelf函数:
function MyVue(opt) {
let self = this
this.vm = this
this.data = opt.data
Object.key(this.data).forEach(function(k) {
this.proxyKeys(k)
})
observe(this.data)
new Compile(opt, this.vm)
return this
}
感觉大致说的差不多了...希望能够对大家有点点启发,谢谢。
原文:https://segmentfault.com/a/1190000021971917
rem是相对于根元素html,这样就意味着,我们只需要在根元素确定一个px字号,则可以来算出元素的宽高。
通过模块化缩放,使用传统属性和calc()来动态缩放你的字体大小.为字体大小使用百分比.给文本内容和媒体查询使用em,针对不同视口尺寸使用不同缩放值.视口越小,缩放比例越小,使用媒体查询或者media()函数基于视口来改变比例和基础字号
在目前的前端开发中,我们经常需要进行响应式的网站开发。本文着重介绍一下弹性图片,也就是响应式图片的解决方案:js或服务端、srcset 、sizes 、picture标签、svg图片
HTML5+CSS3响应式垂直时间轴,使用了HTML5标签<section>,时间轴中所有的内容包括标题、简介、时间和图像都放在.cd-timeline-block的DIV中,多个DIV形成一个序列,并把这些DIV放在<section>中。
CSS 变量是 CSS 引入的一个新特性,目前绝大多数浏览器已经支持了,它可以帮助我们用更少的代码写出同样多的样式,大大提高了工作效率,本篇文章将教你如何使用 CSS 变量(css variable)。CSS中原生的变量定义语法是:--*,变量使用语法是:var(--*),其中*表示变量名称
Vue通过设定对象属性的setter/getter方法来监听数据的变化,通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。
Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的JavaScript 对象,而当你修改它们时,视图会进行更新,这使得状态管理非常简单直接,我们可以只关注数据本身
常见的布局方案:固定布局:以像素作为页面的基本单位,不管设备屏幕及浏览器宽度,只设计一套尺寸;可切换的固定布局:同样以像素作为页面单位,参考主流设备尺寸
响应式布局,即 Responsive design,在实现不同屏幕分辨率的终端上浏览网页的不同展示方式。通过响应式设计能使网站在手机和平板电脑上有更好的浏览阅读体验。响应式布局的关键不仅仅在于布局
说到响应式原理其实就是双向绑定的实现,说到 双向绑定 其实有两个操作,数据变化修改dom,input等文本框修改值的时候修改数据1. 数据变化 -> 修改dom;2. 通过表单修改value -> 修改数据
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!