从Proxy和Reflect中解读Vue3响应式原理及组合式API
数据响应式
数据响应式是什么?
通过数据的改变去驱动 DOM 视图的变化。
Vue2与Vue3实现数据响应式有何区别?
Vue2响应式:通过Object.defineProperty
来实现。对data对象的每个属性都劫持监听。
let data = {
name: '李四',
car: '路虎',
work: {
a: '1'
}
}
observer(data);
function observer(data) {
if (typeof data !== 'object' || data == null) {
return;
}
// 如果是对象,则需要递归调用响应式函数
const keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
let value = obj[key];
reactive(obj, key, value);
}
}
// `Object.defineProperty()` 是 Vue2 的核心
// Vue2 在初始化时会对数据进行劫持,如果劫持的属性还是对象的话需要递归劫持。
// 响应式函数
function reactive(obj, key, value) {
observer(value);
Object.defineProperty(obj, key, {
get() {
return value;
},
set(newValue) {
if (newValue === value) return;
observer(newValue);
value = newValue;
}
})
}
💥通过Object.defineProperty
来实现响应式有何缺点?
- 从上面的代码中我们发现
Object.defineProperty()
是有缺陷的,当观察的数据嵌套非常深时,这样是非常耗费性能的。 - 只对初始对象里的属性有劫持,当此对象新增某个属性或者移除某属性时,都是无响应式。故可通过
Vue.set()
或Vue.delete()
解决此类问题。 - 无法监听数组索引的直接赋值,无法监听修改数组的长度,故Vue2中通过修改数组的继承关系,重写数组方法的方式进行拦截调用。例如这些数组的方法
push
,pop
,shift
,unshift
,splice
,sort
,reverse
。
在Vue2中,通过arr[0]='newVal'这种根据数组索引值直接赋值的操作不会触发页面更新。这也并不是
Object.defineProperty
无法做到,而是因为考虑性能问题没有这么做。因为不确定数组的长度,对数组遍历进行劫持性能会损失很大。
Vue3响应式:使用ES6新增的Proxy
(代理)实现。先看看使用Proxy
代理的例子。
// 定义处理函数
const handler = {
// get捕捉器-获取属性值
get (target, prop) {
console.log(`拦截了读取数据: 属性${prop}`)
return target
},
// set捕捉器-修改属性值或者是添加属性
set (target, prop, value) {
target[prop] = value
console.log(`拦截了修改数据或者是添加属性: 属性${prop},属性值${value}`)
return target
},
// deleteProperty捕捉器-删除某个属性
deleteProperty (target, prop) {
delete target[prop]
console.log(`拦截了删除数据: 属性${prop}`)
return target
}
//...一共13个配置项(捕捉器)
}
// 使用Proxy,此时p就是代理后的对象了
const p = new Proxy({}, handler);
// 验证修改/添加属性(会走到handler的set捕捉器方法)
p.name = '李梅'
// 验证读取属性(会走到handler的set捕捉器方法)
p.name
// 验证删除属性(会走到handler的deleteProperty捕捉器方法)
delete p.name
💥从上面例子可以看出Vue3使用Proxy
代理实现响应式的优势:
- 可以劫持整个对象(而不是仅对属性劫持),并返回一个新对象。
Proxy
在代码量上远远优于Object.defineProperty()
的数据劫持操作。 Proxy
提供了13种劫持捕捉操作,可以更加精细化劫持捕捉操作,这是Object.defineProperty
无法做到的。
Vue2与Vue3的兼容
因为Object.defineProperty
兼容到IE8,所以Vue2一般可以兼容到IE8。而Proxy
对IE11是不兼容的,故Vue3目前来看是不对IE11兼容的。
详解Proxy和Reflect
Proxy定义?
刚刚已经知道了Vue3是通过Proxy
来实现数据响应式的,现在我们来看看它是如何使用的。
ES6新增的最明显的元编程特性之一就是Proxy
(代理)特性。
简单来说,代理可以看作是对目标对象的“包装”,为目标对象架设一层拦截,外界对该对象的访问,都必须通过这层拦截。提供了像get
,set
,deleteProperty
等13种捕捉器。
Proxy使用?
语法:
const p = new Proxy(target, handler)
参数:
-
target
-
要使用
Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。 -
handler
-
代理配置:带有“捕捉器”(“traps”,即拦截操作的方法)的对象。比如
get
捕捉器方法用于读取target
的属性,set
捕捉器方法用于写入target
的属性,等等。
let target = {};
let proxy = new Proxy(target, {}); // 空的 handler 对象
proxy.test = 5; // 写入 proxy 对象 (1)
console.log(target.test); // 5,test 属性出现在了 target 中!
console.log(proxy.test); // 5,我们也可以从 proxy 对象读取它 (2)
for(let key in proxy) console.log(key);// test,迭代也正常工作 (3)
上面例子🌰由于没有捕捉器(空的 handler 对象),所有对 proxy
的操作都直接转发给了 target
。
- 写入操作
proxy.test
会将值写入target
。 - 读取操作
proxy.test
会从target
返回对应的值。 - 迭代
proxy
会从target
返回对应的值。
我们可以看到,没有任何捕捉器,proxy
是一个 target
的透明包装器(wrapper)。
当然可以添加捕捉器(13个)以此进行拦截,扩展激活出proxy
更多的功能。
首先,对于对象的大多数操作,JavaScript 规范中有一个所谓的“内部方法”,它描述了最底层的工作方式。例如 [[Get]]
,用于读取属性的内部方法,[[Set]]
,用于写入属性的内部方法,等等。这些方法仅在规范中使用,我们不能直接通过方法名调用它们。
Proxy 捕捉器会拦截这些方法的调用。对于每个内部方法,表中都有一个捕捉器:捕捉器可用于添加到 new Proxy
的 handler
参数中以拦截对象内部方法的使用:
💥为什么要使用Proxy
?
- 代理成为了代码交互的主要对象,而实际目标对象保持隐藏/被保护的状态。
- 可以拦截(并覆盖)对象的几乎所有行为,这意味着可以以强有力的方式扩展对象特性超出JavaScript内容。
- 降低函数或类的复杂度
Reflect的定义
Reflect
称为反射。它也是ES6中为了操作对象而提供的新的API,用来替代直接调用Object
的方法。
Reflect
是一个内置的对象,它提供拦截 JavaScript 操作的方法。
前面所讲过对象的一些内部方法,例如 [[Get]]
和 [[Set]]
等,都只是规范性的,不能直接调用。
Reflect
对象使调用这些内部方法成为了可能。它的方法是内部方法的最小包装。
以下是执行相同操作和 Reflect
调用的示例:
例如,我们可以通过Reflect.set
代替obj[prop] = value
的操作:
let user = {};
Reflect.set(user, 'name', 'Fengmaybe');
console.log(user.name); // Fengmaybe
所以,我们可以更方便使用 Reflect
来将操作转发给原始对象。这也是Reflect
的出现的原因之一:操作对象更为方便和语义化。
而Reflect
使用在Proxy
中的转发对象上也十分典型。对于每个可被 Proxy
捕获的内部方法,在 Reflect
中都有一个对应的方法,其名称和参数与 Proxy
捕捉器相同。 这一点很关键,13种捕获器可以与Reflect
方法一一对应,简化 Proxy
的创建。这也是Reflect
的出现的原因之一:完美的与Proxy
搭配使用。 看下面这个例子🌰:
let p = new Proxy(target, {
get (target, prop, receiver) {
// proxy的get捕获器和Reflect.get方法名和参数一一对应
return Reflect.get(target, prop, receiver);
// 因为两者参数一直故可以简写为 Reflect.get(...arguments)
}
});
💥总结Reflect
的一些特点:
- 不可构造,不能使用 new 进行调用
- 所有方法和
Proxy
handlers中的捕捉器相同(13个一一对应) - 提供的API比
Object
更为丰富且使用起来更为语义化 - 所有的方法都是静态方法,类似于
Math
。(静态方法是可以直接用类名.方法名
去调用的;而实例方法是不可以的,必须要用实例才可以去调用) - 部分方法和
Object.*
相同,但行为略微有所区别。譬如Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。(见下例子🌰)
//Object对象方法
try {
Object.defineProperty(target, name, property);
} catch (e) {
console.log("error");
}
//Reflect对象方法
if (Reflect(target, name, property)) {
console.log("success");
} else {
console.log("error")
}
receiver参数具有不可替代性
让我们看一个例子🌰,来说明为什么 Reflect.get
更好。此外,我们还将看到为什么 get/set
有第三个参数 receiver
,如何使用它。
let animal = {
_name: "动物",
get name() {
return this._name;
}
};
let animalProxy = new Proxy(animal, {
get(target, prop) {
return target[prop]; // (*) target = cat
}
});
let cat = {
__proto__: animalProxy,
_name: "猫"
};
// 期望输出:猫
console.log(cat.name); // 输出:动物
读取 cat.name
应该返回 "猫"
,而不是 "动物"
!
发生了什么?或许我们在继承方面做错了什么?
但是,如果我们移除代理,那么一切都会按预期进行。
问题实际上出在代理中,在 (*)
行。
-
当我们读取
cat.name
时,由于cat
对象自身没有对应的的属性,搜索将转到其原型。 -
原型是
animalProxy
。 -
从代理读取
name
属性时,get
捕捉器会被触发,并从原始对象返回target[prop]
属性,在(*)
行。
当调用 target[prop]
时,若 prop
是一个 getter,它将在 this=target
上下文中运行其代码。因此,结果是来自原始对象 target
的 this._name
,即来自 animal
。
为了解决这种情况,我们需要 get
捕捉器的第三个参数 receiver
。它保证将正确的 this
传递给 getter。在我们的例子中是 cat
。
如何把上下文传递给 getter?对于一个常规函数,我们可以使用 call/apply
,但这是一个 getter,它不能“被调用”,只能被访问。
Reflect.get
可以做到。如果我们使用它,一切都会正常运行。
这是更正后的变体:
let animal = {
_name: "动物",
get name() {
return this._name;
}
};
let animalProxy = new Proxy(animal, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver); // (*)
}
});
let cat = {
__proto__: animalProxy,
_name: "缅因猫"
};
// 期望输出:缅因猫
console.log(cat.name); // 输出:猫
现在 receiver
保留了对正确 this
的引用(即 cat
),该引用是在 (*)
行中被通过 Reflect.get
传递给 getter 的。
可以看出捕捉器的第三个参数 receiver
具有不可替代性。
我们可以把捕捉器重写得更短:
get(target, prop, receiver) {
return Reflect.get(···arguments);
}
Reflect
调用的命名与捕捉器的命名完全相同,并且接受相同的参数。它们是以这种方式专门设计的。
因此,return Reflect...
提供了一个安全的方式,可以轻松地转发操作,并确保我们不会忘记与此相关的任何内容。
Proxy
和Reflect
总是协同工作的
从上一节的最后一个例子🌰也可以看出Proxy
和Reflect
从设计之初就是完美搭配使用的。
💥现在总结下两者协同工作的原因:
Reflect
Api有13个静态函数,这与Proxy
设计是一一对应的。如果Proxy
一个捕捉器想要将调用转发给对象,则只需使用相同的参数调用Reflect.<method>
就足够了。这种映射在设计之初就是有意对称的。- Proxy get/set()方法需要的返回值正是Reflect的get/set方法的返回值,可以天然配合使用,比直接对象赋值/获取值要更方便和准确。
- receiver参数具有不可替代性。
Reflect
和Proxy
搭配使用实现响应式
现在将用Proxy
+ Reflect
搭配改造一下,实现数据响应式效果。
// 定义处理函数
const handler = {
// 获取属性值
get (target, prop) {
console.log('拦截了读取数据')
// ***等同于target[prop]
const result = Reflect.get(target, prop)
return result
},
// 修改属性值或者是添加属性
set (target, prop, value) {
console.log(`拦截了修改数据或者是添加属性: 属性${prop},属性值${value}`)
// ***等同于target[prop] = value
const result = Reflect.set(target, prop, value)
return result
},
// 删除某个属性
deleteProperty (target, prop) {
console.log(`拦截了删除数据: 属性${prop}`)
// ***等同于delete target[prop]
const result = Reflect.deleteProperty(target, prop)
return result
}
//...一共13个配置项
}
const p = new Proxy({}, handler);
// 验证修改或添加属性
p.b = undefined;
// 验证获取属性值
p.b
// 验证删除某个属性
delete p.b
从上面的例子可以看出,如果我们不使用Reflect
也可以达到相同的效果。但是考虑到以下几点,我们通常选择了Proxy
+ Reflect
API搭配使用:
- 只要是
Proxy
对象具有的代理方法,Reflect
对象全部都与之对应,。故无论Proxy
怎么修改默认行为,总是可以通过Reflect
对应的方法获取默认行为。 - 使用
Reflect
API修改行为不会报错,使用起来更为合理。例如上面所提到的Object.defineProperty(obj, name, desc)
和Reflect.defineProperty(obj, name, desc)
。 Reflect
提供这种静态方法调用,更加的具有语义化。
Vue3组合式API实现原理
前言
Vue3引入了新特性——组合式API。其中一些响应性的API就是通过Proxy
+Reflect
实现的。列出一部分的响应性的API看看具体是如何实现的。
API的具体作用和功能可以去官网看看,这里不再叙述,只针对其内部的实现进行说明。
原理
响应性API的实现原理
- 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。
- 通过Reflect(反射):对被代理对象的属性进行操作。
实现
我们这里只是实现数据响应式简单的逻辑编排,并没有实现其中DOM变化。
1. shallowReactive
和reactive
首先看第一对shallowReactive
和reactive
。
需要说明的就是两点:
- 调用
shallowReactive(obj)
会返回一个代理后的对象,所以shallowReactive
函数应该返回一个proxy
:new Proxy(target, reactiveHandler)
。 - 调用
reactive(obj)
会返回一个深层次的代理对象,所以在shallowReactive
函数实现的基础上递归去判断其值是否是对象类型,如果是,就需要递归执行new Proxy(target, reactiveHandler)
这一操作。让对象的每一层都返回一个响应式的代理对象,这样自然就会返回一个深层次的代理对象。
// shallowReactive(浅的劫持,浅的监视,浅的响应数据) 与 reactive(深的)
// 定义一个reactiveHandler处理对象
const reactiveHandler = {
// 获取属性值
get (target, prop) {
const result = Reflect.get(target, prop)
console.log('拦截了读取数据', prop, result)
return result
},
// 修改属性值或者是添加属性
set (target, prop, value) {
const result = Reflect.set(target, prop, value)
console.log('拦截了修改数据或者是添加属性', prop, value)
return result
},
// 删除某个属性
deleteProperty (target, prop) {
const result = Reflect.deleteProperty(target, prop)
console.log('拦截了删除数据', prop)
return result
}
}
// 定义一个shallowReactive函数,传入一个目标对象
function shallowReactive (target) {
// 判断当前的目标对象是不是object类型(对象/数组)
if (target && typeof target === 'object') {
return new Proxy(target, reactiveHandler)
}
// 如果传入的数据是基本类型的数据,那么就直接返回
return target
}
// 定义一个reactive函数,传入一个目标对象
function reactive (target) {
// 判断当前的目标对象是不是object类型(对象/数组)
if (target && typeof target === 'object') {
// 对数组或者是对象中所有的数据进行reactive的递归处理
// 先判断当前的数据是不是数组
if (Array.isArray(target)) {
// 数组的数据要进行遍历操作
target.forEach((item, index) => {
target[index] = reactive(item)
})
} else {
// 再判断当前的数据是不是对象
// 对象的数据也要进行遍历的操作
Object.keys(target).forEach(key => {
target[key] = reactive(target[key])
})
}
return new Proxy(target, reactiveHandler)
}
// 如果传入的数据是基本类型的数据,那么就直接返回
return target
}
这样就简单实现了shallowReactive
和reactive
函数对读/写/删除操作的响应式了。其中可以单独抽取reactiveHandler
处理函数,供shallowReactive
和reactive
函数使用。
✅现在使用下自己写的shallowReactive
和reactive
函数,看是否是预期效果。
// 验证使用shallowReactive
const proxyUser1 = shallowReactive({
name: '小明',
car: {
color: 'red'
}
})
// 拦截到了读和写的数据
proxyUser1.name +='=='
// 拦截到了读取数据,但是拦截不到写的数据
proxyUser1.car.color+'=='
// 拦截到了删除数据
delete proxyUser1.name
// 只拦截到了读,但是拦截不到删除
delete proxyUser1.car.color
// 验证使用reactive
const proxyUser2 = reactive({
name: '小明',
car: {
color: 'red'
}
})
// 拦截到了读和修改的数据
proxyUser2.name += '=='
// 拦截到了读和修改的数据
proxyUser2.car.color = '=='
// 拦截了删除
delete proxyUser2.name
// 拦截到了读和拦截到了删除
delete proxyUser2.car.color
你也可以点击去codepen工作台去验证下shallowReactive和reactive函数实现
2. shallowReadonly
和readonly
再来看看shallowReadonly
和readonly
实现
shallowReadonly
和readonly
这两个函数的几乎与shallowReactive
和reactive
函数实现一致。
只有在处理函数(handler)中有点区别。readonly
代表只读,故只会在get
中执行Reflect.get(target, prop)
,在其他的地方并不操作对象的行为,只返回一个Boolean
值。这便实现了对代理对象中的属性只可读,不能进行修改/添加/删除等操作。
// 定义了一个readonlyHandler处理器
const readonlyHandler = {
get (target, prop) {
const result = Reflect.get(target, prop)
console.log('拦截到了读取数据了', prop, result)
return result
},
set (target, prop, value) {
console.warn('只能读取数据,不能修改数据或者添加数据')
return true
},
deleteProperty (target, prop) {
console.warn('只能读取数据,不能删除数据')
return true
}
}
// 定义一个shallowReadonly函数
function shallowReadonly (target) {
// 需要判断当前的数据是不是对象
if (target && typeof target === 'object') {
return new Proxy(target, readonlyHandler)
}
return target
}
// 定义一个readonly函数
function readonly (target) {
// 需要判断当前的数据是不是对象
if (target && typeof target === 'object') {
// 判断target是不是数组
if (Array.isArray(target)) {
// 遍历数组
target.forEach((item, index) => {
target[index] = readonly(item)
})
} else { // 判断target是不是对象
// 遍历对象
Object.keys(target).forEach(key => {
target[key] = readonly(target[key])
})
}
return new Proxy(target, readonlyHandler)
}
// 如果不是对象或者数组,那么直接返回
return target
}
✅小小验证下shallowReadonly
和readonly
函数是否达到了预期效果。
// 测试shallowReadonly和readonly函数实现
const proxyUser3 = shallowReadonly({
name: '小明',
cars: ['奔驰', '宝马']
})
// 可以读取
console.log(proxyUser3.name)
// 不能修改
proxyUser3.name = '=='
// 不能删除
delete proxyUser3.name
// 拦截到了读取,可以修改(因为只是浅的响应式)
proxyUser3.cars[0]='奥迪'
// 拦截到了读取,可以删除
delete proxyUser3.cars[0]
const proxyUser4 = readonly({
name: '小明',
cars: ['奔驰', '宝马']
})
// 拦截到了读取
console.log(proxyUser4.name)
console.log(proxyUser4.cars[0])
// 只读的
proxyUser4.name='哈哈'
// 只读的(因为是深的响应式)
proxyUser4.cars[0]='哈哈'
delete proxyUser4.name
delete proxyUser4.cars[0]
你也可以点击去codepen工作台去验证下shallowReadonly和readonly函数实现
3. shallowRef
和ref
再来看看shallowRef
和ref
实现
shallowRef
和ref
和上面两个例子又有所不同,它返回的响应式代理对象仅有一个 .value
property,指向赋值进来的目标对象。
const count = ref({name:'小明'})
// 只能通过.value去访问到值
console.log(count.value) // {name:'小明'}
故shallowRef
函数和ref
函数设计时是需要返回一个对象,这个对象有get/set的访问器属性,当读取对象属性时会走到get
方法中,当修改属性时会走到set
方法中。
// 定义一个shallowRef函数
function shallowRef (target) {
return {
// 将目标对象target数据保存起来
_value: target,
get value () {
console.log('劫持到了读取数据')
return this._value
},
set value (val) {
console.log('劫持到了修改数据,准备更新界面', val)
this._value = val
}
}
}
// 定义一个ref函数
function ref (target) {
// 如果将对象分配为ref值,则将它被处理为深层的响应式对象。故调用reactive函数
target = reactive(target)
return {
// 保存target数据保存起来
_value: target,
get value () {
console.log('劫持到了读取数据')
return this._value
},
set value (val) {
console.log('劫持到了修改数据,准备更新界面', val)
this._value = val
}
}
}
✅验证一下shallowRef
和ref
响应式的实现。
// 验证
const ref1 = shallowRef({
name: "小明",
car: {
color: "red"
}
});
console.log(ref1.value);
// 劫持到(能够劫持监听到这个修改操作)
ref1.value = "==";
// 劫持不到修改属性(修改car属性时并不会被劫持监听到)
ref1.value.car = "==";
const ref2 = ref({
name: "小明",
car: {
color: "red"
}
});
console.log(ref2.value);
// 劫持到(修改ref2的值是可以被劫持监听到的)
ref2.value = "==";
// 劫持到读取数据(修改ref2深层次的属性时也是可以被劫持监听到)
ref2.value.car = "==";
你也可以点击去codepen工作台去验证下shallowRef和ref函数实现
4. isRef
/isReactive
/isReadonly
/isProxy
看看isRef
/isReactive
/isReadonly
/isProxy
函数如何设计
其实很简单,这几个函数返回一个Boolean
值,这时只需要设计一个属性,这个属性挂在生成其代理对象的处理函数中即可。
// 定义一个函数isRef,判断当前的对象是不是ref对象
// 新增_is_ref属性要挂载到ref函数中的返回对象里
function isRef (obj) {
return obj && obj._is_ref
}
// 定义一个函数isReactive,判断当前的对象是不是reactive对象
// 新增_is_reactive属性要挂载到reactive函数中的处理函数
function isReactive (obj) {
return obj && obj._is_reactive
}
// 定义一个函数isReadonly,判断当前的对象是不是readonly对象
// 新增_is_readonly属性要挂载到readonly函数中的处理函数
function isReadonly (obj) {
return obj && obj._is_readonly
}
// 定义一个函数isProxy,判断当前的对象是不是reactive对象或者readonly对象
function isProxy (obj) {
return isReactive(obj) || isReadonly(obj)
}
📍新增_is_ref
属性要挂载到ref
函数中的返回对象里
function ref (target) {
target = reactive(target)
return {
_is_ref: true, // 标识当前的对象是ref对象!!!!
_value: target,
get value () {
return this._value
}
// ...
}
}
📍新增_is_reactive
属性要挂载到reactive
函数中的处理函数
// 定义一个reactiveHandler处理对象
const reactiveHandler = {
get (target, prop) {
// 如果通过obj._is_reactive访问就会到这来并且返回true值
if (prop === '_is_reactive') return true
const result = Reflect.get(target, prop)
return result
}
// ...
}
📍新增_is_readonly
属性要挂载到readonly
函数中的处理函数
// 定义了一个readonlyHandler处理器
const readonlyHandler = {
get (target, prop) {
// 如果通过obj._is_readonly访问就会到这来并且返回true值
if (prop === '_is_readonly') return true
const result = Reflect.get(target, prop)
return result
},
// ...
}
✅简单验证下
// 以下都返回true值
console.log(isRef(ref({})))
console.log(isReactive(reactive({})))
console.log(isReadonly(readonly({})))
console.log(isProxy(reactive({})))
console.log(isProxy(readonly({})))
你也可以点击去codepen工作台去验证下isRef/isReactive/isReadonly/isProxy函数实现
Composition API VS Option API
上一节我们梳理了Vue3中通过Proxy
来实现组合式API(Composition API)的实现原理。那么我们来看看为何选择抛弃Vue2的选项API(Option API)。
Option API的问题
在传统的Vue Options API中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 ,滚动条反复上下移动。
使用Compisition API
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
当我们需要在原有页面新增一个功能时,Compisition API可以让我们的新增功能这块代码尽可能不分散,有序在一块上,从而更好实现“功能代码块”的概念,方便维护。
转载自:https://juejin.cn/post/7077755456059342856