
本文共 11168 字,大约阅读时间需要 37 分钟。
文章目录
涉及知识体系

1、 预备知识
1.1、Object.defineProperty
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
应当直接在 构造器对象上调用此方法,而不是在任意一个
Object
类型的实例上调用。
- 用处—> 可以设置一些额外隐藏的属性
- 测试源码
var obj = { }Object.defineProperty(obj, 'a',{ value:3, // 是否可以被写 writable:false, // 是否可以被枚举 enumerable:true})Object.defineProperty(obj, 'b', { value: 99, writable:true, enumerable:false})Object.defineProperty(obj, 'c',{ value:1, enumerable:true})console.log(obj.a, obj.b); // 3 99obj.a = 100 // 无法更改obj.b = 666 // 可以更改console.log(obj.a, obj.b); // 3 666console.log('enumerable')for(var i in obj){ console.log(i) // a c}
语法
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。
这两种描述符都是对象。它们共享以下可选键值(默认值是指在使用 Object.defineProperty()
定义属性时的默认值):
-
configurable
当且仅当该属性的
configurable
键值为true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为false
。 -
enumerable
当且仅当该属性的
enumerable
键值为true
时,该属性才会出现在对象的枚举属性中。 默认为false
。
数据描述符还具有以下可选键值:
-
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为 。
-
writable
当且仅当该属性的
writable
键值为true
时,属性的值,也就是上面的value
,才能被改变。 默认为false
。
存取描述符还具有以下可选键值:
-
get
属性的 getter 函数,如果没有 getter,则为
undefined
。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this
对象(由于继承关系,这里的this
并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为 。 -
set
属性的 setter 函数,如果没有 setter,则为
undefined
。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的this
对象。 默认为 。
注意,getter与setter需要变量中转
var obj = { }var temp = nullObject.defineProperty(obj, 'a',{ // 数据劫持 getter get(){ console.log('获取obj.a') return temp }, // setter set(a){ console.log('尝试设置a属性 = ',a) temp = a }})console.log(obj)obj.a = 99console.log(obj.a)
技术参考
1.2 函数柯里化
通过函数调用继续返回函数的方式,实现多次接收参数,最后统一处理的函数处理形式
附方便理解的代码
function sum(a){ return(b)=>{ return (c)=>{ return a+b+c } }}console.log(sum(1)(2)(3))
1.3 闭包
1.3.1、如何产生闭包(closure
)
- 当一个函数的内部(子)函数引用了嵌套的外部(父)函数的变量或者函数时,就产生了闭包
1.3.2、闭包是什么?
- 使用chrome调试查看
-
- 理解一: 闭包就是函数内部的嵌套函数
- 理解二:包含被引用变量或者函数的对象
- 注意: 闭包存在于嵌套的内部函数中
-
1.3.3、产生闭包的条件
-
-
函数嵌套
-
内部函数引用了外部函数的数据(变量或者函数)
-
执行外部函数,执行内部函数的定义
-
1.3.4、在chrome中的表现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LqSYULzJ-1618128834529)(.\pics\image-20210213170315426.png)]
1.3.5、常用的闭包
-
将函数作为另一个函数的返回值
Document -
将函数作为实参传递给另一个函数调用
function showDelay(msg,time){ setTimeout(function(){ console.log(msg) },time)}showDelay('hello',1000)
1.3.6、闭包的作用
-
- 使函数内部的变量在函数执行完之后,仍然存活在内存中(延长局部变量的生命周期)
- 让函数外部可以操作函数内部的局部变量
1.3.7、闭包的生命周期
- 产生: 在嵌套内部函数定义执行完时就产生了(并不是在调用时)
- 死亡:在嵌套内部函数成为垃圾对象时
1.3.8、闭包的应用
1.3.8.1 定义js模块
js模块:
- 具有特定功能的js文件
- 将所有的数据和功能都封装在一个函数内部(私有的)
- 只向外暴露一个包含n个方法的对象或者函数
- 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
- 模块的定义方法1 --> 利用封闭函数封装私有属性,利用全局变量函数暴露接口---->使用的时候:通过全局对象来访问内部方法
(function () { let a = 1; function test() { console.log(++a); } // 浏览器的全局变量 window.$ = function () { return { test:test, }; }; })(); $().test() //2 console.log($().test,'\n-->', $().test()) /** * 3 ƒ test() { console.log(++a); } " -->" undefined */
-
模块的定义方法2 --> 利用闭包定义私有属性,通过return 一个对象,暴露模块API —> 使用的时候需要先new 一个对象, 在通过这个对象来访问私有属性
function myModule(){ // 私有数据 var msg = 'hello XiaoMing' // 私有方法 function big(){ console.log(msg.toUpperCase()) } function small(){ console.log(msg.toLowerCase()) } // 向外暴露对象 return { big, small }}
1.3. 9、闭包的缺点
-
9.1 函数执行完之后,函数内的局部变量没雨释放,占用内存时间会变长
-
9.2 容易造成内存泄漏
-
解决方法
-
能不用闭包就不用
-
及时释放
f=null
-
1.4、 webpack
- 涉及依赖包
npm -i webpack@4 webpack-cli webpack-dev-server -D
- webpack.config.js配置文件
const path = require('path');module.exports = { // 模式 开发 mode: 'development', entry: './src/index.js', output: { filename: 'bundle.js' },devServer: { // 静态文件根目录 contentBase: path.join(__dirname, "www"), // 不压缩 compress: false, port: 8080, // 虚拟打包的路径,bundle.js文件没有真正的生成 publicPath: "/xuni/"}};
ps : 注意在package.json
中新增 "dev": "webpack-dev-server"
,后续通过npm run dev
启动测试工程
⚠以下部分为学习视频补充笔记,可能会看不懂
2、模拟数据响应式
2.1 利用Object.defineProperty
定义隐藏属性
- 使用
getter
以及setter
实现简易数据劫持
样例如1.1, 方便学习,重复贴上
var obj = { }var temp = nullObject.defineProperty(obj, 'a',{ // 数据劫持 getter get(){ console.log('获取obj.a') return temp }, // setter set(a){ console.log('尝试设置a属性 = ',a) temp = a }})console.log(obj)obj.a = 99console.log(obj.a)
2.2 构建目录文件
2.3 响应式原理回顾
2.4 模拟代码
index.js
import observe from './Observe';import Watcher from './Watcher';import Dep from './Dep'var obj = { a:{ m: { n: 5 } }, b:{ c:'hello' }, g: [2,3,44,55,66]}observe(obj)// obj.a = 10console.log(obj.a.m.n)// 全局位置new Watcher(obj, 'a.m.n', (val)=>{ console.log('※※※※※', val)})obj.a.m.n = 88console.log(obj.a.m.n)
observe函数
import Observer from './Observer'export default function Observe(value){ // 创建observe 函数 // 要求观测的对象必须是Object if(typeof value != 'object'){ // do nothing return ; } // 定义ob var ob = null if(typeof value.__ob !== 'undefined'){ ob = value.__ob }else{ ob = new Observer(value) } return ob;}
Observer类
import def from './utils';import defineReactive from './defineReactive';import arrayMethods from './Array';import observe from './Observe';import Dep from './Dep';export default class Observer{ // 将一个正常的对象转换成可观测对象 // 创建类的时候要思考如何实例化 constructor(value){ // 每个Observer的实例成员都有Dep的实例对象 this.dep = new Dep(); // __ob__ 一般为不可枚举 // 这里的this指向的是实例对象而不是类 def(value,'__ob__', this, false); // console.log("我是Observer构造器",value) if(Array.isArray(value)){ // 如果是数组则更改原型链 Object.setPrototypeOf(value,arrayMethods) // 让这个数组变得observe this.observeArray(value) }else{ this.walk(value) } // console.log('构造器结束') } // 遍历 walk(value){ for(let k in value){ defineReactive(value, k) } } // 数组的特殊遍历 observeArray(arr){ for(let i=0; i
defineReactive.js
import observe from './Observe';import Dep from './Dep';// 构造闭包环境export default function defineReactive(data, key, val){ // console.log("我是defineReactive",key) // 定义管理依赖成员 const dep = new Dep() if( arguments.length === 2){ // console.log("arguments=",arguments) val = data[key] } // 让子元素进行observe, 至此形成了递归, // 这个递归不是函数自己调用自己,而是多个函数、类循环调用 let childOb = observe(val) Object.defineProperty(data, key,{ // 可枚举 enumerator : true, // 可以被配置 ,比如说可以被delete configurable : true, // 数据劫持 getter get(){ // console.log('获取'+ key + '属性') // 如果现在处于依赖的收集阶段 // 即有watcher被劫持 if(Dep.target){ dep.depend() if(childOb){ childOb.dep.depend() } } return val }, // setter set(newVal){ // console.log(`尝试设置${key}属性 = `,newVal) if(val === newVal){ return } val = newVal // 当设置了新值的时候,这个新值也要被observe childOb = observe(childOb) // 发布订阅模式 // 依赖管理员通知观察者,观察数据是否发生变化, 如果变化了,则保存新数据 dep.notify() }})}
Array.prototype的七大方法重写
import def from './utils';// 得到Array.prototypeconst arrayPrototype = Array.prototype// 以Array.prototype 为原型创建一个arrayMethods对象const arrayMethods = Object.create(arrayPrototype) const methodsNeedChange = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']methodsNeedChange.forEach(methodName =>{ // 备份原来的方法 // 因为push, pop 等7个函数的功能不能被剥夺 const original = arrayPrototype[methodName] // 把类数组对象转换成数组 const args = [...arguments] // 把数组身上的__ob__取出来, __ob__已经被添加了 // 因为数组肯定不是最高层, // 定义新的方法 def(arrayMethods, methodName, function(){ const ob = this.__ob__ // 有三种方法,push\unshift\shift能够插入新项,现在把插入的新项也要变成observe的 const result = original.apply(this,arguments) let inserted = [] // console.log('arguments',arguments); switch(methodName){ case 'push': case 'unshift': inserted = args; break; case 'splice': // splice(下标,数量, 插入的新项) inserted = args.slice(2); break; } // 判断有没有要插入的项,把新插入的项变成响应式 if(inserted){ ob.observeArray(inserted) } ob.dep.notify(); // console.log('aaaa') return result },false)})export default arrayMethods;
utils.js
export default function def(obj, key, value, enumerable){ // console.log('开始定义属性',key) Object.defineProperty(obj,key, { value, enumerable, writable:true, configurable:true })}
重点Dep类
var uid = 0;export default class Dep{ constructor(){ // console.log('我是Dep的构造器'); this.id = uid ++; // 用数组来存储自己的订阅者 subscribes this.subs = []; } // 添加订阅 addSub(sub){ this.subs.push(sub) console.log(this.subs) } // 移除订阅 rmSub(){ // do nothing } // 添加依赖即是Watcher depend(){ // Dep.target 是我们自己指定的一个全局位置 if(Dep.target){ this.addSub(Dep.target) } } // 通知Watcher更新数据 notify(){ // console.log("我是notitfy"); // 浅克隆一份 const subs = this.subs.slice() // 遍历所有对象,看看是否需要更新数据 for(let i=0 ,l=subs.length; i
Watcher类
import def from './utils';import defineReactive from './defineReactive';import arrayMethods from './Array';import observe from './Observe';import Dep from './Dep';export default class Observer{ // 将一个正常的对象转换成可观测对象 // 创建类的时候要思考如何实例化 constructor(value){ // 每个Observer的实例成员都有Dep的实例对象 this.dep = new Dep(); // __ob__ 一般为不可枚举 // 这里的this指向的是实例对象而不是类 def(value,'__ob__', this, false); // console.log("我是Observer构造器",value) if(Array.isArray(value)){ // 如果是数组则更改原型链 Object.setPrototypeOf(value,arrayMethods) // 让这个数组变得observe this.observeArray(value) }else{ this.walk(value) } // console.log('构造器结束') } // 遍历 walk(value){ for(let k in value){ defineReactive(value, k) } } // 数组的特殊遍历 observeArray(arr){ for(let i=0; i
3、Dep类与Watcher类
把依赖收集的代码封装成一个Dep类,它专门用来管理依赖,每个Observer
的实例成员中都有一个Dep
的实例;
Watcher是一个中介,数据发生变化时通过Watcher中转,通知组件。
依赖就是Watcher。
只有Watcher触发的getter才会收集依赖,哪个Watcher触发了getter,就把哪个Watcher收集到Dep中。
Dep使用发布订阅模式
当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍。
代码实现的巧妙之处
Watcher把自己设置到全局的一个指定位置,然后读取数据,因为读取了数据,所以会触发这个数据的getter。在
getter中就能得到当前正在读取数据的Watcher,并把这个Watcher收集到Dep中
4、附中介者模式3S入门精髓
- 使用中介者模式之前
发表评论
最新留言
关于作者
