Angularjs 源码分析2

本文主要分析RootScopeProvider和ParseProvider


RootScopeProvider简介


今天这个rootscope可是angularjs里面比较活跃的一个provider,大家可以理解为一个模型M或者VM,它主要负责与控制器或者指令进行数据交互.
今天使用的源码跟上次分析的一样也是1.2.X系列,只不过这次用的是未压缩合并版的,方便大家阅读,可以在这里下载

从$get属性说起

说起这个$get属性,是每个系统provider都有的,主要是先保存要实例化的函数体,等待instanceinjector.invoke的时候来调用,因为$get的代码比较多,所以先上要讲的那部分,大家可以注意到了,在$get上面有一个digestTtl方法

?





1

2

3

4

5

6

this.digestTtl = function(value) {

    if
(arguments.length) {

      TTL = value;

    }

    return
TTL;

  };

这个是用来修改系统默认的dirty
check次数的,默认是10次,通过在config里引用rootscopeprovider,可以调用这个方法传递不同的值来修改ttl(short for
Time To Live)

下面来看下$get中的scope构造函数

?





1

2

3

4

5

6

7

8

9

10

11

12

13

function Scope() {

    this.$id = nextUid();

    this.$$phase = this.$parent = this.$$watchers =

                     this.$$nextSibling = this.$$prevSibling =

                     this.$$childHead = this.$$childTail = null;

    this[‘this‘] = this.$root =  this;

    this.$$destroyed = false;

    this.$$asyncQueue = [];

    this.$$postDigestQueue = [];

    this.$$listeners = {};

    this.$$listenerCount = {};

    this.$$isolateBindings = {};

}

可以看到在构造函数里定义了很多属性,我们来一一说明一下

  • $id, 通过nextUid方法来生成一个唯一的标识

  • $$phase, 这是一个状态标识,一般在dirty check时用到,表明现在在哪个阶段

  • $parent, 代表自己的上级scope属性

  • $$watchers, 保存scope变量当前所有的监控数据,是一个数组

  • $$nextSibling, 下一个兄弟scope属性

  • $$prevSibling, 前一个兄弟scope属性

  • $$childHead, 第一个子级scope属性

  • $$childTail, 最后一个子级scope属性

  • $$destroyed, 表示是否被销毁

  • $$asyncQueue, 代表异步操作的数组

  • $$postDigestQueue, 代表一个在dirty check之后执行的数组

  • $$listeners, 代表scope变量当前所有的监听数据,是一个数组

  • $$listenerCount, 暂无

  • $$isolateBindings, 暂无

通过这段代码,可以看出,系统默认会创建根作用域,并作为$rootScopeprovider实例返回.

?





1

2

3

var $rootScope = new
Scope();

return $rootScope;

创建子级作用域是通过$new方法,我们来看看.

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

$new: function(isolate) {

        var
ChildScope,

            child;

        if
(isolate) {

          child = new
Scope();

          child.$root = this.$root;

          // ensure that there is just one async queue per $rootScope and its children

          child.$$asyncQueue = this.$$asyncQueue;

          child.$$postDigestQueue = this.$$postDigestQueue;

        } else
{

          // Only create a child scope class if somebody asks for one,

          // but cache it to allow the VM to optimize lookups.

          if
(!this.$$childScopeClass) {

            this.$$childScopeClass = function() {

              this.$$watchers = this.$$nextSibling =

                  this.$$childHead = this.$$childTail = null;

              this.$$listeners = {};

              this.$$listenerCount = {};

              this.$id = nextUid();

              this.$$childScopeClass = null;

            };

            this.$$childScopeClass.prototype = this;

          }

          child = new
this.$$childScopeClass();

        }

        child[‘this‘] = child;

        child.$parent = this;

        child.$$prevSibling = this.$$childTail;

        if
(this.$$childHead) {

          this.$$childTail.$$nextSibling = child;

          this.$$childTail = child;

        } else
{

          this.$$childHead = this.$$childTail = child;

        }

        return
child;

      }

通过分析上面的代码,可以得出

  • isolate标识来创建独立作用域,这个在创建指令,并且scope属性定义的情况下,会触发这种情况,还有几种别的特殊情况,假如是独立作用域的话,会多一个$root属性,这个默认是指向rootscope的

  • 如果不是独立的作用域,则会生成一个内部的构造函数,把此构造函数的prototype指向当前scope实例

  • 通用的操作就是,设置当前作用域的$$childTail,$$childTail.$$nextSibling,$$childHead,this.$$childTail为生成的子级作用域;设置子级域的$parent为当前作用域,$$prevSibling为当前作用域最后一个子级作用域

说完了创建作用域,再来说说$watch函数,这个比较关键

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

$watch: function(watchExp, listener, objectEquality) {

        var
scope = this,

            get = compileToFn(watchExp, ‘watch‘),

            array = scope.$$watchers,

            watcher = {

              fn: listener,

              last: initWatchVal,

              get: get,

              exp: watchExp,

              eq: !!objectEquality

            };

        lastDirtyWatch = null;

        // in the case user pass string, we need to compile it, do we really need this ?

        if
(!isFunction(listener)) {

          var
listenFn = compileToFn(listener || noop, ‘listener‘);

          watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};

        }

        if
(typeof watchExp == ‘string‘
&& get.constant) {

          var
originalFn = watcher.fn;

          watcher.fn = function(newVal, oldVal, scope) {

            originalFn.call(this, newVal, oldVal, scope);

            arrayRemove(array, watcher);

          };

        }

        if
(!array) {

          array = scope.$$watchers = [];

        }

        // we use unshift since we use a while loop in $digest for speed.

        // the while loop reads in reverse order.

        array.unshift(watcher);

        return
function deregisterWatch() {

          arrayRemove(array, watcher);

          lastDirtyWatch = null;

        };

      }

$watch函数有三个参数,第一个是监控参数,可以是字符串或者函数,第二个是监听函数,第三个是代表是否深度监听,注意看这个代码

?





1

get = compileToFn(watchExp, ‘watch‘)

这个compileToFn函数其实是调用$parse实例来分析监控参数,然后返回一个函数,这个会在dirty
check里用到,用来获取监控表达式的值,这个$parseprovider也是angularjs中用的比较多的,下面来重点的说下这个provider

$parse的代码比较长,在源码文件夹中的ng目录里,parse.js里就是$parse的全部代码,当你了解完parse的核心之后,这部份代码其实可以独立出来,做成自己的计算器程序也是可以的,因为它的核心就是解析字符串,而且默认支持四则运算,运算符号的优先级处理,只是额外的增加了对变量的支持以及过滤器的支持,想想,把这块代码放在模板引擎里也是可以的,说多了,让我们来一步一步的分析parse代码吧.

记住,不管是哪个provider,先看它的$get属性,所以我们先来看看$parse的$get吧

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

this.$get = [‘$filter‘, ‘$sniffer‘, ‘$log‘, function($filter, $sniffer, $log) {

    $parseOptions.csp = $sniffer.csp;

    promiseWarning = function
promiseWarningFn(fullExp) {

      if
(!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return;

      promiseWarningCache[fullExp] = true;

      $log.warn(‘[$parse] Promise found in the expression ‘
+ fullExp + ‘. ‘
+

          ‘Automatic unwrapping of promises in Angular expressions is deprecated.‘);

    };

    return
function(exp) {

      var
parsedExpression;

      switch
(typeof exp) {

        case
‘string‘:

          if
(cache.hasOwnProperty(exp)) {

            return
cache[exp];

          }

          var
lexer = new Lexer($parseOptions);

          var
parser = new
Parser(lexer, $filter, $parseOptions);

          parsedExpression = parser.parse(exp, false);

          if
(exp !== ‘hasOwnProperty‘) {

            // Only cache the value if it‘s not going to mess up the cache object

            // This is more performant that using Object.prototype.hasOwnProperty.call

            cache[exp] = parsedExpression;

          }

          return
parsedExpression;

        case
function‘:

          return
exp;

        default:

          return
noop;

      }

    };

  }];

可以看出,假如解析的是函数,则直接返回,是字符串的话,则需要进行parser.parse方法,这里重点说下这个

通过阅读parse.js文件,你会发现,这里有两个关键类

  • lexer, 负责解析字符串,然后生成token,有点类似编译原理中的词法分析器

  • parser, 负责对lexer生成的token,生成执行表达式,其实就是返回一个执行函数

看这里

?





1

2

3

var lexer = new Lexer($parseOptions);

var parser = new
Parser(lexer, $filter, $parseOptions);

parsedExpression = parser.parse(exp, false);

第一句就是创建一个lexer实例,第二句是把lexer实例传给parser构造函数,然后生成parser实例,最后一句是调用parser.parse生成执行表达式,实质是一个函数

现在转到parser.parse里去

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

parse: function
(text, json) {

    this.text = text;

    //TODO(i): strip all the obsolte json stuff from this file

    this.json = json;

    this.tokens = this.lexer.lex(text);

    console.log(this.tokens);

    if
(json) {

      // The extra level of aliasing is here, just in case the lexer misses something, so that

      // we prevent any accidental execution in JSON.

      this.assignment = this.logicalOR;

      this.functionCall =

      this.fieldAccess =

      this.objectIndex =

      this.filterChain = function() {

        this.throwError(‘is not valid json‘, {text: text, index: 0});

      };

    }

    var
value = json ? this.primary() : this.statements();

    if
(this.tokens.length !== 0) {

      this.throwError(‘is an unexpected token‘, this.tokens[0]);

    }

    value.literal = !!value.literal;

    value.constant = !!value.constant;

    return
value;

  }

视线移到这句this.tokens = this.lexer.lex(text),然后来看看lex方法

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

lex: function
(text) {

    this.text = text;

    this.index = 0;

    this.ch = undefined;

    this.lastCh = ‘:‘; // can start regexp

    this.tokens = [];

    var
token;

    var
json = [];

    while
(this.index < this.text.length) {

      this.ch = this.text.charAt(this.index);

      if
(this.is(‘"\‘‘)) {

        this.readString(this.ch);

      } else
if (this.isNumber(this.ch) || this.is(‘.‘) && this.isNumber(this.peek())) {

        this.readNumber();

      } else
if (this.isIdent(this.ch)) {

        this.readIdent();

        // identifiers can only be if the preceding char was a { or ,

        if
(this.was(‘{,‘) && json[0] === ‘{‘
&&

            (token = this.tokens[this.tokens.length - 1])) {

          token.json = token.text.indexOf(‘.‘) === -1;

        }

      } else
if (this.is(‘(){}[].,;:?‘)) {

        this.tokens.push({

          index: this.index,

          text: this.ch,

          json: (this.was(‘:[,‘) && this.is(‘{[‘)) || this.is(‘}]:,‘)

        });

        if
(this.is(‘{[‘)) json.unshift(this.ch);

        if
(this.is(‘}]‘)) json.shift();

        this.index++;

      } else
if (this.isWhitespace(this.ch)) {

        this.index++;

        continue;

      } else
{

        var
ch2 = this.ch + this.peek();

        var
ch3 = ch2 + this.peek(2);

        var
fn = OPERATORS[this.ch];

        var
fn2 = OPERATORS[ch2];

        var
fn3 = OPERATORS[ch3];

        if
(fn3) {

          this.tokens.push({index: this.index, text: ch3, fn: fn3});

          this.index += 3;

        } else
if
(fn2) {

          this.tokens.push({index: this.index, text: ch2, fn: fn2});

          this.index += 2;

        } else
if
(fn) {

          this.tokens.push({

            index: this.index,

            text: this.ch,

            fn: fn,

            json: (this.was(‘[,:‘) && this.is(‘+-‘))

          });

          this.index += 1;

        } else
{

          this.throwError(‘Unexpected next character ‘, this.index, this.index + 1);

        }

      }

      this.lastCh = this.ch;

    }

    return
this.tokens;

  }

这里我们假如传进的字符串是1+2,通常我们分析源码的时候,碰到代码复杂的地方,我们可以简单化处理,因为逻辑都一样,只是情况不一样罢了.

上面的代码主要就是分析传入到lex内的字符串,以一个whileloop开始,然后依次检查当前字符是否是数字,是否是变量标识等,假如是数字的话,则转到
readNumber方法,这里以1+2为例,当前ch是1,然后跳到readNumber方法

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

readNumber: function() {

    var
number = ‘‘;

    var
start = this.index;

    while
(this.index < this.text.length) {

      var
ch = lowercase(this.text.charAt(this.index));

      if
(ch == ‘.‘ || this.isNumber(ch)) {

        number += ch;

      } else
{

        var
peekCh = this.peek();

        if
(ch == ‘e‘ && this.isExpOperator(peekCh)) {

          number += ch;

        } else
if (this.isExpOperator(ch) &&

            peekCh && this.isNumber(peekCh) &&

            number.charAt(number.length - 1) == ‘e‘) {

          number += ch;

        } else
if (this.isExpOperator(ch) &&

            (!peekCh || !this.isNumber(peekCh)) &&

            number.charAt(number.length - 1) == ‘e‘) {

          this.throwError(‘Invalid exponent‘);

        } else
{

          break;

        }

      }

      this.index++;

    }

    number = 1 * number;

    this.tokens.push({

      index: start,

      text: number,

      json: true,

      fn: function() { return
number; }

    });

  }

上面的代码就是检查从当前index开始的整个数字,包括带小数点的情况,检查完毕之后跳出loop,当前index向前进一个,以待以后检查后续字符串,最后保存到lex实例的token数组中,这里的fn属性就是以后执行时用到的,这里的return
number是利用了JS的闭包特性,number其实就是检查时外层的number变量值.以1+2为例,这时index应该停在+这里,在lex的while
loop中,+检查会跳到最后一个else里,这里有一个对象比较关键,OPERATORS,它保存着所有运算符所对应的动作,比如这里的+,对应的动作是

?





1

2

3

4

5

6

7

8

9

‘+‘:function(self, locals, a,b){

      a=a(self, locals); b=b(self, locals);

      if
(isDefined(a)) {

        if
(isDefined(b)) {

          return
a + b;

        }

        return
a;

      }

      return
isDefined(b)?b:undefined;}

大家注意了,这里有4个参数,可以先透露一下,第一个是传的是当前上下文对象,比喻当前scope实例,这个是为了获取字符串中的变量值,第二个参数是本地变量,是传递给函数当入参用的,基本用不到,最后两个参是关键,+是二元运算符,所以a代表左侧运算值,b代表右侧运算值.

最后解析完+之后,index停在了2的位置,跟1一样,也是返回一个token,fn属性也是一个返回当前数字的函数.

当解析完整个1+2字符串后,lex返回的是token数组,这个即可传递给parse来处理,来看看

?





1

var value = json ? this.primary() : this.statements();

默认json是false,所以会跳到this.statements(),这里将会生成执行语句.

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

statements: function() {

   var
statements = [];

   while
(true) {

     if
(this.tokens.length > 0 && !this.peek(‘}‘, ‘)‘, ‘;‘, ‘]‘))

       statements.push(this.filterChain());

     if
(!this.expect(‘;‘)) {

       // optimize for the common case where there is only one statement.

       // TODO(size): maybe we should not support multiple statements?

       return
(statements.length === 1)

           ? statements[0]

           : function(self, locals) {

               var
value;

               for
(var i = 0; i < statements.length; i++) {

                 var
statement = statements[i];

                 if
(statement) {

                   value = statement(self, locals);

                 }

               }

               return
value;

             };

     }

   }

 }

代码以一个无限loop的while开始,语句分析的时候是有运算符优先级的,默认的顺序是,这里以函数名为排序

filterChain

中文翻译下就是这样的

过滤函数<一般表达式<赋值语句<三元运算<逻辑or<逻辑and<比较运算<关系运算<加减法运算<乘法运算<一元运算,最后则默认取第一个token的fn属性

这里以1+2的token为例,这里会用到parse的expect方法,expect会用到peek方法

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

peek: function(e1, e2, e3, e4) {

    if
(this.tokens.length > 0) {

      var
token = this.tokens[0];

      var
t = token.text;

      if
(t === e1 || t === e2 || t === e3 || t === e4 ||

          (!e1 && !e2 && !e3 && !e4)) {

        return
token;

      }

    }

    return
false;

  },

  expect: function(e1, e2, e3, e4){

    var
token = this.peek(e1, e2, e3, e4);

    if
(token) {

      if
(this.json && !token.json) {

        this.throwError(‘is not valid json‘, token);

      }

      this.tokens.shift();

      return
token;

    }

    return
false;

  }

expect方法传空就是默认从token数组中弹出第一个token,数组数量减1

1+2的执行语句最后会定位到加法运算那里additive

?





1

2

3

4

5

6

7

8

additive: function() {

   var
left = this.multiplicative();

   var
token;

   while
((token = this.expect(‘+‘,‘-‘))) {

     left = this.binaryFn(left, token.fn, this.multiplicative());

   }

   return
left;

 }

最后返回一个二元操作的函数binaryFn

?





1

2

3

4

5

6

7

binaryFn: function(left, fn, right) {

    return
extend(function(self, locals) {

      return
fn(self, locals, left, right);

    }, {

      constant:left.constant && right.constant

    });

  }

这个函数参数里的left,right对应的‘1‘,‘2‘两个token的fn属性,即是

js function(){ return number;}

fn函数对应additive方法中+号对应token的fn

?





1

2

3

4

5

6

7

8

9

function(self, locals, a,b){

      a=a(self, locals); b=b(self, locals);

      if
(isDefined(a)) {

        if
(isDefined(b)) {

          return
a + b;

        }

        return
a;

      }

      return
isDefined(b)?b:undefined;}

最后生成执行表达式函数,也就是filterChain返回的left值,被push到statements方法中的statements数组中,仔细看statements方法的返回值,假如表达式数组长度为1,则返回第一个执行表达式,否则返回一个包装的函数,里面是一个loop,不断的执行表达式,只返回最后一个表达式的值

?





1

2

3

4

5

6

7

8

9

10

11

12

return (statements.length === 1)

            ? statements[0]

            : function(self, locals) {

                var
value;

                for
(var i = 0; i < statements.length; i++) {

                  var
statement = statements[i];

                  if
(statement) {

                    value = statement(self, locals);

                  }

                }

                return
value;

              }

好了,说完了生成执行表达式,其实parse的任务已经完成了,现在只需要把这个作为parseprovider的返回值了.

等会再回到rootscope的$watch函数解析里去,我们可以先测试下parse解析生成执行表达式的效果,这里贴一个独立的带parse的例子,不依赖angularjs,感兴趣的可以戳这里

总结

今天先说到这里了,下次有空接着分析rootscope后续的方法.


作者声明


作者: feenan

出处: http://www.cnblogs.com/xuwenmin888

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


Angularjs 源码分析2,布布扣,bubuko.com

时间: 2024-10-16 21:48:20

Angularjs 源码分析2的相关文章

angularjs源码分析之:angularjs执行流程

angularjs用了快一个月了,最难的不是代码本身,而是学会怎么用angular的思路思考问题.其中涉及到很多概念,比如:directive,controller,service,compile,link,scope,isolate scope,双向绑定,mvvm等.最近准备把这些都慢慢搞懂,分析源码并贴到博客园,如有分析不对的地方,还望各位包容并指正. angularjs源码分析之:angularjs执行流程 先上个大图,有个大概印象,注:angularjs的版本为:1.2.1,通过bowe

AngularJS 源码分析1

AngularJS简介 angularjs 是google出品的一款MVVM前端框架,包含一个精简的类jquery库,创新的开发了以指令的方式来组件化前端开发,可以去它的官网看看,请戳这里 再贴上一个本文源码分析对应的angularjs源码合并版本1.2.4,精简版的,除掉了所有的注释, 请戳这里 从启动开始说起 定位到4939行,这里是angularjs开始执行初始化的地方,见代码 ? 1 2 3 bindJQuery(), publishExternalAPI(angular), jqLit

AngularJS 源码分析3

本文接着上一篇讲 回顾 上次说到了rootScope里的$watch方法中的解析监控表达式,即而引出了对parse的分析,今天我们接着这里继续挖代码. $watch续 先上一块$watch代码 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 $watch: function(watchExp, listener, obj

MVVM大比拼之AngularJS源码精析

MVVM大比拼之AngularJS源码精析 简介 AngularJS的学习资源已经非常非常多了,AngularJS基础请直接看官网文档.这里推荐几个深度学习的资料: AngularJS学习笔记 作者:邹业盛 .这个笔记非常细致,记录了作者对于AngularJS各个方面的思考,其中也不乏源码级的分析. 构建自己的AngularJS .虽然放出第一章后作者就写书去了.但这第一部分已经足以带领读者深入窥探angularJS在核心概念上的实现,特别是dirty check.有愿意继续深入的读者可以去买书

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Spark的Master和Worker集群启动的源码分析

基于spark1.3.1的源码进行分析 spark master启动源码分析 1.在start-master.sh调用master的main方法,main方法调用 def main(argStrings: Array[String]) { SignalLogger.register(log) val conf = new SparkConf val args = new MasterArguments(argStrings, conf) val (actorSystem, _, _, _) =