最后更多分享:前端字节跳动真题解析
/**
-
把函数Vue作为参数传递进去,进行第一次初始化,
-
初始化的结果就是将传入的参数上的属性与默认的属性进行一次合并
-
*/
initMixin(Vue);
到这里发现,在初始化的_init函数中合并参数这个功能其实是靠mergeOptions()这个函数完成,当合并参数完成后将值赋值给了vm. o p t i o n s , 到 这 就 解 释 了 , 为 什 么 上 文 例 子 中 , 在 外 界 的 时 候 可 以 使 用 使 用 实 例 化 v m . options,到这就解释了,为什么上文例子中,在外界的时候可以使用使用实例化vm. options,到这就解释了,为什么上文例子中,在外界的时候可以使用使用实例化vm.options访问到vue根实例上的众多属性;
==============================================================
那么,接下来的重点就是mergeOptions()这个函数了,搜索源码查询到(这里剔除了与本文无关的内容,实际上在这些过程中还进行了众多处理,比如命名规范的检测等等,但这些都不在本次学习的内容内,因此暂时剔除)
/**
-
@param {*} parentVal Vue中需要默认带上的对象属性,比如component,directive
-
@param {*} childVal 传入的参数,比如el,data,computed等等
-
@param {*} vm this
*/
function mergeOptions(parentVal, childVal, vm){
var options = {};
var key;
for(key in parentVal){
mergeField(key)
}
for(key in childVal){
/**
- 假如parentVal和childVal都有相同的属性,那么为了保证相同的属性只被添加一次,执行检测
*/
if(!hasOwn(parentVal,key)){
//对于传入的对象上没有保留字的属性,那么执行合并
mergeField(key);
}
}
function mergeField(key){
/**
- strats其实是暴露给使用者的接口,Vue允许自定义一个函数处理传入对象内还没有被定义的方法
*/
var start = strats[key] || defaultStrat;
//执行合并
options[key] = start(parentVal[key], childVal[key], vm, key)
}
return options;
}
/**
-
obj,传入的对象
-
key,传入的属性名
-
判断obj上是否有名为key的属性
*/
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key)
}
在这个函数中分别对parentVal和childVal进行了for…in循环,并将属性名作为参数传递到了mergeField函数中进行处理;
乍一看这个mergeField有点懵,这里面的strats和defaultStrat都是一个跨作用域的属性,到这里甚至这两个属于什么类型都不知道,不慌,搜一下,先搜一下strats,
var config = ({
/**
- Option merge strategies (used in core/util/options)
*/
optionMergeStrategies: Object.create(null)
})
//获得空对象引用 strats === {}
var strats = config.optionMergeStrategies;
发现有点不明所以,定义了一个config,其中有一个属性optionMergeStrategies,这是一个空对象,然后将这个空对象赋值给了strats,那么其实这个strats就是一个空对象,那为什么不直接赋值一个空对象的?
搜索官网得知,这个optionMergeStrategies是暴露给外界的一个接口,它允许用户在外界定义合并策略,具体看optionMergeStrategies合并策略;假如直接赋值一个空对象,那么这里将无法暴露给用户使用;
也就是说,Vue其实是允许用户自定义属性的,比如除去常规属性外,假如用户定义在传入的参数上定义了一个特殊的属性count,可以通过optionMergeStrategies为其添加策略,具体如下例
//传入的参数多了一个count,这个属性不是官方提供的
var vm = new Vue({
el:"#app" ,
data:{
name:"Oliver尹"
},
beforeCreate() {},
count:1
})
//为count扩展策略
Vue.config.optionMergeStrategies.count = function (parent, child, vm) {
return child + 1
}
//合并结束后count的值就会变成2,不再是1
回到主题,那么到这里可以知道strats[key],就是去暴露的接口上查有没有自定义方法,如果有,那么将其对应的方法给到start;
然后再看一下defaultStrat
var defaultStrat = function(parentVal, childVal) {
return childVal === undefined ? parentVal : childVal;
};
根据前面的strats[key]可以猜到,既然前面的是合并的自定义策略,那这个肯定就是默认策略了,这是一个函数表达式,就是判断在传入的参数childVal有没有,如果有,返回childVal,如果没有就返回parentVal;
到这里可以明白
var start = strats[key] || defaultStrat;
这一行代码就是做了个判断,判断自定义的属性有没有对于的合并策略,如果有,那么按用户定义的合并策略进行,如果没有,那么就按默认的策略进行,并且不管哪种策略,都是一个函数;
接着就是执行合并了
//合并,并且将值赋值给了options[key]
options[key] = start(parentVal[key], childVal[key], vm, key)
options是一开始定义的空对象,并且该函数最终也是将options作为返回值返回出去的;
到这里函数基本弄清楚了,**mergeOptions()**这个函数就是在检测判断当前属性是内置的,还是自定义的,如果是自定义的,那么判断有没有自定义的合并策略,如果有,那么就按自定义的合并策略进行合并,如果没有,那么按默认的合并策略进行合并;
接着就是理解参数了,调用的时候一共传了3个参数(其中第一个参数Vue.options,实际上Vue不是这么写的,实际上远复杂的多,这里只是简化了)
vm.$options = mergeOptions(Vue.options, params || {}, vm);
其中params和vm都好理解,params就是实例化时传入的参数,vm指的是this,第一个参数Vue.options则是一个跨作用域的属性
Vue.options = Object.create(null); //等同于Vue.options = {}
通过查询知道,其实Vue.options默认的状态也是一个空对象,之后
//定义属性名,也就是vue上的component等
var ASSET_TYPES = [
'component',
'directive',
'filter'
];
ASSET_TYPES.forEach(el=>{
Vue.options[el+"s"] = Object.create(null);
})
通过内置的ASSET_TYPES对其进行进行循环,将每个属性值添加到空对象上,这里就又有一个疑问,为什么要这么写,直接加s不好吗,为什么要通过遍历再加s,最好发现其实这个是为了保证属性的统一,因为component等这些有的上面是components,有的则是component,因此宁可多写几行代码,也要保证属性的高度统一;
因此,Vue.options就是一个内置属性的集合;
通过mergeOptions()这个函数将内置属性和传入的参数进行合并,并且合并的时候通过hasOwn()函数判断指定对象上是否有指定属性,假如有就跳过,从而达到相同属性只执行一次的效果,优化了合并速度;
之后判断传入对象上是否有自定义属性和自定义合并策略,如果有自定义合并策略,那么合并按自定义的规定合并,如果没有就按默认策略合并;
=================================================================
在合并策略中叙述到了一个自定义策略的问题,既然自定义策略是暴露给外界用户的接口,那么如果被篡改了怎么办,比如
//传入的参数多了一个count,这个属性不是官方提供的
var vm = new Vue({
el:"#app" ,
data:{
name:"Oliver尹"
},
beforeCreate() {},
count:1
})
//为count扩展策略
//Vue.config.optionMergeStrategies.count = function (parent, child, vm) {
// return child + 1
//}
//不对config.optionMergeStrategies进行扩展,而进程篡改,比如
Vue.config.optionMergeStrategies = 1;
直接将暴露的对象给修改掉怎么办,在Vue中的处理是这样的
//还是对自定义策略做一个监听,监听暴露的接口是不是有被修改,如果是被修改,那么报错,如果是访问直接将config返回出去
function initGlobalAPI(Vue){
var configDef = Object.create(null);
configDef.get = function () {
return config
}
configDef.set = function (params) {
console.error("请不要尝试修改Vue.config");
return;
}
Object.defineProperty(Vue, "config", configDef)
}
Vue并没有直接去给对象扩展属性,而是采用监听属性的方式,如果你是访问的方式去获得属性,那么访问哪个,就直接通过get返回哪个,但如果是对其进set行设置,则不行,直接报错,不允许进行修改(新技能获得);
因此,Vue中是通过get和set属性对其进行监听的,而没有直接对一个对象进行修改;
================================================================
在2.5.1的版本中,Vue一共设置了11个生命周期函数,其原理是相同的,就是在不同的阶段执行函数callHook(),具体请看下例
//执行合并参数的初始化
function initMixin(Vue){
//给函数Vue的原型添加一个_init的方法
Vue.prototype._init = function(params) {
console.log(this);
//存储this
var vm = this;
/**
-
初始化前,对vue这个构造函数再扩展一个$options属性,
-
mergeOptions用于合并传入的参数和需要添加的默认参数
-
*/
vm.$options = mergeOptions(Vue.options, params || {}, vm);
/**
-
执行beforeCreate钩子函数,
-
也就是为什么执行beforeCreate的时候,data中以及有值了,因为已经进行了参数合并
-
*/
callHook(vm, 'beforeCreate');
}
}
这是之前的初始化函数initMin,当执行合并参数结束以后,就开始执行beforeCreate这个生命周期,当然在这之前,还需要定义钩子函数;
//2.5.1版本的钩子函数
var LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured'
];
之后会对这11个钩子函数进行一次批量处理,处理的方法就是forEach
/**
- 钩子函数的批量处理
*/
LIFECYCLE_HOOKS.forEach((el)=>{
strats[el] = mergeHook;
})
上面已经知道了,这个strats其实就是暴露给外界的接口config.optionMergeStrategies,这里将每一个钩子函数都作为对象添加到这个接口上;
到这里就解释了,每一个生命周期函数其实都会被添加到这个config.optionMergeStrategies对象上并且作为自定义策略生效了;
接着给每一个生命周期函数赋值了mergeHook
/**
-
所有钩子函数的自定义的策略
-
同样钩子函数也是定义在传入的参数内,因此所有的钩子函数也会经历过mergeOptions这个函数的处理
-
最终将钩子函数合并到vm.$options上,
-
@param {*} parentVal parentVal === undefined ,因为Vue.options上没有这些钩子函数的属性名
-
@param {*} childVal 传入包含el,data的参数
-
最终它会将所有钩子函数包装成一个数组,比如beforeCreate会被包装成[function(){},function(){}]
-
因此所有的钩子函数都可以写出数组的形式,不必局限于函数的形式,因为在vue的内部,所有的钩子函数最终都会被转成数组
*/
function mergeHook(parentVal,childVal){
return childVal? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal;
}
请重点看注释,通过这个mergeHook函数,Vue会将所有钩子函数包装成数组,所以实际上钩子函数还有别的写法不仅仅局限于一个函数,也可以是一个数组,数组中的每一项是一个函数
var vm = new Vue({
el:"#app" ,
data:{
name:"Oliver尹"
},
//通过源码知道,这么写其实也没有问题的
beforeCreate:[function(){}, function(){}],
})
console.log(vm.$options)
先看代码
//vm:当前的this
//beforeCreate:需要执行的函数的名字
callHook(vm, 'beforeCreate');
//执行钩子函数的函数
function callHook(vm, hook){
//这里的时候options上了,并且肯定是数组,因为不是数组也已经转成数组了
var handle = vm.$options[hook];
//如果有handle,依次执行
/**
- 因为使用call将vm传进去了,因此钩子函数里的this指向的是vue的根实例
*/
if(handle){
for(var i = 0 , j = handle.length ; i < j ; i++){
handle[i].call(vm);
}
}
}
总结
前端资料汇总
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
-
框架原理真的深入某一部分具体的代码和实现方式时,要多注意到细节,不要只能写出一个框架。
-
算法方面很薄弱的,最好多刷一刷,不然影响你的工资和成功率😯
-
在投递简历之前,最好通过各种渠道找到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。
-
要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!! 喜欢这篇文章文章的小伙伴们点赞+转发支持,你们的支持是我最大的动力!