Просмотр исходного кода

move weex framework implementation under platforms

Evan You 9 лет назад
Родитель
Сommit
9b23b4c92d
2 измененных файлов с 423 добавлено и 422 удалено
  1. 1 422
      src/entries/weex-framework.js
  2. 422 0
      src/platforms/weex/framework.js

+ 1 - 422
src/entries/weex-framework.js

@@ -1,422 +1 @@
-import Vue from 'weex/runtime/index'
-import renderer from 'weex/runtime/config'
-
-Vue.weexVersion = '__WEEX_VERSION__'
-export { Vue }
-
-const {
-  instances,
-  modules,
-  components
-} = renderer
-
-let activeId
-const oriIsReservedTag = (Vue && Vue.config && typeof Vue.config.isReservedTag === 'function') ? Vue.config.isReservedTag : function () {}
-
-/**
- * Prepare framework config, basically about the virtual-DOM and JS bridge.
- * @param {object} cfg
- */
-export function init (cfg) {
-  renderer.Document = cfg.Document
-  renderer.Element = cfg.Element
-  renderer.Comment = cfg.Comment
-  renderer.sendTasks = cfg.sendTasks
-}
-
-/**
- * Reset framework config and clear all registrations.
- */
-export function reset () {
-  clear(instances)
-  clear(modules)
-  clear(components)
-  delete renderer.Document
-  delete renderer.Element
-  delete renderer.Comment
-  delete renderer.sendTasks
-  Vue.config.isReservedTag = oriIsReservedTag
-}
-
-/**
- * Delete all keys of an object.
- * @param {object} obj
- */
-function clear (obj) {
-  for (const key in obj) {
-    delete obj[key]
-  }
-}
-
-/**
- * Create an instance with id, code, config and external data.
- * @param {string} instanceId
- * @param {string} appCode
- * @param {object} config
- * @param {object} data
- * @param {object} env { info, config, services }
- */
-export function createInstance (
-  instanceId,
-  appCode = '',
-  config = {},
-  data,
-  env = {}
-) {
-  // Set active instance id and put some information into `instances` map.
-  activeId = instanceId
-
-  // Virtual-DOM object.
-  const document = new renderer.Document(instanceId, config.bundleUrl)
-
-  // All function/callback of parameters before sent to native
-  // will be converted as an id. So `callbacks` is used to store
-  // these real functions. When a callback invoked and won't be
-  // called again, it should be removed from here automatically.
-  const callbacks = []
-
-  // The latest callback id, incremental.
-  const callbackId = 1
-
-  instances[instanceId] = {
-    instanceId, config, data,
-    document, callbacks, callbackId
-  }
-
-  // Prepare native module getter and HTML5 Timer APIs.
-  const moduleGetter = genModuleGetter(instanceId)
-  const timerAPIs = getInstanceTimer(instanceId, moduleGetter)
-
-  // Prepare `weex` instance variable.
-  const weexInstanceVar = {
-    config,
-    document,
-    requireModule: moduleGetter
-  }
-  Object.freeze(weexInstanceVar)
-
-  // Each instance has a independent `Vue` variable and it should have
-  // all top-level public APIs.
-  const subVue = Vue.extend({})
-
-  // ensure plain-object components are extended from the subVue
-  subVue.options._base = subVue
-
-  // expose global utility
-  ;['util', 'set', 'delete', 'nextTick', 'version', 'weexVersion', 'config'].forEach(name => {
-    subVue[name] = Vue[name]
-  })
-
-  // The function which create a closure the JS Bundle will run in.
-  // It will declare some instance variables like `Vue`, HTML5 Timer APIs etc.
-  const instanceVars = Object.assign({
-    Vue: subVue,
-    weex: weexInstanceVar,
-    __weex_require_module__: weexInstanceVar.requireModule // deprecated
-  }, timerAPIs)
-  callFunction(instanceVars, appCode)
-
-  // Send `createFinish` signal to native.
-  renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'createFinish', args: [] }], -1)
-}
-
-/**
- * Destroy an instance with id. It will make sure all memory of
- * this instance released and no more leaks.
- * @param {string} instanceId
- */
-export function destroyInstance (instanceId) {
-  const instance = instances[instanceId] || {}
-  if (instance.app instanceof Vue) {
-    instance.app.$destroy()
-  }
-  delete instances[instanceId]
-}
-
-/**
- * Refresh an instance with id and new top-level component data.
- * It will use `Vue.set` on all keys of the new data. So it's better
- * define all possible meaningful keys when instance created.
- * @param {string} instanceId
- * @param {object} data
- */
-export function refreshInstance (instanceId, data) {
-  const instance = instances[instanceId] || {}
-  if (!(instance.app instanceof Vue)) {
-    return new Error(`refreshInstance: instance ${instanceId} not found!`)
-  }
-  for (const key in data) {
-    Vue.set(instance.app, key, data[key])
-  }
-  // Finally `refreshFinish` signal needed.
-  renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'refreshFinish', args: [] }], -1)
-}
-
-/**
- * Get the JSON object of the root element.
- * @param {string} instanceId
- */
-export function getRoot (instanceId) {
-  const instance = instances[instanceId] || {}
-  if (!(instance.app instanceof Vue)) {
-    return new Error(`getRoot: instance ${instanceId} not found!`)
-  }
-  return instance.app.$el.toJSON()
-}
-
-/**
- * Receive tasks from native. Generally there are two types of tasks:
- * 1. `fireEvent`: an device actions or user actions from native.
- * 2. `callback`: invoke function which sent to native as a parameter before.
- * @param {string} instanceId
- * @param {array}  tasks
- */
-export function receiveTasks (instanceId, tasks) {
-  const instance = instances[instanceId] || {}
-  if (!(instance.app instanceof Vue)) {
-    return new Error(`receiveTasks: instance ${instanceId} not found!`)
-  }
-  const { callbacks, document } = instance
-  tasks.forEach(task => {
-    // `fireEvent` case: find the event target and fire.
-    if (task.method === 'fireEvent') {
-      const [nodeId, type, e, domChanges] = task.args
-      const el = document.getRef(nodeId)
-      document.fireEvent(el, type, e, domChanges)
-    }
-    // `callback` case: find the callback by id and call it.
-    if (task.method === 'callback') {
-      const [callbackId, data, ifKeepAlive] = task.args
-      const callback = callbacks[callbackId]
-      if (typeof callback === 'function') {
-        callback(data)
-        // Remove the callback from `callbacks` if it won't called again.
-        if (typeof ifKeepAlive === 'undefined' || ifKeepAlive === false) {
-          callbacks[callbackId] = undefined
-        }
-      }
-    }
-  })
-  // Finally `updateFinish` signal needed.
-  renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'updateFinish', args: [] }], -1)
-}
-
-/**
- * Register native modules information.
- * @param {object} newModules
- */
-export function registerModules (newModules) {
-  for (const name in newModules) {
-    if (!modules[name]) {
-      modules[name] = {}
-    }
-    newModules[name].forEach(method => {
-      if (typeof method === 'string') {
-        modules[name][method] = true
-      } else {
-        modules[name][method.name] = method.args
-      }
-    })
-  }
-}
-
-/**
- * Register native components information.
- * @param {array} newComponents
- */
-export function registerComponents (newComponents) {
-  const config = Vue.config
-  const newConfig = {}
-  if (Array.isArray(newComponents)) {
-    newComponents.forEach(component => {
-      if (!component) {
-        return
-      }
-      if (typeof component === 'string') {
-        components[component] = true
-        newConfig[component] = true
-      } else if (typeof component === 'object' && typeof component.type === 'string') {
-        components[component.type] = component
-        newConfig[component.type] = true
-      }
-    })
-    const oldIsReservedTag = config.isReservedTag
-    config.isReservedTag = name => {
-      return newConfig[name] || oldIsReservedTag(name)
-    }
-  }
-}
-
-// Hack `Vue` behavior to handle instance information and data
-// before root component created.
-Vue.mixin({
-  beforeCreate () {
-    const options = this.$options
-    const parentOptions = (options.parent && options.parent.$options) || {}
-
-    // root component (vm)
-    if (options.el) {
-      // record instance info
-      const instance = instances[activeId] || {}
-      this.$instanceId = activeId
-      options.instanceId = activeId
-      this.$document = instance.document
-
-      // set external data of instance
-      const dataOption = options.data
-      const internalData = (typeof dataOption === 'function' ? dataOption() : dataOption) || {}
-      options.data = Object.assign(internalData, instance.data)
-
-      // record instance by id
-      instance.app = this
-
-      activeId = undefined
-    } else {
-      this.$instanceId = options.instanceId = parentOptions.instanceId
-    }
-  }
-})
-
-/**
- * @deprecated Just instance variable `weex.config`
- * Get instance config.
- * @return {object}
- */
-Vue.prototype.$getConfig = function () {
-  const instance = instances[this.$instanceId] || {}
-  if (instance.app instanceof Vue) {
-    return instance.config
-  }
-}
-
-/**
- * Generate native module getter. Each native module has several
- * methods to call. And all the hebaviors is instance-related. So
- * this getter will return a set of methods which additionally
- * send current instance id to native when called. Also the args
- * will be normalized into "safe" value. For example function arg
- * will be converted into a callback id.
- * @param  {string}  instanceId
- * @return {function}
- */
-function genModuleGetter (instanceId) {
-  const instance = instances[instanceId]
-  return function (name) {
-    const nativeModule = modules[name] || []
-    const output = {}
-    for (const methodName in nativeModule) {
-      output[methodName] = (...args) => {
-        const finalArgs = args.map(value => {
-          return normalize(value, instance)
-        })
-        renderer.sendTasks(instanceId + '', [{ module: name, method: methodName, args: finalArgs }], -1)
-      }
-    }
-    return output
-  }
-}
-
-/**
- * Generate HTML5 Timer APIs. An important point is that the callback
- * will be converted into callback id when sent to native. So the
- * framework can make sure no side effect of the callabck happened after
- * an instance destroyed.
- * @param  {[type]} instanceId   [description]
- * @param  {[type]} moduleGetter [description]
- * @return {[type]}              [description]
- */
-function getInstanceTimer (instanceId, moduleGetter) {
-  const instance = instances[instanceId]
-  const timer = moduleGetter('timer')
-  const timerAPIs = {
-    setTimeout: (...args) => {
-      const handler = function () {
-        args[0](...args.slice(2))
-      }
-      timer.setTimeout(handler, args[1])
-      return instance.callbackId.toString()
-    },
-    setInterval: (...args) => {
-      const handler = function () {
-        args[0](...args.slice(2))
-      }
-      timer.setInterval(handler, args[1])
-      return instance.callbackId.toString()
-    },
-    clearTimeout: (n) => {
-      timer.clearTimeout(n)
-    },
-    clearInterval: (n) => {
-      timer.clearInterval(n)
-    }
-  }
-  return timerAPIs
-}
-
-/**
- * Call a new function body with some global objects.
- * @param  {object} globalObjects
- * @param  {string} code
- * @return {any}
- */
-function callFunction (globalObjects, body) {
-  const globalKeys = []
-  const globalValues = []
-  for (const key in globalObjects) {
-    globalKeys.push(key)
-    globalValues.push(globalObjects[key])
-  }
-  globalKeys.push(body)
-
-  const result = new Function(...globalKeys)
-  return result(...globalValues)
-}
-
-/**
- * Convert all type of values into "safe" format to send to native.
- * 1. A `function` will be converted into callback id.
- * 2. An `Element` object will be converted into `ref`.
- * The `instance` param is used to generate callback id and store
- * function if necessary.
- * @param  {any}    v
- * @param  {object} instance
- * @return {any}
- */
-function normalize (v, instance) {
-  const type = typof(v)
-
-  switch (type) {
-    case 'undefined':
-    case 'null':
-      return ''
-    case 'regexp':
-      return v.toString()
-    case 'date':
-      return v.toISOString()
-    case 'number':
-    case 'string':
-    case 'boolean':
-    case 'array':
-    case 'object':
-      if (v instanceof renderer.Element) {
-        return v.ref
-      }
-      return v
-    case 'function':
-      instance.callbacks[++instance.callbackId] = v
-      return instance.callbackId.toString()
-    default:
-      return JSON.stringify(v)
-  }
-}
-
-/**
- * Get the exact type of an object by `toString()`. For example call
- * `toString()` on an array will be returned `[object Array]`.
- * @param  {any}    v
- * @return {string}
- */
-function typof (v) {
-  const s = Object.prototype.toString.call(v)
-  return s.substring(8, s.length - 1).toLowerCase()
-}
+export * from 'weex/framework'

+ 422 - 0
src/platforms/weex/framework.js

@@ -0,0 +1,422 @@
+import Vue from 'weex/runtime/index'
+import renderer from 'weex/runtime/config'
+
+Vue.weexVersion = '__WEEX_VERSION__'
+export { Vue }
+
+const {
+  instances,
+  modules,
+  components
+} = renderer
+
+let activeId
+const oriIsReservedTag = (Vue && Vue.config && typeof Vue.config.isReservedTag === 'function') ? Vue.config.isReservedTag : function () {}
+
+/**
+ * Prepare framework config, basically about the virtual-DOM and JS bridge.
+ * @param {object} cfg
+ */
+export function init (cfg) {
+  renderer.Document = cfg.Document
+  renderer.Element = cfg.Element
+  renderer.Comment = cfg.Comment
+  renderer.sendTasks = cfg.sendTasks
+}
+
+/**
+ * Reset framework config and clear all registrations.
+ */
+export function reset () {
+  clear(instances)
+  clear(modules)
+  clear(components)
+  delete renderer.Document
+  delete renderer.Element
+  delete renderer.Comment
+  delete renderer.sendTasks
+  Vue.config.isReservedTag = oriIsReservedTag
+}
+
+/**
+ * Delete all keys of an object.
+ * @param {object} obj
+ */
+function clear (obj) {
+  for (const key in obj) {
+    delete obj[key]
+  }
+}
+
+/**
+ * Create an instance with id, code, config and external data.
+ * @param {string} instanceId
+ * @param {string} appCode
+ * @param {object} config
+ * @param {object} data
+ * @param {object} env { info, config, services }
+ */
+export function createInstance (
+  instanceId,
+  appCode = '',
+  config = {},
+  data,
+  env = {}
+) {
+  // Set active instance id and put some information into `instances` map.
+  activeId = instanceId
+
+  // Virtual-DOM object.
+  const document = new renderer.Document(instanceId, config.bundleUrl)
+
+  // All function/callback of parameters before sent to native
+  // will be converted as an id. So `callbacks` is used to store
+  // these real functions. When a callback invoked and won't be
+  // called again, it should be removed from here automatically.
+  const callbacks = []
+
+  // The latest callback id, incremental.
+  const callbackId = 1
+
+  instances[instanceId] = {
+    instanceId, config, data,
+    document, callbacks, callbackId
+  }
+
+  // Prepare native module getter and HTML5 Timer APIs.
+  const moduleGetter = genModuleGetter(instanceId)
+  const timerAPIs = getInstanceTimer(instanceId, moduleGetter)
+
+  // Prepare `weex` instance variable.
+  const weexInstanceVar = {
+    config,
+    document,
+    requireModule: moduleGetter
+  }
+  Object.freeze(weexInstanceVar)
+
+  // Each instance has a independent `Vue` variable and it should have
+  // all top-level public APIs.
+  const subVue = Vue.extend({})
+
+  // ensure plain-object components are extended from the subVue
+  subVue.options._base = subVue
+
+  // expose global utility
+  ;['util', 'set', 'delete', 'nextTick', 'version', 'weexVersion', 'config'].forEach(name => {
+    subVue[name] = Vue[name]
+  })
+
+  // The function which create a closure the JS Bundle will run in.
+  // It will declare some instance variables like `Vue`, HTML5 Timer APIs etc.
+  const instanceVars = Object.assign({
+    Vue: subVue,
+    weex: weexInstanceVar,
+    __weex_require_module__: weexInstanceVar.requireModule // deprecated
+  }, timerAPIs)
+  callFunction(instanceVars, appCode)
+
+  // Send `createFinish` signal to native.
+  renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'createFinish', args: [] }], -1)
+}
+
+/**
+ * Destroy an instance with id. It will make sure all memory of
+ * this instance released and no more leaks.
+ * @param {string} instanceId
+ */
+export function destroyInstance (instanceId) {
+  const instance = instances[instanceId] || {}
+  if (instance.app instanceof Vue) {
+    instance.app.$destroy()
+  }
+  delete instances[instanceId]
+}
+
+/**
+ * Refresh an instance with id and new top-level component data.
+ * It will use `Vue.set` on all keys of the new data. So it's better
+ * define all possible meaningful keys when instance created.
+ * @param {string} instanceId
+ * @param {object} data
+ */
+export function refreshInstance (instanceId, data) {
+  const instance = instances[instanceId] || {}
+  if (!(instance.app instanceof Vue)) {
+    return new Error(`refreshInstance: instance ${instanceId} not found!`)
+  }
+  for (const key in data) {
+    Vue.set(instance.app, key, data[key])
+  }
+  // Finally `refreshFinish` signal needed.
+  renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'refreshFinish', args: [] }], -1)
+}
+
+/**
+ * Get the JSON object of the root element.
+ * @param {string} instanceId
+ */
+export function getRoot (instanceId) {
+  const instance = instances[instanceId] || {}
+  if (!(instance.app instanceof Vue)) {
+    return new Error(`getRoot: instance ${instanceId} not found!`)
+  }
+  return instance.app.$el.toJSON()
+}
+
+/**
+ * Receive tasks from native. Generally there are two types of tasks:
+ * 1. `fireEvent`: an device actions or user actions from native.
+ * 2. `callback`: invoke function which sent to native as a parameter before.
+ * @param {string} instanceId
+ * @param {array}  tasks
+ */
+export function receiveTasks (instanceId, tasks) {
+  const instance = instances[instanceId] || {}
+  if (!(instance.app instanceof Vue)) {
+    return new Error(`receiveTasks: instance ${instanceId} not found!`)
+  }
+  const { callbacks, document } = instance
+  tasks.forEach(task => {
+    // `fireEvent` case: find the event target and fire.
+    if (task.method === 'fireEvent') {
+      const [nodeId, type, e, domChanges] = task.args
+      const el = document.getRef(nodeId)
+      document.fireEvent(el, type, e, domChanges)
+    }
+    // `callback` case: find the callback by id and call it.
+    if (task.method === 'callback') {
+      const [callbackId, data, ifKeepAlive] = task.args
+      const callback = callbacks[callbackId]
+      if (typeof callback === 'function') {
+        callback(data)
+        // Remove the callback from `callbacks` if it won't called again.
+        if (typeof ifKeepAlive === 'undefined' || ifKeepAlive === false) {
+          callbacks[callbackId] = undefined
+        }
+      }
+    }
+  })
+  // Finally `updateFinish` signal needed.
+  renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'updateFinish', args: [] }], -1)
+}
+
+/**
+ * Register native modules information.
+ * @param {object} newModules
+ */
+export function registerModules (newModules) {
+  for (const name in newModules) {
+    if (!modules[name]) {
+      modules[name] = {}
+    }
+    newModules[name].forEach(method => {
+      if (typeof method === 'string') {
+        modules[name][method] = true
+      } else {
+        modules[name][method.name] = method.args
+      }
+    })
+  }
+}
+
+/**
+ * Register native components information.
+ * @param {array} newComponents
+ */
+export function registerComponents (newComponents) {
+  const config = Vue.config
+  const newConfig = {}
+  if (Array.isArray(newComponents)) {
+    newComponents.forEach(component => {
+      if (!component) {
+        return
+      }
+      if (typeof component === 'string') {
+        components[component] = true
+        newConfig[component] = true
+      } else if (typeof component === 'object' && typeof component.type === 'string') {
+        components[component.type] = component
+        newConfig[component.type] = true
+      }
+    })
+    const oldIsReservedTag = config.isReservedTag
+    config.isReservedTag = name => {
+      return newConfig[name] || oldIsReservedTag(name)
+    }
+  }
+}
+
+// Hack `Vue` behavior to handle instance information and data
+// before root component created.
+Vue.mixin({
+  beforeCreate () {
+    const options = this.$options
+    const parentOptions = (options.parent && options.parent.$options) || {}
+
+    // root component (vm)
+    if (options.el) {
+      // record instance info
+      const instance = instances[activeId] || {}
+      this.$instanceId = activeId
+      options.instanceId = activeId
+      this.$document = instance.document
+
+      // set external data of instance
+      const dataOption = options.data
+      const internalData = (typeof dataOption === 'function' ? dataOption() : dataOption) || {}
+      options.data = Object.assign(internalData, instance.data)
+
+      // record instance by id
+      instance.app = this
+
+      activeId = undefined
+    } else {
+      this.$instanceId = options.instanceId = parentOptions.instanceId
+    }
+  }
+})
+
+/**
+ * @deprecated Just instance variable `weex.config`
+ * Get instance config.
+ * @return {object}
+ */
+Vue.prototype.$getConfig = function () {
+  const instance = instances[this.$instanceId] || {}
+  if (instance.app instanceof Vue) {
+    return instance.config
+  }
+}
+
+/**
+ * Generate native module getter. Each native module has several
+ * methods to call. And all the hebaviors is instance-related. So
+ * this getter will return a set of methods which additionally
+ * send current instance id to native when called. Also the args
+ * will be normalized into "safe" value. For example function arg
+ * will be converted into a callback id.
+ * @param  {string}  instanceId
+ * @return {function}
+ */
+function genModuleGetter (instanceId) {
+  const instance = instances[instanceId]
+  return function (name) {
+    const nativeModule = modules[name] || []
+    const output = {}
+    for (const methodName in nativeModule) {
+      output[methodName] = (...args) => {
+        const finalArgs = args.map(value => {
+          return normalize(value, instance)
+        })
+        renderer.sendTasks(instanceId + '', [{ module: name, method: methodName, args: finalArgs }], -1)
+      }
+    }
+    return output
+  }
+}
+
+/**
+ * Generate HTML5 Timer APIs. An important point is that the callback
+ * will be converted into callback id when sent to native. So the
+ * framework can make sure no side effect of the callabck happened after
+ * an instance destroyed.
+ * @param  {[type]} instanceId   [description]
+ * @param  {[type]} moduleGetter [description]
+ * @return {[type]}              [description]
+ */
+function getInstanceTimer (instanceId, moduleGetter) {
+  const instance = instances[instanceId]
+  const timer = moduleGetter('timer')
+  const timerAPIs = {
+    setTimeout: (...args) => {
+      const handler = function () {
+        args[0](...args.slice(2))
+      }
+      timer.setTimeout(handler, args[1])
+      return instance.callbackId.toString()
+    },
+    setInterval: (...args) => {
+      const handler = function () {
+        args[0](...args.slice(2))
+      }
+      timer.setInterval(handler, args[1])
+      return instance.callbackId.toString()
+    },
+    clearTimeout: (n) => {
+      timer.clearTimeout(n)
+    },
+    clearInterval: (n) => {
+      timer.clearInterval(n)
+    }
+  }
+  return timerAPIs
+}
+
+/**
+ * Call a new function body with some global objects.
+ * @param  {object} globalObjects
+ * @param  {string} code
+ * @return {any}
+ */
+function callFunction (globalObjects, body) {
+  const globalKeys = []
+  const globalValues = []
+  for (const key in globalObjects) {
+    globalKeys.push(key)
+    globalValues.push(globalObjects[key])
+  }
+  globalKeys.push(body)
+
+  const result = new Function(...globalKeys)
+  return result(...globalValues)
+}
+
+/**
+ * Convert all type of values into "safe" format to send to native.
+ * 1. A `function` will be converted into callback id.
+ * 2. An `Element` object will be converted into `ref`.
+ * The `instance` param is used to generate callback id and store
+ * function if necessary.
+ * @param  {any}    v
+ * @param  {object} instance
+ * @return {any}
+ */
+function normalize (v, instance) {
+  const type = typof(v)
+
+  switch (type) {
+    case 'undefined':
+    case 'null':
+      return ''
+    case 'regexp':
+      return v.toString()
+    case 'date':
+      return v.toISOString()
+    case 'number':
+    case 'string':
+    case 'boolean':
+    case 'array':
+    case 'object':
+      if (v instanceof renderer.Element) {
+        return v.ref
+      }
+      return v
+    case 'function':
+      instance.callbacks[++instance.callbackId] = v
+      return instance.callbackId.toString()
+    default:
+      return JSON.stringify(v)
+  }
+}
+
+/**
+ * Get the exact type of an object by `toString()`. For example call
+ * `toString()` on an array will be returned `[object Array]`.
+ * @param  {any}    v
+ * @return {string}
+ */
+function typof (v) {
+  const s = Object.prototype.toString.call(v)
+  return s.substring(8, s.length - 1).toLowerCase()
+}