早在AngularJS时代(即Angular1)即有双向绑定这个功能,当然这个概念不见得NG是最早的(ActionScript也有),但NG的流行助其推广开来,这是毋庸置疑的,今天的Vue也有双向绑定,React本身并没有提供支持,但也可以自己实现做到。那,双向绑定是如何做到的,利弊又是什么呢。
以NG为例,这里就深度了解下。
抛砖引玉。
[toc]
使用
先看下双向绑定的使用,如下组件中声明username,使用[(ngModel)]
进行双向绑定后,则M与V可以实时同步,这就是双向绑定。
1 2 3 4 5 6 7 8 9
| <p> form works! <input [(ngModel)]="username"/> </p> <h3> I am {{ username }} </h3>
|
这样的好处就是视图层与表单对象保持一致,你直接获得了表单对象的数据值,并且类型问题也都提前处理好了,不需要自己再操作。
实现
用起来方便,接下来看看NG是如何实现的吧。
[(ngModel)]
是语法糖
要知道[(ngModel)]
是语法糖,拆开可以看作是 <input [ngModel]="username" (ngModelChange)="username=$event"/>
, ngModel是个指令
,ngModelChange是指令中暴露出来的一个方法,所以聚焦ngModel指令的实现逻辑。
data => view 使用[]
view => data 使用()
[ngModel]
当我们在数据中修改了name的值,视图上的值就会实时
变化
两个疑问
- NG如何知道对象变化?
脏检查
- NG如何更新值到最终的原生元素身上呢?
修改元素value属性
脏检查Dirty checking
脏检查全称是脏数据检查
,即产生了变化的数据。
NG中异步操作,比如Click,比如XHR,自动触发change detection,发现这些变化,然后执行更新动作。
但是这些变化,我理解也不是只要你变了就立即去做更新,而是model稳定后,批量更新
注意,NG不是定时轮询去检查
更新视图
通过指令找到nativeElement,修改value属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
constructor( @Optional() @Host() parent: ControlContainer, @Optional() @Self() @Inject(NG_VALIDATORS) validators: Array<Validator|ValidatorFn>, @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array<AsyncValidator|AsyncValidatorFn>, @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) { super(); this._parent = parent; this._rawValidators = validators || []; this._rawAsyncValidators = asyncValidators || []; this.valueAccessor = selectValueAccessor(this, valueAccessors); }
|
1 2 3 4 5 6 7
|
writeValue(value: any): void { const normalizedValue = value == null ? '' : value; this._renderer.setProperty(this._elementRef.nativeElement, 'value', normalizedValue); }
|
1 2 3 4 5 6
|
setProperty(el: any, name: string, value: any): void { NG_DEV_MODE && checkNoSyntheticProp(name, 'property'); el[name] = value; }
|
(ngModelChange)
监测到表单输入值的变化,即更新到数据对象中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
ngOnChanges(changes: SimpleChanges) { this._checkForErrors(); if (!this._registered) this._setUpControl(); if ('isDisabled' in changes) { this._updateDisabled(changes); }
if (isPropertyUpdated(changes, this.viewModel)) { this._updateValue(this.model); this.viewModel = this.model; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
protected detectChanges(): void { if (this.componentRef === null) { return; }
this.callNgOnChanges(this.componentRef); this.componentRef.changeDetectorRef.detectChanges(); }
|
关键点如上,原理大致明白了,那抛开框架,如何手写个双向绑定呢。
其它手段
Object.defineProperty
1 2 3 4 5 6 7 8 9 10 11
| const obj = {}; Object.defineProperty(obj, 'value', { get: function () { console.log('获取value值', value); return val; }, set: function (value) { console.log('设置value值', value); }, });
|
Proxy
- IE不支持
- Proxy为ES6特性
- Vue v3双向绑定实现原理
1 2 3 4 5 6 7 8 9 10 11 12 13
| const obj = {}; const handler = { get: function (target, prop) { console.log('获取value值', target[prop]); return target[prop]; }, set: function (target, prop, value) { console.log('设置value值', value); target[prop] = value; }, };
const proxy = new Proxy(obj, handler);
|
参考文档