<!DOCTYPE html>
<meta charset="UTF-8">
<title></title>
</head>
</body>
<script>
var user = {name:"老王",age:18,sex:"男"};
//[[Prototype]]
console.log(Object.prototype.toString(user));//[object Object]
//[[Extensible]]
console.log(Object.isExtensible(user)); //true
//[[GetOwnProperty]]
console.log(Object.getOwnPropertyNames(user));//Array(3):"name,age,sex"
//[[GetProperty]]
console.log(Object.getPrototypeOf(user));//[object Object]
//[[HasProperty]]
console.log(Object.hasOwnProperty("name"));//true
</script>
</html>
3、常见的对象创建与赋值
<!DOCTYPE html>
<meta charset="UTF-8">
<title></title>
</head>
<script>
//使用构造函数方式创建对象
var obj = new Object();
obj.name = "老王";
obj.age = 18;
obj.sex = "男";
//使用对象字面量方式创建对象
var user = {name:"老王",age:18,sex:"男"};
//分别将其属性描述符打印出来
console.log("obj属性描述:",Object.getOwnPropertyDescriptor(obj,"name"));
console.log("user属性描述:",Object.getOwnPropertyDescriptor(user,"name"));
// 数据属性:[[Configurable]]、[[Enumerable]]、[[Writable]]、[[Value]]
// Configurable是否可以通过delete删除属性
// Enumerable可否for-in
// Writable能否修改属性值
// Value读取这个属性的数据值
</script>
</body>
</html>
使用这种方式创建的对象,我们对一个Object对象设置属性时,一般是通过对象的.
操作符或者[]
操作符直接赋值的,通过这种方式添加的属性后续可以更改属性值,并且默认该属性是可枚举的(他的数据属性描述符默认都是true)。如果我们想要修改他的属性默认的特性呢?那么Object.defineProperty()他来了。
三、Object.defineProperty()方法的使用
Object.defineProperty()
的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,并返回这个对象。详细文档可以看:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
1、Object.defineProperty()语法
语法:Object.defineProperty(obj, prop, descriptor)
obj
要定义属性的对象。
prop
要定义或修改的属性的名称或 Symbol
。
descriptor 要定义或修改的属性描述符。
返回值:被传递给函数的对象。
注:在ES6中,由于 Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而Object.defineProperty
是定义key为Symbol的属性的方法之一。
该方法允许精确地添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到(for...in
或 Object.keys
方法),可以改变这些属性的值,也可以删除
这些属性。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty()
添加的属性值是不可修改(immutable)的。
<!DOCTYPE html>
<meta charset="UTF-8">
<title></title>
</head>
</body>
<script>
var user = {};
Object.defineProperty(user, 'age', {
value: 24,
writable: false
console.log(user);
console.log(user.age);//24
user.age = 28; // throws an error in strict mode
console.log(user.age);//24
</script>
</html>
2、参数descriptor 属性描述符
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。
2.1、数据描述符value 和 writable
栗子:value -值、writable -是否可写
<!DOCTYPE html>
<meta charset="UTF-8">
<title></title>
</head>
</body>
<script>
var user = {};
Object.defineProperty(user, 'age', {
value: 24,
writable:true //默认为false,不可修改
console.log(user); //[object Object]
console.log(user.age);//24
user.age = 28;
console.log(user.age);//28
</script>
</html>
2.2、存取描述符getter 和 setter
和数据属性不同,存取器属性不具有可写性(writable attribute)。如果属性同时具有getter和setter方法,那么它是一个读/写属性。如果它只有getter方法,那么它是一个只读属性。如果它只有setter方法,那么它是一个只写属性(数据属性中有一些例外),读取只写属性总是返回undefined。
<!DOCTYPE html>
<meta charset="UTF-8">
<title></title>
</head>
</body>
<script>
function Archiver() {
var temperature = null;
var archive = [];
Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
return temperature;
set: function(value) {
temperature = value;
archive.push({
val: temperature
this.getArchive = function() {
return archive;
var arc = new Archiver();
arc.temperature; // 'get!' 访问时被调用
arc.temperature = 11; //设置时调用set--archive
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
var obj = {};
Object.defineProperty(obj, 'x', {
set: function(val){
this.x = "要设置我吗?";
console.log(obj.x);//undefined
</script>
</html>
2.3、共有属性configurable 和 enumerable
栗子:configurable - 是否可删除
<!DOCTYPE html>
<meta charset="UTF-8">
<title></title>
</head>
</body>
<script>
var obj = {}
Object.defineProperty(obj, 'name', {
value: '老王',
configurable: true,
writable: true
delete obj.name
console.log(obj.name); // undefined
Object.defineProperty(obj, 'name', {
value: '老李',
configurable: false, //不能被删除
writable: true
delete obj.name
console.log(obj.name); //老李
</script>
</html>
栗子:enumerable - 是否可枚举
<!DOCTYPE html>
<meta charset="UTF-8">
<title></title>
</head>
</body>
<script>
var obj = {}
Object.defineProperty(obj, 'name', {
value: '老王',
enumerable:true
obj.sex = "男";
Object.defineProperty(obj, 'age', {
value: '28',
enumerable:false
console.log(Object.keys(obj)); //name,sex
for(var keys in obj){
console.log(keys+"="+obj[keys]);//name=老王 sex=男
console.log(obj.propertyIsEnumerable('age')); //false
</script>
</html>
了解完了上诉内容,现在开始进入源码模式,探究一下Vue.$data、this._data和this.property 为何都能取到data里面的数据。
四、Vue.$data、this._data源码解析
1、找到Vue函数
找到源码,Vue原来是一个函数。首先process.env.NODE_ENV
是判断你启动时候的参数的,如果不符合的话,就发出警告,否则执行_init
方法。值得一提的是一般属性名前面加_
默认代表是私有属性,不对外展示。
function Vue(options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
warn('Vue is a constructor and should be called with the `new` keyword')
this._init(options)
2、初始化_init
这个_init
是哪来的呢?可以看到下面有很多初始化的函数,我们先看第一个initMixin,然后去查看他的定义。
initMixin(Vue) //定义 _init(初始化就已经加载了,里面定义了Vue的原型方法_init)
stateMixin(Vue) //定义 $set $get $delete $watch 等
eventsMixin(Vue) // 定义事件 $on $once $off $emit
lifecycleMixin(Vue) // 定义 _update $forceUpdate $destroy
renderMixin(Vue) // 定义 _render 返回虚拟dom
3、initMixin初始化
initMixin中定义了原型方法_init,并初始化options参数,对生命周期变量初始化,初始化渲染Render,初始化 vm的状态,prop/data/computed/method/watch都在这里完成初始化,这里就有对data的初始化。initState(vm)
export function initMixin(Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
// a flag to avoid this being observed-一个避免被观察到的标志
vm._isVue = true
// merge options - 合并opt信息
if (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 || {},
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
// expose real self
vm._self = vm
initLifecycle(vm); // 定义 vm.$parent vm.$root vm.$children vm.$refs 等(生命周期变量初始化)
initEvents(vm); // 定义 vm._events vm._hasHookEvent 等(事件监听初始化)
initRender(vm); // 定义 $createElement $c (初始化渲染)
callHook(vm, 'beforeCreate'); // 回调 beforeCreate 钩子函数
initInjections(vm); // resolve injections before data/props (初始化注入)
initState(vm); // 初始化 props methods data computed watch 等方法 (状态初始化)
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created'); // 回调 created 钩子函数
// 如果有el选项,则自动开启模板编译阶段与挂载阶段
// 如果没有传递el选项,则不进入下一个生命周期流程
// 用户需要执行vm.$mount方法,手动开启模板编译阶段与挂载阶段
if (vm.$options.el) {
vm.$mount(vm.$options.el); // 实例挂载渲染dom
4、initState初始化
将该对象赋值给 vm._data 属性,是函数也会转为变量。isReserved 函数通过判断一个字符串的第一个字符是不是 $ 或_来决定其是否是保留的,Vue 是不会代理那些键名以 $ 或 _ 开头的字段的,因为Vue自身的属性和方法都是以 $ 或 _ 开头的,所以这么做是为了避免与 Vue 自身的属性和方法相冲突。 如果 key 既不是以 $ 开头,又不是以 _ 开头,那么将执行 proxy 函数,实现实例对象的代理访问
function initData (vm: Component) {
let data = vm.$options.data //先通过$options获取到data
data = vm._data = typeof data === 'function' //将对象赋值给vm._data
? getData(data, vm) //判断data是不是通过返回函数对象的方式建立的,如果是,那么则执行getdata方法,getdata的方法主要操作就是 data.call(vm, vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
// proxy data on instance
//检测data中的key是不是与props、methods中有重名
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') { //在非生产环境下如果发现在 methods 对象上定义了同样的key,打印一个警告
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
} else if (!isReserved(key)) {//判断starts with $ or _
proxy(vm, `_data`, key)
// observe data
observe(data, true /* asRootData */)
5、代理data->_data
proxy(vm, `_data`, key);proxy 函数的原理是通过 Object.defineProperty 函数在实例对象 vm 上定义与 data 数据字段同名的访问器属性,并且这些属性代理的值是 vm._data 上对应属性的值。假如访问:vm.message,就会触发sharedPropertyDefinition
的get,然后返回vm._data.message。
function proxy(target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key] //如:访问vm.message = 访问vm._data.message
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
Object.defineProperty(target, key, sharedPropertyDefinition);
6、在原型上绑定$data
$data的数据劫持是在stateMixin函数中处理的,因为$data被定义为一个getter,实际上它仍然访问的是this._data。
function stateMixin (Vue) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
var dataDef = {};
dataDef.get = function () { return this._data };
var propsDef = {};
propsDef.get = function () { return this._props };
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
propsDef.set = function () {
warn("$props is readonly.", this);
Object.defineProperty(Vue.prototype, '$data', dataDef); //将$data绑定到原型上
Object.defineProperty(Vue.prototype, '$props', propsDef);
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
Vue.prototype.$watch = function (
expOrFn,
options
var vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
options = options || {};
options.user = true;
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
try {
cb.call(vm, watcher.value);
} catch (error) {
handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
return function unwatchFn () {
watcher.teardown();
注:数据劫持最著名的应用当属双向绑定,比较典型的是Object.defineProperty()和 ES2016 中新增的Proxy对象。Vue 2.x
使用的是Object.defineProperty()(Vue 在 3.x
版本之后改用 Proxy
进行实现)。
Vue.$data源码解析 $data是Vue实例中的实例属性,表示Vue实例观察的数据对象。官网给出的解释:vm.$data,类型:Object,详细:Vue 实例观察的数据对象。Vue 实例代理了对其 data 对象 property 的访问。在了解vm.$data之前我们先来复习一下原型,然后再了解一下Object.defineProperty()。正是Vue内部实...
data中定义了一个数据msg, vue实例上访问这个数据有两种方式,this.$data.msg 和 this.msg,请问,为vue如何实现this.msg能直接访问到data中的msg变量???data又为什么是个函数?
clone 下 vue 的项目源码,然后打开src/core/instance/index.js
调用 init 方法时先进行了检查,确保已经使用 n...
Vue关于data和this的解析总结:this.$data、this._data、this.xxx 为什么都能获取数据?data为什么是个函数?
如果 data 是个对象,那么整个vue实例将共享一份数据,也就是各个组件实例间可以随意修改其他组件的任意值,这样对于实际开发时很难受的。
但是 data 定义成一个函数,将会 return 出一个唯一的对象,不会和其他组件共享一个对象。
我们注册组件的时候实际上是建立了一个组件构造器的引用,只有使用组件的时候才会真正创建一个组件实例。
总而言之:
this.d
$data是Vue实例中的实例属性,表示Vue实例观察的数据对象。实际上在Vue官网对这部分有较为详细的描述,这里就不再赘述了(具体可看官网的描述Vue选项/数据)。本篇文章从源码层次来梳理$data背后的逻辑,实际上就是一个问题:
假设存在属性name,为什么修改vm.$data.name与vm.name可以达到相同效果?
实例属性$data
假设存在属性name,为什么修改vm.$data.name与vm.name可以达到相同效果?
以此问题为出发点,假设data中存在name,实际上就是
Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象
this.$data获取当前状态下的data
this.$options.data()获取该组件初始状态下的data。
object.assign(this.$data, this.$options.data())
使用场景:有一个表单,表单提交成功后,希望组件恢复到初始状态,重置data数据。
然后只要使用Object.assign(this.$data, this.$options.data())就可以将当前
在Vue实例中,我们可以通过`this`来访问实例的数据和方法。其中,`this.data`和`this.$data`都是用来访问Vue实例的数据的方式。
`this.$data`是Vue提供的一个属性,它指向Vue实例的数据对象,也就是我们在`new Vue()`时传入的`data`选项。例如:
new Vue({
data: {
message: 'Hello World'
created: function () {
console.log(this.$data.message) // 'Hello World'
上面的代码中,我们在Vue实例的`created`生命周期函数中使用了`this.$data.message`来访问Vue实例的数据对象。
而`this.data`并不是Vue提供的属性,它在Vue实例中并没有任何意义。如果我们在Vue实例中使用`this.data`来访问数据,将会得到`undefined`的结果。例如:
new Vue({
data: {
message: 'Hello World'
created: function () {
console.log(this.data.message) // undefined
因此,在Vue实例中,我们应该使用`this.$data`来访问实例的数据对象。