热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

vue实现原理解析及一步步实现vue框架

深入解析vue1实现原理,并实现vue双向数据绑定模型vueImitate,此模型(vueImitate)只适用于学习和了解vue实现原理;无法作为项目中使用,没有进行任何异常错误

深入解析vue 1实现原理,并实现vue双向数据绑定模型
vueImitate,此模型(vueImitate)只适用于学习和了解vue实现原理;无法作为项目中使用,没有进行任何异常错误处理及各种使用场景的兼容;但通过此项目,可以让你:

  • 深入了解vue实现原理
  • 亲手一步一步自己实现vue相应功能,包括双向绑定、指令如v-model、v-show、v-bind等

整体效果如下:

《vue实现原理解析及一步步实现vue框架》

下面我们重头开始框架的实现,我们知道,vue的使用方式如下:

var vm = new Vue({
el: 'root',
data() {
return {
message: 'this is test',
number: 5,
number1: 1,
number2: 2,
showNode: false
}
},
methods: {
add() {
this.number1 += 1;
this.number += 1;
},
show() {
this.showNode = !this.showNode;
}
}
})

由此可见,vue为一个构造函数,并且调用时传入一个对象参数,所以主函数vueImitate可以如下,源码可见这里;并对参数进行对应的初始化处理:

// init.js
export default function vueImitate(options) {
this.optiOns= options || {};
this.selector = options.el ? ('#' + options.el) : 'body'; // 根节点selector
this.data = typeof options.data === 'function' ? options.data() : options.data; // 保存传入的data
this.el = document.querySelectorAll(this.selector)[0]; // 保存根节点
this._directives = [];
}

此时可以使用new vueImitate(options)的方式进行调用,首先,我们需要界面上展示正确的数据,也就是将下面页面进行处理,使其可以正常访问;

《vue实现原理解析及一步步实现vue框架》

我们可以参考vue的实现方式,vue将{{ }}这种绑定数据的方式转化为指令(directive),即v-text类似;而v-text又是如何进行数据绑定的呢?通过下面代码可知,是通过对文本节点重新赋值方式实现,源码见这里:

export default {
bind () {
this.attr = this.el.nodeType === 3
? 'data'
: 'textContent'
},
update (value) {
this.el[this.attr] = value
}
}

那么,问题来了,如果需要按照上面的方式实现数据的绑定,我们需要将现在的字符串{{number}}转化为一个文本节点,并对它进行指令化处理;这些其实也就是vue compile(编译)、link过程完成的,下面我们就先实现上面功能需求;

compile

整个编译过程肯定从根元素开始,逐步向子节点延伸处理;

export default function Compile(vueImitate) {
vueImitate.prototype.compile = function() {
let nodeLink = compileNode(this.el),
nodeListLink = compileNodeList(this.el.childNodes, this),
_dirLength = this._directives.length;
nodeLink && nodeLink(this);
nodeListLink && nodeListLink(this);
let newDirectives = this._directives.slice(_dirLength);
for(let i = 0, _i = newDirectives.length; i <_i; i++) {
newDirectives[i]._bind();
}
}
}
function compileNode(el) {
let textLink, elementLink;
// 编译文本节点
if(el.nodeType === 3 && el.data.trim()) {
textLink = compileTextNode(el);
} else if(el.nodeType === 1) {
elementLink = compileElementNode(el);
}
return function(vm) {
textLink && textLink(vm);
elementLink && elementLink(vm);
}
}
function compileNodeList(nodeList, vm) {
let nodeLinks = [], nodeListLinks = [];
if(!nodeList || !nodeList.length) {
return;
}
for(let i = 0, _i = nodeList.length; i <_i; i++) {
let node = nodeList[i];
nodeLinks.push(compileNode(node)),
nodeListLinks.push(compileNodeList(node.childNodes, vm));
}
return function(vm) {
if(nodeLinks && nodeLinks.length) {
for(let i = 0, _i = nodeLinks.length; i <_i; i++) {
nodeLinks[i] && nodeLinks[i](vm);
}
}
if(nodeListLinks && nodeListLinks.length) {
for(let i = 0, _i = nodeListLinks.length; i <_i; i++) {
nodeListLinks[i] && nodeListLinks[i](vm);
}
}
}
}

如上代码,首先,我们通过定义一个Compile函数,将编译方法放到构造函数vueImitate.prototype,而方法中,首先主要使用compileNode编译根元素,然后使用compileNodeList(this.el.childNodes, this)编译根元素下面的子节点;而在compileNodeList中,通过对子节点进行循环,继续编译对应节点及其子节点,如下代码:

// function compileNodeList
for(let i = 0, _i = nodeList.length; i <_i; i++) {
let node = nodeList[i];
nodeLinks.push(compileNode(node)),
nodeListLinks.push(compileNodeList(node.childNodes, vm));
}

然后进行递归调用,直到最下层节点:而在对节点进行处理时,主要分为文本节点和元素节点;文本节点主要处理上面说的{{number}}的编译,元素节点主要处理节点属性如v-modelv-textv-showv-bind:click等处理;

function compileTextNode(el) {
let tokens = parseText(el.wholeText);
var frag = document.createDocumentFragment();
for(let i = 0, _i = tokens.length; i <_i; i++) {
let token = tokens[i], el = document.createTextNode(token.value)
frag.appendChild(el);
}
return function(vm) {
var fragClOne= frag.cloneNode(true);
var childNodes = Array.prototype.slice.call(fragClone.childNodes), token;
for(let j = 0, _j = tokens.length; j <_j; j++) {
if((token = tokens[j]) && token.tag) {
let _el = childNodes[j], description = {
el: _el,
token: tokens[j],
def: publicDirectives['text']
}
vm._directives.push(new Directive(vm, _el, description))
}
}
// 通过这儿将`THIS IS TEST {{ number }} test` 这种转化为三个textNode
if(tokens.length) {
replace(el, fragClone);
}
}
}
function compileElementNode(el) {
let attrs = getAttrs(el);
return function(vm) {
if(attrs && attrs.length) {
attrs.forEach((attr) => {
let name = attr.name, description, matched;
if(bindRE.test(attr.name)) {
description = {
el: el,
def: publicDirectives['bind'],
name: name.replace(bindRE, ''),
value: attr.value
}
} else if((matched = name.match(dirAttrRE))) {
description = {
el: el,
def: publicDirectives[matched[1]],
name: matched[1],
value: attr.value
}
}
if(description) {
vm._directives.push(new Directive(vm, el, description));
}
})
}
}
}

这里,先主要说明对文本节点的处理,我们上面说过,我们需要对{{number}}之类进行处理,我们首先必须将其字符串转化为文本节点,如this is number1: {{number1}}这种,我们必须转换为两个文本节点,一个是this is number1: ,它不需要进行任何处理;另一个是{{number1}},它需要进行数据绑定,并实现双向绑定;因为只有转化为文本节点,才能使用v-text类似功能实现数据的绑定;而如何进行将字符串文本分割为不同的文本节点呢,那么,就只能使用正则方式let reg = /\{\{(.+?)\}\}/ig;{{ number }}这种形式数据与普通正常文本分割之后,再分别创建textNode,如下:

function parseText(str) {
let reg = /\{\{(.+?)\}\}/ig;
let matchs = str.match(reg), match, tokens = [], index, lastIndex = 0;
while (match = reg.exec(str)) {
index = match.index
if (index > lastIndex) {
tokens.push({
value: str.slice(lastIndex, index)
})
}
tokens.push({
value: match[1],
html: match[0],
tag: true
})
lastIndex = index + match[0].length
}
return tokens;
}

通过上面parseText方法,可以将this is number: {{number}}转化为如下结果:

《vue实现原理解析及一步步实现vue框架》

转化为上图结果后,就对返回数组进行循环,分别通过创建文本节点;这儿为了性能优化,先创建文档碎片,将节点放入文档碎片中;

// function compileTextNode
// el.wholeText => 'this is number: {{number}}'
let tokens = parseText(el.wholeText);
var frag = document.createDocumentFragment();
for(let i = 0, _i = tokens.length; i <_i; i++) {
let token = tokens[i], el = document.createTextNode(token.value)
frag.appendChild(el);
}

而在最后编译完成,执行linker时,主要做两件事,第一是对需要双向绑定的节点创建directive,第二是将整个文本节点进行替换;怎么替换呢?如最开始是一个文本节点this is number: {{number}},经过上面处理之后,在frag中其实是两个文本节点this is number: {{number}};此时就使用replaceChild方法使用新的节点替换原始的节点;

// compile.js
function compileTextNode(el) {
let tokens = parseText(el.wholeText);
var frag = document.createDocumentFragment();
for(let i = 0, _i = tokens.length; i <_i; i++) {
let token = tokens[i], el = document.createTextNode(token.value)
frag.appendChild(el);
}
return function(vm) {
var fragClOne= frag.cloneNode(true);
var childNodes = Array.prototype.slice.call(fragClone.childNodes), token;

// 创建directive
......
// 通过这儿将`THIS IS TEST {{ number }} test` 这种转化为三个textNode
if(tokens.length) {
replace(el, fragClone);
}
}
}
// util.js
export function replace (target, el) {
var parent = target.parentNode
if (parent) {
parent.replaceChild(el, target)
}
}

替换后结果如下图:

《vue实现原理解析及一步步实现vue框架》

经过与最开始图比较可以发现,已经将this is number: {{number}} middle {{number2}}转化为this is number: number middle number2;只是此时,仍然展示的是变量名称,如number,number2;那么,我们下面应该做的肯定就是需要根据我们初始化时传入的变量的值,将其进行正确的展示;最终结果肯定应该为this is number: 5 middle 2;即将number替换为5、将number2替换为2;那么,如何实现上述功能呢,我们上面提过,使用指令(directive)的方式;下面,就开始进行指令的处理;

Directive(指令)

对于每一个指令,肯定是隔离开的,互相不受影响且有自己的一套处理方式;所以,我们就使用对象的方式;一个指令就是一个实例化的对象,彼此之间互不影响;如下代码:

export default function Directive(vm, el, description) {
this.vm = vm;
this.el = el;
this.description = description;
this.expression = description ? description.value : '';
}

在创建一个指令时,需要传入三个参数,一个是最开始初始化var vm = new vueImitate(options)时实例化的对象;而el是需要初始化指令的当前元素,如

this is test

,需要创建v-show的指令,此时的el就是当前的p标签;而description主要包含指令的描述信息;主要包含如下:

// 源码见 './directives/text.js'
var text = {
bind () {
this.attr = this.el.nodeType === 3
? 'data'
: 'textContent'
},
update (value) {
this.el[this.attr] = value
}
}
// 如,'{{number}}'
description = {
el: el, // 需要创建指令的元素
def: text, // 对指令的操作方法,包括数据绑定(bind)、数据更新(update),见上面 text
name: 'text', // 指令名称
value: 'number' // 指令对应数据的key
}

通过new Directive(vm, el, description)就创建了一个指令,并初始化一些数据;下面就先通过指令对界面进行数据渲染;所有逻辑就放到了_bind方法中,如下:

// directive.js
Directive.prototype._bind = function() {
extend(this, this.description.def);
if(this.bind) {
this.bind();
}
var self = this, watcher = new Watcher(this.vm, this.expression, function() {
self.update(watcher.value);
})
if(this.update) {
this.update(watcher.value);
}
}
// util.js
export function extend(to, from) {
Object.keys(from).forEach((key) => {
to[key] = from[key];
})
return to;
}

方法首先将传入的指令操作方法合并到this上,方便调用,主要包括上面说的bindupdate等方法;其主要根据指令不同,功能不同而不同定义;所有对应均在./directives/*文件夹下面,包括文本渲染text.js、事件添加bind.js、v-model对应model.js、v-show对应show.js等;通过合并以后,就执行this.bind()方法进行数据初始化绑定;但是,目前为止,当去看界面时,仍然没有将number转化为5;为什么呢?通过查看代码:

export default {
bind () {
this.attr = this.el.nodeType === 3
? 'data'
: 'textContent'
},
update (value) {
this.el[this.attr] = value
}
}

bind并没有改变节点展示值,而是通过update; 所以,如果调用this.update(123),可发现有如下结果:

《vue实现原理解析及一步步实现vue框架》

其实我们并不是直接固定数值,而是根据初始化时传入的值动态渲染;但是目前为止,至少已经完成了界面数据的渲染,只是数据不对而已;
然后,我们回头看下编译过程,我们需要在编译过程去实例化指令(directive),并调用其_bind方法,对指令进行初始化处理;

// 见compile.js 'function compileTextNode'
let _el = childNodes[j], description = {
el: _el,
name: 'text',
value: tokens[j].value,
def: publicDirectives['text']
}
vm._directives.push(new Directive(vm, _el, description));
// 见compile.js 'function compile'
let newDirectives = this._directives.slice(_dirLength);
for(let i = 0, _i = newDirectives.length; i <_i; i++) {
newDirectives[i]._bind();
}

上面说了,目前还没有根据传入的数据进行绑定,下面,就来对数据进行处理;

数据处理

数据处理包括以下几个方面:

  • 数据双向绑定
  • 数据变化后,需要通知到ui界面,并自动变化
  • 对于输入框,使用v-model时,需要将输入内容反应到对应数据

数据双向绑定

需要实现双向绑定,就是在数据变化后能够自动的将对应界面进行更新;那么,如何监控数据的变化呢?目前有几种方式,一种是angular的脏检查方式,就是对用户所以操作、会导致数据变化的行为进行拦截,如ng-click$http$timeout等;当用户进行请求数据、点击等时,会对所有的数据进行检查,如果数据变化了,就会触发对应的处理;而另一种是vue的实现方式,使用Object.definProperty()方法,对数据添加settergetter;当对数据进行赋值时,会自动触发setter;就可以监控数据的变化;主要处理如下, 源码见这里:

export function Observer(data) {
this.data = data;
Object.keys(data).forEach((key) => {
defineProperty(data, key, data[key]);
})
}
export function observer(data, vm) {
if(!data || typeof data !== 'object') {
return;
}
let o = new Observer(data);
return o;
}
function defineProperty(data, key, val) {
let _value = data[key];
let childObj = observer(_value);
let dep = new Dep(); //生成一个调度中心,管理此字段的所有订阅者
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function() {
if (Dep.target) {
dep.depend();
}
return val;
},
set: function(value) {
val = value;
childObj = observer(value);
dep.notify();
}
})
}

Observer是一个构造函数,主要对传入的数据进行Object.defineProperty绑定;可以监控到数据的变化;而在每一个Observer中,会初始化一个Dep的称为‘调度管理器’的对象,它主要负责保存界面更新的操作和操作的触发;

界面更新

在通过上面Observer实现数据监控之后,如何通知界面更新呢?这里使用了‘发布/订阅模式’;如果需要对此模式进行更深入理解,可查看此链接;而每个数据key都会维护了一个独立的调度中心Dep;通过在上面defineProperty时创建;而Dep主要保存数据更新后的处理任务及对任务的处理,代码也非常简单,就是使用subs保存所有任务,使用addSub添加任务,使用notify处理任务,depend作用会在下面watcher中进行说明:

// Dep.js
let uid = 0;
// 调度中心
export default function Dep() {
this.id = uid++;
this.subs = []; //订阅者数组
this.target = null; // 有何用处?
}
// 添加任务
Dep.prototype.addSub = function(sub) {
this.subs.push(sub);
}
// 处理任务
Dep.prototype.notify = function() {
this.subs.forEach((sub) => {
if(sub && sub.update && typeof sub.update === 'function') {
sub.update();
}
})
}
Dep.prototype.depend = function() {
Dep.target.addDep(this);
}

那么,处理任务来源哪儿呢?vue中又维护了一个watcher的对象,主要是对任务的初始化和收集处理;也就是一个watcher就是一个任务;而整个watcher代码如下, 线上源码见这里:

export default function Watcher(vm, expression, cb) {
this.cb = cb;
this.vm = vm;
this.expression = expression;
this.depIds = {};
if (typeof expression === 'function') {
this.getter = expOrFn;
} else {
this.getter = this.parseGetter(expression);
}
this.value = this.get();
}
let _prototype = Watcher.prototype;
_prototype.update = function() {
this.run();
}
_prototype.run = function() {
let newValue = this.get(), oldValue = this.value;
if(newValue != oldValue) {
this.value = newValue;
this.cb.call(this.vm, newValue);
}
}
_prototype.addDep = function(dep) {
// console.log(dep)
if (!this.depIds.hasOwnProperty(dep.id)) {
dep.addSub(this);
this.depIds[dep.id] = dep;
}
}
_prototype.get = function() {
Dep.target = this;
var value = this.getter && this.getter.call(this.vm, this.vm);
Dep.target = null;
return value;
}
_prototype.parseGetter = function(exp) {
if (/[^\w.$]/.test(exp)) return;
var exps = exp.split('.');
return function(obj) {
let value = '';
for (var i = 0, len = exps.length; i if (!obj) return;
value = obj[exps[i]];
}
return value;
}
}

在初始化watcher时,需要传入vm(整个项目初始化时实例化的vueImitate对象,因为需要用到里面的对应数据)、expression(任务对应的数据的key,如上面的‘number’)、cb(一个当数据变化后,界面如何更新的函数,也就是上面directive里面的update方法);我们需要实现功能有,第一是每个任务有个update方法,主要用于在数据变化时,进行调用,即:

// 处理任务
Dep.prototype.notify = function() {
this.subs.forEach((sub) => {
if(sub && sub.update && typeof sub.update === 'function') {
sub.update();
}
})
}

第二个是在初始化watcher时,需要将实例化的watcher(任务)放入调度中心depsubs中;如何实现呢?这里,使用了一些黑科技,流程如下,这儿我们以expression为&#8217;number&#8217;为例:

1、在初始化watcher时,会去初始化一个获取数据的方法this.getter就是,能够通过传入的expression取出对应的值;如通过number取出对应的初始化时的值5;

2、调用this.value = this.get();方法,方法中会去数据源中取值,并将此时的watcher放入Dep.target中备用,并返回取到的值;

// watcher.js
_prototype.get = function() {
Dep.target = this;
var value = this.getter && this.getter.call(this.vm, this.vm);
Dep.target = null;
return value;
}

3、因为我们在上面Observer已经对数据进行了Object.defineProperty绑定,所以,当上面2步取值时,会触发对应的getter,如下, 触发get函数之后,因为上面2已经初始化Dep.target = this;了,所以会执行dep.depend();,就是上面说的depend函数了:

// Observer.js
let dep = new Dep(); //生成一个调度中心,管理此字段的所有订阅者
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function() {
if (Dep.target) {
dep.depend();
}
return val;
},
set: function(value) {
val = value;
childObj = observer(value);
dep.notify();
}
})

3、触发dep.depend();之后,如下代码,会执行Dep.target.addDep(this);, 此时的this就是上面实例化的dep, Dep.target则对应的是刚刚1步中实例化的watcher,即执行watcher.addDep(dep);

// Dep.js
Dep.prototype.depend = function() {
Dep.target.addDep(this);
}

4、触发watcher.addDep(dep),如下代码,如果目前还没此dep;就执行dep.addSub(this);,此时的this就是指代当前watcher,也就是1步时实例化的watcher;此时dep是步骤3中实例化的dep; 即是,dep.addSub(watcher);

// watcher.js
_prototype.addDep = function(dep) {
// console.log(dep)
if (!this.depIds.hasOwnProperty(dep.id)) {
dep.addSub(this);
this.depIds[dep.id] = dep;
}
}

5、最后执行dep.addSub(watcher);,如下代码,到这儿,就将初始化的watcher添加到了调度中心的数组中;

// Dep.js
Dep.prototype.addSub = function(sub) {
this.subs.push(sub);
}

那么,在哪儿去初始化watcher呢?就是在对指令进行_bind()时,如下代码,在执行_bind时,会实例化Watcher; 在第三个参数的回调函数里执行self.update(watcher.value);,也就是当监控到数据变化,会执行对应的update方法进行更新;

// directive.js
Directive.prototype._bind = function() {
extend(this, this.description.def);
if(this.bind) {
this.bind();
}
var self = this,
watcher = new Watcher(this.vm, this.expression, function() {
self.update(watcher.value);
})
if(this.update) {
this.update(watcher.value);
}
}

而前面说了,开始时没有数据,使用this.update(123)会将界面对应number更新为123,当时没有对应number真实数据;而此时,在watcher中,获取到了对应数据并保存到value中,因此,就执行this.update(watcher.value);,此时就可以将真实数据与界面进行绑定,并且当数据变化时,界面也会自动进行更新;最终结果如下图:

《vue实现原理解析及一步步实现vue框架》

为什么所有数据都是undefined呢?我们可以通过下面代码知道, 在实例化watcher时,调用this.value = this.get();时,其实是通过传入的key在this.vm中直接取值;但是我们初始化时,所有值都是通过this.optiOns= options || {}; 放到this.options里面,所以根本无法取到:

// watcher.js
_prototype.get = function() {
Dep.target = this;
var value = this.getter && this.getter.call(this.vm, this.vm);
Dep.target = null;
return value;
}
_prototype.parseGetter = function(exp) {
if (/[^\w.$]/.test(exp)) return;
var exps = exp.split('.');
return function(obj) {
let value = '';
for (var i = 0, len = exps.length; i if (!obj) return;
value = obj[exps[i]];
}
return value;
}
}

那么,我们如何能直接可以通过诸如this.number取到值呢?只能如下,通过下面extend(this, data);方式,就将数据绑定到了实例化的vueImitate上面;

import { extend } from './util.js';
import { observer } from './Observer.js';
import Compile from './compile.js';
export default function vueImitate(options) {
this.optiOns= options || {};
this.selector = options.el ? ('#' + options.el) : 'body';
this.data = typeof options.data === 'function' ? options.data() : options.data;
this.el = document.querySelectorAll(this.selector)[0];
this._directives = [];
this.initData();
this.compile();
}
Compile(vueImitate);
vueImitate.prototype.initData = function() {
let data = this.data, self = this;
extend(this, data);
observer(this.data);
}

处理后结果如下:

《vue实现原理解析及一步步实现vue框架》

数据也绑定上了,但是当我们尝试使用下面方式对数据进行改变时,发现并没有自动更新到界面,界面数据并没有变化;

methods: {
add() {
this.number1 += 1;
this.number += 1;
}
}

为什么呢?通过上面代码可知,我们其实observer的是vueImitate实例化对象的data对象;而我们更改值是通过this.number += 1;实现的;其实并没有改vueImitate.data.number的值,而是改vueImitate.number的值,所以也就不会触发observer里面的setter;也不会去触发对应的watcher里面的update;那如何处理呢?我们可以通过如下方式实现, 完整源码见这里:

// init.js
vueImitate.prototype.initData = function() {
let data = this.data, self = this;
extend(this, data);
Object.keys(data).forEach((key) => {
Object.defineProperty(self, key, {
set: function(newVal) {
self.data[key] = newVal;
},
get: function() {
return self.data[key];
}
})
})

observer(this.data);
}

这里通过对vueImitate里对应的data的属性进行Object.defineProperty处理,当对其进行赋值时,会再将其值赋值到vueImitate.data对应的属性上面,那样,就会去触发observer(this.data);里面的setter,从而去更新界面数据;

至此,整个数据处理就已经完成,总结一下:

1、首先,在初始化vueImitate时,我们会将初始化数据通过options.data传入,后会进行处理,保存至this.data中;

2、通过initData方法将数据绑定到vueImitate实例化对象上面,并对其进行数据监控,然后使用observerthis.data进行监控,在实例化Observer时,会去实例化一个对应的调度中心Dep

3、在编译过程中,会创建指令,通过指令实现每个需要处理节点的数据处理和双向绑定;

4、在指令_bind()时,会去实例化对应的watcher,创建一个任务,主要实现数据获取、数据变化时,对应界面更新(也就是更新函数的调用)、并将生成的watcher存储到对应的步骤2中实例化的调度中心中;

5、当数据更新时,会触发对应的setter,然后调用dep.notify();触发调度中心中所有任务的更新,即执行所有的watcher.update,从而实现对应界面的更新;

到目前为止,整个框架的实现基本已经完成。其中包括compile、linker、oberver、directive(v-model、v-show、v-bind、v-text)、watcher;如果需要更深入的研究,可见项目代码; 可以自己clone下来,运行起来;文中有些可能思考不够充分,忘见谅,也欢迎大家指正;


推荐阅读
  • 在 Vue 应用开发中,页面状态管理和跨页面数据传递是常见需求。本文将详细介绍 Vue Router 提供的两种有效方式,帮助开发者高效地实现页面间的数据交互与状态同步,同时分享一些最佳实践和注意事项。 ... [详细]
  • 本文总结了JavaScript的核心知识点和实用技巧,涵盖了变量声明、DOM操作、事件处理等重要方面。例如,通过`event.srcElement`获取触发事件的元素,并使用`alert`显示其HTML结构;利用`innerText`和`innerHTML`属性分别设置和获取文本内容及HTML内容。此外,还介绍了如何在表单中动态生成和操作``元素,以便更好地处理用户输入。这些技巧对于提升前端开发效率和代码质量具有重要意义。 ... [详细]
  • 深入解析 Vue 中的 Axios 请求库
    本文深入探讨了 Vue 中的 Axios 请求库,详细解析了其核心功能与使用方法。Axios 是一个基于 Promise 的 HTTP 客户端,支持浏览器和 Node.js 环境。文章首先介绍了 Axios 的基本概念,随后通过具体示例展示了如何在 Vue 项目中集成和使用 Axios 进行数据请求。无论你是初学者还是有经验的开发者,本文都能为你解决 Vue.js 相关问题提供有价值的参考。 ... [详细]
  • 深入解析 Vue 中通过 $route.params 实现参数传递的方法与技巧
    本文深入探讨了在 Vue 框架中利用 `$route.params` 进行参数传递的方法和技巧。通过详细解析 `$route.params` 的工作机制及其与 `$route.query` 的区别,帮助开发者更好地理解和应用这一功能。文章不仅涵盖了基本的使用方法,还提供了实际案例和最佳实践,以便读者能够灵活运用这些技术,提升开发效率和代码质量。 ... [详细]
  • Python 数据可视化实战指南
    本文详细介绍如何使用 Python 进行数据可视化,涵盖从环境搭建到具体实例的全过程。 ... [详细]
  • javascript分页类支持页码格式
    前端时间因为项目需要,要对一个产品下所有的附属图片进行分页显示,没考虑ajax一张张请求,所以干脆一次性全部把图片out,然 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 本文全面解析了 Python 中字符串处理的常用操作与技巧。首先介绍了如何通过 `s.strip()`, `s.lstrip()` 和 `s.rstrip()` 方法去除字符串中的空格和特殊符号。接着,详细讲解了字符串复制的方法,包括使用 `sStr1 = sStr2` 进行简单的赋值复制。此外,还探讨了字符串连接、分割、替换等高级操作,并提供了丰富的示例代码,帮助读者深入理解和掌握这些实用技巧。 ... [详细]
  • 本文探讨了使用JavaScript在不同页面间传递参数的技术方法。具体而言,从a.html页面跳转至b.html时,如何携带参数并使b.html替代当前页面显示,而非新开窗口。文中详细介绍了实现这一功能的代码及注释,帮助开发者更好地理解和应用该技术。 ... [详细]
  • 如何撰写初级和高级前端开发者的专业简历
    如何撰写初级和高级前端开发者的专业简历 ... [详细]
  • 使用 Vuex 管理表单状态:当输入框失去焦点时自动恢复初始值 ... [详细]
  • 在最近的学习过程中,我对Vue.js中的Prop属性有了更深入的理解,并认为这一知识点至关重要,因此在此记录一些心得体会。Prop属性用于在组件之间传递数据。由于每个组件实例的作用域都是独立的,无法直接引用父组件的数据。通过使用Prop,可以将数据从父组件安全地传递到子组件,确保数据的隔离性和可维护性。 ... [详细]
  • 作为软件工程专业的学生,我深知课堂上教师讲解速度之快,很多时候需要课后自行消化和巩固。因此,撰写这篇Java Web开发入门教程,旨在帮助初学者更好地理解和掌握基础知识。通过详细记录学习过程,希望能为更多像我一样在基础方面还有待提升的学员提供有益的参考。 ... [详细]
  • JavaScript XML操作实用工具类:XmlUtilsJS技巧与应用 ... [详细]
  • 本文详细介绍了 Python 中字符串处理的各种技巧,包括查找、复制、替换、删除、截取、连接、比较、包含检测及大小写转换等操作。特别强调了如何使用 `strip()`、`lstrip()` 和 `rstrip()` 方法去除字符串中的空格和特殊符号,以及如何通过简单的赋值语句实现字符串的复制。此外,还提供了丰富的示例代码,帮助读者更好地理解和应用这些技巧。 ... [详细]
author-avatar
叶小丹丶丶01_520
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有