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

baidu / san / 16292369092

15 Jul 2025 11:45AM UTC coverage: 95.1%. Remained the same
16292369092

push

github

errorrik
bump 3.15.2

1997 of 2190 branches covered (91.19%)

Branch coverage included in aggregate %.

3592 of 3687 relevant lines covered (97.42%)

711.04 hits per line

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

96.17
/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

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

55

56
var proxySupported = typeof Proxy !== 'undefined';
1✔
57

58

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

76

77
    options = options || {};
1,304✔
78
    this.lifeCycle = LifeCycle.start;
1,304✔
79
    this.id = guid++;
1,304✔
80

81
    if (typeof this.construct === 'function') {
1,304✔
82
        this.construct(options);
4✔
83
    }
84

85
    this.children = [];
1,304✔
86
    this.listeners = {};
1,304✔
87
    this.slotChildren = [];
1,304✔
88
    this.implicitChildren = [];
1,304✔
89

90
    var clazz = this.constructor;
1,304✔
91

92
    this.inheritAttrs = !(this.inheritAttrs === false || clazz.inheritAttrs === false);
1,304✔
93
    this.filters = this.filters || clazz.filters || {};
1,304✔
94
    this.computed = this.computed || clazz.computed || {};
1,304✔
95
    this.messages = this.messages || clazz.messages || {};
1,304✔
96
    this.ssr = this.ssr || clazz.ssr;
1,304✔
97

98
    if (options.transition) {
1,304✔
99
        this.transition = options.transition;
1✔
100
    }
101

102
    this.owner = options.owner;
1,304✔
103
    this.scope = options.scope;
1,304✔
104
    this.el = options.el;
1,304✔
105
    var parent = options.parent;
1,304✔
106
    if (parent) {
1,304✔
107
        this.parent = parent;
471✔
108
        this.parentComponent = parent.nodeType === NodeType.CMPT
471✔
109
            ? parent
471✔
110
            : parent && parent.parentComponent;
426✔
111
    }
112
    else if (this.owner) {
833✔
113
        this.parentComponent = this.owner;
6✔
114
        this.scope = this.owner.data;
6✔
115
    }
116

117
    this.sourceSlotNameProps = [];
1,304✔
118
    this.sourceSlots = {
1,304✔
119
        named: {}
120
    };
121

122
    // #[begin] devtool
123
    this._toPhase('beforeCompile');
1,304✔
124
    // #[end]
125

126
    var proto = clazz.prototype;
1,304✔
127

128
    // pre define components class
129
    /* istanbul ignore else  */
130
    if (!proto.hasOwnProperty('_cmptReady')) {
1,304✔
131
        preprocessComponents(clazz);
1,135✔
132
    }
133

134
    // compile
135
    if (!proto.hasOwnProperty('aNode')) {
1,304✔
136
        var aPack = clazz.aPack || proto.hasOwnProperty('aPack') && proto.aPack;
1,131✔
137
        if (aPack) {
1,131✔
138
            proto.aNode = unpackANode(aPack);
2✔
139
            clazz.aPack = proto.aPack = null;
2✔
140
        }
141
        else {
142
            proto.aNode = parseComponentTemplate(clazz);
1,129✔
143
        }
144
    }
145

146
    preheatANode(proto.aNode, this);
1,302✔
147

148
    this.tagName = proto.aNode.tagName;
1,302✔
149
    this.source = typeof options.source === 'string'
1,302✔
150
        ? parseTemplate(options.source).children[0]
1,302✔
151
        : options.source;
152

153
    preheatANode(this.source);
1,302✔
154
    proto.aNode._i++;
1,302✔
155

156

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

165
        if (firstCommentNode && firstCommentNode.nodeType === 8) {
156✔
166
            var stumpMatch = firstCommentNode.data.match(/^\s*s-data:([\s\S]+)?$/);
155✔
167
            if (stumpMatch) {
155!
168
                var stumpText = stumpMatch[1];
155✔
169
                
170
                // fill component data
171
                // #[begin] allua
172
                options.data = (new Function('return '
173
                    + stumpText
174
                        .replace(/^[\s\n]*/, '')
175
                        .replace(
176
                            /"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z"/g,
177
                            function (match) {
178
                                return 'new Date(Date.parse(' + match + '))';
179
                            }
180
                        )
181
                ))();
182
                // #[end]
183
                // #[begin] modern
184
                options.data = JSON.parse(
155✔
185
                    stumpText.replace(/\\([^\\\/"bfnrtu])/g, "$1"), 
186
                    function (key, value) {
187
                        if (typeof value === 'string') {
621✔
188
                            var ma = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z/g.exec(value);
296✔
189
                            if (ma) {
296✔
190
                                return new Date(Date.parse(ma[0]));
2✔
191
                            }
192
                        }
193
                        return value;
619✔
194
                    }
195
                );
196
                // #[end]
197

198
                if (firstCommentNode.previousSibling) {
155✔
199
                    removeEl(firstCommentNode.previousSibling);
1✔
200
                }
201
                removeEl(firstCommentNode);
155✔
202
            }
203
        }
204
    }
205
    // #[end]
206

207

208
    if (this.source) {
1,302✔
209
        // 组件运行时传入的结构,做slot解析
210
        this._initSourceSlots(1);
475✔
211

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

225
                this.on(
16✔
226
                    eventBind.name,
227
                    getEventListener(eventBind, options.owner, this.scope, 1),
228
                    eventBind
229
                );
230
            }
231
        }
232

233
        this.tagName = this.tagName || this.source.tagName;
475✔
234
        this.binds = this.source._b;
475✔
235
        this.attrs = this.source.attrs;
475✔
236

237
        // init s-bind data
238
        this._srcSbindData = nodeSBindInit(this.source.directives.bind, this.scope, this.owner);
475✔
239
    }
240

241
    this.tagName = this.tagName || 'div';
1,302✔
242
    // #[begin] allua
243
    // ie8- 不支持innerHTML输出自定义标签
244
    /* istanbul ignore if */
245
    if (ieOldThan9 && this.tagName.indexOf('-') > 0) {
246
        this.tagName = 'div';
247
    }
248
    // #[end]
249

250
    this._toPhase('compiled');
1,302✔
251

252

253
    // #[begin] devtool
254
    this._toPhase('beforeInit');
1,302✔
255
    // #[end]
256

257
    // init data
258
    var initData;
1,302✔
259
    try {
1,302✔
260
        initData = typeof this.initData === 'function' && this.initData();
1,302✔
261
    }
262
    catch (e) {
263
        handleError(e, this, 'initData');
1✔
264
    }
265
    initData = extend(initData || {}, options.data || this._srcSbindData);
1,302✔
266

267
    if (this.binds && this.scope) {
1,302✔
268
        for (var i = 0, l = this.binds.length; i < l; i++) {
475✔
269
            var bindInfo = this.binds[i];
498✔
270

271
            var value = evalExpr(bindInfo.expr, this.scope, this.owner);
498✔
272
            if (typeof value !== 'undefined') {
498✔
273
                // See: https://github.com/ecomfe/san/issues/191
274
                initData[bindInfo.name] = value;
443✔
275
            }
276
        }
277

278
        if (this.attrs) {
475✔
279
            initData.$attrs = {};
25✔
280
            for (var i = 0, l = this.attrs.length; i < l; i++) {
25✔
281
                var attr = this.attrs[i];
60✔
282
    
283
                var value = evalExpr(attr.expr, this.scope, this.owner);
60✔
284
                if (typeof value !== 'undefined') {
60!
285
                    // See: https://github.com/ecomfe/san/issues/191
286
                    initData.$attrs[attr.name] = value;
60✔
287
                }
288
            }
289
        }
290
    }
291

292
    this.data = new Data(initData);
1,302✔
293
    if (proxySupported) {
1,302!
294
        this.d = dataProxy(this.data);
1,302✔
295
    }
296

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

311
    this._computedDeps = {};
1,261✔
312
    this._computedDepsIndex = {};
1,261✔
313
    for (var expr in this.computed) {
1,261✔
314
        if (this.computed.hasOwnProperty(expr) && !this._computedDeps[expr]) {
50✔
315
            this._calcComputed(expr);
42✔
316
        }
317
    }
318

319
    this._initDataChanger();
1,260✔
320
    this._sbindData = nodeSBindInit(this.aNode.directives.bind, this.data, this);
1,260✔
321
    this._toPhase('inited');
1,260✔
322

323
    // #[begin] hydrate
324
    var hydrateWalker = options.hydrateWalker;
1,260✔
325
    var aNode = this.aNode;
1,260✔
326
    if (hydrateWalker) {
1,260✔
327
        if (this.ssr === 'client-render') {
101✔
328
            this.attach(hydrateWalker.target, hydrateWalker.current);
5✔
329
        }
330
        else {
331
            this._toPhase('created');
96✔
332

333
            if (aNode.Clazz || this.components[aNode.tagName]) {
96✔
334
                if (!aNode.Clazz && this.attrs && this.inheritAttrs) {
6✔
335
                    aNode = mergeANodeSourceAttrs(aNode, this.source);
1✔
336
                }
337
                this._rootNode = createHydrateNode(aNode, this, this.data, this, hydrateWalker);
6✔
338
                this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
6✔
339
            }
340
            else {
341
                var currentNode = hydrateWalker.current;
90✔
342
                if (currentNode && currentNode.nodeType === 1) {
90✔
343
                    this.el = currentNode;
90✔
344
                    hydrateWalker.goNext();
90✔
345
                }
346

347
                hydrateElementChildren(this, this.data, this);
90✔
348
            }
349

350
            this._attached();
96✔
351
            this._toPhase('attached');
96✔
352
        }
353
    }
354
    else if (this.el) {
1,159✔
355
        this._toPhase('created');
156✔
356
        
357
        if (aNode.Clazz || this.components[aNode.tagName]) {
156✔
358
            if (!aNode.Clazz && this.attrs && this.inheritAttrs) {
5!
359
                aNode = mergeANodeSourceAttrs(aNode, this.source);
×
360
            }
361
            hydrateWalker = new DOMChildrenWalker(this.el.parentNode, this.el);
5✔
362
            this._rootNode = createHydrateNode(aNode, this, this.data, this, hydrateWalker);
5✔
363
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
5✔
364
        }
365
        else {
366
            hydrateElementChildren(this, this.data, this);
151✔
367
        }
368

369
        this._attached();
156✔
370
        this._toPhase('attached');
156✔
371
    }
372
    // #[end]
373
}
374

375

376
/**
377
 * 初始化创建组件外部传入的插槽对象
378
 *
379
 * @protected
380
 * @param {boolean} isFirstTime 是否初次对sourceSlots进行计算
381
 */
382
Component.prototype._initSourceSlots = function (isFirstTime) {
1✔
383
    this.sourceSlots.named = {};
488✔
384

385
    // 组件运行时传入的结构,做slot解析
386
    if (this.source && this.scope) {
488✔
387
        var sourceChildren = this.source.children;
488✔
388

389
        for (var i = 0, l = sourceChildren.length; i < l; i++) {
488✔
390
            var child = sourceChildren[i];
268✔
391
            var target;
268✔
392

393
            var slotBind = !child.textExpr && getANodeProp(child, 'slot');
268✔
394
            if (slotBind) {
268✔
395
                isFirstTime && this.sourceSlotNameProps.push(slotBind);
93✔
396

397
                var slotName = evalExpr(slotBind.expr, this.scope, this.owner);
93✔
398
                target = this.sourceSlots.named[slotName];
93✔
399
                if (!target) {
93✔
400
                    target = this.sourceSlots.named[slotName] = [];
59✔
401
                }
402
                target.push(child);
93✔
403
            }
404
            else if (isFirstTime) {
175!
405
                target = this.sourceSlots.noname;
175✔
406
                if (!target) {
175✔
407
                    target = this.sourceSlots.noname = [];
150✔
408
                }
409
                target.push(child);
175✔
410
            }
411
        }
412
    }
413
};
414

415
/**
416
 * 类型标识
417
 *
418
 * @type {string}
419
 */
420
Component.prototype.nodeType = NodeType.CMPT;
1✔
421

422
/**
423
 * 在下一个更新周期运行函数
424
 *
425
 * @param {Function} fn 要运行的函数
426
 */
427
Component.prototype.nextTick = nextTick;
1✔
428

429
Component.prototype._ctx = (new Date()).getTime().toString(16);
1✔
430

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

450
        this._afterLife = this.lifeCycle;
15,516✔
451

452
        // 通知devtool
453
        // #[begin] devtool
454
        emitDevtool('comp-' + name, this);
15,516✔
455
        // #[end]
456
    }
457
};
458
/* eslint-enable operator-linebreak */
459

460

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

477
/**
478
 * 移除事件监听器
479
 *
480
 * @param {string} name 事件名
481
 * @param {Function=} listener 监听器
482
 */
483
Component.prototype.un = function (name, listener) {
1✔
484
    var nameListeners = this.listeners[name];
2✔
485
    var len = nameListeners && nameListeners.length;
2✔
486

487
    while (len--) {
2✔
488
        if (!listener || listener === nameListeners[len].fn) {
2✔
489
            nameListeners.splice(len, 1);
2✔
490
        }
491
    }
492
};
493

494

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

511
    each(this.listeners[name], function (listener) {
25✔
512
        try {
22✔
513
            listener.fn.call(me, event);
22✔
514
        }
515
        catch (e) {
516
            handleError(e, me, 'event:' + name);
1✔
517
        }
518
    });
519
};
520

521

522
var componentComputedProxyHandler = {
1✔
523
    get: function (obj, prop) {
524
        var value = obj[prop];
132✔
525
        if (value && typeof value === 'object') {
132✔
526
            return new Proxy(value, componentComputedProxyHandler);
39✔
527
        }
528
        return value;
93✔
529
    }, 
530
    set: empty
531
};
532

533

534
/**
535
 * 计算 computed 属性的值
536
 *
537
 * @private
538
 * @param {string} computedExpr computed表达式串
539
 */
540
Component.prototype._calcComputed = function (computedExpr) {
1✔
541
    var computedDeps = this._computedDeps[computedExpr];
129✔
542
    var isFirstCalc = false;
129✔
543
    if (!computedDeps) {
129✔
544
        isFirstCalc = true;
50✔
545
        computedDeps = this._computedDeps[computedExpr] = {};
50✔
546
    }
547

548
    var me = this;
129✔
549

550
    var that = {
129✔
551
        data: {
552
            get: function (exprLiteral) {
553
                // #[begin] error
554
                if (!exprLiteral) {
96✔
555
                    throw new Error('[SAN ERROR] call get method in computed need argument');
1✔
556
                }
557
                // #[end]
558

559
                var expr = parseExpr(exprLiteral);
95✔
560
                var firstItem = expr.paths[0].value;
95✔
561

562
                if (!computedDeps[firstItem]) {
95✔
563
                    computedDeps[firstItem] = 1;
39✔
564
                    if (!me._computedDepsIndex[firstItem]) {
39✔
565
                        me._computedDepsIndex[firstItem] = [];
37✔
566
                    }
567
                    me._computedDepsIndex[firstItem].push(computedExpr);
39✔
568

569
                    if (me.computed[firstItem] && !me._computedDeps[firstItem]) {
39✔
570
                        me._calcComputed(firstItem);
4✔
571
                    }
572
                }
573

574
                return me.data.get(expr);
95✔
575
            }
576
        }
577
    };
578

579
    if (proxySupported) {
129!
580
        that.d = new Proxy(me.data.raw, {
129✔
581
            set: empty,
582
            get: function (obj, prop) {
583
                if (!computedDeps[prop]) {
109✔
584
                    computedDeps[prop] = 1;
36✔
585
                    if (!me._computedDepsIndex[prop]) {
36✔
586
                        me._computedDepsIndex[prop] = [];
32✔
587
                    }
588
                    me._computedDepsIndex[prop].push(computedExpr);
36✔
589

590
                    if (me.computed[prop] && !me._computedDeps[prop]) {
36✔
591
                        me._calcComputed(prop);
4✔
592
                    }
593
                }
594

595
                var value = obj[prop];
109✔
596
                if (value && typeof value === 'object') {
109✔
597
                    return new Proxy(value, componentComputedProxyHandler);
54✔
598
                }
599
                return value;
55✔
600
            }
601
        });
602
    }
603

604
    try {
129✔
605
        this.data.set(
129✔
606
            {type: ExprType.ACCESSOR, paths: [{type: ExprType.STRING, value: computedExpr}]}, 
607
            this.computed[computedExpr].call(that),
608
            {silent: isFirstCalc}
609
        );
610
    }
611
    catch (e) {
612
        handleError(e, this, 'computed:' + computedExpr);
2✔
613
    }
614
};
615

616
/**
617
 * 派发消息
618
 * 组件可以派发消息,消息将沿着组件树向上传递,直到遇上第一个处理消息的组件
619
 *
620
 * @param {string} name 消息名称
621
 * @param {*?} value 消息值
622
 */
623
Component.prototype.dispatch = function (name, value) {
1✔
624
    var parentComponent = this.parentComponent;
27✔
625

626
    while (parentComponent) {
27✔
627
        var handler = parentComponent.messages[name] || parentComponent.messages['*'];
28✔
628
        if (typeof handler === 'function') {
28✔
629
            // #[begin] devtool
630
            emitDevtool('comp-message', {
27✔
631
                target: this,
632
                value: value,
633
                name: name,
634
                receiver: parentComponent
635
            });
636
            // #[end]
637

638
            try {
27✔
639
                handler.call(
27✔
640
                    parentComponent,
641
                    {target: this, value: value, name: name}
642
                );
643
            }
644
            catch (e) {
645
                handleError(e, parentComponent, 'message:' + (name || '*'));
1!
646
            }
647
            return;
27✔
648
        }
649

650
        parentComponent = parentComponent.parentComponent;
1✔
651
    }
652

653
    // #[begin] devtool
654
    emitDevtool('comp-message', {target: this, value: value, name: name});
×
655
    // #[end]
656
};
657

658
/**
659
 * 获取组件内部的 slot
660
 *
661
 * @param {string=} name slot名称,空为default slot
662
 * @return {Array}
663
 */
664
Component.prototype.slot = function (name) {
1✔
665
    var result = [];
24✔
666
    var me = this;
24✔
667

668
    function childrenTraversal(children) {
1✔
669
        each(children, function (child) {
70✔
670
            if (child.nodeType === NodeType.SLOT && child.owner === me) {
99✔
671
                if (child.isNamed && child.name === name
53✔
672
                    || !child.isNamed && !name
673
                ) {
674
                    result.push(child);
26✔
675
                }
676
            }
677
            else {
678
                childrenTraversal(child.children);
46✔
679
            }
680
        });
681
    }
682

683
    childrenTraversal(this.children);
24✔
684
    return result;
24✔
685
};
686

687
/**
688
 * 获取带有 san-ref 指令的子组件引用
689
 *
690
 * @param {string} name 子组件的引用名
691
 * @return {Component}
692
 */
693
Component.prototype.ref = function (name) {
1✔
694
    var refTarget;
77✔
695
    var owner = this;
77✔
696

697
    function childrenTraversal(children) {
1✔
698
        if (children) {
167✔
699
            for (var i = 0, l = children.length; i < l; i++) {
132✔
700
                elementTraversal(children[i]);
146✔
701
                if (refTarget) {
146✔
702
                    return;
99✔
703
                }
704
            }
705
        }
706
    }
707

708
    function elementTraversal(element) {
1✔
709
        var nodeType = element.nodeType;
148✔
710
        if (nodeType === NodeType.TEXT) {
148✔
711
            return;
22✔
712
        }
713

714
        if (element.owner === owner) {
126✔
715
            var ref;
117✔
716
            switch (element.nodeType) {
117✔
717
                case NodeType.ELEM:
99✔
718
                    ref = element.aNode.directives.ref;
23✔
719
                    if (ref && evalExpr(ref.value, element.scope, owner) === name) {
23✔
720
                        refTarget = element.el;
6✔
721
                    }
722
                    break;
23✔
723

724
                case NodeType.CMPT:
725
                    ref = element.source.directives.ref;
76✔
726
                    if (ref && evalExpr(ref.value, element.scope, owner) === name) {
76✔
727
                        refTarget = element;
68✔
728
                    }
729
            }
730

731
            if (refTarget) {
117✔
732
                return;
74✔
733
            }
734

735
            childrenTraversal(element.slotChildren);
43✔
736
        }
737

738
        if (refTarget) {
52✔
739
            return;
3✔
740
        }
741

742
        childrenTraversal(element.children);
49✔
743
    }
744

745
    this._rootNode ? elementTraversal(this._rootNode) : childrenTraversal(this.children);
77✔
746

747
    return refTarget;
77✔
748
};
749

750

751
/**
752
 * 视图更新函数
753
 *
754
 * @param {Array?} changes 数据变化信息
755
 */
756
Component.prototype._update = function (changes) {
1✔
757
    if (this.lifeCycle.disposed) {
1,231✔
758
        return;
158✔
759
    }
760

761
    var me = this;
1,073✔
762

763

764
    var needReloadForSlot = false;
1,073✔
765
    this._notifyNeedReload = function () {
1,073✔
766
        needReloadForSlot = true;
36✔
767
    };
768

769
    if (changes) {
1,073✔
770
        if (this.source) {
282✔
771
            this._srcSbindData = nodeSBindUpdate(
280✔
772
                this.source.directives.bind,
773
                this._srcSbindData,
774
                this.scope,
775
                this.owner,
776
                changes,
777
                function (name, value) {
778
                    if (name in me.source._pi) {
14✔
779
                        return;
2✔
780
                    }
781

782
                    me.data.set(name, value, {
12✔
783
                        target: {
784
                            node: me.owner
785
                        }
786
                    });
787
                }
788
            );
789
        }
790

791
        each(changes, function (change) {
282✔
792
            var changeExpr = change.expr;
419✔
793

794
            each(me.binds, function (bindItem) {
419✔
795
                var relation;
602✔
796
                var setExpr = bindItem.name;
602✔
797
                var updateExpr = bindItem.expr;
602✔
798

799
                if (!isDataChangeByElement(change, me, setExpr)
602✔
800
                    && (relation = changeExprCompare(changeExpr, updateExpr, me.scope))
801
                ) {
802
                    if (relation > 2) {
217✔
803
                        setExpr = {
41✔
804
                            type: ExprType.ACCESSOR,
805
                            paths: [
806
                                {
807
                                    type: ExprType.STRING,
808
                                    value: setExpr
809
                                }
810
                            ].concat(changeExpr.paths.slice(updateExpr.paths.length))
811
                        };
812
                        updateExpr = changeExpr;
41✔
813
                    }
814

815
                    if (relation >= 2 && change.type === DataChangeType.SPLICE) {
217✔
816
                        me.data.splice(setExpr, [change.index, change.deleteCount].concat(change.insertions), {
18✔
817
                            target: {
818
                                node: me.owner
819
                            }
820
                        });
821
                    }
822
                    else {
823
                        me.data.set(setExpr, evalExpr(updateExpr, me.scope, me.owner), {
199✔
824
                            target: {
825
                                node: me.owner
826
                            }
827
                        });
828
                    }
829
                }
830
            });
831

832
            each(me.attrs, function (bindItem) {
419✔
833
                if (changeExprCompare(changeExpr, bindItem.expr, me.scope)) {
72✔
834
                    me.data.set(
50✔
835
                        bindItem._data,
836
                        evalExpr(bindItem.expr, me.scope, me.owner)
837
                    );
838
                }
839
            });
840

841
            each(me.sourceSlotNameProps, function (bindItem) {
419✔
842
                needReloadForSlot = needReloadForSlot || changeExprCompare(changeExpr, bindItem.expr, me.scope);
142✔
843
                return !needReloadForSlot;
142✔
844
            });
845
        });
846

847
        if (needReloadForSlot) {
282✔
848
            this._initSourceSlots();
4✔
849
            this._repaintChildren();
4✔
850
        }
851
        else {
852
            var slotChildrenLen = this.slotChildren.length;
278✔
853
            while (slotChildrenLen--) {
278✔
854
                var slotChild = this.slotChildren[slotChildrenLen];
232✔
855

856
                if (slotChild.lifeCycle.disposed) {
232✔
857
                    this.slotChildren.splice(slotChildrenLen, 1);
6✔
858
                }
859
                else if (slotChild.isInserted) {
226✔
860
                    slotChild._update(changes, 1);
169✔
861
                }
862
            }
863
        }
864
    }
865

866
    var dataChanges = this._dataChanges;
1,073✔
867
    if (dataChanges) {
1,073✔
868
        // #[begin] devtool
869
        this._toPhase('beforeUpdate');
922✔
870
        // #[end]
871

872
        this._dataChanges = null;
922✔
873

874
        this._sbindData = nodeSBindUpdate(
922✔
875
            this.aNode.directives.bind,
876
            this._sbindData,
877
            this.data,
878
            this,
879
            dataChanges,
880
            function (name, value) {
881
                if (me._rootNode || (name in me.aNode._pi)) {
18✔
882
                    return;
3✔
883
                }
884

885
                getPropHandler(me.tagName, name)(me.el, value, name, me);
15✔
886
            }
887
        );
888

889
        var htmlDirective = this.aNode.directives.html;
922✔
890

891
        if (this._rootNode) {
922✔
892
            this._rootNode._update(dataChanges);
52✔
893
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
52✔
894
        }
895
        else if (htmlDirective) {
870✔
896
            var len = dataChanges.length;
1✔
897
            while (len--) {
1✔
898
                if (changeExprCompare(dataChanges[len].expr, htmlDirective.value, this.data)) {
1!
899
                    // #[begin] error
900
                    warnSetHTML(this.el);
1✔
901
                    // #[end]
902

903
                    this.el.innerHTML = evalExpr(htmlDirective.value, this.data, this);
1✔
904
                    break;
1✔
905
                }
906
            }
907
        }
908
        else {
909
            var dynamicProps = this.aNode._dp;
869✔
910
            for (var i = 0; i < dynamicProps.length; i++) {
869✔
911
                var prop = dynamicProps[i];
2,638✔
912

913
                for (var j = 0; j < dataChanges.length; j++) {
2,638✔
914
                    var change = dataChanges[j];
3,597✔
915
                    if (changeExprCompare(change.expr, prop.expr, this.data)
3,597!
916
                        || prop.hintExpr && changeExprCompare(change.expr, prop.hintExpr, this.data)
917
                    ) {
918
                        prop.handler(this.el, evalExpr(prop.expr, this.data, this), prop.name, this);
65✔
919
                        break;
65✔
920
                    }
921
                }
922
            }
923

924
            if (this.attrs && this.inheritAttrs) {
869✔
925
                var attrsData = this.data.get('$attrs');
18✔
926

927
                for (var i = 0; i < this.attrs.length; i++) {
18✔
928
                    var attr = this.attrs[i];
44✔
929

930
                    if (this.aNode._pi[attr.name] == null) {
44✔
931
                        for (var j = 0; j < dataChanges.length; j++) {
42✔
932
                            var changePaths = dataChanges[j].expr.paths;
62✔
933

934
                            if (changePaths[0].value === '$attrs' && changePaths[1].value === attr.name) {
62✔
935
                                getPropHandler(this.tagName, attr.name)(this.el, attrsData[attr.name], attr.name, this);
32✔
936
                                break;
32✔
937
                            }
938
                        }
939
                    }
940
                }
941
            }
942

943
            for (var i = 0; i < this.children.length; i++) {
869✔
944
                this.children[i]._update(dataChanges);
1,234✔
945
            }
946
        }
947

948
        if (needReloadForSlot) {
922✔
949
            this._initSourceSlots();
9✔
950
            this._repaintChildren();
9✔
951
        }
952

953
        for (var i = 0; i < this.implicitChildren.length; i++) {
922✔
954
            this.implicitChildren[i]._update(dataChanges);
4✔
955
        }
956

957
        if (typeof this.updated === 'function') {
922✔
958
            this.updated();
5✔
959
        }
960

961
        if (this.owner && this._updateBindxOwner(dataChanges)) {
922✔
962
            this.owner._update();
25✔
963
        }
964
    }
965

966
    this._notifyNeedReload = null;
1,073✔
967
};
968

969
Component.prototype._updateBindxOwner = function (dataChanges) {
1✔
970
    var me = this;
237✔
971
    var xbindUped;
237✔
972

973
    each(dataChanges, function (change) {
237✔
974
        each(me.binds, function (bindItem) {
333✔
975
            var changeExpr = change.expr;
515✔
976
            if (bindItem.x
515✔
977
                && !isDataChangeByElement(change, me.owner)
978
                && changeExprCompare(changeExpr, parseExpr(bindItem.name), me.data)
979
            ) {
980
                var updateScopeExpr = bindItem.expr;
32✔
981
                if (changeExpr.paths.length > 1) {
32✔
982
                    updateScopeExpr = {
13✔
983
                        type: ExprType.ACCESSOR,
984
                        paths: bindItem.expr.paths.concat(changeExpr.paths.slice(1))
985
                    };
986
                }
987

988
                xbindUped = 1;
32✔
989
                me.scope.set(
32✔
990
                    updateScopeExpr,
991
                    evalExpr(changeExpr, me.data, me),
992
                    {
993
                        target: {
994
                            node: me,
995
                            prop: bindItem.name
996
                        }
997
                    }
998
                );
999
            }
1000
        });
1001
    });
1002

1003
    return xbindUped;
237✔
1004
};
1005

1006
/**
1007
 * 重新绘制组件的内容
1008
 * 当 dynamic slot name 发生变更或 slot 匹配发生变化时,重新绘制
1009
 * 在组件级别重绘有点粗暴,但是能保证视图结果正确性
1010
 */
1011
Component.prototype._repaintChildren = function () {
1✔
1012
    // TODO: repaint once?
1013

1014
    if (this._rootNode) {
13!
1015
        var parentEl = this._rootNode.el.parentNode;
×
1016
        var beforeEl = this._rootNode.el.nextSibling;
×
1017
        this._rootNode.dispose(0, 1);
×
1018
        this.slotChildren = [];
×
1019

1020
        var aNode = this.aNode;
×
1021
        if (!aNode.Clazz && this.attrs && this.inheritAttrs) {
×
1022
            aNode = mergeANodeSourceAttrs(aNode, this.source);
×
1023
        }
1024

1025
        this._rootNode = createNode(aNode, this, this.data, this);
×
1026
        this._rootNode.attach(parentEl, beforeEl);
×
1027
        this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
×
1028
    }
1029
    else {
1030
        elementDisposeChildren(this.children, 0, 1);
13✔
1031
        this.children = [];
13✔
1032
        this.slotChildren = [];
13✔
1033

1034
        for (var i = 0, l = this.aNode.children.length; i < l; i++) {
13✔
1035
            var child = createNode(this.aNode.children[i], this, this.data, this);
52✔
1036
            this.children.push(child);
52✔
1037
            child.attach(this.el);
52✔
1038
        }
1039
    }
1040
};
1041

1042

1043
/**
1044
 * 初始化组件内部监听数据变化
1045
 *
1046
 * @private
1047
 * @param {Object} change 数据变化信息
1048
 */
1049
Component.prototype._initDataChanger = function () {
1✔
1050
    var me = this;
1,260✔
1051

1052
    this._dataChanger = function (change) {
1,260✔
1053
        if (me._afterLife.created) {
1,500✔
1054
            if (!me._dataChanges) {
1,278✔
1055
                nextTick(me._update, me);
924✔
1056
                me._dataChanges = [];
924✔
1057
            }
1058

1059
            me._dataChanges.push(change);
1,278✔
1060
        }
1061
        else if (me.lifeCycle.inited && me.owner) {
222✔
1062
            me._updateBindxOwner([change]);
1✔
1063
        }
1064

1065
        var changeItem = change.expr.paths[0].value;
1,500✔
1066
        var depComputeds = me._computedDepsIndex[changeItem];
1,500✔
1067
        if (depComputeds) {
1,500✔
1068
            for (var i = 0; i < depComputeds.length; i++) {
68✔
1069
                me._calcComputed(depComputeds[i]);
79✔
1070
            }
1071
        }
1072
    };
1073

1074
    this.data.listen(this._dataChanger);
1,260✔
1075
};
1076

1077

1078
/**
1079
 * 监听组件的数据变化
1080
 *
1081
 * @param {string} dataName 变化的数据项
1082
 * @param {Function} listener 监听函数
1083
 */
1084
Component.prototype.watch = function (dataName, listener) {
1✔
1085
    var dataExpr = parseExpr(dataName);
9✔
1086
    var value = evalExpr(dataExpr, this.data, this);
9✔
1087
    var me = this;
9✔
1088

1089
    this.data.listen(function (change) {
9✔
1090
        if (changeExprCompare(change.expr, dataExpr, me.data)) {
16✔
1091
            var newValue = evalExpr(dataExpr, me.data, me);
13✔
1092

1093
            if (newValue !== value) {
13!
1094
                var oldValue = value;
13✔
1095
                value = newValue;
13✔
1096

1097
                try {
13✔
1098
                    listener.call(
13✔
1099
                        me,
1100
                        newValue,
1101
                        {
1102
                            oldValue: oldValue,
1103
                            newValue: newValue,
1104
                            change: change
1105
                        }
1106
                    );
1107
                }
1108
                catch (e) {
1109
                    handleError(e, me, 'watch:' + dataName);
1✔
1110
                }
1111
            }
1112
        }
1113
    });
1114
};
1115

1116
Component.prototype._getElAsRootNode = function () {
1✔
1117
    return this.el;
34✔
1118
};
1119

1120
/**
1121
 * 将组件attach到页面
1122
 *
1123
 * @param {HTMLElement} parentEl 要添加到的父元素
1124
 * @param {HTMLElement=} beforeEl 要添加到哪个元素之前
1125
 */
1126
Component.prototype.attach = function (parentEl, beforeEl) {
1✔
1127
    if (!this.lifeCycle.attached) {
996✔
1128
        // #[begin] devtool
1129
        this._toPhase('beforeAttach');
995✔
1130
        // #[end]
1131

1132
        var aNode = this.aNode;
995✔
1133

1134
        if (aNode.Clazz || this.components[aNode.tagName]) {
995✔
1135
            // #[begin] devtool
1136
            this._toPhase('beforeCreate');
43✔
1137
            // #[end]
1138

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

1145
            this._rootNode = this._rootNode || createNode(aNode, this, this.data, this);
43✔
1146
            this._rootNode.attach(parentEl, beforeEl);
43✔
1147
            this._rootNode._getElAsRootNode && (this.el = this._rootNode._getElAsRootNode());
43✔
1148
            this._toPhase('created');
43✔
1149
        }
1150
        else {
1151
            if (!this.el) {
952✔
1152
                // #[begin] devtool
1153
                this._toPhase('beforeCreate');
947✔
1154
                // #[end]
1155

1156
                var props;
947✔
1157
                var doc = parentEl.ownerDocument;
947✔
1158
                if (aNode._ce && aNode._i > 2) {
947✔
1159
                    props = aNode._dp;
41✔
1160
                    this.el = (aNode._el || preheatEl(aNode, doc)).cloneNode(false);
41✔
1161
                }
1162
                else {
1163
                    props = aNode.props;
906✔
1164
                    this.el = svgTags[this.tagName] && doc.createElementNS
906✔
1165
                        ? doc.createElementNS('http://www.w3.org/2000/svg', this.tagName)
906✔
1166
                        : doc.createElement(this.tagName);
1167
                }
1168

1169
                if (this._sbindData) {
947✔
1170
                    for (var key in this._sbindData) {
1✔
1171
                        if (this._sbindData.hasOwnProperty(key)) {
6!
1172
                            getPropHandler(this.tagName, key)(
6✔
1173
                                this.el,
1174
                                this._sbindData[key],
1175
                                key,
1176
                                this
1177
                            );
1178
                        }
1179
                    }
1180
                }
1181

1182
                for (var i = 0, l = props.length; i < l; i++) {
947✔
1183
                    var prop = props[i];
2,926✔
1184
                    var value = evalExpr(prop.expr, this.data, this);
2,926✔
1185

1186
                    if (value || !styleProps[prop.name]) {
2,926✔
1187
                        prop.handler(this.el, value, prop.name, this);
1,103✔
1188
                    }
1189
                }
1190

1191
                if (this.attrs && this.inheritAttrs) {
947✔
1192
                    var attrsData = this.data.get('$attrs');
10✔
1193
                    for (var i = 0; i < this.attrs.length; i++) {
10✔
1194
                        var attr = this.attrs[i];
23✔
1195
                        if (this.aNode._pi[attr.name] == null) {
23✔
1196
                            getPropHandler(this.tagName, attr.name)(this.el, attrsData[attr.name], attr.name, this);
22✔
1197
                        }
1198
                    }
1199
                }
1200

1201
                this._toPhase('created');
947✔
1202
            }
1203

1204
            insertBefore(this.el, parentEl, beforeEl);
952✔
1205

1206
            if (!this._contentReady) {
952✔
1207
                var htmlDirective = aNode.directives.html;
947✔
1208

1209
                if (htmlDirective) {
947✔
1210
                    // #[begin] error
1211
                    warnSetHTML(this.el);
1✔
1212
                    // #[end]
1213

1214
                    this.el.innerHTML = evalExpr(htmlDirective.value, this.data, this);
1✔
1215
                }
1216
                else {
1217
                    for (var i = 0, l = aNode.children.length; i < l; i++) {
946✔
1218
                        var childANode = aNode.children[i];
1,233✔
1219
                        var child = childANode.Clazz
1,233✔
1220
                            ? new childANode.Clazz(childANode, this, this.data, this)
1,233✔
1221
                            : createNode(childANode, this, this.data, this);
1222
                        this.children.push(child);
1,232✔
1223
                        child.attach(this.el);
1,232✔
1224
                    }
1225
                }
1226

1227
                this._contentReady = 1;
946✔
1228
            }
1229

1230
            this._attached();
951✔
1231
        }
1232

1233
        this._toPhase('attached');
994✔
1234

1235
        // element 都是内部创建的,只有动态创建的 component 才会进入这个分支
1236
        if (this.owner && !this.parent) {
994✔
1237
            this.owner.implicitChildren.push(this);
6✔
1238
        }
1239
    }
1240
};
1241

1242
Component.prototype.detach = elementOwnDetach;
1✔
1243
Component.prototype.dispose = elementOwnDispose;
1✔
1244
Component.prototype._attached = elementOwnAttached;
1✔
1245
Component.prototype._leave = function () {
1✔
1246
    if (this.leaveDispose) {
1,243✔
1247
        if (!this.lifeCycle.disposed) {
1,231!
1248
            // #[begin] devtool
1249
            this._toPhase('beforeDetach');
1,231✔
1250
            // #[end]
1251
            this.data.unlisten();
1,231✔
1252
            this.dataChanger = null;
1,231✔
1253
            this._dataChanges = null;
1,231✔
1254

1255
            var len = this.implicitChildren.length;
1,231✔
1256
            while (len--) {
1,231✔
1257
                this.implicitChildren[len].dispose(0, 1);
6✔
1258
            }
1259

1260
            this.implicitChildren = null;
1,231✔
1261

1262
            this.source = null;
1,231✔
1263
            this.sourceSlots = null;
1,231✔
1264
            this.sourceSlotNameProps = null;
1,231✔
1265

1266
            // 这里不用挨个调用 dispose 了,因为 children 释放链会调用的
1267
            this.slotChildren = null;
1,231✔
1268

1269

1270
            if (this._rootNode) {
1,231✔
1271
                // 如果没有parent,说明是一个root component,一定要从dom树中remove
1272
                this._rootNode.dispose(this.disposeNoDetach && this.parent);
48✔
1273
            }
1274
            else {
1275
                var len = this.children.length;
1,183✔
1276
                while (len--) {
1,183✔
1277
                    this.children[len].dispose(1, 1);
1,557✔
1278
                }
1279

1280
                if (this._elFns) {
1,183✔
1281
                    len = this._elFns.length;
26✔
1282
                    while (len--) {
26✔
1283
                        var fn = this._elFns[len];
33✔
1284
                        un(this.el, fn[0], fn[1], fn[2]);
33✔
1285
                    }
1286
                    this._elFns = null;
26✔
1287
                }
1288

1289
                // #[begin] allua
1290
                /* istanbul ignore if */
1291
                if (this._inputTimer) {
1292
                    clearInterval(this._inputTimer);
1293
                    this._inputTimer = null;
1294
                }
1295
                // #[end]
1296

1297
                // 如果没有parent,说明是一个root component,一定要从dom树中remove
1298
                if (!this.disposeNoDetach || !this.parent) {
1,183✔
1299
                    removeEl(this.el);
804✔
1300
                }
1301
            }
1302

1303
            this._toPhase('detached');
1,231✔
1304

1305
            // #[begin] devtool
1306
            this._toPhase('beforeDispose');
1,231✔
1307
            // #[end]
1308

1309
            this._rootNode = null;
1,231✔
1310
            this.el = null;
1,231✔
1311
            this.owner = null;
1,231✔
1312
            this.scope = null;
1,231✔
1313
            this.children = null;
1,231✔
1314

1315
            this._toPhase('disposed');
1,231✔
1316

1317
            if (this._ondisposed) {
1,231✔
1318
                this._ondisposed();
15✔
1319
            }
1320
        }
1321
    }
1322
    else if (this.lifeCycle.attached) {
12✔
1323
        // #[begin] devtool
1324
        this._toPhase('beforeDetach');
11✔
1325
        // #[end]
1326

1327
        if (this._rootNode) {
11✔
1328
            if (this._rootNode.detach) {
5✔
1329
                this._rootNode.detach();
1✔
1330
            }
1331
            else {
1332
                this._rootNode.dispose();
4✔
1333
                this._rootNode = null;
4✔
1334
            }
1335
        }
1336
        else {
1337
            removeEl(this.el);
6✔
1338
        }
1339

1340
        this._toPhase('detached');
11✔
1341
    }
1342
};
1343

1344

1345
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