资讯专栏INFORMATION COLUMN

angular1.x 中重要指令介绍($eval,$parse和$compile)

terasum / 2208人阅读

摘要:实际上就是这里要表现的其实是上下文的替换功能。这就是死模板了,而所谓的活模板,就是这里面的数据全部经过了数据的绑定会自动找到当前的上下文,来绑定数据。最后显示出来的就是活模板,也就是经过数据绑定的模板。

这篇文章是我两年前在博客园写的,现在移植过来,不过Angular 1.x 在国内用的人已经不多了,希望能帮助到有需要的人

在 angular 的服务中,有一些服务你不得不去了解,因为他可以说是 ng 的核心,而今天,我要介绍的就是 ng 的两个核心服务,$parse$compile 。其实这两个服务讲的人已经很多了,但是 100 个读者就有 100 个哈姆雷特,我在这里讲讲自己对于他们两个服务的理解。

大家可能会疑问,$eval 呢,其实他并不是一个服务,他是 scope 里面的一个方法,并不能算服务,而且它也基于 parse 的,所以只能算是$parse 的另一种写法而已,我们看一下 ng 源码中 $eval 的定义是怎样的就知道了

$eval: function(expr, locals) {
  return $parse(expr)(this, locals);
}

相信看完源码大家就明白了吧,好了,现在就开始两种核心服务的讲解了,如果感觉我说的不对的话,欢迎在评论区或者私聊指出,免得祸害其他读者。

再讲这两个服务的时候,我要先讲一个在本贴的概念:上下文

我相信,很多人都听过这个“上下文”,但是可能有点模糊,在我这里给大家解释解释看看大家接不接受这个说法。

还记得 angular 的数据绑定吗?比如:我现在有个有个叫 TestCtrl 的控制器,他的内容如下:

.controller("TestCtrl", function($scope) {
  $scope.test = "Boo!!!"
})

而在 html 中我们的代码是这样的

  
    {{test}}
  

那么,大家不用想都知道结果了,页面上肯定会显示 Boo!!! 的字样。

但是如果我删掉 ng-controller 的指令呢?也就是我没有在 html 申明控制器,你直接绑定{{test}}会如何呢?

结果只有一个,那就是页面啥都没有(ps:因为你申明了 ng-app)。讲到这里大家明白了吗?

控制器就相当于一个上下文的容器,真正的上下文其实是 $scope ,当页面绑定 test,如果申明了控制器,当前上下文就是控制器里面的$scope ,ng 会去找一下你这个控制器的上下文$scope 有没有 test,如果有,他当然就显示出来了,但是你不申明控制器的时候呢?他的上下文容器就是 ng-app 了,那么他真正的上下文就是 $rootScope ,这个时候他就会寻找 $rootScope 有没有 test。

好了,上下文的概念已经讲完了,其实挺容易理解的,基本上和 this 非常相似

那么言归正传,我们开始讲 $parse,首先我们要看的是 ng 的 API 文档

var getter = $parse("user.name");
var setter = getter.assign;
var context = {user:{name:"angular"}};
var locals = {user:{name:"local"}};

expect(getter(context)).toEqual("angular");
setter(context, "newValue");
expect(context.user.name).toEqual("newValue");
expect(getter(context, locals)).toEqual("local");

大家看到的是 ng 文档里面对于$parse 服务性价比最高的几行代码,

gettersetter 就是大家所熟知的 get 方法和 set 方法了,contextlocals 仅仅是 json 对象而已,目的就是模拟上下文关系

大家看到的下面四个语句最终都能通过测试,现在我们一个个来分析,分析之前我要解释一遍什么叫 $parse

$parse 服务其实就是一种解析表达式的功能,就像 ng-model=“test”,你在 html 中写这个东西谁知道你 ng-model=“test”中,其实你想绑定的是当前控制器(上下文容器)中 scope(上下文)中的 test 里面的值,ng 就是通过$parse 服务去帮助你解析这个表达式的,所以在调用$parse 服务的时候你需要传递上下文对象,让 ng 知道你是要去哪里的 scope(上下文)去找你这个 test。

所以我们看到第一行测试代码是这样的:

getter(context)).toEqual("angular") //实际上就是 $parse("user.name")(context)

在这个 context 就是上下文,他能返回“angular“这个字符串的原理就是经过这三步的:

获取当前的表达式 user.name

获取当前的上下文对象{user:{name:"angular"}}

在上下问对象中寻找表达式,最终获得“angular“这个字符创

所以这句测试代码是成功的。

我们看第二个方法 setter 方法

setter(context, "newValue");  //实际上就是 $parse("user.name").assign(context, "newValue")
expect(context.user.name).toEqual("newValue");  //测试数据上下文的值是否被改变  这里的 setter 方法其实是改变值得方法

获取当前的表达式 user.name

获取当前的上下文对象{user:{name:"angular"}}

改变表达式中的值,将上下文对象编程{user:{name:"newValue"}}

于是上下文对象发生了改变,重新用 getter 方法去获取表达式的时候,上下文已经从 {user:{name:"angular"}} --> {user:{name:"newValue"}},最后获取的表达式的值自然就是 newValue 了,所以测试代码也是通过的。

expect(getter(context, locals)).toEqual("local");//实际上就是$parse("user.name")(context, locals)

这里要表现的其实是上下文的替换功能。

在 getter 的方法中我们不仅可以选择第一个上下文,但是如果我们传递了第二个参数,那么第一个上下文就会被第二个上下文覆盖,注意是覆盖.

获取当前的表达式 user.name

获取当前的上下文对象{user:{name:"angular"}}

覆盖当前的上下文{user:{name:"local"}}

获取解析之后表达式的值

重新回到 $eval 这个地方,我们看待 $eval 源码中可以看出$eval 只有 get 功能,而没有 set 功能,但是有些时候我们可以选择传递第二个上下文,来达到修改值得效果。

在这里 $parse 服务就已将说完了,接下来就是 $compile

如果你了解了 $parse 的概念之后,我想 $compile 也差不多理解了,其实和 $parse 很像。但是他是解析一段 html 代码的,他的功能就是将死模板变成活模板,也是指令的核心服务。

比如你有一段 html 代码

{{test}}

,如果你将这段代码直接放在 html 代码里面,它所呈现的内容是怎样的我不说大家也应该懂。这就是死模板了,而所谓的活模板,就是这里面的数据全部经过了数据的绑定 {{test}}会自动找到当前的上下文,来绑定数据。最后显示出来的 就是活模板,也就是经过数据绑定的模板。

$compile("死模板")(上下文对象),这样就将死模板编程了活模板,你就可以对这段活的 html 代码做操作了,例如增加到当前节点,等等。

但是在指令中,她会返回两个函数 pre-linkpost-link

第一个执行的是 pre-link,它对于同一个指令的遍历顺序是从父节点到子节点的遍历,在这个阶段,dom 节点还没有稳定下来,无法做一些绑定事件的操作,但是我们可以在这里进行一些初始化数据的处理。

第二个执行的是 post-link,也就是我们常说的 link 函数,他是从子节点到父节点遍历的,在这个阶段,DOM 节点已经稳定下来了,我们一般会在这里进行很多的操作。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92912.html

相关文章

  • 如何实现一个基于 DOM 的模板引擎

    摘要:我们提取变量的目的就是为了在函数中生成相应的变量赋值的字符串便于在函数中使用,例如这样的话,只需要在调用这个匿名函数的时候传入对应的即可获得我们想要的结果了。 showImg(https://segmentfault.com/img/bVSspq?w=4000&h=2670); 题图:Vincent Guth 注:本文所有代码均可在本人的个人项目colon中找到,本文也同步到了知乎专栏...

    maxmin 评论0 收藏0
  • Angular1.x + TypeScript 编码风格

    摘要:组件还包含数据事件的输入与输出,生命周期钩子和使用单向数据流以及从父组件上获取数据的事件对象备份。 说明:参照了Angular1.x+es2015的中文翻译,并将个人觉得不合适、不正确的地方进行了修改,欢迎批评指正。 架构,文件结构,组件,单向数据流以及最佳实践 来自@toddmotto团队的实用编码指南 Angular 的编码风格以及架构已经使用ES2015进行重写,这些在Angul...

    ytwman 评论0 收藏0
  • 使用 ES2015 开发 Angular1.x 应用指南

    摘要:他们即不是指令,也不应该使用组件代替指令,除非你正在用控制器升级模板指令,组件还包含数据事件的输入与输出,生命周期钩子和使用单向数据流以及从父组件上获取数据的事件对象。 showImg(https://segmentfault.com/img/bVynsJ); 关键词 架构, 文件结构, 组件, 单向数据流以及最佳实践 来自 @toddmotto 团队的编码指南 Angular 的编码...

    Andrman 评论0 收藏0
  • 关于Angularjs自定义指令一些有价值的细节技巧

    摘要:属性为时,指示优先级小于当前指令的指令都不执行,仅执行到本指令。 作者:心叶时间:2018-04-22 10:58 一:自定义指令常用模板 下面是大致的说明,不是全面的,后面来具体说明一些没有提及的细节和重要的相关知识: angular.module(yelloxingApp, []).directive(uiDirective, function() { return { ...

    Markxu 评论0 收藏0
  • 聊聊Vue.js的template编译

    摘要:具体可以查看抽象语法树。而则是带缓存的编译器,同时以及函数会被转换成对象。会用正则等方式解析模板中的指令等数据,形成语法树。是将语法树转化成字符串的过程,得到结果是的字符串以及字符串。里面的节点与父节点的结构类似,层层往下形成一棵语法树。 写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。 文...

    gnehc 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<