Vue 源码解析 (二)initProxy 初始化代理
Vue 源码解析 (二)initProxy 初始化代理
在 src/core/instance/proxy.js 找到源码
makeMap,allowedGlobals
我们先来看看 makeMap 这个方法,做了什么处理:
/*makeMap函数, str参数是接受的字符串, expectsLowerCase参数是否需要小写*/
function makeMap(str, expectsLowerCase ) {
/* 创建一个对象 */
var map = Object.create(null);
/*将字符串分割成数组*/
var list = str.split(',');
/*对数组进行遍历*/
for (var i = 0; i < list.length; i++) {
/*将每个key对应的值设置为true*/
map[list[i]] = true;
}
/*最终返回, 根据参数设置是否是需要转换大小写*/
return expectsLowerCase
? function (val) {
return map[val.toLowerCase()];
}
: function (val) {
return map[val];
}
}
然后给一些 js 内置的全局方法做了相应的处理:
var allowedGlobals = makeMap(
'Infinity,undefined,NaN,isFinite,isNaN,' +
'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
'require' // for Webpack/Browserify
);
makeMap 函数的只要作用把这些全局的API转成以下的形式,
{
Infinity:true,
undefined:true
}
isNative
可以学习一下源码是如何检测是不是支持原生方法
export function isNative (Ctor: any): boolean {
return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}
warnNonPresent
这个方法的意思是不存在,未定义的属性,方法被使用给出警告,我们来看看例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="../vue.js"></script>
<div id="app">
<p>
{{msg}}
</p>
</div>
<script>
const vm = new Vue({
el: '#app',
})
console.log(vm)
</script>
</body>
</html>
上面例子直接在魔板中使用 msg 变量,但是他没有在 data 中定义,此时 warnNonPresent 会处理抛出警告如图所示
warnReservedPrefix
源码如下:
const warnReservedPrefix = (target, key) => {
warn(
`Property "${key}" must be accessed with "$data.${key}" because ` +
'properties starting with "$" or "_" are not proxied in the Vue instance to ' +
'prevent conflicts with Vue internals. ' +
'See: https://vuejs.org/v2/api/#data',
target
)
}
用于检测属性 key 的声明方法,是否是 $ 或者 _ 开头的,如果是,会给出警告,拿个简单的例子来看下效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="../vue.js"></script>
<div id="app">
<p>
{{$hhh}}
{{_dddd}}
</p>
</div>
<script>
const vm = new Vue({
el: '#app',
data() {
return {
$hhh: 'ddd',
_dddd: 'ffff'
}
},
})
console.log(vm)
</script>
</body>
</html>
hasHandler
var hasHandler = {
/*target要代理的对象, key在外部操作时访问的属性*/
has: function has(target, key) {
/*key in target返回true或者false*/
var has = key in target;
/*在模板引擎里面,有一些属性vm没有进行代理, 但是也能使用, 像Number,Object等*/
var isAllowed = allowedGlobals(key) ||
(typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data));
/*在上面的has和isAllowed为false的情况下*/
if (!has && !isAllowed) {
if (key in target.$data) {
warnReservedPrefix(target, key);
}
/*warnNonPresent函数, 当访问属性,没有存在vm实例上, 会报错提示*/
else {
warnNonPresent(target, key);
}
}
/*has或者isAllowed*/
return has || !isAllowed
}
};
hasHandler 只配置了 has 钩子 ,当进行propKey in proxy in 操作符 或者 with() 操作时, 会触发 has钩子函数
hasHandler在查找key时,从三个方向进行查找
-
代理的 target 对象 通过 in 操作符 -
全局对象API allowedGlobals 函数 -
查找是否是渲染函数的内置方法 第一个字符以_开始 typeof key === 'string' && key.charAt(0) === '_'
hasHandler, 首先去检测 vm 实例上是否有该属性, 下面的代码是vm实例上可以查看到test
new Vue({
el:"#app",
template:"<div>{{test}}</div>",
data:{
test
}
})
如果在 vm 实例上没有找到, 然后再去判断下是否是一些全局的对象, 例如 Number 等, Number是语言所提供的 在模板中也可以使用
new Vue({
el:"#app",
/*Number属于语言提供的全局API*/
template:"<div> {{ Number(test) +1 }}</div>",
data:{
test
}
})
getHandler
const getHandler = {
get(target, key) {
if (typeof key === 'string' && !(key in target)) {
// 检测 data 是属性 key 是不是 $,_ 开头
if (key in target.$data) warnReservedPrefix(target, key)
else warnNonPresent(target, key)
}
return target[key]
}
}
initProxy
initProxy = function initProxy(vm) {
/*hasProxy 判断当前环境是否支持es 提供的 Proxy*/
if (hasProxy) {
// determine which proxy handler to use
var options = vm.$options;
/*不同条件返回不同的handlers, getHandler或者hasHandler */
var handlers = options.render && options.render._withStripped
? getHandler
: hasHandler;
/* 代理vm实例 */
vm._renderProxy = new Proxy(vm, handlers);
} else {
vm._renderProxy = vm;
}
};