Angular2 ngModel:为什么它可以改变不可变的字符串?

ch4*_*40s 2 javascript string immutability angular-ngmodel angular

我有点困惑.使用ngModel我可以执行以下操作:

import {NgModel} from "angular2/common";
@Component({
  selector: "test",
  template: `<input tpye="text" [(ngModel)]="strInObj.str">  {{strInObj|json}}`,
  directives: [NgModel]
})
class Test{
  strInObj: {str: string} = {str: "this is a string"};
}  
Run Code Online (Sandbox Code Playgroud)

现在当我输入输入时,strInObj.str会更新.我对此感到困惑,因为据我所知,字符串是不可变的,并且无法找到引用的父级.

在这种情况下,我str直接传递属性,这意味着ngModel获取对字符串的引用.如果它"更改"该引用上的字符串,则会创建一个新字符串,因此不会更改strInObj.str指向的原始字符串.至少这是我的理解.
并且没有办法找到传递给ngModel的引用的父级(没有概念str.parent(),会返回strInObj)那么他们如何做到这一点?我试图理解ts和js的来源,但是......好吧,我在这里.

我试图建立一个类似的指令,但最终只能传递包裹字符串的对象,我没有找到一种方法来修改原始对象str直接传递属性....(在示例中,我将传递strInObj给我指令,然后将使用str传递的对象的属性,这工作正常).

如果有人能帮我解开这个,我会很高兴:)

编辑 在这个plunker中,我有自定义指令StrDirective和带有NgModel指令的输入字段.两者都具有相同的绑定exampleStr,在简单的范围内输出.
现在,当我在输入中输入文本时,您可以看到exampleStr正在更新.这是预期的行为.我知道这很有效.
StrDirective更新的时候点击约束力.您可以看到它更新了字符串的"工作副本",但exampleStr没有更新.
我现在的问题是:他们是如何做到的/我怎样才能让我的指令更新ExampleStr而不必将其包装在对象中?

pix*_*its 6

在Javascript中,所有字符串都是不可变的.当某人键入输入字段时,它会更新字符串的"工作副本",以便工作副本指向新的引用.或换句话说,每次字符串更改时,它都是一个新的字符串引用.

当按下一个更改模型的键时,将ngModelChange触发输出事件,然后使用新引用更新父组件的模型.引用现在是同步的.当你说"修改传递给它的字符串"时,这是不可能的,因为字符串是不可变的.

当存在与模型的双向绑定时:

[(ngModel)]="str"
Run Code Online (Sandbox Code Playgroud)

绑定相当于:

[ngModel]="str" (ngModelChange)="str=$event"
Run Code Online (Sandbox Code Playgroud)

@Output ngModelChange事件被触发时str(该模式势必给)的变化.通过这种方式,引用的更改向上传播到设置了双向模型绑定的所有组件,以便每个模型指向相同的引用.

[编辑]

在更新的问题的Plnkr中,它显示用户在输入框中键入一个键后正在恢复双向绑定.问题是如何以及为什么?

要了解发生了什么,让我们看看这两种情况:

  1. 用户单击标签并触发事件处理程序,这会更改绑定的@Input值

  2. 用户在输入框中键入一个键.两个标签都自动重新绑定到同一个引用,双向模型绑定适用于两个标签.

在用户单击标签之前,所有绑定都是同步的:

                       S1 (app component)
                       /\
(exampleStr binding) S1  S1 (str component)
Run Code Online (Sandbox Code Playgroud)

在click事件之后,绑定的@Input模型会更改.然后,从根开始进行一轮更改检测,并以深度优先顺序运行到子组件.由于@Input绑定向下传播,因此其他绑定没有任何改变.

在第一个场景中,这是click事件后绑定的状态:

                       S1 (app component)
                       /\
(exampleStr binding) S1  S2 (str component)
Run Code Online (Sandbox Code Playgroud)

当用户开始键入具有双向绑定设置的文本框时,它会触发一个ngModelChange更改exampleStrto 值的事件S3.

                       S3 (app component)
                       /\
(exampleStr binding) S3  S2 (str component)
Run Code Online (Sandbox Code Playgroud)

然后启动默认的更改检测策略,该策略从根开始,并以深度优先顺序向下运行到子组件.

按下键后绑定的状态是:

                       S3 (app component)
                       /\
(exampleStr binding) S3  S3 (str component)
Run Code Online (Sandbox Code Playgroud)

如您所见,所有绑定都再次同步.默认的更改检测策略检查所有组件; 对模型的更改通过组件的@Input绑定传播,从父节点到子节点的可预测和单向流.

要了解变更检测的工作原理,请将其视为分阶段进行.这是过度简化的,但它可能有助于您的理解:

  1. 输入绑定从root传播到子节点.Angular跟踪哪些模型绑定到哪些输入属性.稍后需要进行更改检测.
  2. 触发事件(例如单击事件),修改@Input属性.通过双向模型绑定,模型中的更改将从子级向上传播.
  3. 在事件触发后,从根开始触发单轮更改检测(重复步骤1)以重新同步所有绑定.在此过程中,将更新所有应用程序和视图绑定.

注意:Angular使用区域来修补浏览器事件,因此它知道何时触发更改检测.

[编辑]

如果您希望在单击标签时更新消息,请像使用ngModel一样设置双向绑定:

@Input("str") value : string;
@Output("strChange") valueChange:EventEmitter<string> = new EventEmitter();


onClick(){
  this.value = "new string";
  this.valueChange.next(this.value);
}
Run Code Online (Sandbox Code Playgroud)

HTML

<span [(str)]="exampleStr"></span><br>
Run Code Online (Sandbox Code Playgroud)

演示Plnkr