热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

angular1.0源代码分析以及与vue和react比较

本文分析angular1.0从初始化开始到编译网页更新页面的源代码过程以及一些重要细节。测试项目例子:

本文分析angular 1.0从初始化开始到编译网页更新页面的源代码过程以及一些重要细节。

测试项目例子:




controller和指令用angular.module的方法定义。
angular 1.0 以指令为中心,directive指令标签就是组件,有template,而属性指令主要是修改元素属性,angular 2.0改为以组件为中心设计。

angular入口初始化程序:
function angularInit(element, bootstrap) {
  bootstrap(appElement, module ? [module] : []);
}

function bootstrap(element, modules) {

  var doBootstrap = function() {

    modules = modules || [];
    modules.unshift(['$provide', function($provide) {
      $provide.value('$rootElement', element);
    }]);
    modules.unshift('ng');
    var injector = createInjector(modules);
    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', //angular代码执行时已经创建内部对象,这些是对象的key(名字)
    function(scope, element, compile, injector, animate) { // invoke([模块1,模块2,...,fn])就是调用执行fn,传递依赖模块(angular内部对象)
      scope.$apply(function() { // 外套$apply执行方法,执行完方法之后扫描watcher重新获取所有watcher表达式的值进行必要的页面更新
      element.data('$injector', injector);
      compile(element)(scope); // 编译根元素,返回link函数,再执行link函数,更新页面的代码在每个指令表达式watcher的update方法中。
      });
    }]);
  return injector;
  };
  return doBootstrap();

}

function createInternalInjector(cache, factory) {
  function invoke(fn, self, locals){ //invoke就是变换参数和作用域调用函数, angular内部对象机制和依赖模块注入非常复杂,本文忽略
    fn = fn[length];
    return fn.apply(self, args);

 

下面来看compile()代码:

function compile($compileNodes, transcludeFn, maxPrior //从根元素开始编译
  var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, // 递归编译子节点
    function compileNodes(nodeList, transcludeFn, $rootElement,

      applyDirectivesToNode(directives,

        function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
          $compileNode.html(directiveValue); // 把指令的template

{{title}}
插入网页中的节点元素中,

      childLinkFn = compileNodes(childNodes,nodeLinkFn ? nodeLinkFn.transclude : transcludeFn); // 有子节点则递归调用自身      return linkFnFound ? compositeLinkFn : null//每一次递归子节点时已经编译了子节点的指令,递归子节点层层返回到最上层compile代码位置时,返回的link函数已经包含每一层递归时产生的link函数,也就是每一层递归时编译结果。最后只要再执行最终返回的link函数,传递根scope,把根scope保存在根元素对象属性中,就完成了整个编译插入网页的过程,在每一次递归编译子节点时如果有指令会编译指令更新子节点生效。路由

组件有template是编译插入网页生效。

        function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) { //每一次递归返回的通用link函数,含每一次递归编译的结果数据

//递归编译子节点每次都要产生这个link函数,编译完成执行link函数时,也是层层递归每一个子节点执行子节点的link函数(含编译结果数据),执行link函数的过程跟执行compile函数的过程一样复杂,每个子节点都要递归处理一遍,包括网页中的换行文本text节点。
angular要扫描编译整个网页,循环递归处理所有的元素节点,没有必要,应该只编译template插入到占位元素。

        if (nodeLinkFn) {
          if (nodeLinkFn.scope) {
            childScope = scope.$new();
            $node.data('$scope', childScope);
          } else {
            childScope = scope;
          }

            function nodeLinkFn(childLinkFn, scope, linkNod // 编译每个节点产生的通用link函数

              cOntrollerInstance= $controller(controller, locals); // body元素有指令调用controller

                  return function(expression, locals) {

                    instance = $injector.instantiate(expression, locals); // exp就是controller构造函数,这是生成一个实例继承controller构造函数,不是new controller实例,
$controller()是生成一个实例继承controller构造函数

                    function instantiate(Type, locals) {
                      var COnstructor= function() {},
                      instance, returnedValue;

//定义模块还有一种写法是 someModule.factory('greeter', ['$window', function(renamed$window) {}]); []中前面是依赖模块,最后一个是构造函数
                      Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
                      instance = new Constructor();
                      returnedValue = invoke(Type, instance, locals); //调用执行controller构造函数

                      return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
                    }

                        function invoke(fn, self, locals){
                          //准备依赖对象,args依赖对象用locals[key]或getService(key)方法获取
                          return fn.apply(self, args);  //调用执行controller构造函数,是在这里执行controller初始化代码的
//因此controller代码被执行时this是一个instance但不是new controller实例
//至此可以知道在link阶段执行controller初始化代码,设置title属性=hello,更新之后网页中的{{title}}被hello替换

            $element.data('$' + directive.name + 'Controller', controllerInstance); // $ng-controllerController
//可见scope和controller instance会保存在元素对象属性中,在需要的时候可以从元素对象中获取scope或controller instance


//递归结束之后返回到这里执行时网页显示{{title}}
return function publicLinkFn(scope, cloneConnectFn, // compile返回的通用link函数,返回后再执行link函数传递scope

  $linkNode.eq(i).data('$scope', scope); // node是元素jquery对象,把scope保存到元素对象的属性中
  if (cloneConnectFn) cloneConnectFn($linkNode, scope);
  if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);

  return $linkNode;

小结一下:

compile编译是解析处理页面表达式,比如{{title}},比如ng-cOntroller="mycontroller"。

而link函数是处理数据层面,执行link函数需要传递 scope,因为数据作用域是scope,执行controller代码初始化数据是在link函数进行的,是用fn.apply方式执行controller构造函数,compile和link函数执行完之后,数据层面处理已经结束,比如controller的scope.title="hello"已经初始化,但页面没有变化,需要更新页面,把数据插入页面,这是通过$apply->$digest->watcher->fn.apply完成的。

 

编译方法compile外套$apply,$apply会调用$digest:
  $digest: function() {
    //递归scope找watcher,watcher数据中没有scope,因此执行watcher的方法时要传递scope,执行watcher的方法时会变换作用域为scope
    value = watch.get(current) // 获取表达式的值时要传递scope,表达式{{title}}的值是hello
    watch.fn(value, ((last === initWatchVal) ? value : last), current); // 执行watch.fn之后网页显示hello,fn就是handler/update函数

代码中还涉及到defer和settimeout是延迟函数,实现异步调度,非功能性流程。

因此angular在初始化时编译网页时针对每个表达式建立了watcher,编译程序外套$apply,会调用$digest扫描执行watcher的update方法更新网页。

vue也是在初始化编译template时针对每个表达式建立watcher,为组件的data属性建立set/get方法,只要set数据操作,就会执行相应的watcher的update更新页面。
vue 2.0是针对组件建立watcher,初始化时编译根组件执行根组件watcher的update更新页面,set数据时执行相应的组件的watcher的update方法更新页面,组件
可能有子组件嵌套,那么从组件递归重新编译template产生vnode,再根据vnode更新页面。

angular是在数据操作之后执行$digest扫描执行watcher更新页面,vue是set触发执行watcher更新页面,angular和vue都使用watcher机制来实现组件页面更新问题,

watcher中含表达式和更新方法,当数据变化触发更新时,只需要执行相应的watcher的update更新方法更新页面。

react是在初始化编译时递归编译子节点,然后把编译结果插入网页生效,当数据变化时,通过setState执行runBatchedUpdates -> performUpdateIfNecessary -> updateComponent,重新编译dirty component组件更新页面,react不用watcher机制。

angular和vue都是用compile方法递归编译网页元素,这是它们的核心程序,react也是类似的,其mountComponent其实就是递归编译程序,都是设计一个核心编译程序,递归编译所有的子节点。

在template和表达式的写法方面不太一样,react比较特殊,它用render()方法写template,用JSX语法,用babel编译解析,产生一个含层层嵌套的createElement()的函数,执行这个函数就产生一个根元素对象,里面含层层嵌套的子元素对象,再编译这个根元素对象,层层递归子元素对象。

vue 2.0也是用类似的方法,编译template产生一个render方法代码,含层层嵌套的createElement方法,再执行render方法代码产生一个根元素Element对象,里面含层层嵌套的子元素对象,再编译这个根元素对象,层层递归子元素对象,这是前端框架编译与网页tree对应的元素对象的唯一设计方法,也是最好的办法,没有其它办法或更好的办法。

总体来说,angular和react是高大上的软件,源代码非常复杂如天书一般,非常人能看懂源代码,而vue是个人设计的大众化软件,相对简单实用,有些设计也很巧妙很聪明,比如数据响应设计方法(get/set方法)个人认为超越了angular和react的设计,应该可以成为浏览器和框架的新标准,因为它太好了,很简单就把难题给解决了,何必像react使用redux那么复杂费劲地折腾呢。vue还有一个好的地方就是它使用js标准对象方法,就是new fn()方法,非常好理解,非常容易看和跟踪源代码,而angular使用内部模块机制,一般人就看不懂了,其实用webpack开发再加上框架的组件机制,已经充分实现了模块化编程,框架自己再搞一套模块机制已经没有意义了,angular 1.0也早已经被google放弃了。

 



推荐阅读
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了在使用vue和webpack进行异步组件按需加载时可能出现的报错问题,并提供了解决方法。同时还解答了关于局部注册组件和v-if指令的相关问题。 ... [详细]
  • 随着前端技术的发展,越来越多的开发者开始使用react、vue等web框架,但很少有人深入理解这些框架的源码。然而,这些框架底层都是由原生的javascript构建而成。对于初学前端的人来说,可能会认为javascript很容易上手,但实际上只是因为它被高度封装了。与能够使用封装类的人相比,能够理解框架原理的人则处于另一个层面。本文将深入剖析jquery源码,探寻框架底层的原理,帮助读者更好地理解web框架的运行机制。 ... [详细]
  • CentOS7.8下编译muduo库找不到Boost库报错的解决方法
    本文介绍了在CentOS7.8下编译muduo库时出现找不到Boost库报错的问题,并提供了解决方法。文章详细介绍了从Github上下载muduo和muduo-tutorial源代码的步骤,并指导如何编译muduo库。最后,作者提供了陈硕老师的Github链接和muduo库的简介。 ... [详细]
  • wpf+mvvm代码组织结构及实现方式
    本文介绍了wpf+mvvm代码组织结构的由来和实现方式。作者回顾了自己大学时期接触wpf开发和mvvm模式的经历,认为mvvm模式使得开发更加专注于业务且高效。与此同时,作者指出mvvm模式相较于mvc模式的优势。文章还提到了当没有mvvm时处理数据和UI交互的例子,以及前后端分离和组件化的概念。作者希望能够只关注原始数据结构,将数据交给UI自行改变,从而解放劳动力,避免加班。 ... [详细]
  • 本文讨论了将HashRouter改为Router后,页面全部变为空白页且没有报错的问题。作者提到了在实际部署中需要在服务端进行配置以避免刷新404的问题,并分享了route/index.js中hash模式的配置。文章还提到了在vueJs项目中遇到过类似的问题。 ... [详细]
  • 本文介绍了自学Vue的第01天的内容,包括学习目标、学习资料的收集和学习方法的选择。作者解释了为什么要学习Vue以及选择Vue的原因,包括完善的中文文档、较低的学习曲线、使用人数众多等。作者还列举了自己选择的学习资料,包括全新vue2.5核心技术全方位讲解+实战精讲教程、全新vue2.5项目实战全家桶单页面仿京东电商等。最后,作者提出了学习方法,包括简单的入门课程和实战课程。 ... [详细]
  • Vue基础一、什么是Vue1.1概念Vue(读音vjuː,类似于view)是一套用于构建用户界面的渐进式JavaScript框架,与其它大型框架不 ... [详细]
  • 腾讯T3大牛亲自教你!2021大厂Android面试经验,经典好文
    本篇将由环境搭建、实现原理、编程开发、插件开发、编译运行、性能稳定、发展未来等七个方面,对当前的ReactNative和Flutter进行全面的分析对比, ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • C语言注释工具及快捷键,删除C语言注释工具的实现思路
    本文介绍了C语言中注释的两种方式以及注释的作用,提供了删除C语言注释的工具实现思路,并分享了C语言中注释的快捷键操作方法。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
author-avatar
繁华落尽的星空
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有