建设部质监局信息查询官方网站,做一个官网要多少钱,莱西市城乡建设局网站,做电脑网站手机能显示不出来怎么办1. 前言大家好#xff0c;我是若川。最近组织了源码共读活动《1个月#xff0c;200人#xff0c;一起读了4周源码》#xff0c;已经有超50人提交了笔记#xff0c;群里已经有超1200人#xff0c;感兴趣的可以点此链接扫码加我微信 ruochuan12之前写的《学习源码整体架构系… 1. 前言大家好我是若川。最近组织了源码共读活动《1个月200人一起读了4周源码》已经有超50人提交了笔记群里已经有超1200人感兴趣的可以点此链接扫码加我微信 ruochuan12之前写的《学习源码整体架构系列》jQuery、underscore、lodash、vuex、sentry、axios、redux、koa、vue-devtools、vuex4十余篇源码文章。其中最新的三篇是50行代码串行Promisekoa洋葱模型原来是这么实现Vue 3.2 发布了那尤雨溪是怎么发布 Vue.js 的初学者也能看懂的 Vue3 源码中那些实用的基础工具函数写相对很难的源码耗费了自己的时间和精力也没收获多少阅读点赞其实是一件挺受打击的事情。从阅读量和读者受益方面来看不能促进作者持续输出文章。所以转变思路写一些相对通俗易懂的文章。其实源码也不是想象的那么难至少有很多看得懂。歌德曾说读一本好书就是在和高尚的人谈话。同理可得读源码也算是和作者的一种学习交流的方式。本文源于一次源码共读群里群友的提问请问若川“为什么 data 中的数据可以用 this 直接获取到啊”当时我翻阅源码做出了解答。想着如果下次有人再次问到我还需要回答一次。当时打算有空写篇文章告诉读者自己探究原理于是就有了这篇文章。阅读本文你将学到1. 如何学习调试 vue2 源码
2. data 中的数据为什么可以用 this 直接获取到
3. methods 中的方法为什么可以用 this 直接获取到
4. 学习源码中优秀代码和思想投入到自己的项目中本文不难用过 Vue 的都看得懂希望大家动手调试和学会看源码。看源码可以大胆猜测最后小心求证。2. 示例this 能够直接获取到 data 和 methods众所周知这样是可以输出我是若川的。好奇的人就会思考为啥 this 就能直接访问到呢。const vm new Vue({data: {name: 我是若川,},methods: {sayName(){console.log(this.name);}},
});
console.log(vm.name); // 我是若川
console.log(vm.sayName()); // 我是若川那么为什么 this.xxx 能获取到data里的数据能获取到 methods 方法。我们自己构造写的函数如何做到类似Vue的效果呢。function Person(options){}const p new Person({data: {name: 若川},methods: {sayName(){console.log(this.name);}}
});console.log(p.name);
// undefined
console.log(p.sayName());
// Uncaught TypeError: p.sayName is not a function如果是你你会怎么去实现呢。带着问题我们来调试 Vue2源码学习。3. 准备环境调试源码一探究竟可以在本地新建一个文件夹examples新建文件index.html文件。在body/body中加上如下js。script srchttps://unpkg.com/vue2.6.14/dist/vue.js/script
scriptconst vm new Vue({data: {name: 我是若川,},methods: {sayName(){console.log(this.name);}},});console.log(vm.name);console.log(vm.sayName());
/script再全局安装npm i -g http-server启动服务。npm i -g http-server
cd examples
http-server .
// 如果碰到端口被占用也可以指定端口
http-server -p 8081 .这样就能在http://localhost:8080/打开刚写的index.html页面了。对于调试还不是很熟悉的读者可以看这篇文章《前端容易忽略的 debugger 调试技巧》调试在 F12 打开调试source 面板在例子中const vm new Vue({打上断点。debugger刷新页面后按F11进入函数这时断点就走进了 Vue 构造函数。3.1 Vue 构造函数function Vue (options) {if (!(this instanceof Vue)) {warn(Vue is a constructor and should be called with the new keyword);}this._init(options);
}
// 初始化
initMixin(Vue);
stateMixin(Vue);
eventsMixin(Vue);
lifecycleMixin(Vue);
renderMixin(Vue);值得一提的是if (!(this instanceof Vue)){} 判断是不是用了 new 关键词调用构造函数。一般而言我们平时应该不会考虑写这个。当然看源码库也可以自己函数内部调用 new 。但 vue 一般一个项目只需要 new Vue() 一次所以没必要。而 jQuery 源码的就是内部 new 对于使用者来说就是无new构造。jQuery function( selector, context ) {// 返回new之后的对象return new jQuery.fn.init( selector, context );
};因为使用 jQuery 经常要调用。其实 jQuery 也是可以 new 的。和不用 new 是一个效果。如果不明白 new 操作符的用处可以看我之前的文章。面试官问能否模拟实现JS的new操作符调试继续在this._init(options);处打上断点按F11进入函数。3.2 _init 初始化函数进入 _init 函数后这个函数比较长做了挺多事情我们猜测跟data和methods相关的实现在initState(vm)函数里。// 代码有删减
function initMixin (Vue) {Vue.prototype._init function (options) {var vm this;// a uidvm._uid uid$3;// a flag to avoid this being observedvm._isVue true;// merge optionsif (options options._isComponent) {// optimize internal component instantiation// since dynamic options merging is pretty slow, and none of the// internal component options needs special treatment.initInternalComponent(vm, options);} else {vm.$options mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm);}// expose real selfvm._self vm;initLifecycle(vm);initEvents(vm);initRender(vm);callHook(vm, beforeCreate);initInjections(vm); // resolve injections before data/props// 初始化状态initState(vm);initProvide(vm); // resolve provide after data/propscallHook(vm, created);};
}调试接着我们在initState(vm)函数这里打算断点按F8可以直接跳转到这个断点然后按F11接着进入initState函数。3.3 initState 初始化状态从函数名来看这个函数主要实现功能是初始化 props
初始化 methods
监测数据
初始化 computed
初始化 watchfunction initState (vm) {vm._watchers [];var opts vm.$options;if (opts.props) { initProps(vm, opts.props); }// 有传入 methods初始化方法if (opts.methods) { initMethods(vm, opts.methods); }// 有传入 data初始化 dataif (opts.data) {initData(vm);} else {observe(vm._data {}, true /* asRootData */);}if (opts.computed) { initComputed(vm, opts.computed); }if (opts.watch opts.watch ! nativeWatch) {initWatch(vm, opts.watch);}
}我们重点来看初始化 methods之后再看初始化 data。调试在 initMethods 这句打上断点同时在initData(vm)处打上断点看完initMethods函数后可以直接按F8回到initData(vm)函数。继续按F11先进入initMethods函数。3.4 initMethods 初始化方法function initMethods (vm, methods) {var props vm.$options.props;for (var key in methods) {{if (typeof methods[key] ! function) {warn(Method \ key \ has type \ (typeof methods[key]) \ in the component definition. Did you reference the function correctly?,vm);}if (props hasOwn(props, key)) {warn((Method \ key \ has already been defined as a prop.),vm);}if ((key in vm) isReserved(key)) {warn(Method \ key \ conflicts with an existing Vue instance method. Avoid defining component methods that start with _ or $.);}}vm[key] typeof methods[key] ! function ? noop : bind(methods[key], vm);}
}initMethods函数主要有一些判断。判断 methods 中的每一项是不是函数如果不是警告。
判断 methods 中的每一项是不是和 props 冲突了如果是警告。
判断 methods 中的每一项是不是已经在 new Vue实例 vm 上存在而且是方法名是保留的 _ $ 在JS中一般指内部变量标识开头如果是警告。除去这些判断我们可以看出initMethods函数其实就是遍历传入的methods对象并且使用bind绑定函数的this指向为vm也就是new Vue的实例对象。这就是为什么我们可以通过this直接访问到methods里面的函数的原因。我们可以把鼠标移上 bind 变量按alt键可以看到函数定义的地方这里是218行点击跳转到这里看 bind 的实现。3.4.1 bind 返回一个函数修改 this 指向function polyfillBind (fn, ctx) {function boundFn (a) {var l arguments.length;return l? l 1? fn.apply(ctx, arguments): fn.call(ctx, a): fn.call(ctx)}boundFn._length fn.length;return boundFn
}function nativeBind (fn, ctx) {return fn.bind(ctx)
}var bind Function.prototype.bind? nativeBind: polyfillBind;简单来说就是兼容了老版本不支持 原生的bind函数。同时兼容写法对参数多少做出了判断使用call和apply实现据说是因为性能问题。如果对于call、apply、bind的用法和实现不熟悉可以查看我在面试官问系列面试官问能否模拟实现JS的call和apply方法面试官问能否模拟实现JS的bind方法调试看完了initMethods函数按F8回到上文提到的initData(vm)函数断点处。3.5 initData 初始化 datainitData 函数也是一些判断。主要做了如下事情先给 _data 赋值以备后用。
最终获取到的 data 不是对象给出警告。
遍历 data 其中每一项
如果和 methods 冲突了报警告。
如果和 props 冲突了报警告。
不是内部私有的保留属性做一层代理代理到 _data 上。
最后监测 data使之成为响应式的数据。function initData (vm) {var data vm.$options.data;data vm._data typeof data function? getData(data, vm): data || {};if (!isPlainObject(data)) {data {};warn(data functions should return an object:\n https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function,vm);}// proxy data on instancevar keys Object.keys(data);var props vm.$options.props;var methods vm.$options.methods;var i keys.length;while (i--) {var key keys[i];{if (methods hasOwn(methods, key)) {warn((Method \ key \ has already been defined as a data property.),vm);}}if (props hasOwn(props, key)) {warn(The data property \ key \ is already declared as a prop. Use prop default value instead.,vm);} else if (!isReserved(key)) {proxy(vm, _data, key);}}// observe dataobserve(data, true /* asRootData */);
}3.5.1 getData 获取数据是函数时调用函数执行获取到对象。function getData (data, vm) {// #7573 disable dep collection when invoking data getterspushTarget();try {return data.call(vm, vm)} catch (e) {handleError(e, vm, data());return {}} finally {popTarget();}
}3.5.2 proxy 代理其实就是用 Object.defineProperty 定义对象这里用处是this.xxx 则是访问的 this._data.xxx。/*** Perform no operation.* Stubbing args to make Flow happy without leaving useless transpiled code* with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).*/
function noop (a, b, c) {}
var sharedPropertyDefinition {enumerable: true,configurable: true,get: noop,set: noop
};function proxy (target, sourceKey, key) {sharedPropertyDefinition.get function proxyGetter () {return this[sourceKey][key]};sharedPropertyDefinition.set function proxySetter (val) {this[sourceKey][key] val;};Object.defineProperty(target, key, sharedPropertyDefinition);
}3.5.3 Object.defineProperty 定义对象属性Object.defineProperty 算是一个非常重要的API。还有一个定义多个属性的APIObject.defineProperties(obj, props) (ES5)Object.defineProperty 涉及到比较重要的知识点面试也常考。value——当试图获取属性时所返回的值。
writable——该属性是否可写。
enumerable——该属性在for in循环中是否会被枚举。
configurable——该属性是否可被删除。
set()——该属性的更新操作所调用的函数。
get()——获取属性值时所调用的函数。详细举例见此链接3.6 文中出现的一些函数最后统一解释下3.6.1 hasOwn 是否是对象本身拥有的属性调试模式下按alt键把鼠标移到方法名上可以看到函数定义的地方。点击可以跳转。/*** Check whether an object has the property.*/
var hasOwnProperty Object.prototype.hasOwnProperty;
function hasOwn (obj, key) {return hasOwnProperty.call(obj, key)
}hasOwn({ a: undefined }, a) // true
hasOwn({}, a) // false
hasOwn({}, hasOwnProperty) // false
hasOwn({}, toString) // false
// 是自己的本身拥有的属性不是通过原型链向上查找的。3.6.2 isReserved 是否是内部私有保留的字符串$ 和 _ 开头/*** Check if a string starts with $ or _*/
function isReserved (str) {var c (str ).charCodeAt(0);return c 0x24 || c 0x5F
}
isReserved(_data); // true
isReserved($options); // true
isReserved(data); // false
isReserved(options); // false4. 最后用60余行代码实现简化版function noop (a, b, c) {}
var sharedPropertyDefinition {enumerable: true,configurable: true,get: noop,set: noop
};
function proxy (target, sourceKey, key) {sharedPropertyDefinition.get function proxyGetter () {return this[sourceKey][key]};sharedPropertyDefinition.set function proxySetter (val) {this[sourceKey][key] val;};Object.defineProperty(target, key, sharedPropertyDefinition);
}
function initData(vm){const data vm._data vm.$options.data;const keys Object.keys(data);var i keys.length;while (i--) {var key keys[i];proxy(vm, _data, key);}
}
function initMethods(vm, methods){for (var key in methods) {vm[key] typeof methods[key] ! function ? noop : methods[key].bind(vm);}
}function Person(options){let vm this;vm.$options options;var opts vm.$options;if(opts.data){initData(vm);}if(opts.methods){initMethods(vm, opts.methods)}
}const p new Person({data: {name: 若川},methods: {sayName(){console.log(this.name);}}
});console.log(p.name);
// 未实现前undefined
// 若川
console.log(p.sayName());
// 未实现前Uncaught TypeError: p.sayName is not a function
// 若川5. 总结本文涉及到的基础知识主要有如下构造函数
this 指向
call、bind、apply
Object.defineProperty
等等基础知识。本文源于解答源码共读群友的疑惑通过详细的描述了如何调试 Vue 源码来探寻答案。解答文章开头提问通过this直接访问到methods里面的函数的原因是因为methods里的方法通过 bind 指定了this为 new Vue的实例(vm)。通过 this 直接访问到 data 里面的数据的原因是data里的属性最终会存储到new Vue的实例vm上的 _data对象中访问 this.xxx是访问Object.defineProperty代理后的 this._data.xxx。Vue的这种设计好处在于便于获取。也有不方便的地方就是props、methods 和 data三者容易产生冲突。文章整体难度不大但非常建议读者朋友们自己动手调试下。调试后你可能会发现原来 Vue 源码也没有想象中的那么难也能看懂一部分。启发我们工作使用常用的技术和框架或库时保持好奇心多思考内部原理。能够做到知其然知其所以然。就能远超很多人。你可能会思考为什么模板语法中可以省略this关键词写法呢内部模板编译时其实是用了with。有余力的读者可以探究这一原理。最后欢迎加我微信 ruochuan12源码共读 活动大家一起学习源码共同进步。最近组建了一个湖南人的前端交流群如果你是湖南人可以加我微信 ruochuan12 私信 湖南 拉你进群。推荐阅读1个月200人一起读了4周源码我读源码的经历老姚浅谈怎么学JavaScript我在阿里招前端该怎么帮你可进面试群················· 若川简介 ·················你好我是若川毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列从2014年起每年都会写一篇年度总结已经写了7篇点击查看年度总结。同时最近组织了源码共读活动识别上方二维码加我微信、拉你进源码共读群今日话题略。欢迎分享、收藏、点赞、在看我的公众号文章~