Skip to content

Commit

Permalink
compiler rewrite - observe scope directly and proxy access through vm
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Dec 23, 2013
1 parent f29be01 commit 14d8ce2
Show file tree
Hide file tree
Showing 18 changed files with 151 additions and 185 deletions.
2 changes: 2 additions & 0 deletions examples/todomvc/js/app.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Vue.config({debug:true})

var filters = {
all: function () { return true },
active: function (completed) { return !completed },
Expand Down
2 changes: 1 addition & 1 deletion examples/todomvc/js/benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// then delete them one by one

// do not run when testing in PhantomJS
if (navigator.userAgent.indexOf('PhantomJS') === -1) {
if (window.location.search === '?benchmark=1') {
runBenchmark()
}

Expand Down
134 changes: 47 additions & 87 deletions src/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ function Compiler (vm, options) {
var el = compiler.setupElement(options)
log('\nnew VM instance:', el.tagName, '\n')

// copy scope properties to vm
var scope = options.scope
if (scope) utils.extend(vm, scope, true)
// init scope
var scope = compiler.scope = options.scope || {}
utils.extend(vm, scope, true)

compiler.vm = vm
def(vm, '$', makeHash())
def(vm, '$el', el)
def(vm, '$scope', scope)
def(vm, '$compiler', compiler)

// keep track of directives and expressions
Expand Down Expand Up @@ -85,36 +86,23 @@ function Compiler (vm, options) {

// beforeCompile hook
compiler.execHook('beforeCompile', 'created')

// create bindings for things already in scope
var key, keyPrefix
for (key in vm) {
keyPrefix = key.charAt(0)
if (keyPrefix !== '$' && keyPrefix !== '_') {
compiler.createBinding(key)
}
}
// the user might have set some props on the vm
// so copy it back to the scope...
utils.extend(scope, vm)
// observe the scope
Observer.observe(scope, '', compiler.observer)

// for repeated items, create an index binding
// which should be inenumerable but configurable
if (compiler.repeat) {
vm.$index = compiler.repeatIndex
def(vm, '$collection', compiler.repeatCollection)
scope.$index = compiler.repeatIndex
compiler.createBinding('$index')
}

// now parse the DOM, during which we will create necessary bindings
// and bind the parsed directives
compiler.compile(el, true)

// observe root values so that they emit events when
// their nested values change (for an Object)
// or when they mutate (for an Array)
var i = observables.length, binding
while (i--) {
binding = observables[i]
Observer.observe(binding.value, binding.key, compiler.observer)
}
// extract dependencies for computed properties
if (computed.length) DepsParser.parse(computed)

Expand Down Expand Up @@ -374,24 +362,25 @@ CompilerProto.bindDirective = function (directive) {
var binding,
compiler = this,
key = directive.key,
baseKey = key.split('.')[0],
ownerCompiler = traceOwnerCompiler(directive, compiler)
baseKey = key.split('.')[0]

if (directive.isExp) {
// expression bindings are always created on current compiler
binding = compiler.createBinding(key, true, directive.isFn)
} else if (ownerCompiler.vm.hasOwnProperty(baseKey)) {
// If the directive's owner compiler's VM has the key,
// it belongs there. Create the binding if it's not already
// created, and return it.
binding = hasOwn.call(ownerCompiler.bindings, key)
? ownerCompiler.bindings[key]
: ownerCompiler.createBinding(key)
} else if (
hasOwn.call(compiler.scope, baseKey) ||
hasOwn.call(compiler.vm, baseKey)
) {
// If the directive's compiler's VM has the base key,
// it belongs here. Create the binding if it's not created already.
binding = hasOwn.call(compiler.bindings, key)
? compiler.bindings[key]
: compiler.createBinding(key)
} else {
// due to prototypal inheritance of bindings, if a key doesn't exist
// on the owner compiler's VM, then it doesn't exist in the whole
// on the bindings object, then it doesn't exist in the whole
// prototype chain. In this case we create the new binding at the root level.
binding = ownerCompiler.bindings[key] || compiler.rootCompiler.createBinding(key)
binding = compiler.bindings[key] || compiler.rootCompiler.createBinding(key)
}

binding.instances.push(directive)
Expand Down Expand Up @@ -419,6 +408,7 @@ CompilerProto.bindDirective = function (directive) {
CompilerProto.createBinding = function (key, isExp, isFn) {

var compiler = this,
scope = compiler.scope,
bindings = compiler.bindings,
binding = new Binding(compiler, key, isExp, isFn)

Expand All @@ -443,7 +433,8 @@ CompilerProto.createBinding = function (key, isExp, isFn) {
// this is a root level binding. we need to define getter/setters for it.
compiler.define(key, binding)
} else {
Observer.ensurePath(compiler.vm, key)
// ensure path in scope so it can be observed
Observer.ensurePath(scope, key)
var parentKey = key.slice(0, key.lastIndexOf('.'))
if (!hasOwn.call(bindings, parentKey)) {
// this is a nested value binding, but the binding for its parent
Expand All @@ -464,52 +455,35 @@ CompilerProto.define = function (key, binding) {
log(' defined root binding: ' + key)

var compiler = this,
scope = compiler.scope,
vm = compiler.vm,
ob = compiler.observer,
value = binding.value = vm[key], // save the value before redefinening it
type = utils.typeOf(value)
value = binding.value = scope[key] // save the value before redefinening it

if (type === 'Object' && value.$get) {
// computed property
if (utils.typeOf(value) === 'Object' && value.$get) {
compiler.markComputed(binding)
} else if (type === 'Object' || type === 'Array') {
// observe objects later, because there might be more keys
// to be added to it during Observer.ensurePath().
// we also want to emit all the set events after all values
// are available.
compiler.observables.push(binding)
}

if (!(key in scope)) {
scope[key] = undefined
}

if (scope.__observer__) {
Observer.convert(scope, key)
}

Object.defineProperty(vm, key, {
enumerable: true,
get: function () {
var value = binding.value
if (depsOb.active && (!binding.isComputed && (!value || !value.__observer__)) ||
Array.isArray(value)) {
// only emit non-computed, non-observed (primitive) values, or Arrays.
// because these are the cleanest dependencies
ob.emit('get', key)
}
var val = scope[key]
return binding.isComputed
? value.$get()
: value
? val.$get()
: val
},
set: function (newVal) {
var value = binding.value
var val = scope[key]
if (binding.isComputed) {
if (value.$set) {
value.$set(newVal)
}
} else if (newVal !== value) {
// unwatch the old value
Observer.unobserve(value, key, ob)
// set new value
binding.value = newVal
ob.emit('set', key, newVal)
Observer.ensurePaths(key, newVal, compiler.bindings)
// now watch the new value, which in turn emits 'set'
// for all its nested values
Observer.observe(newVal, key, ob)
if (val.$set) val.$set(newVal)
} else {
scope[key] = newVal
}
}
})
Expand Down Expand Up @@ -631,28 +605,14 @@ CompilerProto.destroy = function () {

// Helpers --------------------------------------------------------------------

/**
* determine which viewmodel a key belongs to based on nesting symbols
*/
function traceOwnerCompiler (key, compiler) {
if (key.nesting) {
var levels = key.nesting
while (compiler.parentCompiler && levels--) {
compiler = compiler.parentCompiler
}
} else if (key.root) {
while (compiler.parentCompiler) {
compiler = compiler.parentCompiler
}
}
return compiler
}

/**
* shorthand for getting root compiler
*/
function getRoot (compiler) {
return traceOwnerCompiler({ root: true }, compiler)
while (compiler.parentCompiler) {
compiler = compiler.parentCompiler
}
return compiler
}

module.exports = Compiler
7 changes: 4 additions & 3 deletions src/deps-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ var Emitter = require('./emitter'),
function catchDeps (binding) {
if (binding.isFn) return
utils.log('\n─ ' + binding.key)
var has = []
var got = utils.hash()
observer.on('get', function (dep) {
if (has.indexOf(dep) > -1) return
has.push(dep)
var has = got[dep.key]
if (has && has.compiler === dep.compiler) return
got[dep.key] = dep
utils.log(' └─ ' + dep.key)
binding.deps.push(dep)
dep.subs.push(binding)
Expand Down
16 changes: 0 additions & 16 deletions src/directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ var config = require('./config'),
ARG_RE = /^([\w- ]+):(.+)$/,
FILTERS_RE = /\|[^\|]+/g,
FILTER_TOKEN_RE = /[^\s']+|'[^']+'/g,
NESTING_RE = /^\^+/,
SINGLE_VAR_RE = /^[\w\.\$]+$/

/**
Expand Down Expand Up @@ -76,7 +75,6 @@ var DirProto = Directive.prototype
* parse a key, extract argument and nesting/root info
*/
function parseKey (dir, rawKey) {

var key = rawKey
if (rawKey.indexOf(':') > -1) {
var argMatch = rawKey.match(ARG_RE)
Expand All @@ -87,20 +85,6 @@ function parseKey (dir, rawKey) {
? argMatch[1].trim()
: null
}

// nesting
var firstChar = key.charAt(0)
dir.root = firstChar === '*'
dir.nesting = firstChar === '^'
? key.match(NESTING_RE)[0].length
: false

if (dir.nesting) {
key = key.slice(dir.nesting)
} else if (dir.root) {
key = key.slice(1)
}

dir.key = key
}

Expand Down
8 changes: 4 additions & 4 deletions src/directives/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ module.exports = {
var Ctor = this.compiler.getOption('components', this.arg)
if (!Ctor) utils.warn('unknown component: ' + this.arg)
var options = {
el: this.el,
compilerOptions: {
parentCompiler: this.compiler
}
el: this.el,
compilerOptions: {
parentCompiler: this.compiler
}
}
if (value) {
options.scope = {
model: value
Expand Down
4 changes: 2 additions & 2 deletions src/directives/on.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ module.exports = {
if (target) {
e.el = target
e.vm = target.vue_viewmodel
e.item = e.vm[compiler.repeatPrefix]
e.item = e.vm.$scope[compiler.repeatPrefix]
handler.call(ownerVM, e)
}
}
Expand All @@ -66,7 +66,7 @@ module.exports = {
e.el = e.currentTarget
e.vm = vm
if (compiler.repeat) {
e.item = vm[compiler.repeatPrefix]
e.item = vm.$scope[compiler.repeatPrefix]
}
handler.call(ownerVM, e)
}
Expand Down
4 changes: 2 additions & 2 deletions src/directives/repeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ var mutationHandlers = {
data = col[i]
for (j = 0; j < l; j++) {
vm = vms[j]
if (vm[key] === data) {
if (vm.$scope[key] === data) {
sorted[i] = vm
break
}
Expand Down Expand Up @@ -201,7 +201,7 @@ module.exports = {
updateIndexes: function () {
var i = this.vms.length
while (i--) {
this.vms[i].$index = i
this.vms[i].$scope.$index = i
}
},

Expand Down
5 changes: 4 additions & 1 deletion src/exp-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ function getRel (path, compiler) {
? path.slice(0, dot)
: path
while (true) {
if (hasOwn.call(vm, key)) {
if (
hasOwn.call(vm.$scope, key) ||
hasOwn.call(vm, key)
) {
break
} else {
if (vm.$parent) {
Expand Down

0 comments on commit 14d8ce2

Please sign in to comment.