• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

baidu / san / 5728982971

pending completion
5728982971

push

github

web-flow
Merge pull request #769 from andrew703/new-function-fixed

fix unsafe-eval

1921 of 2118 branches covered (90.7%)

Branch coverage included in aggregate %.

9 of 9 new or added lines in 2 files covered. (100.0%)

3489 of 3581 relevant lines covered (97.43%)

1331.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.27
/src/view/component.js
1
/**
2
 * Copyright (c) Baidu Inc. All rights reserved.
3
 *
4
 * This source code is licensed under the MIT license.
5
 * See LICENSE file in the project root for license information.
6
 *
7
 * @file 组件类
8
 */
9

10
var bind = require('../util/bind');
11
var each = require('../util/each');
12
var guid = require('../util/guid');
13
var extend = require('../util/extend');
14
var nextTick = require('../util/next-tick');
15
var emitDevtool = require('../util/emit-devtool');
16
var ExprType = require('../parser/expr-type');
17
var parseExpr = require('../parser/parse-expr');
18
var parseTemplate = require('../parser/parse-template');
19
var unpackANode = require('../parser/unpack-anode');
20
var removeEl = require('../browser/remove-el');
21
var Data = require('../runtime/data');
22
var evalExpr = require('../runtime/eval-expr');
23
var changeExprCompare = require('../runtime/change-expr-compare');
24
var DataChangeType = require('../runtime/data-change-type');
25
var insertBefore = require('../browser/insert-before');
26
var un = require('../browser/un');
27
var preprocessComponents = require('./preprocess-components');
28
var createNode = require('./create-node');
29
var preheatEl = require('./preheat-el');
30
var parseComponentTemplate = require('./parse-component-template');
31
var preheatANode = require('./preheat-a-node');
32
var LifeCycle = require('./life-cycle');
33
var getANodeProp = require('./get-a-node-prop');
34
var isDataChangeByElement = require('./is-data-change-by-element');
35
var getEventListener = require('./get-event-listener');
36
var hydrateElementChildren = require('./hydrate-element-children');
37
var NodeType = require('./node-type');
38
var styleProps = require('./style-props');
39
var nodeSBindInit = require('./node-s-bind-init');
40
var nodeSBindUpdate = require('./node-s-bind-update');
41
var elementOwnAttached = require('./element-own-attached');
42
var elementOwnDetach = require('./element-own-detach');
43
var elementOwnDispose = require('./element-own-dispose');
44
var warnEventListenMethod = require('./warn-event-listen-method');
45
var elementDisposeChildren = require('./element-dispose-children');
46
var createDataTypesChecker = require('../util/create-data-types-checker');
47
var warn = require('../util/warn');
48
var handleError = require('../util/handle-error');
49
var DOMChildrenWalker = require('./dom-children-walker');
50

51

52

53
/**
54
 * 组件类
55
 *
56
 * @class
57
 * @param {Object} options 初始化参数
58
 */
59
function Component(options) { // eslint-disable-line
2✔
60
    // #[begin] error
61
    for (var key in Component.prototype) {
2,418✔
62
        if (this[key] !== Component.prototype[key]) {
55,614✔
63
            /* eslint-disable max-len */
64
            warn('\`' + key + '\` is a reserved key of san components. Overriding this property may cause unknown exceptions.');
2✔
65
            /* eslint-enable max-len */
66
        }
67
    }
68
    // #[end]
69

70

71
    options = options || {};
2,418✔
72
    this.lifeCycle = LifeCycle.start;
2,418✔
73
    this.id = guid++;
2,418✔
74

75
    if (typeof this.construct === 'function') {
2,418✔
76
        this.construct(options);
8✔
77
    }
78

79
    this.children = [];
2,418✔
80
    this.listeners = {};
2,418✔
81
    this.slotChildren = [];
2,418✔
82
    this.implicitChildren = [];
2,418✔
83

84
    var clazz = this.constructor;
2,418✔
85

86
    this.filters = this.filters || clazz.filters || {};
2,418✔
87
    this.computed = this.computed || clazz.computed || {};
2,418✔
88
    this.messages = this.messages || clazz.messages || {};
2,418✔
89
    this.ssr = this.ssr || clazz.ssr;
2,418✔
90

91
    if (options.transition) {
2,418✔
92
        this.transition = options.transition;
2✔
93
    }
94

95
    this.owner = options.owner;
2,418✔
96
    this.scope = options.scope;
2,418✔
97
    this.el = options.el;
2,418✔
98
    var parent = options.parent;
2,418✔
99
    if (parent) {
2,418✔
100
        this.parent = parent;
886✔
101
        this.parentComponent = parent.nodeType === NodeType.CMPT
886✔
102
            ? parent
886✔
103
            : parent && parent.parentComponent;
832✔
104
    }
105
    else if (this.owner) {
1,532✔
106
        this.parentComponent = this.owner;
12✔
107
        this.scope = this.owner.data;
12✔
108
    }
109

110
    this.sourceSlotNameProps = [];
2,418✔
111
    this.sourceSlots = {
2,418✔
112
        named: {}
113
    };
114

115
    // #[begin] devtool
116
    this._toPhase('beforeCompile');
2,418✔
117
    // #[end]
118

119
    var proto = clazz.prototype;
2,418✔
120

121
    // pre define components class
122
    /* istanbul ignore else  */
123
    if (!proto.hasOwnProperty('_cmptReady')) {
2,418✔
124
        preprocessComponents(clazz);
2,082✔
125
    }
126

127
    // compile
128
    if (!proto.hasOwnProperty('aNode')) {
2,418✔
129
        var aPack = clazz.aPack || proto.hasOwnProperty('aPack') && proto.aPack;
2,074✔
130
        if (aPack) {
2,074✔
131
            proto.aNode = unpackANode(aPack);
4✔
132
            clazz.aPack = proto.aPack = null;
4✔
133
        }
134
        else {
135
            proto.aNode = parseComponentTemplate(clazz);
2,070✔
136
        }
137
    }
138

139
    preheatANode(proto.aNode, this);
2,414✔
140

141
    this.tagName = proto.aNode.tagName;
2,414✔
142
    this.source = typeof options.source === 'string'
2,414✔
143
        ? parseTemplate(options.source).children[0]
2,414✔
144
        : options.source;
145

146
    preheatANode(this.source);
2,414✔
147
    proto.aNode._i++;
2,414✔
148

149

150
    // #[begin] hydrate
151
    // 组件反解,读取注入的组件数据
152
    if (this.el) {
2,414✔
153
        var firstCommentNode = this.el.firstChild;
280✔
154
        if (firstCommentNode && firstCommentNode.nodeType === 3) {
280✔
155
            firstCommentNode = firstCommentNode.nextSibling;
2✔
156
        }
157

158
        if (firstCommentNode && firstCommentNode.nodeType === 8) {
280✔
159
            var stumpMatch = firstCommentNode.data.match(/^\s*s-data:([\s\S]+)?$/);
278✔
160
            if (stumpMatch) {
278!
161
                var stumpText = stumpMatch[1];
278✔
162

163
                // fill component data
164
                stumpText = stumpText
278✔
165
                    .replace(/^[\s\n]*/, '')
166
                    .replace(/\\(.)/g, function ($0, $1) { // 去掉转移符
167
                        return $1;
8✔
168
                    });
169

170
                options.data = JSON.parse(stumpText, function (key, value) {
278✔
171
                    var matched = [];
1,166✔
172
                    if (typeof value === 'string') {
1,166✔
173
                        var matched = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.\d+Z/g.exec(value) || [];
558✔
174
                        return matched.length === 7
558✔
175
                            ? new Date(matched[1], matched[2], matched[3], matched[4], matched[5], matched[6])
558✔
176
                            : value;
177
                    }
178
                    return value;
608✔
179
                });
180

181
                if (firstCommentNode.previousSibling) {
278✔
182
                    removeEl(firstCommentNode.previousSibling);
2✔
183
                }
184
                removeEl(firstCommentNode);
278✔
185
            }
186
        }
187
    }
188
    // #[end]
189

190

191
    if (this.source) {
2,414✔
192
        // 组件运行时传入的结构,做slot解析
193
        this._initSourceSlots(1);
894✔
194

195
        for (var i = 0, l = this.source.events.length; i < l; i++) {
894✔
196
            var eventBind = this.source.events[i];
44✔
197
            // 保存当前实例的native事件,下面创建aNode时候做合并
198
            if (eventBind.modifier.native) {
44✔
199
                // native事件数组
200
                this.nativeEvents = this.nativeEvents || [];
12✔
201
                this.nativeEvents.push(eventBind);
12✔
202
            }
203
            else {
204
                // #[begin] error
205
                warnEventListenMethod(eventBind, options.owner);
32✔
206
                // #[end]
207

208
                this.on(
32✔
209
                    eventBind.name,
210
                    getEventListener(eventBind, options.owner, this.scope, 1),
211
                    eventBind
212
                );
213
            }
214
        }
215

216
        this.tagName = this.tagName || this.source.tagName;
894✔
217
        this.binds = this.source._b;
894✔
218

219
        // init s-bind data
220
        this._srcSbindData = nodeSBindInit(this.source.directives.bind, this.scope, this.owner);
894✔
221
    }
222

223
    this._toPhase('compiled');
2,414✔
224

225

226
    // #[begin] devtool
227
    this._toPhase('beforeInit');
2,414✔
228
    // #[end]
229

230
    // init data
231
    var initData;
2,414✔
232
    try {
2,414✔
233
        initData = typeof this.initData === 'function' && this.initData();
2,414✔
234
    }
235
    catch (e) {
236
        handleError(e, this, 'initData');
2✔
237
    }
238
    initData = extend(initData || {}, options.data || this._srcSbindData);
2,414✔
239

240
    if (this.binds && this.scope) {
2,414✔
241
        for (var i = 0, l = this.binds.length; i < l; i++) {
894✔
242
            var bindInfo = this.binds[i];
944✔
243

244
            var value = evalExpr(bindInfo.expr, this.scope, this.owner);
944✔
245
            if (typeof value !== 'undefined') {
944✔
246
                // See: https://github.com/ecomfe/san/issues/191
247
                initData[bindInfo.name] = value;
838✔
248
            }
249
        }
250
    }
251

252
    this.data = new Data(initData);
2,414✔
253

254
    this.tagName = this.tagName || 'div';
2,414✔
255
    // #[begin] allua
256
    // ie8- 不支持innerHTML输出自定义标签
257
    /* istanbul ignore if */
258
    if (ieOldThan9 && this.tagName.indexOf('-') > 0) {
2,414✔
259
        this.tagName = 'div';
2✔
260
    }
261
    // #[end]
262

263

264
    // #[begin] error
265
    // 在初始化 + 数据绑定后,开始数据校验
266
    // NOTE: 只在开发版本中进行属性校验
267
    var dataTypes = this.dataTypes || clazz.dataTypes;
2,414✔
268
    if (dataTypes) {
2,414✔
269
        var dataTypeChecker = createDataTypesChecker(
118✔
270
            dataTypes,
271
            this.name || clazz.name
236✔
272
        );
273
        this.data.setTypeChecker(dataTypeChecker);
118✔
274
        this.data.checkDataTypes();
118✔
275
    }
276
    // #[end]
277

278
    this.computedDeps = {};
2,332✔
279
    for (var expr in this.computed) {
2,332✔
280
        if (this.computed.hasOwnProperty(expr) && !this.computedDeps[expr]) {
46✔
281
            this._calcComputed(expr);
38✔
282
        }
283
    }
284

285
    this._initDataChanger();
2,330✔
286
    this._sbindData = nodeSBindInit(this.aNode.directives.bind, this.data, this);
2,330✔
287
    this._toPhase('inited');
2,330✔
288

289
    // #[begin] hydrate
290
    var hydrateWalker = options.hydrateWalker;
2,330✔
291
    var aNode = this.aNode;
2,330✔
292
    if (hydrateWalker) {
2,330✔
293
        if (this.ssr === 'client-render') {
174✔
294
            this.attach(hydrateWalker.target, hydrateWalker.current);
10✔
295
        }
296
        else {
297
            if (aNode.Clazz || this.components[aNode.tagName]) {
164✔
298
                this._rootNode = createHydrateNode(aNode, this, this.data, this, hydrateWalker);
10✔
299
                this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
10✔
300
            }
301
            else {
302
                var currentNode = hydrateWalker.current;
154✔
303
                if (currentNode && currentNode.nodeType === 1) {
154✔
304
                    this.el = currentNode;
154✔
305
                    hydrateWalker.goNext();
154✔
306
                }
307

308
                hydrateElementChildren(this, this.data, this);
154✔
309
            }
310

311
            this._toPhase('created');
164✔
312
            this._attached();
164✔
313
            this._toPhase('attached');
164✔
314
        }
315
    }
316
    else if (this.el) {
2,156✔
317
        if (aNode.Clazz || this.components[aNode.tagName]) {
280✔
318
            hydrateWalker = new DOMChildrenWalker(this.el.parentNode, this.el);
10✔
319
            this._rootNode = createHydrateNode(aNode, this, this.data, this, hydrateWalker);
10✔
320
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
10✔
321
        }
322
        else {
323
            hydrateElementChildren(this, this.data, this);
270✔
324
        }
325

326
        this._toPhase('created');
280✔
327
        this._attached();
280✔
328
        this._toPhase('attached');
280✔
329
    }
330
    // #[end]
331
}
332

333

334
/**
335
 * 初始化创建组件外部传入的插槽对象
336
 *
337
 * @protected
338
 * @param {boolean} isFirstTime 是否初次对sourceSlots进行计算
339
 */
340
Component.prototype._initSourceSlots = function (isFirstTime) {
2✔
341
    this.sourceSlots.named = {};
920✔
342

343
    // 组件运行时传入的结构,做slot解析
344
    if (this.source && this.scope) {
920✔
345
        var sourceChildren = this.source.children;
920✔
346

347
        for (var i = 0, l = sourceChildren.length; i < l; i++) {
920✔
348
            var child = sourceChildren[i];
480✔
349
            var target;
480✔
350

351
            var slotBind = !child.textExpr && getANodeProp(child, 'slot');
480✔
352
            if (slotBind) {
480✔
353
                isFirstTime && this.sourceSlotNameProps.push(slotBind);
186✔
354

355
                var slotName = evalExpr(slotBind.expr, this.scope, this.owner);
186✔
356
                target = this.sourceSlots.named[slotName];
186✔
357
                if (!target) {
186✔
358
                    target = this.sourceSlots.named[slotName] = [];
118✔
359
                }
360
                target.push(child);
186✔
361
            }
362
            else if (isFirstTime) {
294!
363
                target = this.sourceSlots.noname;
294✔
364
                if (!target) {
294✔
365
                    target = this.sourceSlots.noname = [];
244✔
366
                }
367
                target.push(child);
294✔
368
            }
369
        }
370
    }
371
};
372

373
/**
374
 * 类型标识
375
 *
376
 * @type {string}
377
 */
378
Component.prototype.nodeType = NodeType.CMPT;
2✔
379

380
/**
381
 * 在下一个更新周期运行函数
382
 *
383
 * @param {Function} fn 要运行的函数
384
 */
385
Component.prototype.nextTick = nextTick;
2✔
386

387
Component.prototype._ctx = (new Date()).getTime().toString(16);
2✔
388

389
/* eslint-disable operator-linebreak */
390
/**
391
 * 使节点到达相应的生命周期
392
 *
393
 * @protected
394
 * @param {string} name 生命周期名称
395
 */
396
Component.prototype._toPhase = function (name) {
2✔
397
    if (!this.lifeCycle[name]) {
28,684✔
398
        this.lifeCycle = LifeCycle[name] || this.lifeCycle;
28,672✔
399
        if (typeof this[name] === 'function') {
28,672✔
400
            try {
284✔
401
                this[name]();
284✔
402
            }
403
            catch (e) {
404
                handleError(e, this, 'hook:' + name);
4✔
405
            }
406
        }
407

408
        this._afterLife = this.lifeCycle;
28,672✔
409

410
        // 通知devtool
411
        // #[begin] devtool
412
        emitDevtool('comp-' + name, this);
28,672✔
413
        // #[end]
414
    }
415
};
416
/* eslint-enable operator-linebreak */
417

418

419
/**
420
 * 添加事件监听器
421
 *
422
 * @param {string} name 事件名
423
 * @param {Function} listener 监听器
424
 * @param {string?} declaration 声明式
425
 */
426
Component.prototype.on = function (name, listener, declaration) {
2✔
427
    if (typeof listener === 'function') {
38!
428
        if (!this.listeners[name]) {
38✔
429
            this.listeners[name] = [];
36✔
430
        }
431
        this.listeners[name].push({fn: listener, declaration: declaration});
38✔
432
    }
433
};
434

435
/**
436
 * 移除事件监听器
437
 *
438
 * @param {string} name 事件名
439
 * @param {Function=} listener 监听器
440
 */
441
Component.prototype.un = function (name, listener) {
2✔
442
    var nameListeners = this.listeners[name];
4✔
443
    var len = nameListeners && nameListeners.length;
4✔
444

445
    while (len--) {
4✔
446
        if (!listener || listener === nameListeners[len].fn) {
4✔
447
            nameListeners.splice(len, 1);
4✔
448
        }
449
    }
450
};
451

452

453
/**
454
 * 派发事件
455
 *
456
 * @param {string} name 事件名
457
 * @param {Object} event 事件对象
458
 */
459
Component.prototype.fire = function (name, event) {
2✔
460
    var me = this;
50✔
461
    // #[begin] devtool
462
    emitDevtool('comp-event', {
50✔
463
        name: name,
464
        event: event,
465
        target: this
466
    });
467
    // #[end]
468

469
    each(this.listeners[name], function (listener) {
50✔
470
        try {
44✔
471
            listener.fn.call(me, event);
44✔
472
        }
473
        catch (e) {
474
            handleError(e, me, 'event:' + name);
2✔
475
        }
476
    });
477
};
478

479
/**
480
 * 计算 computed 属性的值
481
 *
482
 * @private
483
 * @param {string} computedExpr computed表达式串
484
 */
485
Component.prototype._calcComputed = function (computedExpr) {
2✔
486
    var computedDeps = this.computedDeps[computedExpr];
114✔
487
    if (!computedDeps) {
114✔
488
        computedDeps = this.computedDeps[computedExpr] = {};
46✔
489
    }
490

491
    var me = this;
114✔
492
    try {
114✔
493
        var result = this.computed[computedExpr].call({
114✔
494
            data: {
495
                get: function (expr) {
496
                    // #[begin] error
497
                    if (!expr) {
152✔
498
                        throw new Error('[SAN ERROR] call get method in computed need argument');
2✔
499
                    }
500
                    // #[end]
501

502
                    if (!computedDeps[expr]) {
150✔
503
                        computedDeps[expr] = 1;
62✔
504

505
                        if (me.computed[expr] && !me.computedDeps[expr]) {
62✔
506
                            me._calcComputed(expr);
8✔
507
                        }
508

509
                        me.watch(expr, function () {
62✔
510
                            me._calcComputed(computedExpr);
68✔
511
                        });
512
                    }
513

514
                    return me.data.get(expr);
150✔
515
                }
516
            }
517
        });
518
        this.data.set(computedExpr, result);
110✔
519
    }
520
    catch (e) {
521
        handleError(e, this, 'computed:' + computedExpr);
4✔
522
    }
523
};
524

525
/**
526
 * 派发消息
527
 * 组件可以派发消息,消息将沿着组件树向上传递,直到遇上第一个处理消息的组件
528
 *
529
 * @param {string} name 消息名称
530
 * @param {*?} value 消息值
531
 */
532
Component.prototype.dispatch = function (name, value) {
2✔
533
    var parentComponent = this.parentComponent;
54✔
534

535
    while (parentComponent) {
54✔
536
        var handler = parentComponent.messages[name] || parentComponent.messages['*'];
56✔
537
        if (typeof handler === 'function') {
56✔
538
            // #[begin] devtool
539
            emitDevtool('comp-message', {
54✔
540
                target: this,
541
                value: value,
542
                name: name,
543
                receiver: parentComponent
544
            });
545
            // #[end]
546

547
            try {
54✔
548
                handler.call(
54✔
549
                    parentComponent,
550
                    {target: this, value: value, name: name}
551
                );
552
            }
553
            catch (e) {
554
                handleError(e, parentComponent, 'message:' + (name || '*'));
2!
555
            }
556
            return;
54✔
557
        }
558

559
        parentComponent = parentComponent.parentComponent;
2✔
560
    }
561

562
    // #[begin] devtool
563
    emitDevtool('comp-message', {target: this, value: value, name: name});
×
564
    // #[end]
565
};
566

567
/**
568
 * 获取组件内部的 slot
569
 *
570
 * @param {string=} name slot名称,空为default slot
571
 * @return {Array}
572
 */
573
Component.prototype.slot = function (name) {
2✔
574
    var result = [];
48✔
575
    var me = this;
48✔
576

577
    function childrenTraversal(children) {
2✔
578
        each(children, function (child) {
140✔
579
            if (child.nodeType === NodeType.SLOT && child.owner === me) {
198✔
580
                if (child.isNamed && child.name === name
106✔
581
                    || !child.isNamed && !name
582
                ) {
583
                    result.push(child);
52✔
584
                }
585
            }
586
            else {
587
                childrenTraversal(child.children);
92✔
588
            }
589
        });
590
    }
591

592
    childrenTraversal(this.children);
48✔
593
    return result;
48✔
594
};
595

596
/**
597
 * 获取带有 san-ref 指令的子组件引用
598
 *
599
 * @param {string} name 子组件的引用名
600
 * @return {Component}
601
 */
602
Component.prototype.ref = function (name) {
2✔
603
    var refTarget;
138✔
604
    var owner = this;
138✔
605

606
    function childrenTraversal(children) {
2✔
607
        if (children) {
318✔
608
            for (var i = 0, l = children.length; i < l; i++) {
248✔
609
                elementTraversal(children[i]);
276✔
610
                if (refTarget) {
276✔
611
                    return;
182✔
612
                }
613
            }
614
        }
615
    }
616

617
    function elementTraversal(element) {
2✔
618
        var nodeType = element.nodeType;
280✔
619
        if (nodeType === NodeType.TEXT) {
280✔
620
            return;
44✔
621
        }
622

623
        if (element.owner === owner) {
236✔
624
            var ref;
218✔
625
            switch (element.nodeType) {
218✔
626
                case NodeType.ELEM:
182✔
627
                    ref = element.aNode.directives.ref;
46✔
628
                    if (ref && evalExpr(ref.value, element.scope, owner) === name) {
46✔
629
                        refTarget = element.el;
12✔
630
                    }
631
                    break;
46✔
632

633
                case NodeType.CMPT:
634
                    ref = element.source.directives.ref;
136✔
635
                    if (ref && evalExpr(ref.value, element.scope, owner) === name) {
136✔
636
                        refTarget = element;
120✔
637
                    }
638
            }
639

640
            if (refTarget) {
218✔
641
                return;
132✔
642
            }
643

644
            childrenTraversal(element.slotChildren);
86✔
645
        }
646

647
        if (refTarget) {
104✔
648
            return;
6✔
649
        }
650

651
        childrenTraversal(element.children);
98✔
652
    }
653

654
    this._rootNode ? elementTraversal(this._rootNode) : childrenTraversal(this.children);
138✔
655

656
    return refTarget;
138✔
657
};
658

659

660
/**
661
 * 视图更新函数
662
 *
663
 * @param {Array?} changes 数据变化信息
664
 */
665
Component.prototype._update = function (changes) {
2✔
666
    if (this.lifeCycle.disposed) {
2,220✔
667
        return;
266✔
668
    }
669

670
    var me = this;
1,954✔
671

672

673
    var needReloadForSlot = false;
1,954✔
674
    this._notifyNeedReload = function () {
1,954✔
675
        needReloadForSlot = true;
72✔
676
    };
677

678
    if (changes) {
1,954✔
679
        if (this.source) {
514✔
680
            this._srcSbindData = nodeSBindUpdate(
510✔
681
                this.source.directives.bind,
682
                this._srcSbindData,
683
                this.scope,
684
                this.owner,
685
                changes,
686
                function (name, value) {
687
                    if (name in me.source._pi) {
28✔
688
                        return;
4✔
689
                    }
690

691
                    me.data.set(name, value, {
24✔
692
                        target: {
693
                            node: me.owner
694
                        }
695
                    });
696
                }
697
            );
698
        }
699

700
        each(changes, function (change) {
514✔
701
            var changeExpr = change.expr;
780✔
702

703
            each(me.binds, function (bindItem) {
780✔
704
                var relation;
1,146✔
705
                var setExpr = bindItem.name;
1,146✔
706
                var updateExpr = bindItem.expr;
1,146✔
707

708
                if (!isDataChangeByElement(change, me, setExpr)
1,146✔
709
                    && (relation = changeExprCompare(changeExpr, updateExpr, me.scope))
710
                ) {
711
                    if (relation > 2) {
434✔
712
                        setExpr = {
82✔
713
                            type: ExprType.ACCESSOR,
714
                            paths: [
715
                                {
716
                                    type: ExprType.STRING,
717
                                    value: setExpr
718
                                }
719
                            ].concat(changeExpr.paths.slice(updateExpr.paths.length))
720
                        };
721
                        updateExpr = changeExpr;
82✔
722
                    }
723

724
                    if (relation >= 2 && change.type === DataChangeType.SPLICE) {
434✔
725
                        me.data.splice(setExpr, [change.index, change.deleteCount].concat(change.insertions), {
36✔
726
                            target: {
727
                                node: me.owner
728
                            }
729
                        });
730
                    }
731
                    else {
732
                        me.data.set(setExpr, evalExpr(updateExpr, me.scope, me.owner), {
398✔
733
                            target: {
734
                                node: me.owner
735
                            }
736
                        });
737
                    }
738
                }
739
            });
740

741
            each(me.sourceSlotNameProps, function (bindItem) {
780✔
742
                needReloadForSlot = needReloadForSlot || changeExprCompare(changeExpr, bindItem.expr, me.scope);
284✔
743
                return !needReloadForSlot;
284✔
744
            });
745
        });
746

747
        if (needReloadForSlot) {
514✔
748
            this._initSourceSlots();
8✔
749
            this._repaintChildren();
8✔
750
        }
751
        else {
752
            var slotChildrenLen = this.slotChildren.length;
506✔
753
            while (slotChildrenLen--) {
506✔
754
                var slotChild = this.slotChildren[slotChildrenLen];
414✔
755

756
                if (slotChild.lifeCycle.disposed) {
414✔
757
                    this.slotChildren.splice(slotChildrenLen, 1);
12✔
758
                }
759
                else if (slotChild.isInserted) {
402✔
760
                    slotChild._update(changes, 1);
288✔
761
                }
762
            }
763
        }
764
    }
765

766
    var dataChanges = this._dataChanges;
1,954✔
767
    if (dataChanges) {
1,954✔
768
        // #[begin] devtool
769
        this._toPhase('beforeUpdate');
1,652✔
770
        // #[end]
771

772
        this._dataChanges = null;
1,652✔
773

774
        this._sbindData = nodeSBindUpdate(
1,652✔
775
            this.aNode.directives.bind,
776
            this._sbindData,
777
            this.data,
778
            this,
779
            dataChanges,
780
            function (name, value) {
781
                if (me._rootNode || (name in me.aNode._pi)) {
36✔
782
                    return;
6✔
783
                }
784

785
                getPropHandler(me.tagName, name)(me.el, value, name, me);
30✔
786
            }
787
        );
788

789
        var htmlDirective = this.aNode.directives.html;
1,652✔
790

791
        if (this._rootNode) {
1,652✔
792
            this._rootNode._update(dataChanges);
100✔
793
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
100✔
794
        }
795
        else if (htmlDirective) {
1,552✔
796
            var len = dataChanges.length;
2✔
797
            while (len--) {
2✔
798
                if (changeExprCompare(dataChanges[len].expr, htmlDirective.value, this.data)) {
2!
799
                    // #[begin] error
800
                    warnSetHTML(this.el);
2✔
801
                    // #[end]
802

803
                    this.el.innerHTML = evalExpr(htmlDirective.value, this.data, this);
2✔
804
                    break;
2✔
805
                }
806
            }
807
        }
808
        else {
809
            var dynamicProps = this.aNode._dp;
1,550✔
810
            for (var i = 0; i < dynamicProps.length; i++) {
1,550✔
811
                var prop = dynamicProps[i];
4,742✔
812

813
                for (var j = 0; j < dataChanges.length; j++) {
4,742✔
814
                    var change = dataChanges[j];
6,316✔
815
                    if (changeExprCompare(change.expr, prop.expr, this.data)
6,316!
816
                        || prop.hintExpr && changeExprCompare(change.expr, prop.hintExpr, this.data)
817
                    ) {
818
                        prop.handler(this.el, evalExpr(prop.expr, this.data, this), prop.name, this);
130✔
819
                        break;
130✔
820
                    }
821
                }
822
            }
823

824
            for (var i = 0; i < this.children.length; i++) {
1,550✔
825
                this.children[i]._update(dataChanges);
2,240✔
826
            }
827
        }
828

829
        if (needReloadForSlot) {
1,652✔
830
            this._initSourceSlots();
18✔
831
            this._repaintChildren();
18✔
832
        }
833

834
        for (var i = 0; i < this.implicitChildren.length; i++) {
1,652✔
835
            this.implicitChildren[i]._update(dataChanges);
8✔
836
        }
837

838
        if (typeof this.updated === 'function') {
1,652✔
839
            this.updated();
10✔
840
        }
841

842
        if (this.owner && this._updateBindxOwner(dataChanges)) {
1,652✔
843
            this.owner._update();
50✔
844
        }
845
    }
846

847
    this._notifyNeedReload = null;
1,954✔
848
};
849

850
Component.prototype._updateBindxOwner = function (dataChanges) {
2✔
851
    var me = this;
424✔
852
    var xbindUped;
424✔
853

854
    each(dataChanges, function (change) {
424✔
855
        each(me.binds, function (bindItem) {
566✔
856
            var changeExpr = change.expr;
958✔
857
            if (bindItem.x
958✔
858
                && !isDataChangeByElement(change, me.owner)
859
                && changeExprCompare(changeExpr, parseExpr(bindItem.name), me.data)
860
            ) {
861
                var updateScopeExpr = bindItem.expr;
64✔
862
                if (changeExpr.paths.length > 1) {
64✔
863
                    updateScopeExpr = {
26✔
864
                        type: ExprType.ACCESSOR,
865
                        paths: bindItem.expr.paths.concat(changeExpr.paths.slice(1))
866
                    };
867
                }
868

869
                xbindUped = 1;
64✔
870
                me.scope.set(
64✔
871
                    updateScopeExpr,
872
                    evalExpr(changeExpr, me.data, me),
873
                    {
874
                        target: {
875
                            node: me,
876
                            prop: bindItem.name
877
                        }
878
                    }
879
                );
880
            }
881
        });
882
    });
883

884
    return xbindUped;
424✔
885
};
886

887
/**
888
 * 重新绘制组件的内容
889
 * 当 dynamic slot name 发生变更或 slot 匹配发生变化时,重新绘制
890
 * 在组件级别重绘有点粗暴,但是能保证视图结果正确性
891
 */
892
Component.prototype._repaintChildren = function () {
2✔
893
    // TODO: repaint once?
894

895
    if (this._rootNode) {
26!
896
        var parentEl = this._rootNode.el.parentNode;
×
897
        var beforeEl = this._rootNode.el.nextSibling;
×
898
        this._rootNode.dispose(0, 1);
×
899
        this.slotChildren = [];
×
900

901
        this._rootNode = createNode(this.aNode, this, this.data, this);
×
902
        this._rootNode.attach(parentEl, beforeEl);
×
903
        this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
×
904
    }
905
    else {
906
        elementDisposeChildren(this.children, 0, 1);
26✔
907
        this.children = [];
26✔
908
        this.slotChildren = [];
26✔
909

910
        for (var i = 0, l = this.aNode.children.length; i < l; i++) {
26✔
911
            var child = createNode(this.aNode.children[i], this, this.data, this);
104✔
912
            this.children.push(child);
104✔
913
            child.attach(this.el);
104✔
914
        }
915
    }
916
};
917

918

919
/**
920
 * 初始化组件内部监听数据变化
921
 *
922
 * @private
923
 * @param {Object} change 数据变化信息
924
 */
925
Component.prototype._initDataChanger = function () {
2✔
926
    var me = this;
2,330✔
927

928
    this._dataChanger = function (change) {
2,330✔
929
        if (me._afterLife.created) {
2,658✔
930
            if (!me._dataChanges) {
2,232✔
931
                nextTick(me._update, me);
1,656✔
932
                me._dataChanges = [];
1,656✔
933
            }
934

935
            me._dataChanges.push(change);
2,232✔
936
        }
937
        else if (me.lifeCycle.inited && me.owner) {
426✔
938
            me._updateBindxOwner([change]);
2✔
939
        }
940
    };
941

942
    this.data.listen(this._dataChanger);
2,330✔
943
};
944

945

946
/**
947
 * 监听组件的数据变化
948
 *
949
 * @param {string} dataName 变化的数据项
950
 * @param {Function} listener 监听函数
951
 */
952
Component.prototype.watch = function (dataName, listener) {
2✔
953
    var dataExpr = parseExpr(dataName);
80✔
954
    var value = evalExpr(dataExpr, this.data, this);
80✔
955
    var me = this;
80✔
956

957
    this.data.listen(function (change) {
80✔
958
        if (changeExprCompare(change.expr, dataExpr, me.data)) {
306✔
959
            var newValue = evalExpr(dataExpr, me.data, me);
94✔
960

961
            if (newValue !== value) {
94!
962
                var oldValue = value;
94✔
963
                value = newValue;
94✔
964

965
                try {
94✔
966
                    listener.call(
94✔
967
                        me,
968
                        newValue,
969
                        {
970
                            oldValue: oldValue,
971
                            newValue: newValue,
972
                            change: change
973
                        }
974
                    );
975
                }
976
                catch (e) {
977
                    handleError(e, me, 'watch:' + dataName);
2✔
978
                }
979
            }
980
        }
981
    });
982
};
983

984
Component.prototype._getElAsRootNode = function () {
2✔
985
    return this.el;
60✔
986
};
987

988
/**
989
 * 将组件attach到页面
990
 *
991
 * @param {HTMLElement} parentEl 要添加到的父元素
992
 * @param {HTMLElement=} beforeEl 要添加到哪个元素之前
993
 */
994
Component.prototype.attach = function (parentEl, beforeEl) {
2✔
995
    if (!this.lifeCycle.attached) {
1,862✔
996
        // #[begin] devtool
997
        this._toPhase('beforeAttach');
1,860✔
998
        // #[end]
999

1000
        var aNode = this.aNode;
1,860✔
1001

1002
        if (aNode.Clazz || this.components[aNode.tagName]) {
1,860✔
1003
            // #[begin] devtool
1004
            this._toPhase('beforeCreate');
84✔
1005
            // #[end]
1006
            this._rootNode = this._rootNode || createNode(aNode, this, this.data, this);
84✔
1007
            this._rootNode.attach(parentEl, beforeEl);
84✔
1008
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
84✔
1009
            this._toPhase('created');
84✔
1010
        }
1011
        else {
1012
            if (!this.el) {
1,776✔
1013
                // #[begin] devtool
1014
                this._toPhase('beforeCreate');
1,766✔
1015
                // #[end]
1016

1017
                var props;
1,766✔
1018

1019
                if (aNode._ce && aNode._i > 2) {
1,766✔
1020
                    props = aNode._dp;
126✔
1021
                    this.el = (aNode._el || preheatEl(aNode)).cloneNode(false);
126✔
1022
                }
1023
                else {
1024
                    props = aNode.props;
1,640✔
1025
                    this.el = createEl(this.tagName);
1,640✔
1026
                }
1027

1028
                if (this._sbindData) {
1,766✔
1029
                    for (var key in this._sbindData) {
2✔
1030
                        if (this._sbindData.hasOwnProperty(key)) {
12!
1031
                            getPropHandler(this.tagName, key)(
12✔
1032
                                this.el,
1033
                                this._sbindData[key],
1034
                                key,
1035
                                this
1036
                            );
1037
                        }
1038
                    }
1039
                }
1040

1041
                for (var i = 0, l = props.length; i < l; i++) {
1,766✔
1042
                    var prop = props[i];
5,484✔
1043
                    var value = evalExpr(prop.expr, this.data, this);
5,484✔
1044

1045
                    if (value || !styleProps[prop.name]) {
5,484✔
1046
                        prop.handler(this.el, value, prop.name, this);
2,080✔
1047
                    }
1048
                }
1049

1050
                this._toPhase('created');
1,766✔
1051
            }
1052

1053
            insertBefore(this.el, parentEl, beforeEl);
1,776✔
1054

1055
            if (!this._contentReady) {
1,776✔
1056
                var htmlDirective = aNode.directives.html;
1,766✔
1057

1058
                if (htmlDirective) {
1,766✔
1059
                    // #[begin] error
1060
                    warnSetHTML(this.el);
2✔
1061
                    // #[end]
1062

1063
                    this.el.innerHTML = evalExpr(htmlDirective.value, this.data, this);
2✔
1064
                }
1065
                else {
1066
                    for (var i = 0, l = aNode.children.length; i < l; i++) {
1,764✔
1067
                        var childANode = aNode.children[i];
2,304✔
1068
                        var child = childANode.Clazz
2,304✔
1069
                            ? new childANode.Clazz(childANode, this, this.data, this)
2,304✔
1070
                            : createNode(childANode, this, this.data, this);
1071
                        this.children.push(child);
2,302✔
1072
                        child.attach(this.el);
2,302✔
1073
                    }
1074
                }
1075

1076
                this._contentReady = 1;
1,764✔
1077
            }
1078

1079
            this._attached();
1,774✔
1080
        }
1081

1082
        this._toPhase('attached');
1,858✔
1083

1084
        // element 都是内部创建的,只有动态创建的 component 才会进入这个分支
1085
        if (this.owner && !this.parent) {
1,858✔
1086
            this.owner.implicitChildren.push(this);
12✔
1087
        }
1088
    }
1089
};
1090

1091
Component.prototype.detach = elementOwnDetach;
2✔
1092
Component.prototype.dispose = elementOwnDispose;
2✔
1093
Component.prototype._attached = elementOwnAttached;
2✔
1094
Component.prototype._leave = function () {
2✔
1095
    if (this.leaveDispose) {
2,294✔
1096
        if (!this.lifeCycle.disposed) {
2,270!
1097
            // #[begin] devtool
1098
            this._toPhase('beforeDetach');
2,270✔
1099
            // #[end]
1100
            this.data.unlisten();
2,270✔
1101
            this.dataChanger = null;
2,270✔
1102
            this._dataChanges = null;
2,270✔
1103

1104
            var len = this.implicitChildren.length;
2,270✔
1105
            while (len--) {
2,270✔
1106
                this.implicitChildren[len].dispose(0, 1);
12✔
1107
            }
1108

1109
            this.implicitChildren = null;
2,270✔
1110

1111
            this.source = null;
2,270✔
1112
            this.sourceSlots = null;
2,270✔
1113
            this.sourceSlotNameProps = null;
2,270✔
1114

1115
            // 这里不用挨个调用 dispose 了,因为 children 释放链会调用的
1116
            this.slotChildren = null;
2,270✔
1117

1118

1119
            if (this._rootNode) {
2,270✔
1120
                // 如果没有parent,说明是一个root component,一定要从dom树中remove
1121
                this._rootNode.dispose(this.disposeNoDetach && this.parent);
92✔
1122
            }
1123
            else {
1124
                var len = this.children.length;
2,178✔
1125
                while (len--) {
2,178✔
1126
                    this.children[len].dispose(1, 1);
2,874✔
1127
                }
1128

1129
                if (this._elFns) {
2,178✔
1130
                    len = this._elFns.length;
52✔
1131
                    while (len--) {
52✔
1132
                        var fn = this._elFns[len];
66✔
1133
                        un(this.el, fn[0], fn[1], fn[2]);
66✔
1134
                    }
1135
                    this._elFns = null;
52✔
1136
                }
1137

1138
                // #[begin] allua
1139
                /* istanbul ignore if */
1140
                if (this._inputTimer) {
2,178!
1141
                    clearInterval(this._inputTimer);
2✔
1142
                    this._inputTimer = null;
2✔
1143
                }
1144
                // #[end]
1145

1146
                // 如果没有parent,说明是一个root component,一定要从dom树中remove
1147
                if (!this.disposeNoDetach || !this.parent) {
2,178✔
1148
                    removeEl(this.el);
1,472✔
1149
                }
1150
            }
1151

1152
            this._toPhase('detached');
2,270✔
1153

1154
            // #[begin] devtool
1155
            this._toPhase('beforeDispose');
2,270✔
1156
            // #[end]
1157

1158
            this._rootNode = null;
2,270✔
1159
            this.el = null;
2,270✔
1160
            this.owner = null;
2,270✔
1161
            this.scope = null;
2,270✔
1162
            this.children = null;
2,270✔
1163

1164
            this._toPhase('disposed');
2,270✔
1165

1166
            if (this._ondisposed) {
2,270✔
1167
                this._ondisposed();
30✔
1168
            }
1169
        }
1170
    }
1171
    else if (this.lifeCycle.attached) {
24✔
1172
        // #[begin] devtool
1173
        this._toPhase('beforeDetach');
22✔
1174
        // #[end]
1175

1176
        if (this._rootNode) {
22✔
1177
            if (this._rootNode.detach) {
10✔
1178
                this._rootNode.detach();
2✔
1179
            }
1180
            else {
1181
                this._rootNode.dispose();
8✔
1182
                this._rootNode = null;
8✔
1183
            }
1184
        }
1185
        else {
1186
            removeEl(this.el);
12✔
1187
        }
1188

1189
        this._toPhase('detached');
22✔
1190
    }
1191
};
1192

1193

1194
exports = module.exports = Component;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc