一、Vue2.0的生命周期

Vue2.0的整个生命周期有八个:分别是 1.beforeCreate,2.created,3.beforeMount,4.mounted,5.beforeUpdate,6.updated,7.beforeDestroy,8.destroyed。

用官方的一张图就可以清晰的了解整个生命周期:

Vue最新源码下载:地址

二:源码分析

1.先看new Vue实例的方法

创建Vue实例的文件是: src/core/instance/index.js

function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}

Vue的构造函数调用了this._init()方法,this._init()方法存在Vue的原型链中。在src/core/instance/init.js文件中:

export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
// a flag to avoid this being observed
vm._isVue = true
// merge options 第一步: options参数的处理
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else 第二步:renderProxy */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
// 第三步:vm的生命周期相关变量初始化
initLifecycle(vm)
// 第四步:vm的事件监听初始化
initEvents(vm)
// 第五步: render
initRender(vm)
callHook(vm, 'beforeCreate')
// 第六步:vm的状态初始化,prop/data/computed/method/watch都在这里完成初始化
initState(vm)
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}

接下来继续分析每一步的详细实现。

第一步: options参数的处理

 // merge options 第一步: options参数的处理
if (options && options._isComponent) {
// optimize internal component instantiation 优化内部组件实例
// since dynamic options merging is pretty slow, and none of the 因为动态options融合比较慢,而内部组件options不需要特别处理
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}

initInternalComponent的方法为:

function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration. 做这些是因为它比动态计数要快
opts.parent = options.parent
opts.propsData = options.propsData
opts._parentVnode = options._parentVnode
opts._parentListeners = options._parentListeners
opts._renderChildren = options._renderChildren
opts._componentTag = options._componentTag
opts._parentElm = options._parentElm
opts._refElm = options._refElm
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}

Vue是一套组件化系统,子组件的options必然受到父组件的影响、即使是同一个组件,我们也有公用的options(挂载在构造器上)和差异的options(实例传入的options),因此处理options时我们要处理四个相关的options:

  • 父组件构造器上的options
  • 父组件实例上的options
  • 当前组件构造器上的options
  • 当前组件实例化传入的options
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)

resolveConstructorOptions的方法为:

export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
if (Ctor.super) { // 如果有父级
const superOptions = Ctor.super.options // 获取父级的options
const cachedSuperOptions = Ctor.superOptions // 获取父级缓存的options
const extendOptions = Ctor.extendOptions // 获取自身的options
if (superOptions !== cachedSuperOptions) { // 如果父级options有变化
// super option changed
Ctor.superOptions = superOptions // 更新缓存
extendOptions.render = options.render
extendOptions.staticRenderFns = options.staticRenderFns
extendOptions._scopeId = options._scopeId
options = Ctor.options = mergeOptions(superOptions, extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}

接下来就重点看mergeOptions(文件位置在src\core\util\options.js)的实现了:

/**
* Merge two option objects into a new one. 将两个参数融合成一个
* Core utility used in both instantiation and inheritance. 核心公用的会被用于实例和继承中
*/
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
// 统一props格式
normalizeProps(child)
// 统一directives的格式
normalizeDirectives(child)
const extendsFrom = child.extends
// 如果存在child.extends
if (extendsFrom) {
parent = typeof extendsFrom === 'function'
? mergeOptions(parent, extendsFrom.options, vm)
: mergeOptions(parent, extendsFrom, vm) // 递归调用该方法
}
if (child.mixins) {
//如果存在child.mixins
for (let i = 0, l = child.mixins.length; i < l; i++) {
let mixin = child.mixins[i]
if (mixin.prototype instanceof Vue) {
mixin = mixin.options
}
parent = mergeOptions(parent, mixin, vm)
}
}
//针对不同的键值,采用不同的merge策略
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}

上面采取了对不同的field采取不同的策略,Vue提供了一个strats对象,其本身就是一个hook,如果strats有提供特殊的逻辑,就走strats,否则走默认merge逻辑。

/**
* Option overwriting strategies are functions that handle
* how to merge a parent option value and a child option
* value into the final value.
*/
const strats = config.optionMergeStrategies

第二步:renderProxy

主要是定义了vm._renderProxy,这是后期为render做准备的

/* istanbul ignore else 第二步:renderProxy */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}

作用是在render中将this指向vm._renderProxy。一般而言,vm._renderProxy是等于vm的,但在开发环境,Vue动用了Proxy这个新API

看下initProxy(存放于src\core\instance\proxy.js) 这个方法

 initProxy = function initProxy (vm) {
if (hasProxy) {
// determine which proxy handler to use 确定用哪个proxy handler
const options = vm.$options
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler // getHandler和hasHandler在上面有定义
vm._renderProxy = new Proxy(vm, handlers)
} else {
vm._renderProxy = vm
}
}
Proxy 学习资源

ES6规范定义了一个全新的全局构造函数:代理(Proxy)。它可以接受两个参数:目标对象(vm)和句柄对象(handlers)。

一个简单示例:

var target = {}, handler = {};
var proxy = new Proxy(target, handler);

1.代理和目标对象之间的关系:

代理的行为很简单:将代理的所有内部方法转发至目标。简单来说,如果调用proxy.[[Enumerate]](),就会返回target.[[Enumerate]]()

现在,让我们尝试执行一条能够触发调用proxy.[[Set]]()方法的语句。

此时target的结果看看

2.代理和句柄对象的关系:

句柄对象的方法可以覆写任意代理的内部方法。举个例子,定义一个handler.set()方法来拦截所有给对象属性赋值的行为:

 var target = {};
var handler = {
set: function (target, key, value, receiver) {
throw new Error("请不要为这个对象设置属性。");
}
};
var proxy = new Proxy(target, handler);

结果:

第三步:vm的生命周期相关变量初始化

// 第三步:vm的生命周期相关变量初始化
initLifecycle(vm)

initLifecycle该方法存在于src\core\instance\lifecycle.js文件中

export function initLifecycle (vm: Component) {
const options = vm.$options // locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
} vm.$parent = parent
vm.$root = parent ? parent.$root : vm vm.$children = []
vm.$refs = {} vm._watcher = null
vm._inactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}

第四步:vm的事件监听初始化

// 第四步:vm的事件监听初始化
initEvents(vm)

initEvents方法存在于src\core\instance\event.js中

export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, vm)
}

updateListeners方法存在于src\core\vdom\helper\update-listeners.js

export function updateListeners (
on: Object,
oldOn: Object,
add: Function,
remove: Function,
vm: Component
) {
let name, cur, old, event
for (name in on) {
cur = on[name]
old = oldOn[name]
event = normalizeEvent(name)
if (!cur) {
process.env.NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
vm
)
} else if (!old) {
// 新添加的listener
if (!cur.invoker) {
cur = on[name] = createEventHandle(cur)
}
add(event.name, cur.invoker, event.once, event.capture)
} else if (cur !== old) {
// 替换旧的事件监听
old.fn = cur
on[name] = old
}
}
// 删除无用的listeners
for (name in oldOn) {
if (!on[name]) {
event = normalizeEvent(name)
remove(event.name, oldOn[name].invoker, event.capture)
}
}
}

第五步: render

// 第五步: render
initRender(vm)

initRender存放于src\core\instance\render.js

export function initRender (vm: Component) {
vm.$vnode = null // the placeholder node in parent tree 在父级树上的提示节点?
vm._vnode = null // the root of the child tree 子级的根节点
vm._staticTrees = null
const parentVnode = vm.$options._parentVnode
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject
// bind the createElement fn to this instance 绑定创建元素到这个实例
// so that we get proper render context inside it. 以方便我们能获得正确的渲染内容
// args order: tag, data, children, normalizationType, alwaysNormalize 参数提供: tag,data, children,normalizationType,alwaysNormalize
// internal version is used by render functions compiled from templates 内部版本用来编译从templates来的函数?
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // 箭头函数,相当于function(a,b,c,d) { return createElement(vm, a, b, c, d, false) }
// normalization is always applied for the public version, used in
// user-written render functions. 统一化?
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}

createElement方法存放于src\core\vdom\create-element.js

// wrapper function for providing a more flexible interface 封装方法用来提供一个可扩展性的接口
// without getting yelled at by flow 不是流程式
export function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
children = data
data = undefined
}
if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE
return _createElement(context, tag, data, children, normalizationType)
}

_createElement方法也在这个文件中

export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode {
if (data && data.__ob__) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// support single function children as default scoped slot
if (Array.isArray(children) &&
typeof children[0] === 'function') {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
if (vnode) {
if (ns) applyNS(vnode, ns)
return vnode
} else {
return createEmptyVNode()
}
}

文件中引用了VNode这个类和createEmptyVNode方法,这两个东西存放于src\core\vdom\vnode.js

export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
functionalContext: Component | void; // only for functional component root nodes
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node? constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions
) {
this.tag = tag
this.data = data
this.children = children
this.text = text
this.elm = elm
this.ns = undefined
this.context = context
this.functionalContext = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
this.parent = undefined
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
this.isCloned = false
this.isOnce = false
} // DEPRECATED: alias for componentInstance for backwards compat.
/* istanbul ignore next */
get child (): Component | void {
return this.componentInstance
}
}

createEmptyVNode方法

export const createEmptyVNode = () => {
const node = new VNode()
node.text = ''
node.isComment = true
return node
}

第六步:vm的状态初始化,prop/data/computed/method/watch都在这里完成初始化

 // 第六步:vm的状态初始化,prop/data/computed/method/watch都在这里完成初始化
initState(vm)

initState存放于src\core\instance\state.js

export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch) initWatch(vm, opts.watch)
}

vm的状态初始化是整个初始化中最复杂的异步,其data、props、methods、computed、watch都在这一步进行初始化,因此这一步也是Vue真正的创建。

initProps
function initProps (vm: Component, propsOptions: Object) {
const propsData = vm.$options.propsData || {}
const props = vm._props = {}
// cache prop keys so that future props updates can iterate using Array
// instead of dynamic object key enumeration.
const keys = vm.$options._propKeys = []
const isRoot = !vm.$parent
// root instance props should be converted
observerState.shouldConvert = isRoot
for (const key in propsOptions) {
keys.push(key)
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
if (isReservedProp[key]) {
warn(
`"${key}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
defineReactive(props, key, value, () => { // 监控prop的变化
if (vm.$parent && !observerState.isSettingProps) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})
} else {
defineReactive(props, key, value)
}
// static props are already proxied on the component's prototype
// during Vue.extend(). We only need to proxy props defined at
// instantiation here.
if (!(key in vm)) {
proxy(vm, `_props`, key)
}
}
observerState.shouldConvert = true
}
initMethods
function initMethods (vm: Component, methods: Object) {
for (const key in methods) {
vm[key] = methods[key] == null ? noop : bind(methods[key], vm) // 作用域重新绑定
if (process.env.NODE_ENV !== 'production' && methods[key] == null) {
warn(
`method "${key}" has an undefined value in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
}
}
initData
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? data.call(vm)
: data || {}
if (!isPlainObject(data)) {
// 保证data必须为纯对象
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
let i = keys.length
while (i--) {
if (props && hasOwn(props, keys[i])) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${keys[i]}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(keys[i])) {
proxy(vm, `_data`, keys[i]) //将属性代理到vm上
}
}
// observe data 将data转换为监控对象
observe(data, true /* asRootData */)
}
initComputed
const computedWatcherOptions = { lazy: true }

function initComputed (vm: Component, computed: Object) {
const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) {
const userDef = computed[key]
const getter = typeof userDef === 'function' ? userDef : userDef.get
// create internal watcher for the computed property.
watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions) // component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
// at instantiation here.
if (!(key in vm)) {
defineComputed(vm, key, userDef)
}
}
} export function defineComputed (target: any, key: string, userDef: Object | Function) {
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = createComputedGetter(key)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop
sharedPropertyDefinition.set = userDef.set
? userDef.set
: noop
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}

computed其实本身也是一种特殊的并且lazy的watcher,在get时它作为所计算的属性依赖而被收集,同时它把依赖自己的watcher也添加到属性的依赖中去,这样当原属性变化时,就会通知到依赖computed的依赖重新获取最新值。

function createComputedGetter (key) {
return function computedGetter () {
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
if (watcher.dirty) {
// 将自己添加到属性的依赖列表中去
watcher.evaluate()
}
if (Dep.target) {
// 将依赖watcher的依赖也收集到属性依赖列表中去
watcher.depend()
}
return watcher.value
}
}
}
initWatch
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
// 可以是数组,为key创建多个watcher
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
} function createWatcher (vm: Component, key: string, handler: any) {
let options
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
// 如果handle传入为字符串,则直接找vm上的方法,一般是methods中定义的方法,这也是methods的初始化要先于watch初始化的原因
if (typeof handler === 'string') {
handler = vm[handler]
}
vm.$watch(key, handler, options) // 没找到$watch在原型上的定义
}

经过这些初始化的步骤,一个组件就被创造出来了,紧接着就可以callHook(vm, 'created')。

至此,我们主要了解了Vue整个生命周期,以及Vue创建(created)前的过程,从生命周期图中可以看到还有重要一步 Observe Data没有分析,状态初始化过程中用到监控对象如observe(data),依赖收集Dep等等,分析完Observe Data 应该就可以了解到Vue的数据绑定原理,这个分析留给下一篇文章。

参考资料:http://blog.cgsdream.org/2016/11/11/vue-source-analysis-2/

Vue2.0源码阅读笔记--生命周期的更多相关文章

  1. Vue2.0源码阅读笔记--双向绑定实现原理

    上一篇 文章 了解了Vue.js的生命周期.这篇分析Observe Data过程,了解Vue.js的双向数据绑定实现原理. 一.实现双向绑定的做法 前端MVVM最令人激动的就是双向绑定机制了,实现双向 ...

  2. Vue2.0源码阅读笔记(一):选项合并

      Vue本质是上来说是一个函数,在其通过new关键字构造调用时,会完成一系列初始化过程.通过Vue框架进行开发,基本上是通过向Vue函数中传入不同的参数选项来完成的.参数选项往往需要加以合并,主要有 ...

  3. Vue2.0源码阅读笔记(四):nextTick

      在阅读 nextTick 的源码之前,要先弄明白 JS 执行环境运行机制,介绍 JS 执行环境的事件循环机制的文章很多,大部分都阐述的比较笼统,甚至有些文章说的是错误的,以下为个人理解,如有错误, ...

  4. Vue2.0源码阅读笔记(二):响应式原理

      Vue是数据驱动的框架,在修改数据时,视图会进行更新.数据响应式系统使得状态管理变的简单直接,在开发过程中减少与DOM元素的接触.而深入学习其中的原理十分有必要,能够回避一些常见的问题,使开发变的 ...

  5. Vue2.0源码阅读笔记(三):计算属性

      计算属性是基于响应式依赖进行缓存的,只有在相关响应式依赖发生改变时才会重新求值,这种缓存机制在求值消耗比较大的情况下能够显著提高性能. 一.计算属性初始化   Vue 在做数据初始化时,通过 in ...

  6. tomcat源码阅读之生命周期(LifeCycle)

    一.事件机制流程: 1.     当外部事件源发生事件(比如点击了按钮,数据发生改变更新等)时,事件源将事件封装成事件对象Event: 2.     将事件对象交由对应的事件派发器Dispatcher ...

  7. Linux 0.11源码阅读笔记-文件管理

    Linux 0.11源码阅读笔记-文件管理 文件系统 生磁盘 未安装文件系统的磁盘称之为生磁盘,生磁盘也可以作为文件读写,linux中一切皆文件. 磁盘分区 生磁盘可以被分区,分区中可以安装文件系统, ...

  8. Linux 0.11源码阅读笔记-中断过程

    Linux 0.11源码阅读笔记-中断过程 是什么中断 中断发生时,计算机会停止当前运行的程序,转而执行中断处理程序,然后再返回原被中断的程序继续运行.中断包括硬件中断和软件中断,硬中断是由外设自动产 ...

  9. Linux 0.11源码阅读笔记-总览

    Linux 0.11源码阅读笔记-总览 阅读源码的目的 加深对Linux操作系统的了解,了解Linux操作系统基本架构,熟悉进程管理.内存管理等主要模块知识. 通过阅读教复杂的代码,锻炼自己复杂项目代 ...

随机推荐

  1. CSS学习中的瓶颈期深入分析

    虽已数年,但未就学习专门写过文章,这回破处了.苍蝇不叮没有缝隙的鸡蛋,领导不做没有跟拍的表演,同样,想到写CSS学习的文章也是有原因的(虽然我的不少行为没有原因). 情景再现(尊重隐私,下面故事中人名 ...

  2. 如何深入学习CSS

    学习CSS有了一定基础后,有的人会觉得好象没有什么学的.因为知道一些基本的理论性的东西.CSS说它容易是因为它的知识点有限.说它难学就在于各浏览器对CSS的支持程度不同.如何深入学习我给出以下几点见意 ...

  3. windows下查找指定端口被哪个程序占用

    在Windows环境下,用netstat命令查看某个端口号是否占用,为哪个进程所占用. eg.查看端口号为61078被哪个程序占用 1.查看端口号为61079被哪个PID所占用:Netstat –an ...

  4. 修改替换/system/framework/framework.jar后重启手机为何没有效果?

    自Android 5.0开始android默认使用art(Android4.4开始有实验性质的art),取代原来的Dalvik, art会加载boot.art和boot.oat两个文件(静态编译优化, ...

  5. The Elements of Statistical Learning第3章导读

    1. 公式(3.4)的推导. 可以直接对公式(3.3)中的$\beta_0$求导就得到$\hat{\beta}_0=\bar{y}-\beta_1\bar{x}$. 对公式(3.3)中的$\beta_ ...

  6. android studio 更新失败解决办法

    在andriod studio目录下找到studio.exe.vmoptions这个文件,用记事本打开,在后面加上 -Djava.net.preferIPv4Stack=true -Didea.upd ...

  7. IOS开发中使用CNContact\CNMutableContact 对通讯录增删改查

    IOS开发中使用CNContact\CNMutableContact 对通讯录增删改查 首先当然是把CNcontact包含在工程中: @import Contacts; 1.下面是增加联系人的程序段: ...

  8. cocos2d-x介绍

    总体来说,cocos2d-x是一个优秀的库. Cocos2d-x没有很复杂的一个架构,基本上是一些以单件形式提供的管理器和是一些围绕SceneGraph(CCNode及其派生类)展开的类.这个设计使得 ...

  9. Heka GeoIpDecoder 配置

    Prepare: 安装geoip-api-c,确保/usr/include/GeoIP.h存在: 源码编译安装Heka (容易出现问题): 下载GeoLiteCity.dat数据库. 配置文件举例: ...

  10. ARM处理器工作模式

    学习ARM处理器参考的首选资料是ARM Architecture Reference Manual,是最专业权威的学习资料. ARM处理器共有7种工作模式,如表1-1和1-2所示: 表1-1 处理器工 ...