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

baidu / san / 7136888281

08 Dec 2023 03:09AM UTC coverage: 95.074% (+0.04%) from 95.036%
7136888281

push

github

errorrik
bump 3.14.1

1955 of 2143 branches covered (0.0%)

Branch coverage included in aggregate %.

3507 of 3602 relevant lines covered (97.36%)

688.91 hits per line

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

96.18
/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 svgTags = require('../browser/svg-tags');
26
var insertBefore = require('../browser/insert-before');
27
var un = require('../browser/un');
28
var preprocessComponents = require('./preprocess-components');
29
var createNode = require('./create-node');
30
var preheatEl = require('./preheat-el');
31
var parseComponentTemplate = require('./parse-component-template');
32
var preheatANode = require('./preheat-a-node');
33
var LifeCycle = require('./life-cycle');
34
var mergeANodeSourceAttrs = require('./merge-a-node-source-attrs');
35
var getANodeProp = require('./get-a-node-prop');
36
var isDataChangeByElement = require('./is-data-change-by-element');
37
var getEventListener = require('./get-event-listener');
38
var hydrateElementChildren = require('./hydrate-element-children');
39
var NodeType = require('./node-type');
40
var styleProps = require('./style-props');
41
var nodeSBindInit = require('./node-s-bind-init');
42
var nodeSBindUpdate = require('./node-s-bind-update');
43
var elementOwnAttached = require('./element-own-attached');
44
var elementOwnDetach = require('./element-own-detach');
45
var elementOwnDispose = require('./element-own-dispose');
46
var warnEventListenMethod = require('./warn-event-listen-method');
47
var elementDisposeChildren = require('./element-dispose-children');
48
var createDataTypesChecker = require('../util/create-data-types-checker');
49
var warn = require('../util/warn');
50
var handleError = require('../util/handle-error');
51
var DOMChildrenWalker = require('./dom-children-walker');
52

53

54

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

72

73
    options = options || {};
1,252✔
74
    this.lifeCycle = LifeCycle.start;
1,252✔
75
    this.id = guid++;
1,252✔
76

77
    if (typeof this.construct === 'function') {
1,252✔
78
        this.construct(options);
4✔
79
    }
80

81
    this.children = [];
1,252✔
82
    this.listeners = {};
1,252✔
83
    this.slotChildren = [];
1,252✔
84
    this.implicitChildren = [];
1,252✔
85

86
    var clazz = this.constructor;
1,252✔
87

88
    this.inheritAttrs = !(this.inheritAttrs === false || clazz.inheritAttrs === false);
1,252✔
89
    this.filters = this.filters || clazz.filters || {};
1,252✔
90
    this.computed = this.computed || clazz.computed || {};
1,252✔
91
    this.messages = this.messages || clazz.messages || {};
1,252✔
92
    this.ssr = this.ssr || clazz.ssr;
1,252✔
93

94
    if (options.transition) {
1,252✔
95
        this.transition = options.transition;
1✔
96
    }
97

98
    this.owner = options.owner;
1,252✔
99
    this.scope = options.scope;
1,252✔
100
    this.el = options.el;
1,252✔
101
    var parent = options.parent;
1,252✔
102
    if (parent) {
1,252✔
103
        this.parent = parent;
460✔
104
        this.parentComponent = parent.nodeType === NodeType.CMPT
460✔
105
            ? parent
460✔
106
            : parent && parent.parentComponent;
416✔
107
    }
108
    else if (this.owner) {
792✔
109
        this.parentComponent = this.owner;
6✔
110
        this.scope = this.owner.data;
6✔
111
    }
112

113
    this.sourceSlotNameProps = [];
1,252✔
114
    this.sourceSlots = {
1,252✔
115
        named: {}
116
    };
117

118
    // #[begin] devtool
119
    this._toPhase('beforeCompile');
1,252✔
120
    // #[end]
121

122
    var proto = clazz.prototype;
1,252✔
123

124
    // pre define components class
125
    /* istanbul ignore else  */
126
    if (!proto.hasOwnProperty('_cmptReady')) {
1,252✔
127
        preprocessComponents(clazz);
1,084✔
128
    }
129

130
    // compile
131
    if (!proto.hasOwnProperty('aNode')) {
1,252✔
132
        var aPack = clazz.aPack || proto.hasOwnProperty('aPack') && proto.aPack;
1,080✔
133
        if (aPack) {
1,080✔
134
            proto.aNode = unpackANode(aPack);
2✔
135
            clazz.aPack = proto.aPack = null;
2✔
136
        }
137
        else {
138
            proto.aNode = parseComponentTemplate(clazz);
1,078✔
139
        }
140
    }
141

142
    preheatANode(proto.aNode, this);
1,250✔
143

144
    this.tagName = proto.aNode.tagName;
1,250✔
145
    this.source = typeof options.source === 'string'
1,250✔
146
        ? parseTemplate(options.source).children[0]
1,250✔
147
        : options.source;
148

149
    preheatANode(this.source);
1,250✔
150
    proto.aNode._i++;
1,250✔
151

152

153
    // #[begin] hydrate
154
    // 组件反解,读取注入的组件数据
155
    if (this.el) {
1,250✔
156
        var firstCommentNode = this.el.firstChild;
153✔
157
        if (firstCommentNode && firstCommentNode.nodeType === 3) {
153✔
158
            firstCommentNode = firstCommentNode.nextSibling;
1✔
159
        }
160

161
        if (firstCommentNode && firstCommentNode.nodeType === 8) {
153✔
162
            var stumpMatch = firstCommentNode.data.match(/^\s*s-data:([\s\S]+)?$/);
152✔
163
            if (stumpMatch) {
152!
164
                var stumpText = stumpMatch[1];
152✔
165
                
166
                // fill component data
167
                // #[begin] allua
168
                options.data = (new Function('return '
169
                    + stumpText
170
                        .replace(/^[\s\n]*/, '')
171
                        .replace(
172
                            /"(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.\d+Z"/g,
173
                            function (match, y, mon, d, h, m, s) {
174
                                return 'new Date(' + (+y) + ',' + (+mon) + ',' + (+d)
175
                                    + ',' + (+h) + ',' + (+m) + ',' + (+s) + ')';
176
                            }
177
                        )
178
                ))();
179
                // #[end]
180
                // #[begin] modern
181
                options.data = JSON.parse(
152✔
182
                    stumpText.replace(/\\([^\\\/"bfnrtu])/g, "$1"), 
183
                    function (key, value) {
184
                        if (typeof value === 'string') {
612✔
185
                            var ma = /(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.\d+Z/g.exec(value);
293✔
186
                            if (ma) {
293✔
187
                                return new Date(ma[1], ma[2], ma[3], ma[4], ma[5], ma[6]);
2✔
188
                            }
189
                        }
190
                        return value;
610✔
191
                    }
192
                );
193
                // #[end]
194

195
                if (firstCommentNode.previousSibling) {
152✔
196
                    removeEl(firstCommentNode.previousSibling);
1✔
197
                }
198
                removeEl(firstCommentNode);
152✔
199
            }
200
        }
201
    }
202
    // #[end]
203

204

205
    if (this.source) {
1,250✔
206
        // 组件运行时传入的结构,做slot解析
207
        this._initSourceSlots(1);
464✔
208

209
        for (var i = 0, l = this.source.events.length; i < l; i++) {
464✔
210
            var eventBind = this.source.events[i];
22✔
211
            // 保存当前实例的native事件,下面创建aNode时候做合并
212
            if (eventBind.modifier.native) {
22✔
213
                // native事件数组
214
                this.nativeEvents = this.nativeEvents || [];
6✔
215
                this.nativeEvents.push(eventBind);
6✔
216
            }
217
            else {
218
                // #[begin] error
219
                warnEventListenMethod(eventBind, options.owner);
16✔
220
                // #[end]
221

222
                this.on(
16✔
223
                    eventBind.name,
224
                    getEventListener(eventBind, options.owner, this.scope, 1),
225
                    eventBind
226
                );
227
            }
228
        }
229

230
        this.tagName = this.tagName || this.source.tagName;
464✔
231
        this.binds = this.source._b;
464✔
232
        this.attrs = this.source.attrs;
464✔
233

234
        // init s-bind data
235
        this._srcSbindData = nodeSBindInit(this.source.directives.bind, this.scope, this.owner);
464✔
236
    }
237

238
    this._toPhase('compiled');
1,250✔
239

240

241
    // #[begin] devtool
242
    this._toPhase('beforeInit');
1,250✔
243
    // #[end]
244

245
    // init data
246
    var initData;
1,250✔
247
    try {
1,250✔
248
        initData = typeof this.initData === 'function' && this.initData();
1,250✔
249
    }
250
    catch (e) {
251
        handleError(e, this, 'initData');
1✔
252
    }
253
    initData = extend(initData || {}, options.data || this._srcSbindData);
1,250✔
254

255
    if (this.binds && this.scope) {
1,250✔
256
        for (var i = 0, l = this.binds.length; i < l; i++) {
464✔
257
            var bindInfo = this.binds[i];
489✔
258

259
            var value = evalExpr(bindInfo.expr, this.scope, this.owner);
489✔
260
            if (typeof value !== 'undefined') {
489✔
261
                // See: https://github.com/ecomfe/san/issues/191
262
                initData[bindInfo.name] = value;
434✔
263
            }
264
        }
265

266
        if (this.attrs) {
464✔
267
            initData.$attrs = {};
17✔
268
            for (var i = 0, l = this.attrs.length; i < l; i++) {
17✔
269
                var attr = this.attrs[i];
42✔
270
    
271
                var value = evalExpr(attr.expr, this.scope, this.owner);
42✔
272
                if (typeof value !== 'undefined') {
42!
273
                    // See: https://github.com/ecomfe/san/issues/191
274
                    initData.$attrs[attr.name] = value;
42✔
275
                }
276
            }
277
        }
278
    }
279

280
    this.data = new Data(initData);
1,250✔
281

282
    this.tagName = this.tagName || 'div';
1,250✔
283
    // #[begin] allua
284
    // ie8- 不支持innerHTML输出自定义标签
285
    /* istanbul ignore if */
286
    if (ieOldThan9 && this.tagName.indexOf('-') > 0) {
287
        this.tagName = 'div';
288
    }
289
    // #[end]
290

291

292
    // #[begin] error
293
    // 在初始化 + 数据绑定后,开始数据校验
294
    // NOTE: 只在开发版本中进行属性校验
295
    var dataTypes = this.dataTypes || clazz.dataTypes;
1,250✔
296
    if (dataTypes) {
1,250✔
297
        var dataTypeChecker = createDataTypesChecker(
59✔
298
            dataTypes,
299
            this.name || clazz.name
118✔
300
        );
301
        this.data.setTypeChecker(dataTypeChecker);
59✔
302
        this.data.checkDataTypes();
59✔
303
    }
304
    // #[end]
305

306
    this.computedDeps = {};
1,209✔
307
    for (var expr in this.computed) {
1,209✔
308
        if (this.computed.hasOwnProperty(expr) && !this.computedDeps[expr]) {
23✔
309
            this._calcComputed(expr);
19✔
310
        }
311
    }
312

313
    this._initDataChanger();
1,208✔
314
    this._sbindData = nodeSBindInit(this.aNode.directives.bind, this.data, this);
1,208✔
315
    this._toPhase('inited');
1,208✔
316

317
    // #[begin] hydrate
318
    var hydrateWalker = options.hydrateWalker;
1,208✔
319
    var aNode = this.aNode;
1,208✔
320
    if (hydrateWalker) {
1,208✔
321
        if (this.ssr === 'client-render') {
95✔
322
            this.attach(hydrateWalker.target, hydrateWalker.current);
5✔
323
        }
324
        else {
325
            this._toPhase('created');
90✔
326

327
            if (aNode.Clazz || this.components[aNode.tagName]) {
90✔
328
                if (!aNode.Clazz && this.attrs && this.inheritAttrs) {
6✔
329
                    aNode = mergeANodeSourceAttrs(aNode, this.source);
1✔
330
                }
331
                this._rootNode = createHydrateNode(aNode, this, this.data, this, hydrateWalker);
6✔
332
                this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
6✔
333
            }
334
            else {
335
                var currentNode = hydrateWalker.current;
84✔
336
                if (currentNode && currentNode.nodeType === 1) {
84✔
337
                    this.el = currentNode;
84✔
338
                    hydrateWalker.goNext();
84✔
339
                }
340

341
                hydrateElementChildren(this, this.data, this);
84✔
342
            }
343

344
            this._attached();
90✔
345
            this._toPhase('attached');
90✔
346
        }
347
    }
348
    else if (this.el) {
1,113✔
349
        this._toPhase('created');
153✔
350
        
351
        if (aNode.Clazz || this.components[aNode.tagName]) {
153✔
352
            if (!aNode.Clazz && this.attrs && this.inheritAttrs) {
5!
353
                aNode = mergeANodeSourceAttrs(aNode, this.source);
×
354
            }
355
            hydrateWalker = new DOMChildrenWalker(this.el.parentNode, this.el);
5✔
356
            this._rootNode = createHydrateNode(aNode, this, this.data, this, hydrateWalker);
5✔
357
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
5✔
358
        }
359
        else {
360
            hydrateElementChildren(this, this.data, this);
148✔
361
        }
362

363
        this._attached();
153✔
364
        this._toPhase('attached');
153✔
365
    }
366
    // #[end]
367
}
368

369

370
/**
371
 * 初始化创建组件外部传入的插槽对象
372
 *
373
 * @protected
374
 * @param {boolean} isFirstTime 是否初次对sourceSlots进行计算
375
 */
376
Component.prototype._initSourceSlots = function (isFirstTime) {
1✔
377
    this.sourceSlots.named = {};
477✔
378

379
    // 组件运行时传入的结构,做slot解析
380
    if (this.source && this.scope) {
477✔
381
        var sourceChildren = this.source.children;
477✔
382

383
        for (var i = 0, l = sourceChildren.length; i < l; i++) {
477✔
384
            var child = sourceChildren[i];
257✔
385
            var target;
257✔
386

387
            var slotBind = !child.textExpr && getANodeProp(child, 'slot');
257✔
388
            if (slotBind) {
257✔
389
                isFirstTime && this.sourceSlotNameProps.push(slotBind);
93✔
390

391
                var slotName = evalExpr(slotBind.expr, this.scope, this.owner);
93✔
392
                target = this.sourceSlots.named[slotName];
93✔
393
                if (!target) {
93✔
394
                    target = this.sourceSlots.named[slotName] = [];
59✔
395
                }
396
                target.push(child);
93✔
397
            }
398
            else if (isFirstTime) {
164!
399
                target = this.sourceSlots.noname;
164✔
400
                if (!target) {
164✔
401
                    target = this.sourceSlots.noname = [];
139✔
402
                }
403
                target.push(child);
164✔
404
            }
405
        }
406
    }
407
};
408

409
/**
410
 * 类型标识
411
 *
412
 * @type {string}
413
 */
414
Component.prototype.nodeType = NodeType.CMPT;
1✔
415

416
/**
417
 * 在下一个更新周期运行函数
418
 *
419
 * @param {Function} fn 要运行的函数
420
 */
421
Component.prototype.nextTick = nextTick;
1✔
422

423
Component.prototype._ctx = (new Date()).getTime().toString(16);
1✔
424

425
/* eslint-disable operator-linebreak */
426
/**
427
 * 使节点到达相应的生命周期
428
 *
429
 * @protected
430
 * @param {string} name 生命周期名称
431
 */
432
Component.prototype._toPhase = function (name) {
1✔
433
    if (!this.lifeCycle[name]) {
14,860✔
434
        this.lifeCycle = LifeCycle[name] || this.lifeCycle;
14,854✔
435
        if (typeof this[name] === 'function') {
14,854✔
436
            try {
142✔
437
                this[name]();
142✔
438
            }
439
            catch (e) {
440
                handleError(e, this, 'hook:' + name);
2✔
441
            }
442
        }
443

444
        this._afterLife = this.lifeCycle;
14,854✔
445

446
        // 通知devtool
447
        // #[begin] devtool
448
        emitDevtool('comp-' + name, this);
14,854✔
449
        // #[end]
450
    }
451
};
452
/* eslint-enable operator-linebreak */
453

454

455
/**
456
 * 添加事件监听器
457
 *
458
 * @param {string} name 事件名
459
 * @param {Function} listener 监听器
460
 * @param {string?} declaration 声明式
461
 */
462
Component.prototype.on = function (name, listener, declaration) {
1✔
463
    if (typeof listener === 'function') {
19!
464
        if (!this.listeners[name]) {
19✔
465
            this.listeners[name] = [];
18✔
466
        }
467
        this.listeners[name].push({fn: listener, declaration: declaration});
19✔
468
    }
469
};
470

471
/**
472
 * 移除事件监听器
473
 *
474
 * @param {string} name 事件名
475
 * @param {Function=} listener 监听器
476
 */
477
Component.prototype.un = function (name, listener) {
1✔
478
    var nameListeners = this.listeners[name];
2✔
479
    var len = nameListeners && nameListeners.length;
2✔
480

481
    while (len--) {
2✔
482
        if (!listener || listener === nameListeners[len].fn) {
2✔
483
            nameListeners.splice(len, 1);
2✔
484
        }
485
    }
486
};
487

488

489
/**
490
 * 派发事件
491
 *
492
 * @param {string} name 事件名
493
 * @param {Object} event 事件对象
494
 */
495
Component.prototype.fire = function (name, event) {
1✔
496
    var me = this;
25✔
497
    // #[begin] devtool
498
    emitDevtool('comp-event', {
25✔
499
        name: name,
500
        event: event,
501
        target: this
502
    });
503
    // #[end]
504

505
    each(this.listeners[name], function (listener) {
25✔
506
        try {
22✔
507
            listener.fn.call(me, event);
22✔
508
        }
509
        catch (e) {
510
            handleError(e, me, 'event:' + name);
1✔
511
        }
512
    });
513
};
514

515
/**
516
 * 计算 computed 属性的值
517
 *
518
 * @private
519
 * @param {string} computedExpr computed表达式串
520
 */
521
Component.prototype._calcComputed = function (computedExpr) {
1✔
522
    var computedDeps = this.computedDeps[computedExpr];
57✔
523
    if (!computedDeps) {
57✔
524
        computedDeps = this.computedDeps[computedExpr] = {};
23✔
525
    }
526

527
    var me = this;
57✔
528
    try {
57✔
529
        var result = this.computed[computedExpr].call({
57✔
530
            data: {
531
                get: function (expr) {
532
                    // #[begin] error
533
                    if (!expr) {
76✔
534
                        throw new Error('[SAN ERROR] call get method in computed need argument');
1✔
535
                    }
536
                    // #[end]
537

538
                    if (!computedDeps[expr]) {
75✔
539
                        computedDeps[expr] = 1;
31✔
540

541
                        if (me.computed[expr] && !me.computedDeps[expr]) {
31✔
542
                            me._calcComputed(expr);
4✔
543
                        }
544

545
                        me.watch(expr, function () {
31✔
546
                            me._calcComputed(computedExpr);
34✔
547
                        });
548
                    }
549

550
                    return me.data.get(expr);
75✔
551
                }
552
            }
553
        });
554
        this.data.set(computedExpr, result);
55✔
555
    }
556
    catch (e) {
557
        handleError(e, this, 'computed:' + computedExpr);
2✔
558
    }
559
};
560

561
/**
562
 * 派发消息
563
 * 组件可以派发消息,消息将沿着组件树向上传递,直到遇上第一个处理消息的组件
564
 *
565
 * @param {string} name 消息名称
566
 * @param {*?} value 消息值
567
 */
568
Component.prototype.dispatch = function (name, value) {
1✔
569
    var parentComponent = this.parentComponent;
27✔
570

571
    while (parentComponent) {
27✔
572
        var handler = parentComponent.messages[name] || parentComponent.messages['*'];
28✔
573
        if (typeof handler === 'function') {
28✔
574
            // #[begin] devtool
575
            emitDevtool('comp-message', {
27✔
576
                target: this,
577
                value: value,
578
                name: name,
579
                receiver: parentComponent
580
            });
581
            // #[end]
582

583
            try {
27✔
584
                handler.call(
27✔
585
                    parentComponent,
586
                    {target: this, value: value, name: name}
587
                );
588
            }
589
            catch (e) {
590
                handleError(e, parentComponent, 'message:' + (name || '*'));
1!
591
            }
592
            return;
27✔
593
        }
594

595
        parentComponent = parentComponent.parentComponent;
1✔
596
    }
597

598
    // #[begin] devtool
599
    emitDevtool('comp-message', {target: this, value: value, name: name});
×
600
    // #[end]
601
};
602

603
/**
604
 * 获取组件内部的 slot
605
 *
606
 * @param {string=} name slot名称,空为default slot
607
 * @return {Array}
608
 */
609
Component.prototype.slot = function (name) {
1✔
610
    var result = [];
24✔
611
    var me = this;
24✔
612

613
    function childrenTraversal(children) {
1✔
614
        each(children, function (child) {
70✔
615
            if (child.nodeType === NodeType.SLOT && child.owner === me) {
99✔
616
                if (child.isNamed && child.name === name
53✔
617
                    || !child.isNamed && !name
618
                ) {
619
                    result.push(child);
26✔
620
                }
621
            }
622
            else {
623
                childrenTraversal(child.children);
46✔
624
            }
625
        });
626
    }
627

628
    childrenTraversal(this.children);
24✔
629
    return result;
24✔
630
};
631

632
/**
633
 * 获取带有 san-ref 指令的子组件引用
634
 *
635
 * @param {string} name 子组件的引用名
636
 * @return {Component}
637
 */
638
Component.prototype.ref = function (name) {
1✔
639
    var refTarget;
77✔
640
    var owner = this;
77✔
641

642
    function childrenTraversal(children) {
1✔
643
        if (children) {
167✔
644
            for (var i = 0, l = children.length; i < l; i++) {
132✔
645
                elementTraversal(children[i]);
146✔
646
                if (refTarget) {
146✔
647
                    return;
99✔
648
                }
649
            }
650
        }
651
    }
652

653
    function elementTraversal(element) {
1✔
654
        var nodeType = element.nodeType;
148✔
655
        if (nodeType === NodeType.TEXT) {
148✔
656
            return;
22✔
657
        }
658

659
        if (element.owner === owner) {
126✔
660
            var ref;
117✔
661
            switch (element.nodeType) {
117✔
662
                case NodeType.ELEM:
99✔
663
                    ref = element.aNode.directives.ref;
23✔
664
                    if (ref && evalExpr(ref.value, element.scope, owner) === name) {
23✔
665
                        refTarget = element.el;
6✔
666
                    }
667
                    break;
23✔
668

669
                case NodeType.CMPT:
670
                    ref = element.source.directives.ref;
76✔
671
                    if (ref && evalExpr(ref.value, element.scope, owner) === name) {
76✔
672
                        refTarget = element;
68✔
673
                    }
674
            }
675

676
            if (refTarget) {
117✔
677
                return;
74✔
678
            }
679

680
            childrenTraversal(element.slotChildren);
43✔
681
        }
682

683
        if (refTarget) {
52✔
684
            return;
3✔
685
        }
686

687
        childrenTraversal(element.children);
49✔
688
    }
689

690
    this._rootNode ? elementTraversal(this._rootNode) : childrenTraversal(this.children);
77✔
691

692
    return refTarget;
77✔
693
};
694

695

696
/**
697
 * 视图更新函数
698
 *
699
 * @param {Array?} changes 数据变化信息
700
 */
701
Component.prototype._update = function (changes) {
1✔
702
    if (this.lifeCycle.disposed) {
1,171✔
703
        return;
150✔
704
    }
705

706
    var me = this;
1,021✔
707

708

709
    var needReloadForSlot = false;
1,021✔
710
    this._notifyNeedReload = function () {
1,021✔
711
        needReloadForSlot = true;
36✔
712
    };
713

714
    if (changes) {
1,021✔
715
        if (this.source) {
274✔
716
            this._srcSbindData = nodeSBindUpdate(
272✔
717
                this.source.directives.bind,
718
                this._srcSbindData,
719
                this.scope,
720
                this.owner,
721
                changes,
722
                function (name, value) {
723
                    if (name in me.source._pi) {
14✔
724
                        return;
2✔
725
                    }
726

727
                    me.data.set(name, value, {
12✔
728
                        target: {
729
                            node: me.owner
730
                        }
731
                    });
732
                }
733
            );
734
        }
735

736
        each(changes, function (change) {
274✔
737
            var changeExpr = change.expr;
411✔
738

739
            each(me.binds, function (bindItem) {
411✔
740
                var relation;
602✔
741
                var setExpr = bindItem.name;
602✔
742
                var updateExpr = bindItem.expr;
602✔
743

744
                if (!isDataChangeByElement(change, me, setExpr)
602✔
745
                    && (relation = changeExprCompare(changeExpr, updateExpr, me.scope))
746
                ) {
747
                    if (relation > 2) {
217✔
748
                        setExpr = {
41✔
749
                            type: ExprType.ACCESSOR,
750
                            paths: [
751
                                {
752
                                    type: ExprType.STRING,
753
                                    value: setExpr
754
                                }
755
                            ].concat(changeExpr.paths.slice(updateExpr.paths.length))
756
                        };
757
                        updateExpr = changeExpr;
41✔
758
                    }
759

760
                    if (relation >= 2 && change.type === DataChangeType.SPLICE) {
217✔
761
                        me.data.splice(setExpr, [change.index, change.deleteCount].concat(change.insertions), {
18✔
762
                            target: {
763
                                node: me.owner
764
                            }
765
                        });
766
                    }
767
                    else {
768
                        me.data.set(setExpr, evalExpr(updateExpr, me.scope, me.owner), {
199✔
769
                            target: {
770
                                node: me.owner
771
                            }
772
                        });
773
                    }
774
                }
775
            });
776

777
            each(me.attrs, function (bindItem) {
411✔
778
                if (changeExprCompare(changeExpr, bindItem.expr, me.scope)) {
54✔
779
                    me.data.set(
34✔
780
                        bindItem._data,
781
                        evalExpr(bindItem.expr, me.scope, me.owner)
782
                    );
783
                }
784
            });
785

786
            each(me.sourceSlotNameProps, function (bindItem) {
411✔
787
                needReloadForSlot = needReloadForSlot || changeExprCompare(changeExpr, bindItem.expr, me.scope);
142✔
788
                return !needReloadForSlot;
142✔
789
            });
790
        });
791

792
        if (needReloadForSlot) {
274✔
793
            this._initSourceSlots();
4✔
794
            this._repaintChildren();
4✔
795
        }
796
        else {
797
            var slotChildrenLen = this.slotChildren.length;
270✔
798
            while (slotChildrenLen--) {
270✔
799
                var slotChild = this.slotChildren[slotChildrenLen];
224✔
800

801
                if (slotChild.lifeCycle.disposed) {
224✔
802
                    this.slotChildren.splice(slotChildrenLen, 1);
6✔
803
                }
804
                else if (slotChild.isInserted) {
218✔
805
                    slotChild._update(changes, 1);
161✔
806
                }
807
            }
808
        }
809
    }
810

811
    var dataChanges = this._dataChanges;
1,021✔
812
    if (dataChanges) {
1,021✔
813
        // #[begin] devtool
814
        this._toPhase('beforeUpdate');
870✔
815
        // #[end]
816

817
        this._dataChanges = null;
870✔
818

819
        this._sbindData = nodeSBindUpdate(
870✔
820
            this.aNode.directives.bind,
821
            this._sbindData,
822
            this.data,
823
            this,
824
            dataChanges,
825
            function (name, value) {
826
                if (me._rootNode || (name in me.aNode._pi)) {
18✔
827
                    return;
3✔
828
                }
829

830
                getPropHandler(me.tagName, name)(me.el, value, name, me);
15✔
831
            }
832
        );
833

834
        var htmlDirective = this.aNode.directives.html;
870✔
835

836
        if (this._rootNode) {
870✔
837
            this._rootNode._update(dataChanges);
52✔
838
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
52✔
839
        }
840
        else if (htmlDirective) {
818✔
841
            var len = dataChanges.length;
1✔
842
            while (len--) {
1✔
843
                if (changeExprCompare(dataChanges[len].expr, htmlDirective.value, this.data)) {
1!
844
                    // #[begin] error
845
                    warnSetHTML(this.el);
1✔
846
                    // #[end]
847

848
                    this.el.innerHTML = evalExpr(htmlDirective.value, this.data, this);
1✔
849
                    break;
1✔
850
                }
851
            }
852
        }
853
        else {
854
            var dynamicProps = this.aNode._dp;
817✔
855
            for (var i = 0; i < dynamicProps.length; i++) {
817✔
856
                var prop = dynamicProps[i];
2,482✔
857

858
                for (var j = 0; j < dataChanges.length; j++) {
2,482✔
859
                    var change = dataChanges[j];
3,293✔
860
                    if (changeExprCompare(change.expr, prop.expr, this.data)
3,293!
861
                        || prop.hintExpr && changeExprCompare(change.expr, prop.hintExpr, this.data)
862
                    ) {
863
                        prop.handler(this.el, evalExpr(prop.expr, this.data, this), prop.name, this);
65✔
864
                        break;
65✔
865
                    }
866
                }
867
            }
868

869
            if (this.attrs && this.inheritAttrs) {
817✔
870
                var attrsData = this.data.get('$attrs');
10✔
871

872
                for (var i = 0; i < this.attrs.length; i++) {
10✔
873
                    var attr = this.attrs[i];
26✔
874

875
                    if (this.aNode._pi[attr.name] == null) {
26✔
876
                        for (var j = 0; j < dataChanges.length; j++) {
24✔
877
                            var changePaths = dataChanges[j].expr.paths;
34✔
878

879
                            if (changePaths[0].value === '$attrs' && changePaths[1].value === attr.name) {
34✔
880
                                getPropHandler(this.tagName, attr.name)(this.el, attrsData[attr.name], attr.name, this);
16✔
881
                                break;
16✔
882
                            }
883
                        }
884
                    }
885
                }
886
            }
887

888
            for (var i = 0; i < this.children.length; i++) {
817✔
889
                this.children[i]._update(dataChanges);
1,162✔
890
            }
891
        }
892

893
        if (needReloadForSlot) {
870✔
894
            this._initSourceSlots();
9✔
895
            this._repaintChildren();
9✔
896
        }
897

898
        for (var i = 0; i < this.implicitChildren.length; i++) {
870✔
899
            this.implicitChildren[i]._update(dataChanges);
4✔
900
        }
901

902
        if (typeof this.updated === 'function') {
870✔
903
            this.updated();
5✔
904
        }
905

906
        if (this.owner && this._updateBindxOwner(dataChanges)) {
870✔
907
            this.owner._update();
25✔
908
        }
909
    }
910

911
    this._notifyNeedReload = null;
1,021✔
912
};
913

914
Component.prototype._updateBindxOwner = function (dataChanges) {
1✔
915
    var me = this;
229✔
916
    var xbindUped;
229✔
917

918
    each(dataChanges, function (change) {
229✔
919
        each(me.binds, function (bindItem) {
317✔
920
            var changeExpr = change.expr;
515✔
921
            if (bindItem.x
515✔
922
                && !isDataChangeByElement(change, me.owner)
923
                && changeExprCompare(changeExpr, parseExpr(bindItem.name), me.data)
924
            ) {
925
                var updateScopeExpr = bindItem.expr;
32✔
926
                if (changeExpr.paths.length > 1) {
32✔
927
                    updateScopeExpr = {
13✔
928
                        type: ExprType.ACCESSOR,
929
                        paths: bindItem.expr.paths.concat(changeExpr.paths.slice(1))
930
                    };
931
                }
932

933
                xbindUped = 1;
32✔
934
                me.scope.set(
32✔
935
                    updateScopeExpr,
936
                    evalExpr(changeExpr, me.data, me),
937
                    {
938
                        target: {
939
                            node: me,
940
                            prop: bindItem.name
941
                        }
942
                    }
943
                );
944
            }
945
        });
946
    });
947

948
    return xbindUped;
229✔
949
};
950

951
/**
952
 * 重新绘制组件的内容
953
 * 当 dynamic slot name 发生变更或 slot 匹配发生变化时,重新绘制
954
 * 在组件级别重绘有点粗暴,但是能保证视图结果正确性
955
 */
956
Component.prototype._repaintChildren = function () {
1✔
957
    // TODO: repaint once?
958

959
    if (this._rootNode) {
13!
960
        var parentEl = this._rootNode.el.parentNode;
×
961
        var beforeEl = this._rootNode.el.nextSibling;
×
962
        this._rootNode.dispose(0, 1);
×
963
        this.slotChildren = [];
×
964

965
        var aNode = this.aNode;
×
966
        if (!aNode.Clazz && this.attrs && this.inheritAttrs) {
×
967
            aNode = mergeANodeSourceAttrs(aNode, this.source);
×
968
        }
969

970
        this._rootNode = createNode(aNode, this, this.data, this);
×
971
        this._rootNode.attach(parentEl, beforeEl);
×
972
        this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
×
973
    }
974
    else {
975
        elementDisposeChildren(this.children, 0, 1);
13✔
976
        this.children = [];
13✔
977
        this.slotChildren = [];
13✔
978

979
        for (var i = 0, l = this.aNode.children.length; i < l; i++) {
13✔
980
            var child = createNode(this.aNode.children[i], this, this.data, this);
52✔
981
            this.children.push(child);
52✔
982
            child.attach(this.el);
52✔
983
        }
984
    }
985
};
986

987

988
/**
989
 * 初始化组件内部监听数据变化
990
 *
991
 * @private
992
 * @param {Object} change 数据变化信息
993
 */
994
Component.prototype._initDataChanger = function () {
1✔
995
    var me = this;
1,208✔
996

997
    this._dataChanger = function (change) {
1,208✔
998
        if (me._afterLife.created) {
1,390✔
999
            if (!me._dataChanges) {
1,177✔
1000
                nextTick(me._update, me);
872✔
1001
                me._dataChanges = [];
872✔
1002
            }
1003

1004
            me._dataChanges.push(change);
1,177✔
1005
        }
1006
        else if (me.lifeCycle.inited && me.owner) {
213✔
1007
            me._updateBindxOwner([change]);
1✔
1008
        }
1009
    };
1010

1011
    this.data.listen(this._dataChanger);
1,208✔
1012
};
1013

1014

1015
/**
1016
 * 监听组件的数据变化
1017
 *
1018
 * @param {string} dataName 变化的数据项
1019
 * @param {Function} listener 监听函数
1020
 */
1021
Component.prototype.watch = function (dataName, listener) {
1✔
1022
    var dataExpr = parseExpr(dataName);
40✔
1023
    var value = evalExpr(dataExpr, this.data, this);
40✔
1024
    var me = this;
40✔
1025

1026
    this.data.listen(function (change) {
40✔
1027
        if (changeExprCompare(change.expr, dataExpr, me.data)) {
153✔
1028
            var newValue = evalExpr(dataExpr, me.data, me);
47✔
1029

1030
            if (newValue !== value) {
47!
1031
                var oldValue = value;
47✔
1032
                value = newValue;
47✔
1033

1034
                try {
47✔
1035
                    listener.call(
47✔
1036
                        me,
1037
                        newValue,
1038
                        {
1039
                            oldValue: oldValue,
1040
                            newValue: newValue,
1041
                            change: change
1042
                        }
1043
                    );
1044
                }
1045
                catch (e) {
1046
                    handleError(e, me, 'watch:' + dataName);
1✔
1047
                }
1048
            }
1049
        }
1050
    });
1051
};
1052

1053
Component.prototype._getElAsRootNode = function () {
1✔
1054
    return this.el;
34✔
1055
};
1056

1057
/**
1058
 * 将组件attach到页面
1059
 *
1060
 * @param {HTMLElement} parentEl 要添加到的父元素
1061
 * @param {HTMLElement=} beforeEl 要添加到哪个元素之前
1062
 */
1063
Component.prototype.attach = function (parentEl, beforeEl) {
1✔
1064
    if (!this.lifeCycle.attached) {
953✔
1065
        // #[begin] devtool
1066
        this._toPhase('beforeAttach');
952✔
1067
        // #[end]
1068

1069
        var aNode = this.aNode;
952✔
1070

1071
        if (aNode.Clazz || this.components[aNode.tagName]) {
952✔
1072
            // #[begin] devtool
1073
            this._toPhase('beforeCreate');
43✔
1074
            // #[end]
1075

1076
            // aNode.Clazz 在 preheat 阶段为 if/else/for/fragment 等特殊标签或指令预热生成
1077
            // 这里不能用 this.components[aNode.tagName] 判断,因为可能特殊指令和组件在同一个节点上并存
1078
            if (!aNode.Clazz && this.attrs && this.inheritAttrs) {
43✔
1079
                aNode = mergeANodeSourceAttrs(aNode, this.source);
1✔
1080
            }
1081

1082
            this._rootNode = this._rootNode || createNode(aNode, this, this.data, this);
43✔
1083
            this._rootNode.attach(parentEl, beforeEl);
43✔
1084
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
43✔
1085
            this._toPhase('created');
43✔
1086
        }
1087
        else {
1088
            if (!this.el) {
909✔
1089
                // #[begin] devtool
1090
                this._toPhase('beforeCreate');
904✔
1091
                // #[end]
1092

1093
                var props;
904✔
1094
                var doc = parentEl.ownerDocument;
904✔
1095
                if (aNode._ce && aNode._i > 2) {
904✔
1096
                    props = aNode._dp;
41✔
1097
                    this.el = (aNode._el || preheatEl(aNode, doc)).cloneNode(false);
41✔
1098
                }
1099
                else {
1100
                    props = aNode.props;
863✔
1101
                    this.el = svgTags[this.tagName] && doc.createElementNS
863✔
1102
                        ? doc.createElementNS('http://www.w3.org/2000/svg', this.tagName)
863✔
1103
                        : doc.createElement(this.tagName);
1104
                }
1105

1106
                if (this._sbindData) {
904✔
1107
                    for (var key in this._sbindData) {
1✔
1108
                        if (this._sbindData.hasOwnProperty(key)) {
6!
1109
                            getPropHandler(this.tagName, key)(
6✔
1110
                                this.el,
1111
                                this._sbindData[key],
1112
                                key,
1113
                                this
1114
                            );
1115
                        }
1116
                    }
1117
                }
1118

1119
                for (var i = 0, l = props.length; i < l; i++) {
904✔
1120
                    var prop = props[i];
2,797✔
1121
                    var value = evalExpr(prop.expr, this.data, this);
2,797✔
1122

1123
                    if (value || !styleProps[prop.name]) {
2,797✔
1124
                        prop.handler(this.el, value, prop.name, this);
1,060✔
1125
                    }
1126
                }
1127

1128
                if (this.attrs && this.inheritAttrs) {
904✔
1129
                    var attrsData = this.data.get('$attrs');
5✔
1130
                    for (var i = 0; i < this.attrs.length; i++) {
5✔
1131
                        var attr = this.attrs[i];
13✔
1132
                        if (this.aNode._pi[attr.name] == null) {
13✔
1133
                            getPropHandler(this.tagName, attr.name)(this.el, attrsData[attr.name], attr.name, this);
12✔
1134
                        }
1135
                    }
1136
                }
1137

1138
                this._toPhase('created');
904✔
1139
            }
1140

1141
            insertBefore(this.el, parentEl, beforeEl);
909✔
1142

1143
            if (!this._contentReady) {
909✔
1144
                var htmlDirective = aNode.directives.html;
904✔
1145

1146
                if (htmlDirective) {
904✔
1147
                    // #[begin] error
1148
                    warnSetHTML(this.el);
1✔
1149
                    // #[end]
1150

1151
                    this.el.innerHTML = evalExpr(htmlDirective.value, this.data, this);
1✔
1152
                }
1153
                else {
1154
                    for (var i = 0, l = aNode.children.length; i < l; i++) {
903✔
1155
                        var childANode = aNode.children[i];
1,173✔
1156
                        var child = childANode.Clazz
1,173✔
1157
                            ? new childANode.Clazz(childANode, this, this.data, this)
1,173✔
1158
                            : createNode(childANode, this, this.data, this);
1159
                        this.children.push(child);
1,172✔
1160
                        child.attach(this.el);
1,172✔
1161
                    }
1162
                }
1163

1164
                this._contentReady = 1;
903✔
1165
            }
1166

1167
            this._attached();
908✔
1168
        }
1169

1170
        this._toPhase('attached');
951✔
1171

1172
        // element 都是内部创建的,只有动态创建的 component 才会进入这个分支
1173
        if (this.owner && !this.parent) {
951✔
1174
            this.owner.implicitChildren.push(this);
6✔
1175
        }
1176
    }
1177
};
1178

1179
Component.prototype.detach = elementOwnDetach;
1✔
1180
Component.prototype.dispose = elementOwnDispose;
1✔
1181
Component.prototype._attached = elementOwnAttached;
1✔
1182
Component.prototype._leave = function () {
1✔
1183
    if (this.leaveDispose) {
1,190✔
1184
        if (!this.lifeCycle.disposed) {
1,178!
1185
            // #[begin] devtool
1186
            this._toPhase('beforeDetach');
1,178✔
1187
            // #[end]
1188
            this.data.unlisten();
1,178✔
1189
            this.dataChanger = null;
1,178✔
1190
            this._dataChanges = null;
1,178✔
1191

1192
            var len = this.implicitChildren.length;
1,178✔
1193
            while (len--) {
1,178✔
1194
                this.implicitChildren[len].dispose(0, 1);
6✔
1195
            }
1196

1197
            this.implicitChildren = null;
1,178✔
1198

1199
            this.source = null;
1,178✔
1200
            this.sourceSlots = null;
1,178✔
1201
            this.sourceSlotNameProps = null;
1,178✔
1202

1203
            // 这里不用挨个调用 dispose 了,因为 children 释放链会调用的
1204
            this.slotChildren = null;
1,178✔
1205

1206

1207
            if (this._rootNode) {
1,178✔
1208
                // 如果没有parent,说明是一个root component,一定要从dom树中remove
1209
                this._rootNode.dispose(this.disposeNoDetach && this.parent);
48✔
1210
            }
1211
            else {
1212
                var len = this.children.length;
1,130✔
1213
                while (len--) {
1,130✔
1214
                    this.children[len].dispose(1, 1);
1,478✔
1215
                }
1216

1217
                if (this._elFns) {
1,130✔
1218
                    len = this._elFns.length;
26✔
1219
                    while (len--) {
26✔
1220
                        var fn = this._elFns[len];
33✔
1221
                        un(this.el, fn[0], fn[1], fn[2]);
33✔
1222
                    }
1223
                    this._elFns = null;
26✔
1224
                }
1225

1226
                // #[begin] allua
1227
                /* istanbul ignore if */
1228
                if (this._inputTimer) {
1229
                    clearInterval(this._inputTimer);
1230
                    this._inputTimer = null;
1231
                }
1232
                // #[end]
1233

1234
                // 如果没有parent,说明是一个root component,一定要从dom树中remove
1235
                if (!this.disposeNoDetach || !this.parent) {
1,130✔
1236
                    removeEl(this.el);
762✔
1237
                }
1238
            }
1239

1240
            this._toPhase('detached');
1,178✔
1241

1242
            // #[begin] devtool
1243
            this._toPhase('beforeDispose');
1,178✔
1244
            // #[end]
1245

1246
            this._rootNode = null;
1,178✔
1247
            this.el = null;
1,178✔
1248
            this.owner = null;
1,178✔
1249
            this.scope = null;
1,178✔
1250
            this.children = null;
1,178✔
1251

1252
            this._toPhase('disposed');
1,178✔
1253

1254
            if (this._ondisposed) {
1,178✔
1255
                this._ondisposed();
15✔
1256
            }
1257
        }
1258
    }
1259
    else if (this.lifeCycle.attached) {
12✔
1260
        // #[begin] devtool
1261
        this._toPhase('beforeDetach');
11✔
1262
        // #[end]
1263

1264
        if (this._rootNode) {
11✔
1265
            if (this._rootNode.detach) {
5✔
1266
                this._rootNode.detach();
1✔
1267
            }
1268
            else {
1269
                this._rootNode.dispose();
4✔
1270
                this._rootNode = null;
4✔
1271
            }
1272
        }
1273
        else {
1274
            removeEl(this.el);
6✔
1275
        }
1276

1277
        this._toPhase('detached');
11✔
1278
    }
1279
};
1280

1281

1282
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