JS笔试手撕题

数据劫持

Vue2的Object.defineProperty()

Vue2的响应式是通过Object.defineProperty()拦截数据,将数据转换成getter/setter的形式,在访问数据的时候调用getter函数,在修改数据的时候调用setter函数。然后利用发布-订阅模式,在数据变动时触发依赖,也即发布更新给订阅者,订阅者收到消息后进行相应的处理

描述符分为数据描述符和存取描述符,只能是其中之一,不可两者同时存在

configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性,默认值为false。

enumerable:表示能否通过for in循环访问属性,默认值为false

//数据描述符

1.writable:表示能否修改属性的值。默认值为false。

2.value:包含这个属性的数据值。默认值为undefined。

//存取描述符

1.get:在读取属性时调用的函数,默认值是undefined

2.set:在写入属性的时候调用的函数,默认值是undefined

js 复制代码
  <script>
        function defineReactive(data) {
            if (!data || Object.prototype.toString.call(data) !== '[object Object]')
                return
            for (let key in data) {
                let val = data[key]
                Object.defineProperty(data, key, {
                    configurable: true,//可配置
                    enumerable: true,//可枚举
                    set: function (newval) {
                        console.log('调用set');
                        if (newval === val) {
                           return ;
                        }
                        console.log('属性值发生变化');
                        val = newval

                    },
                    get() {
                        console.log('调用get');
                        return val
                    },

                })
                if (typeof val === 'object') {
                    defineReactive(val)
                }
            }
        }
        const data = {
            name: 'better',
            firends: ['1', '2']
        }
        defineReactive(data)

        console.log('data.friends[0]:', data.firends[0]);
        console.log('data.name:', data.name);
        data.name = 'lihua'
        console.log('更改后的data.name:', data.name);
        data.friends[0] = '100'
        // 在JavaScript中,数组是一种特殊的对象,因此直接对数组进行defineProperty并不能监听数组元素的变化。
        // 当我们尝试修改数组元素时,比如data.friends[0] = 100,实际上并不会触发set函数,也就无法实现数组元素的响应式更新。
        console.log('更改后的data.friends[0]:', data.firends[0]);

    </script>


弊端:

1.无法劫持对象的添加和删除操作,Vue2的解决方法(新增set和delete)

2.无法劫持到数组的api(push、pop...),Vue2的解决方法是重写数组的api

3.存在深层嵌套关系,通过无脑递归,但也会劫持到项目始终都不会用到的对象,造成性能损耗

Vue3的proxy(解决vue2响应式的弊端)

Proxy(代理) :用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Reflect(反射):对源对象的属性进行操作

Vue3基于ES6新增的Proxy对象实现数据代理以及通过Reflect对源数据进行操作,它解决了Vue2中无法追踪数据新增或删除属性的问题。另外,Proxy可以直接监听数组,无需像Vue2响应式那样需要重写数组方法进行拦截

proxy详解

js 复制代码
const p = new Proxy(target, handler)
//target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
//handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
js 复制代码
 <script>

        function isObject(obj) {
            if (typeof obj !== "object" || !obj) {
                return false;
            }
            return true;
        }

        // vue3使用reactive定义响应式对象时返回的就是reactive的实例
        function reactive(obj) {
            // 对整个对象进行劫持
            return new Proxy(obj, {
                get(target, key) {
                    console.log('get:', key);
                    // proxy想要监听深层对象也需要进行递归处理,不过这里用懒加载,用不到的属性不用进行深层监听
                    let res = Reflect.get(target, key)
                    return isObject(res) ? reactive(res) : res;
                },
                set(target, key, value) {
                    console.log('set:', key, value);
                    return Reflect.set(target, key, value)
                },
                deleteProperty(target, key) {
                    console.log('delete', key);
                    return Reflect.deleteProperty(target, key)
                }
            })
        }

        let person = reactive({ name: 'zs', age: 18, job: { code: "前端" } })
        console.log('person.name:', person.name);
        // 直接对proxy实例操作
        person.name = '小张'
        console.log('------------');
        person.gender = '男'
        delete person.age
        console.log('------------');
        console.log('测试对数组的监听');
        //数组的push方法,作为push的属性理应也该被劫持到
        let arr = reactive([1, 2, 3, 4, 5])
        arr.push(11)

        console.log('--------------------');
        console.log('对深层对象的测试');
        console.log('person.job.code:', person.job.code);
        person.job.code = "php"


    </script>
总结
优点

1.Proxy直接劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式的目的

2.Proxy可以直接监听数组的变化(push,shift,splice...)

3.Proxy有多达13中拦截方法,不限于apply、ownKeys、deleteProperty、has等等,这是Object.defineProperty所不具备的

4.Proxy懒加载,解决递归造成的性能问题

缺点

Proxy不兼容IE

闭包

js 复制代码
 <script>
        function fn1() {
            debugger;
            let num = 999;
            function innerFn1() {
                debugger;
                console.log(num);
            }
            innerFn1()
        }
        fn1()
    </script>


函数依赖外部变量就形成闭包

闭包的应用场景

函数内部return出去一个函数,就是结合了闭包的应用场景之一,目的是为了让那个闭包持久化

js 复制代码
    <script>
        function fn1() {
            debugger;
            let num = 999;
            function innerFn1() {
                debugger;
                console.log(num);
            }
           return innerFn1
        }
        let closure1=fn1();//闭包 num:999
        // 两个不同的闭包,因此闭包可以解决全局变量命名冲突问题
        let closure2=fn1();//闭包 num:999
    </script>
js 复制代码
<script>
        // 面试题
        function fn3(a, b) {
            console.log('第二个参数的值是:', b);
            return {
                innerFn: function (c) {
                    return fn3(c, a);
                }
            }
        }
        // 链式调用
        fn3(1).innerFn(2).innerFn(3).innerFn(4)
        // 1.外部调用 传递a:1 返回一个对象 形成闭包 (a:1)=>输出undefined
        // 2.传递c:2 执行函数,并返回一个对象  fn3(c,a) (c:2,a:1)=>输出1
        // 3.外部a:2,b:1,返回一个对象(a:2)
        // 4.参数c:3,执行fn(3,2)   外部:(a:3,b:2)=>输出2
        // 5.闭包(a:3)
    </script>
js 复制代码
        // 面试题
        function fn(a, b) {
            debugger
            console.log('第二个参数的值是:', b);
            return {
                //对象中的函数形成一个闭包
                fn: function (c) {
                    debugger
                    return fn(c, a)
                }
            }
        }
        let obj = fn(1)
        // 一个新对象有一个闭包=>1个
        // 访问的都是同一个闭包
        obj.fn(2)
        obj.fn(3)
        obj.fn(4)
js 复制代码
 <script>
        // 面试题
        function fn(a, b) {
            debugger
            console.log('第二个参数的值是:', b);
            return {
                //对象中的函数形成一个闭包
                fn: function (c) {
                    debugger
                    return fn(c, a)
                }
            }
        }
        // let obj = fn(1)
        // // 一个新对象有一个闭包=>1个
        // // 访问的都是同一个闭包
        // obj.fn(2)
        // obj.fn(3)
        // obj.fn(4)

        console.log('----------------');
        let a = fn(1).fn(2)
        a.fn(3).fn(4)
        a.fn(5).fn(6)

    </script>
相关推荐
PandaCave几秒前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟3 分钟前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
Bruce小鬼6 分钟前
QT文件基本操作
开发语言·qt
2202_7544215412 分钟前
生成MPSOC以及ZYNQ的启动文件BOOT.BIN的小软件
java·linux·开发语言
我只会发热19 分钟前
Java SE 与 Java EE:基础与进阶的探索之旅
java·开发语言·java-ee
醉の虾24 分钟前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
懷淰メ28 分钟前
PyQt飞机大战游戏(附下载地址)
开发语言·python·qt·游戏·pyqt·游戏开发·pyqt5
张小小大智慧33 分钟前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm42 分钟前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
宁静@星空1 小时前
006-自定义枚举注解
java·开发语言