网站建设在哪物业建设网站

news/2025/9/24 0:47:50/文章来源:
网站建设在哪,物业建设网站,怎么把网站做成手机网站,网站建设设计规划书对于源代码分析有一个基本原则#xff1a;要找到它的最早期的版本#xff0c;比如1.0版本。1.0版本奠定了一款框架的基础结构#xff0c;之后的版本迭代都是基于这套结构进行更新的。所以掌握了基础结构#xff0c;那也就掌握了这个框架。这个原则适用于世界上绝大多数事务…对于源代码分析有一个基本原则要找到它的最早期的版本比如1.0版本。1.0版本奠定了一款框架的基础结构之后的版本迭代都是基于这套结构进行更新的。所以掌握了基础结构那也就掌握了这个框架。这个原则适用于世界上绝大多数事务 计算机基本组成结构汽车等各类交通工具的基本结构Android等框架类的基本结构 所以基于以上原则我在分析Vue源代码时采用的是它的0.10版本这是我能找到的最早的、也能顺利运行的版本。 执行以下命令便可以得到0.10版本 git clone https://github.com/vuejs/vue.gitgit checkout 0.10之后便可以通过顺手的IDE工具比如VS Code将这个项目加载开始正式进入我们的解析过程。 本篇文章的目的 读完这篇文章你可以学到以下内容 Vue对于JS文件的解析。Vue对于DOM树的解析。简单的TEXT赋值更新事件的整个执行过程。 引用结构图 一切从这张图开始 上面这张图描述了Vue各个部分的引用关系它有助于我们梳理Vue的主体结构。 从上图中我们可以确认compiler应当是Vue的核心部分。 分析所需要的环境 一切从我们熟悉的Vue用法开始说起以下内容是摘自于项目中的./examples/commits文件夹 // app.js var demo new Vue({el: #demo,data: {branch: master, title: tl},created: function () {this.$watch(branch, function () {this.fetchData()})},filters: {truncate: function (v) {var newline v.indexOf(\n)return newline 0 ? v.slice(0, newline) : v},formatDate: function (v) {return v.replace(/T|Z/g, )}},methods: {fetchData: function () {var xhr new XMLHttpRequest(),self thisxhr.open(GET, https://api.github.com/repos/yyx990803/vue/commits?per_page3sha self.branch)xhr.onload function () {self.commits JSON.parse(xhr.responseText)}xhr.send()}} })!-- index.html -- !DOCTYPE htmlstyle#demo {font-family: Helvetica, Arial, sans-serif;}a {text-decoration: none;color: #f66;}li {line-height: 1.5em;margin-bottom: 20px;}.author, .date {font-weight: bold;} /stylediv iddemoh1Latest Vue.js Commits/h1p{{title}}/pinput typeradio idmaster namebranch v-modelbranch valuemasterlabel formastermaster/labelbrinput typeradio iddev namebranch v-modelbranch valuedevlabel fordevdev/labelulli v-repeatcommitsa href{{html_url}} target_blank classcommit{{sha.slice(0, 7)}}/a- span classmessage{{commit.message | truncate}}/spanbrby span classauthor{{commit.author.name}}/spanat span classdate{{commit.author.date | formatDate}}/span/li/ul /divscript src../../dist/vue.js/script script srcapp.js/script典型的Vue用法如上那我们的分析就从new Vue()开始说起。 *注意 如果要达到良好的学习效果需要自己clone一份源代码跟着查看反复查看。 为了节省篇幅不影响主流程的代码都以“…”代替。 不是核心的代码会直接略过。 Vue的入口 我们可以在Vue的源代码中找到 if (typeof exports object) {module.exports require(vue);} else if (typeof define function define.amd) {define(function () { return require(vue); });} else {window[Vue] require(vue);}那也就是说我们在new Vue时调用的构造方法应当是require(vue);方法所返回的。 经过一轮探寻(这个过程可自行探寻这不是我们的关注的重点)可以找到Vue实际的入口为vue/src/main.js方法中所返回的内容 require.register(vue/src/main.js, function (exports, require, module) {var config require(./config);var ViewModel require(./viewmodel);...module.exports ViewModel});所以我们真正的入口便是ViewModel的构造方法。 真正的入口ViewModel() 数据的执行入口 /*** ViewModel exposed to the user that holds data,* computed properties, event handlers* and a few reserved methods*/function ViewModel(options) {//对外暴露的入口console.info(options);// compile if options passed, if false return. options are passed directly to compilerif (options false) returnnew Compiler(this, options)}而后开始进入Compiler构造方法 /*** The DOM compiler* scans a DOM node and compile bindings for a ViewModel* options: custom data.*/function Compiler(vm, options) {...}最开始processOptions内部会对自定义的四种类型做初步处理components,partials,template,filters我们没有定义也不是核心流程直接跳过。 /*** convert certain option values to the desired format.*/processOptions:(options);接下来将自定义编译选项与主编译器合并 // copy compiler optionsextend(compiler, options.compilerOptions);通过setupElement方法查找el所定义的元素其内部使用了document.querySelector()方法参数为id选择器的值#demo。 // initialize elementvar el compiler.el compiler.setupElement(options);这里的el就代表了整个根节点。接下来的操作都围绕着这个根节点进行操作。 接下来给compiler添加了一些属性这些属性为接下来做铺垫 // set other compiler propertiescompiler.vm el.vue_vm vmcompiler.bindings utils.hash()compiler.dirs []compiler.deferred []compiler.computed []compiler.children []compiler.emitter new Emitter(vm)上面给el赋了一个属性el.vue_vm。 vue_vm拥有以下属性 vm.$ {}vm.$el elvm.$options optionsvm.$compiler compilervm.$event nullvm.$root getRoot(compiler).vm其中这些为循环引用需要注意 vue_vm.el vm.el elcompiler.options vm.$options optionsvm.$compiler compiler而compiler.vm el.vue_vm vm接下来我们需要进入compiler.setupObserver()方法一探究竟这是个关键的地方。 CompilerProto.setupObserver function () {var compiler this,bindings compiler.bindings,options compiler.options,observer compiler.observer new Emitter(compiler.vm)...// add own listeners which trigger binding updatesobserver.on(get, onGet).on(set, onSet).on(mutate, onSet)// register hooks// 对自定义的钩子方法做处理hooks [created, ready,beforeDestroy, afterDestroy,attached, detached]var i hooks.length, j, hook, fnswhile (i--) {hook hooks[i]fns options[hook]if (Array.isArray(fns)) {j fns.length// since hooks were merged with child at head,// we loop reversely.while (j--) {registerHook(hook, fns[j])}} else if (fns) {registerHook(hook, fns)}}// broadcast attached/detached hooksobserver.on(hook:attached, function () {broadcast(1)}).on(hook:detached, function () {broadcast(0)})function onGet(key) {check(key)DepsParser.catcher.emit(get, bindings[key])}function onSet(key, val, mutation) {observer.emit(change: key, val, mutation)check(key)bindings[key].update(val)}function registerHook(hook, fn) {observer.on(hook: hook, function () {fn.call(compiler.vm)})}function broadcast(event) {...}...}上面做了这么几件重要的事情 compiler.observer初始化其中compiler.observer是一个Emitter对象的实例。给compiler.observer注册需要观察的事件,需要观察的事件包含get、set、mutate、hook:attached、hook:detached。其中后两项会在事件被触发时将事件广播出去。将自定义生命周期方法与生命周期事件挂钩。 observer.on方法实现如下它用来注册事件与回调的关系。是一对多的关系。 EmitterProto.on function (event, fn) {this._cbs this._cbs || {};(this._cbs[event] this._cbs[event] || []).push(fn)return this}通过setupObserver方法的执行我们可知如下对应关系 compiler.observer._cbs.get [onGet] compiler.observer._cbs.set [onSet] compiler.observer._cbs.mutate [onSet] compiler.observer._cbs.hook:attached [broadcast function] compiler.observer._cbs.hook:detached [broadcast function] ... 自定义生命周期观察者如果有的话以上对分析最重要的就是onSet的回调在这里先有个印象后面很关键。onSet实现如下 function onSet(key, val, mutation) {observer.emit(change: key, val, mutation)check(key)bindings[key].update(val)}到这里跳出setupObserver方法回到Compiler(vm, options)构造方法内继续往下 接下来对自定义方法处理我们的示例中有自定义方法fetchData // create bindings for computed propertiesif (options.methods) {for (key in options.methods) {compiler.createBinding(key)}}内部实现如下 CompilerProto.createBinding function (key, directive) {...var compiler this,methods compiler.options.methods,isExp directive directive.isExp,isFn (directive directive.isFn) || (methods methods[key]),bindings compiler.bindings,computed compiler.options.computed,binding new Binding(compiler, key, isExp, isFn)if (isExp) {...} else if (isFn) {bindings[key] bindingcompiler.defineVmProp(key, binding, methods[key])} else {bindings[key] binding...}return binding}这里的key是fetchData它是一个方法所以isFn true。然后将这些关键的信息生成了一个Binding对象。Binding通过类似的建造者模式将所有的关键信息维护在一起。现在这个binding对象是专门为fetchData方法所产生的。 然后代码进入isFn条件继续执行便产生了如下关系 compiler.bindings.fetchData new Binding(compiler, fetchData, false, true); 然后继续执行 compiler.defineVmProp(fetchData, binding, fetchDataFunc);//fetchDataFunc为fetchData所对应的自定义方法。方法内部如下 CompilerProto.defineVmProp function (key, binding, value) {var ob this.observerbinding.value valuedef(this.vm, key, {get: function () {if (Observer.shouldGet) ob.emit(get, key)return binding.value},set: function (val) {ob.emit(set, key, val)}})}经过 defineVmProp代码的执行可以得出以下结论 compiler.vm.fetchData有了代理get/set方法后期对于自定义方法的读取或者赋值都需要经过这一层代理。binding.value也指向了用户自定义的方法。当读取vm.fetchData时就会得到自定义的方法。我们跳出defineVmProp方法然后继续向下执行createBinding方法执行完毕我们返回到createBinding方法调用处也就是Compiler的构造方内继续向下执行。 我们的示例中没有computed的相关定义这里跳过。 接下来对defaultData做处理我们没有定义跳过。 也没有对paramAttributes的定义跳过。 走到这里 // copy data properties to vm// so user can access them in the created hookextend(vm, data)vm.$data data这里将data里面的属性全部赋值给了vm。并且vm.$data属性也指向data。 // extend方法的实现如下extend: function (obj, ext) {for (var key in ext) {if (obj[key] ! ext[key]) {obj[key] ext[key]}}return obj}extend方法将第二个参数的所有属性全部赋值给了第一个参数。对于示例会产生如下关系 vm.branch master vm.title tl vm.$data data接着向下触发created生命周期方法 // beforeCompile hookcompiler.execHook(created)我们没有定义created生命周期方法然后继续。 对于自定义数据的事件监听 略过中间的数据处理到达这里 // now we can observe the data.// this will convert data properties to getter/setters// and emit the first batch of set events, which will// in turn create the corresponding bindings.compiler.observeData(data)observeData方法内部如下 CompilerProto.observeData function (data) {var compiler this,observer compiler.observer// recursively observe nested propertiesObserver.observe(data, , observer)...}observeData方法中比较重要的地方是 Observer.observe(data, , observer)然后是observe方法内部 ...// 第一次执行alreadyConverted falseif (alreadyConverted) {// for objects that have already been converted,// emit set events for everything insideemitSet(obj)} else {watch(obj)}所以第一次走的是watch方法 /*** Watch target based on its type*/ function watch (obj) {if (isArray(obj)) {watchArray(obj)} else {watchObject(obj)} }watch方法对对象做了一个初步的分拣。示例的代码不是Array走watchObject /*** Watch an Object, recursive.*/ function watchObject (obj) {// 用户给对象添加$add/$delete两个属性augment(obj, ObjProxy)for (var key in obj) {convertKey(obj, key)} }我们到这里稍微等一下这里的obj还是 data: {branch: master, title: tl}watchObject对对象的每个属性进行遍历而convertKey方法内做了比较重要的事情 function convertKey(obj, key, propagate) {var keyPrefix key.charAt(0)// 初步对以$开头的、以_开头的做过滤if (keyPrefix $ || keyPrefix _) {return}...// 重要之所在oDef(obj, key, {enumerable: true,configurable: true,get: function () {var value values[key]// only emit get on tip valuesif (pub.shouldGet) {emitter.emit(get, key)}return value},set: function (newVal) {var oldVal values[key]unobserve(oldVal, key, emitter)copyPaths(newVal, oldVal)// an immediate property should notify its parent// to emit set for itself tooinit(newVal, true)}})...}convertKey方法中比较重要的就是这里了这里对new Vue()时传入的对象的data对象中的每个属性添加相应的get/set方法也就是说在给某个属性赋值时就会触发这里。如果给branch/title赋予新值就会触发上面提到的set方法。到这里我们有理由相信set方法中的init方法是用来更新界面的。 好了到了这里convertKey方法就分析完了我们再一路往回convertKey - watchObject - watch - observe - observeData。回到observeData方法内接下的代码是对compiler.vm.$data添加观察事件它暂时不是我们关心的内容observeData返回调用处并接着向下 // before compiling, resolve content insertion pointsif (options.template) {this.resolveContent()}上面这段代码我们没有定义template略过。 对于DOM树的解析 向下到了又一个很关键的地方 // now parse the DOM and bind directives.// During this stage, we will also create bindings for// encountered keypaths that dont have a binding yet.compiler.compile(el, true)compile内部实现 CompilerProto.compile function (node, root) {var nodeType node.nodeTypeif (nodeType 1 node.tagName ! SCRIPT) { // a normal nodethis.compileElement(node, root)} else if (nodeType 3 config.interpolate) {this.compileTextNode(node)}}执行到这里el使我们的根节点demo其中node demoNode, root true。上面的分发会进入compileElement CompilerProto.compileElement function (node, root) {// textarea is pretty annoying// because its value creates childNodes which// we dont want to compile.if (node.tagName TEXTAREA node.value) {node.value this.eval(node.value)}// only compile if this element has attributes// or its tagName contains a hyphen (which means it could// potentially be a custom element)if (node.hasAttributes() || node.tagName.indexOf(-) -1) {...}// recursively compile childNodesif (node.hasChildNodes()) {slice.call(node.childNodes).forEach(this.compile, this)}}compileElement方法内部细节比较多也比较长。 先来说说compileElement方法的作用compileElement方法用来对dom树的所有节点进行遍历会处理所有的属性节点与文本节点。其中就会遇到v-model等指令以及{{value}}这样的占位符。 compileElement方法内分为几大块 1.对TEXTAREA的处理if (node.tagName TEXTAREA node.value)2.对用于属性的或者tag的名称中包含’-的处理if (node.hasAttributes() || node.tagName.indexOf(-) -1) {3.如果不符合1或2的条件则对其子节点进行处理。 子节点的处理会进一步进行递归走compile方法。compile方法继续进行分发如果是元素节点则走compileElement如果是文本节点则走compileTextNode。这个过程直到将整颗DOM树遍历完毕。 CompilerProto.compile function (node, root) {var nodeType node.nodeTypeif (nodeType 1 node.tagName ! SCRIPT) { // a normal nodethis.compileElement(node, root)} else if (nodeType 3 config.interpolate) {this.compileTextNode(node)}}以下代码从index.html摘除它有利于我们的继续分析 p{{title}}/p如果渲染以上内容那么它的处理就会被分发到compileTextNode方法中 CompilerProto.compileTextNode function (node) {var tokens TextParser.parse(node.nodeValue)if (!tokens) returnvar el, token, directivefor (var i 0, l tokens.length; i l; i) {token tokens[i]directive nullif (token.key) { // a bindingif (token.key.charAt(0) ) { // a partialel document.createComment(ref)directive this.parseDirective(partial, token.key.slice(1), el)} else {if (!token.html) { // text binding// 示例中会在这里处理{{title}}的逻辑并绑定与之对应的directive处理函数。el document.createTextNode()directive this.parseDirective(text, token.key, el)} else { // html bindingel document.createComment(config.prefix -html)directive this.parseDirective(html, token.key, el)}}} else { // a plain stringel document.createTextNode(token)}// insert nodenode.parentNode.insertBefore(el, node)// bind directivethis.bindDirective(directive)}node.parentNode.removeChild(node)}上面方法中的TextParser.parse(node.nodeValue)的实现细节不去了解了它是用来匹配各种占位符和表达式的纯算法型代码。 对于p{{title}}/p这种类型的处理会进入 el document.createTextNode() directive this.parseDirective(text, token.key, el)其中token.key ‘title’, el为刚刚创建好的新文本节点。parseDirective方法内 CompilerProto.parseDirective function (name, value, el, multiple) {var compiler this,definition compiler.getOption(directives, name)if (definition) {// parse into AST-like objectsvar asts Directive.parse(value)return multiple? asts.map(build): build(asts[0])}function build(ast) {return new Directive(name, ast, definition, compiler, el)}}上面代码最为核心的调用是getOption其中type ‘directives’, id ‘text’, silent undefined CompilerProto.getOption function (type, id, silent) {var opts this.options,parent this.parent,globalAssets config.globalAssets,res (opts[type] opts[type][id]) || (parent? parent.getOption(type, id, silent): globalAssets[type] globalAssets[type][id])if (!res !silent typeof id string) {utils.warn(Unknown type.slice(0, -1) : id)}return res}其中globalAssets存储了vue所支持类型的所有对应关系 然后getOption返回的就是处理类型与处理方法的对应关系对象。最后parseDirective方法返回一个新的Directive对象。这个对象包含了处理类型与处理方法的相关关系。这是很重要的一点。 对于text类型的它的Directive对象则是 directives.text {bind: function () {this.attr this.el.nodeType 3? nodeValue: textContent},update: function (value) {this.el[this.attr] utils.guard(value)}}回到compileTextNode方法继续向下执行 CompilerProto.bindDirective function (directive, bindingOwner) {if (!directive) return...if (directive.isExp) {// expression bindings are always created on current compilerbinding compiler.createBinding(key, directive)} else {// recursively locate which compiler owns the binding...compiler compiler || thisbinding compiler.bindings[key] || compiler.createBinding(key)}binding.dirs.push(directive)...}上面又执行了compiler.createBinding(key)这里的key ‘title’。 经过bindDirective方法的执行最后会产生如下关系(这里很重要) compiler.bindings.title new Binding(compiler, ttile, false, false); compiler.bindings.title.binding.dirs [directive]; // 这里存放的是title对应的处理方法执行到了这里就可以返回至compileTextNode方法的调用处。compileTextNode的初始化到这里就算完成了一步。 到这里可以返回至function Compiler(vm, options)方法处继续向下。中间略过一些非核心的内容 // done!compiler.init false// post compile / ready hookcompiler.execHook(ready)到这里初始化就算完成并通过ready方法告知Vue已经准备好了。 事件的执行 接下来如果执行demo.title Hello就会触发set方法的内部的init方法而init方法内部有这样的关键 function init(val, propagate) {values[key] val/重要/ emitter.emit(set, key, val, propagate)/重要/ if (isArray(val)) {emitter.emit(set, key .length, val.length, propagate)}observe(val, key, emitter)}能看到上面的emitter.emit(set, key, val, propagate)方法被执行我们就根据这个set查看它是怎么执行的 EmitterProto.emit function (event, a, b, c) {this._cbs this._cbs || {}var callbacks this._cbs[event]if (callbacks) {callbacks callbacks.slice(0)for (var i 0, len callbacks.length; i len; i) {callbacks[i].call(this._ctx, a, b, c)}}return this}上面这段代码通过event获取到对应的callbacks并进行回调我们在上面已经得知set所对应的callbacks是onSet方法我们再来回顾一下onSet function onSet(key, val, mutation) {observer.emit(change: key, val, mutation)check(key)compiler.bindings[key].update(val)}而compiler.bindings的属性添加是在createBinding中进行的这个我们上面就有提到。执行到这里key ‘title’。 于是这里执行的便是 BindingProto.update function (value) {if (!this.isComputed || this.isFn) {this.value value}if (this.dirs.length || this.subs.length) {var self thisbindingBatcher.push({id: this.id,execute: function () {if (!self.unbound) {self._update()}}})}} 以下是bindingBatcher.push的实现细节 BatcherProto.push function (job) {if (!job.id || !this.has[job.id]) {this.queue.push(job)this.has[job.id] jobif (!this.waiting) {this.waiting trueutils.nextTick(utils.bind(this.flush, this))}} else if (job.override) {var oldJob this.has[job.id]oldJob.cancelled truethis.queue.push(job)this.has[job.id] job} }bindingBatcher.push方法会将参数对象经过包装交给 /*** used to defer batch updates*/nextTick: function (cb) {defer(cb, 0)},而这里的defer为requestAnimationFrame方法requestAnimationFrame会在下一次浏览器绘制时触发cb回调方法。 其中的cb回调对象是由这个bind方法生成的 /*** Most simple bind* enough for the usecase and fast than native bind()*/bind: function (fn, ctx) {return function (arg) {return fn.call(ctx, arg)}},这里的fn是 BatcherProto.flush function () {// before flush hookif (this._preFlush) this._preFlush()// do not cache length because more jobs might be pushed// as we execute existing jobsfor (var i 0; i this.queue.length; i) {var job this.queue[i]if (!job.cancelled) {job.execute()}}this.reset() }也就说紧接着flush方法会被requestAnimationFrame方法调用 flush方法的核心是 job.execute()而这里的job对象就是刚刚被Push进去的 {id: this.id,execute: function () {if (!self.unbound) {self._update()}}}这里会执行self._update() /*** Actually update the directives.*/ BindingProto._update function () {var i this.dirs.length,value this.val()while (i--) {this.dirs[i].$update(value)}this.pub() }可以理解为这是一个事件分发过程。 这里从dirs中取出是一个与text相关的directive对象这里执行的是directive对象的$update方法 DirProto.$update function (value, init) {if (this.$lock) returnif (init || value ! this.value || (value typeof value object)) {this.value valueif (this.update) {this.update(this.filters !this.computeFilters? this.$applyFilters(value): value,init)}}}上面的this对应的是之前提到的与text对应的处理器 directives.text {bind: function () {this.attr this.el.nodeType 3? nodeValue: textContent},update: function (value) {this.el[this.attr] utils.guard(value)}}而这里的update则是执行整个text更新的核心所在通过对相应元素的nodeValue赋值便达到的更新值的效果。 以上内容仅仅是更新data值的粗略过程。vue还包括其它内容如列表渲染、条件渲染、生命周期方法等等。 对于列表渲染和条件渲染它们分别有对应的处理器对于它们的执行过程也和text的过程是一致的。 零散的记录一下 emitter是vue引擎的核心负责各种事件的分发。 它含有两个关键的方法 // 注册观察者方法每个event可以理解为观察者,fn为观察者对应的事件回调对象集合。EmitterProto.on function (event, fn) {this._cbs this._cbs || {};(this._cbs[event] this._cbs[event] || []).push(fn)return this}// 通知观察者针对于观察的事件进行事件的分发处理EmitterProto.emit function (event, a, b, c) {this._cbs this._cbs || {}var callbacks this._cbs[event]if (callbacks) {callbacks callbacks.slice(0)for (var i 0, len callbacks.length; i len; i) {callbacks[i].call(this._ctx, a, b, c)}}return this}其中在vue中注册的观察者为 compiler.observer.on(get, onGet).on(set, onSet).on(mutate, onSet).on(hook:attached, function () {broadcast(1)}).on(hook:detached, function () {broadcast(0)}).on(created, 自定义生命周期方法).on(ready, 自定义生命周期方法).on(beforeDestroy, 自定义生命周期方法).on(afterDestroy, 自定义生命周期方法).on(attached, 自定义生命周期方法).on(detached, 自定义生命周期方法).on(set, function (key) {if (key ! $data) update()}).on(mutate, function (key) {if (key ! $data) update()})......当某个Key所对应的事件被触发时它所对应的回调就会被触发并执行。 总结 所以到此为止我们搞清楚了Vue的主体框架。上文中有些乱我们来梳理一下 最开始new Vue new ViewModel new CompilerCompiler执行了对于自定义数据、自定义方法、自定义生命周期、自定义模板等等的处理。我们的示例演示了如何为自定义数据添加观察者方法。Compiler解析了整颗DOM树为树里面定义的占位符、v-指令、自定义组件做了处理。示例中演示了如何对占位符中的值进行解析以及添加观察者。Compiler.bindings中存放了所有需要观察对象的绑定关系Binding对象。Binding中的dirs存放了相关key的处理对象Directive。Emitter负责关键中转事件的注册与分发。Batcher负责更新事件的提交。它将事件交给浏览器由浏览器触发事件的执行。Directives中存放了所有的指令。包括:if,repeat,on,model,with等等。TextParser负责文本的萃取解析。Directive负责单个事件的触发通过directive使更新执行。Observer用于添加观察者。Binding用于维护一些运行时的关键信息。Utils中提供了一些非常棒的基础工具。Config提供了一些可配的配置信息。main.js是整个程序的执行入口负责一些模块的加载和组装。 额外学习到的内容 除了摸清楚Vue的基础框架之外我从代码中读到了以下信息 代码非常整洁注释全面结构合理、清晰。无额外注释和冗余代码。对于日志的输出做了控制这也是一个优秀程序员所必备的。对于JS语言针对于类的使用值得借鉴。一些非常奇妙的用法。 良好的日志管控无处不在 function enableDebug() {/*** log for debugging*/utils.log function (msg) {if (config.debug console) {console.log(msg)}}/*** warnings, traces by default* can be suppressed by silent option.*/utils.warn function (msg) {if (!config.silent console) {console.warn(msg)if (config.debug console.trace) {console.trace()}}}}很多地方会看到这种写法 slice.call(node.childNodes).forEach(this.compile, this);slice方法在这里的作用是拷贝了一个副本出来对于副本的操作不会引起原型的变动。这个对于拷贝数组副本的用法很妙。 以上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/914399.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

信息网站建设wordpress 怎么加入插件

windows计划任务 查看 Windows 自动执行的指令取消 Windows 中的计划任务启动执行计划任务 查看 Windows 自动执行的指令 您可以使用以下方法: 使用任务计划程序:任务计划程序是 Windows 内置的工具,可以用于创建、编辑和管理计划任务。您可…

做网站服务器租一年多少钱阿里云1元域名

快捷操作 快捷键 快捷键功能备注Ctrl P打印 Ctrl W关闭 Ctrl B书签 鼠标放菜单栏,单击右键即可导入/导出 自定义菜单栏文件-->偏好设置-->文档 1、多实例:单击PDF后均重新打开一个新界面。

服务器 多个网站wordpress page页

近年来,随着我国工业化、城镇化步伐的不断加快,城市配电网络取得令人瞩目的发展成果。变配电室是供配电系统的核心,在供配电系统中占有特殊的重要地位[1]。变配电室电气设备运行状态和环境信息缺乏必要的监测评估预警手段,如有一日遭遇突发情…

天津建设银行网站北京网站设计与网站制作

1、static关键字的作用 修饰局部变量 作用域:无变化 生命周期:函数--->程序(数据段) 特点:只初始化一次 修饰全局变量 作用域:整个工程----->本模块 生命周期:无变化 修饰函数 作…

成都网站建设 全美室内设计网站模板

多线程是 Java 编程中的一个重要概念,它允许程序同时执行多个任务,提高了程序的性能和响应能力。本篇博客将深入探讨 Java 多线程,从基础概念到实际应用,适用于 Java 初学者和希望深入了解多线程的开发人员。 什么是多线程&#…

湖北工程建设信息网站网站建设域名

一、Homebrew的镜像设置 对于Java JDK的安装,我们更推荐使用Homebrew来进行安装管理。但Homebrew的curl国外源的下载速度实在是一言难尽,时常还会发生无法访问的情况。 那么我们此时的解决方法就有两种了,第一种便是使用全局的VPN代理进行下载…

电商平台网站大全湖南建设厅网站勘查设计

罗其胜3d角色强化 CGwhat-Maya变形金刚擎天柱建模教程 Pixar in the box - khan academy Siggraph历届优秀动画 CG软件发展史:MAYA动画十年历程 maya 2014奥迪汽车模型制作教程 (yj6k) 海贼王路飞建模教程高清全集 转载于:https://www.cnblog…

手机企业网站源码深圳龙岗有什么好玩的地方

Condition 源码解析 文章目录 Condition 源码解析一、Condition二、Condition 源码解读2.1. lock.newCondition() 获取 Condition 对象2.2. condition.await() 阻塞过程2.3. condition.signal() 唤醒过程2.4. condition.await() 被唤醒后 三、总结 一、Condition 在并发情况下…

厦门定制网站建设网站模板50元

第一部分 安装参考网址: https://blog.csdn.net/a1004084857/article/details/128512612; 以上步骤执行完,进入找到sbin目录,查看下面是不是有nginx可执行文件,如果有在当前sbin下执行./nginx,就会发现NGINX已启动 第…

做哪种类型的网站赚钱呢手机上怎么创建自己的网站

当我们在使用git的时候,又是会有这种情况:当新的需求了的时候。我们需要为此需求新建一个分支,再次分支上进行修改,当经过测试,提交代码时,在将其合并到主分支,或生产分支上。 但是有时候也有失…

外包网站推荐现在室内设计师好做吗

这段时间我们一直规划LSGO Group的学习网络平台,需求部分已经规划完毕,说做就做,开始搭建环境,由于利用PHPMYSQL技术,在服务器端首先安装了WAMPServer,以便提供Apache服务与MYSQL服务! 在代码的…

制作网页的流程步骤免费seo视频教学

AndroidStudio 很多时候会出现提示插件解析失败问题。可按如下步骤进行排查: 1. 翻墙后点击sync 按钮去同步;如果网络没问题,但一直同步失败,可试2. 2. C:\Users\[yourName]\.gradle\caches 中用git bash 等客户端工具去搜同步不…

鞍山市信息网站网站灰色建设

File/file 装入想要调试的可执行文件 run(r) 执行当前被调试的程序 kill(k) 终止正在调试的程序 quit(q) 退出gdb shell 使用户不离开gdb就可以执行Linux的shell命令 backtrace(bt) 回溯跟踪(当对代码进行调试时,run后…

南阳医疗网站制作价格网站建设应走什么会计科目

可以参照 Stanford大神DaphneKoller的概率图模型,里面贝叶斯网络一节讲到了explaining away。我看过之后试着谈谈自己的理解。 explainingaway指的是这样一种情况:对于一个多因一果的问题,假设各种“因”之间都是相互独立的,如果…

一例电动车充电器防反接电路分析

家里电动车充电器不充电了,经过拆开测试,二次侧输出电压正常;且输出线未出现断路,因此判断是防反接晶闸管故障,更换晶闸管后,电路正常工作。简略原理图如下:VCC为二次侧输出电源正极。该防反接电路,不是控正,…

做网站一天能接多少单windows 2008 wordpress

文章目录 前言一、数组的概念二、一维数组的定义三、一维数组的初始化四、一维数组的使用及举例1. 元素顺次前移的问题2. 数组元素逆序调整问题3. 统计输入的各个数据的个数 五、课后练习1. 从数组中查找某个元素2. 求一个数组中元素的平均值和均方差3. 编程统计某班某次考试的…

网站建设蓝图pptwordpress启用特色

在软件开发过程中,项目的构建是一个不可避免的环节。而随着项目规模的增大,手动管理编译过程变得越来越繁琐。为了简化构建流程并实现跨平台支持,CMake作为一种流行的构建系统被广泛采用。本文将介绍CMakeLists.txt文件的结构,以及…

套餐网站樊城网站建设

史上最全的JAVA面试题总结 为什么要做这件事情前言JAVA基础开发框架springSpringMVCmybatisdubbospringbootspringcloudnacos 数据库mysqloracle 缓存redismongodbElasticSearch 消息队列rabbitmqrocketmqkafka 监控prometheusgraylogzabbix 工具篇tcpdumpgitjenkins 容器docke…

烟台做网站价格石家庄正定网站建设

1、PTP模型 Point-to-Point,点对点通信模型。PTP是基于队列(Queue)的,一个队列可以有多个生产者,和多个消费者。消息服务器按照收到消息的先后顺序,将消息放到队列中。队列中的每一条消息,只能由一个消费者进行消费&a…

做的漂亮的家居网站做网站业务员

计算机基础知识同步练习题一、判断题下列各题中,您认为对的请在括号中填入“是” ,错的填入“非” 。1. 世界上第一台电子计算机是 1946 年在美国研制成功的。 ( )2. 电子计算机的用途是进行各种科学研究的数值计算。 ( )3. 电子计算机的计算速度很快但计…