精华内容
下载资源
问答
  • 来源:公众号【编程珠玑...什么是初始化初始化的是对数据对象或者变量赋予初始值。例如:intvalue=8;//声明整型变量并初始化为8intarr[]={1,2,3};//声明整型数组arr,并初始化其值为1,2,3为什么要初始化我们来看一...

    来源:公众号【编程珠玑】

    作者:守望先生

    前言

    什么是初始化?为什么要初始化?静态变量和局部变量的初始化又有什么区别?实际应用中应该怎么做?本文将一一回答这些问题。

    什么是初始化

    初始化指的是对数据对象或者变量赋予初始值。例如:

    int value = 8//声明整型变量并初始化为8
    int arr[] = {1,2,3}; //声明整型数组arr,并初始化其值为1,2,3

    为什么要初始化

    我们来看一个示例程序。
    test0.c程序清单如下:

    #include 
    #include 
    int main(void){
        int sum;
        int randNum;
        while(10 > sum)
        {
            randNum =  rand() % 10;

            sum += randNum;
            printf("rand num is %d,sum is %d\n",randNum,sum);
        }
        printf("the final sum is %d\n",sum);
        return 0;
    }

    程序随机产生0到9的数字,使得sum的值大于或等于10时,退出程序。
    编译并运行:

    gcc  -o test0 test0.c
    ./test0

    运行结果如下(每次运行结果可能不同):

    rand num is 3,sum is -4040865
    rand num is 6,sum is -4040859
    rand num is 7,sum is -4040852
    rand num is 5,sum is -4040847
    rand num is 3,sum is -4040844
    rand num is 5,sum is -4040839
    (省略其他内容)

    从运行结果来看,程序并没有达到我们的预期,这是为什么呢?

    很多读者可能已经知道,问题在于声明sum之后,没有为其赋初始值,在这样的情况下,sum的值是随机的,因此在一开始sum可能是一个很小的负数,导致多次循环出现。很显然,初始化避免使用了变量的“脏值”。而将sum的声明改成如下定义即可:

    int sum = 0

    如果将sum声明为静态变量,情况又会如何呢?

    //test1.c
    #include 
    #include 
    int main(void){
        static int sum;
        int randNum;
        while(10 > sum)
        {
            randNum =  rand() % 10;

            sum += randNum;
            printf("rand num is %d,sum is %d\n",randNum,sum);
        }
        printf("the final sum is %d\n",sum);
        return 0;
    }

    编译并运行:

    rand num is 3,sum is 3
    rand num is 6,sum is 9
    rand num is 7,sum is 16
    the final sum is 16

    在这种情况下,程序是能够符合我们预期的结果,这又是为什么呢?原因在于静态变量会被默认初始化。例如,int类型会被初始化为0。那么问题来了:

    • 为什么局部变量未初始化的时候的值是“脏值”?

    • 静态变量和局部变量为什么又不一样呢?

    在解答上面这两个问题之前,我们需要简单了解一下程序的存储空间布局。

    程序的存储空间布局

    C程序主要由以下几部分组成:

    • 正文段。即机器指令部分,为防止意外被修改,设为只读。

    • 初始化数据段。它包含了程序中需要明确赋初值的静态变量。

    • 未初始化数据段。它包含了程序中未赋初值的或初始化为0的静态变量,在程序开始执行之前,内核将此段中的数据初始化为0。

    • 栈。它保存了自动(局部)变量以及函数调用所要的信息。

    • 堆。用于动态内存分配。例如使用malloc函数进行内存分配。

    其中,正文段和数据段的内容是“静态”的,因为在程序被编译出来之后,在整个程序地址就确定了,而堆栈中的内容是”动态”变化的,它随着进行的运行而不断变化着,再加上栈随机化的策略,使得程序每次运行时,栈的地址也是不确定的。

    局部变量和静态变量的初始化有何不同

    有了前面的铺垫,就很好理解两者的差别了。
    未初始化的局部变量位于栈中,它的位置是不确定的,因此其值也是不确定的。当然,在windows下它的值是0xcccccccc,而“烫”字在MBCS字符集中的值为0xcccccccc,你说巧不巧?

    1bae612e6ff633e1063a0b4582f2a5ed.png

    而静态变量就不一样的,它的地址是确定的,并且存放在了数据段,而程序在运行之前,未初始化数据段的内容可以很方便地统一被初始化为0。这也就解释了前面的两个示例程序的结果为什么会不一样。我们加上一些打印,来看一看是否真的如此?

    //test2.c
    #include 
    #include 
    int main(void){
        static int sum;
        int randNum;
        while(10 > sum)
        {
            randNum =  rand() % 10;

            sum += randNum;
            printf("rand num is %d,sum is %d\n",randNum,sum);
        }
        printf("the final sum is %d\n",sum);
        printf("sum addr %p,randNum addr %p\n",&sum,&randNum);
        return 0;
    }

    编译并运行:

    gcc -o test2 test2.c

    运行结果1:

    rand num is 3,sum is 3
    rand num is 6,sum is 9
    rand num is 7,sum is 16
    the final sum is 16
    sum addr 0x60104c,randNum addr 0x7ffd0ea8cf54

    运行结果2:

    rand num is 3,sum is 3
    rand num is 6,sum is 9
    rand num is 7,sum is 16
    the final sum is 16
    sum addr 0x60104c,randNum addr 0x7ffff5e3ddb4

    在这里,sum是静态局部变量,而randNun是局部变量(自动变量),因此可以发现,sum的地址值总是不变的,而randNum的值却不断变化着。我们也可以通过nm命令查看sum的地址:

    nm test2 |grep sum
    000000000060104c b sum.2805

    总结

    我们来总结一下本文的主要内容:

    • 如果变量是静态的,它会被初始化为0;如果变量是自动的,它不会被初始化。

    • 静态的变量包括全局变量、静态全局变量、静态局部变量。

    • 使用局部变量之前对其进行初始化,避免使用“脏值”。

    • 从可读性考虑,静态变量也建议显示初始化。

    • 初始化为0的静态变量仍然存在未初始化数据段中(BSS段)。

    送几句熟悉的话给大家:

    手持两把锟斤拷,
    口中疾呼烫烫烫。
    脚踏千朵屯屯屯,
    笑看万物锘锘锘。

    思考

    test1.c的代码运行结果每次都一样吗?为什么?该如何修改才能使得每次的运行结果不一样?

    栈随机化的作用是什么?

    推荐阅读:

    什么是全局变量,局部变量,静态全局变量,静态局部变量

    关注公众号【编程珠玑】,获取更多Linux/C/C++/Python/Go/算法/工具等原创技术文章。后台免费获取经典电子书和视频资源

    2b1a947d0ff67e2e1e59b319b8f5af3f.png

    展开全文
  • 首先,我们要知道,HashMap 在储存数据结构的时候, 扩...4) 、负载因子0.75(听说这个值是经过大量实践算出来了的,这个值设定最合理),初始值16 的是数组的长度 16(1<<4 是2 的4次方 ,这样写计算...

    首先,我们要知道,HashMap 在储存数据结构的时候, 扩容最影响 HashMap 的性能 ;

    我们都知道 HashMap 的初始值是16 (1<<4) 、负载因子0.75(听说这个值是经过大量实践算出来了的,这个值设定最合理),初始值16 指的是数组的长度 16(1<<4 是2 的4次方 ,这样写计算机执行更快),当数组的容量达到 12( 16x0.75)时 ,这时开始扩容, 扩容为 32 (1<<5 即2的5次方)  ,每次扩容按2的倍数递增,扩容是为了减少 hash 碰撞 ,让链表的数据更少(最好链表上就一个数据,即为数组的下标数据);

    所以,我们合理的设置 hashmap 的初始长度(即数组长度),可有效降低扩容次数,那么如何设定呢?

    我总结下面的公式, 其中 total 为预估map存储的总数量 ,n 为位移数

    total *0.75 < 2^{n}     这个公式可以求出 n 的最小值  ,HashMap初始设定长度为1<<n

    下面简单计算一下  

    when  total  =  100  ,then n = 8  ;(1<<8  =  256 )

    when total = 1000 , then n = 11 ; (1<<11  = 2048)

    when total = 10000 ,then n = 14;(1<<14  = 16384 )

    展开全文
  • “响应式”,是当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新。举个简单的例子,对于模板:<div id="root">{{ name }}</div>创建一个 Vue ...

    aa92b976adb102919bbe427cc80a55d2.png

    Vue.js 其核心包括是一套“响应式系统”。

    “响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码。例如,视图渲染中使用了数据,数据改变后,视图也会自动更新

    举个简单的例子,对于模板:

    <div id="root">{{ name }}</div>

    创建一个 Vue 组件:

    var vm = new Vue({
      el: '#root',
      data: {
        name: 'zhangsan'
      }
    })
    

    代码执行后,页面上对应位置会显示:zhangsan .

    如果想改变显示的名字,只需要执行:

    vm.name = 'lisi'
    

    这样页面上就会显示修改后的名字了,且并不需要去手动修改 DOM 更新数据。

    下面我们就一起深入了解 Vue 的数据响应式原理,搞清楚响应式的实现机制。

    基本概念

    Vue 的响应式,核心机制是 观察者模式

    数据是被观察的一方,发生改变时,通知所有的观察者,这样观察者可以做出响应,比如,重新渲染然后更新视图。

    我们把依赖数据的观察者称为 watcher,那么这种关系可以表示为:

    data -> watcher

    数据可以有多个观察者,怎么记录这种依赖关系呢?

    Vue 通过在 data 和 watcher 间创建一个 dep 对象,来记录这种依赖关系:

    data - dep -> watcher
    

    dep 的结构很简单,除了唯一标识属性 id,另一个属性就是用于记录所有观察者的 subs:

    • id - number
    • subs - [Watcher]

    再来看 watcher。

    Vue 中 watcher 的观察对象,确切来说是一个求值表达式,或者函数。这个表达式或者函数,在一个 Vue 实例的上下文中求值或执行。这个过程中,使用到数据,也就是 watcher 所依赖的数据。用于记录依赖关系的属性是 deps,对应的是由 dep 对象组成的数组,对应所有依赖的数据。而表达式或函数,最终会作为求值函数记录到 getter 属性,每次求值得到的结果记录在 value 属性:

    • vm - VueComponent
    • deps - [Dep]
    • getter - function
    • value - *

    另外,还有一个重要的属性 cb,记录回调函数,当 getter 返回的值与当前 value 不同时被调用:

    • cb - function

    我们通过示例来整理下 data、dep、watcher 的关系:

    var vm = new Vue({
      data: {
        name: 'zhangsan',
        age: 18
      }
    })
    
    var userInfo = function ( ) {
      return this.name + ' - ' + this.age
    }
    
    var onUserInfoChange = function (userInfo) {
      console.log(userInfo)
    }
    
    vm.$watch(userInfo, onUserInfoChange)

    上面代码首先创建了一个新的 Vue 实例对象 vm,包含两个数据字段:name、age。对于这两个字段,Vue 会分别创建对应的 dep 对象,用于记录依赖该数据的 watcher。

    然后定义了一个求值函数 userInfo,注意,这个函数会在对应的 Vue 示例上下文中执行,也就是说,执行时的 this 对应的就是 vm。

    回调函数 onUserInfoChange 只是打印出新的 watcher 得到的新的值,由 userInfo 执行后生成。

    通过 vm.$watch(userInfo, onUserInfoChange),将 vm、getter、cb 集成在一起创建了新的 watcher。创建成功后,watcher 在内部已经记录了依赖关系,watcher.deps 中记录了 vm 的 name、age 对应的 dep 对象(因为 userInfo 中使用了这两个数据)。

    接下来,我们修改数据:

    vm.name = 'lisi'

    执行后,控制台会输出:

    lisi - 18

    同样,如果修改 age 的值,也会最终触发 onUserInfoChange 打印出新的结果。

    用个简单的图来整理下上面的关系:

    vm.name -- dep1
    vm.age  -- dep2
    watcher.deps --> [dep1, dep2]

    修改 vm.name 后,dep1 通知相关的 watcher,然后 watcher 执行 getter,得到新的 value,再将新的 value 传给 cb:

    vm.name -> dep1 -> watcher -> getter -> value -> cb

    可能你也注意到了,上面例子中的 userInfo,貌似就是计算属性的作用嘛:

    var vm = new Vue({
      data: {
        name: 'zhangsan',
        age: 18
      },
      computed: {
        userInfo() {
          return this.name + ' - ' + this.age
        }
      }
    })
    
    其实,计算属性在内部也是基于 watcher 实现的,每个计算属性对应一个 watcher,其 getter 也就是计算属性的声明函数。
    不过,计算属性对应的 watcher 与直接通过 vm.$watch() 创建的 watcher 略有不同,毕竟如果没有地方使用到这个计算属性,数据改变时都重新进行计算会有点浪费,这个在本文后面会讲到。

    上面描述了 data、dep、watcher 的关系,但是问题来了,这种依赖关系是如何建立的呢?数据改变后,又是如何通知 watcher 的呢?

    接下来我们深入 Vue 源码,搞清楚这两个问题。

    建立依赖关系

    Vue 源码版本 v2.5.13,文中摘录的部分代码为便于分析进行了简化或改写。

    响应式的核心逻辑,都在 Vue 项目的 “vue/src/core/observer” 目录下面。

    我们还是先顺着前面示例代码来捋一遍,首先是 Vue 实例化过程:

    var vm = new Vue(/* ... */)

    跟将传入的 data 进行响应式初始化相关的代码,在 “vue/src/core/instance/state.js” 文件中:

    observer/state.js#L149

    // new Vue() -> ... -> initState() -> initData()
    observe(data)

    函数 observe() 的目的是让传入的整个对象成为响应式的,它会遍历对象的所有属性,然后执行:

    observer/index.js#L64

    // observe() -> new Observer() -> observer.walk()
    defineReactive(obj, key, value)

    defineReactive() 就是用于定义响应式数据的核心函数。它主要做的事情包括:

    • 新建一个 dep 对象,与当前数据对应
    • 通过 Object.defineProperty() 重新定义对象属性,配置属性的 set、get,从而数据被获取、设置时可以执行 Vue 的代码

    OK,先到这里,关于 Vue 实例化告一段落。

    需要要注意的是,传入 Vue 的 data 的所有属性,会被代理到新创建的 Vue 实例对象上,这样通过 vm.name 进行操作的其实就是 data.name,这也是借助 Object.defineProperty() 实现的。

    再来看 watcher 的创建过程:

    vm.$watch(userInfo, onUserInfoChange)
    

    上述代码执行后,会调用:

    instance/state.js#L346

    // Vue.prototype.$watch()
    new Watcher(vm, expOrFn, cb, options)
    

    也就是:

    new Watcher(vm, userInfo, onUserInfoChange, {/* 略 */})
    

    在 watcher 对象创建过程中,除了记录 vm、getter、cb 以及初始化各种属性外,最重要的就是调用了传入的 getter 函数:

    observer/watcher.js#L103

    // new Watcher() -> watcher.get()
    value = this.getter.call(vm, vm)
    

    在 getter 函数的执行过程中,获取读取需要的数据,于是触发了前面通过 defineReactive() 配置的 get 方法:

    if (Dep.target) {
      dep.depend()
    }
    

    这是做什么呢?

    回到 watcher.get() 方法,在执行 getter 函数的前后,分别有如下代码:

    pushTarget(this)
    // ... 
    value = this.getter.call(vm, vm)
    // ...
    popTarget()
    

    pushTarget() 将当前 watcher 设置为 Dep.target,这样在执行到 vm.name 进一步执行对应的 get 方法时,Dep.target 的值就是这里的 watcher,然后通过 dep.depend() 就建立了依赖关系。

    dep.depend() 执行的逻辑就比较好推测了,将 watcher(通过 Dep.target 引用到)记录到 dep.subs 中,将 dep 记录到 watcher.deps 中 —— 依赖关系建立了!

    然后来看建立的依赖关系是如何使用的。

    数据变更同步

    继续前面的例子,执行如下代码时:

    vm.name = 'lisi'
    

    会触发通过 defineReactive() 配置的 set 方法,如果数据改变,那么:

    // defineReactive() -> set()
    dep.notify()
    

    通过 dep 对象来通知所有的依赖方法,于是 dep 遍历内部的 subs 执行:

    // dep.notify()
    watcher.update()
    

    这样 watcher 就被通知到了,知道了数据改变,从而继续后续的处理。这里先不展开。

    到这里,基本就搞清楚响应式的基本机制了,整理一下:

    • 通过 Object.defineProperty() 替换配置对象属性的 set、get 方法,实现“拦截”
    • watcher 在执行 getter 函数时触发数据的 get 方法,从而建立依赖关系
    • 写入数据时触发 set 方法,从而借助 dep 发布通知,进而 watcher 进行更新

    这样再看 Vue 官方的图就比较好理解了:

    d96d40055595f758b0f5a588b1f18119.png


    Vue 响应式原理

    图片来源:https://vuejs.org/v2/guide/reactivity.html
    上图中左侧是以组件渲染(render)作为 getter 函数来演示响应式过程的,这其实就是 RenderWatcher 这种特殊类型 watcher 的作用机制,后面还会再讲。

    计算属性

    本文前面提到过计算属性,在 Vue 中也是作为 watcher 进行处理的。计算属性(ComputedWatcher)特殊的地方在于,它其实没有 cb(空函数),只有 getter,并且它的值只在被使用时才计算并缓存。

    什么意思呢?

    首先,ComputedWatcher 在创建时,不会立即执行 getter(lazy 选项值为 false),这样一开始 ComputedWatcher 并没有和使用到的数据建立依赖关系。

    计算属性在被“get”时,首先执行预先定义的 ComputedGetter 函数,这里有一段特殊逻辑:

    instance/state.js#L238

    function computedGetter () {
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
    

    首先判断 watcher 是不是 dirty 状态,什么意思呢?

    计算属性对应的 watcher 初始创建的时候,并没有执行 getter,这个时候就会设置 dirty 为 true,这样当前获取计算属性的值的时候,会执行 getter 得到 value,然后标记 dirty 为 false。这样后续再获取计算属性的值,不需要再计算(执行 getter),直接就能返回缓存的 value。

    另外,计算属性的 watcher 在执行 watcher.evaluate() 是,进一步调用 watcher.get(),从而进行依赖收集。而依赖的数据在改变后,会通知计算属性的 watcher,但是 watcher 只是标记自身为 dirty,而不计算。这样的好处是可以减小开销,只在有地方需要计算属性的值时才执行计算。

    如果依赖的数据发生变更,计算属性只是标记 dirty 为 true,会不会有问题呢?

    解决这个问题的是上面代码的这一部分:

    if (Dep.target) {
      watcher.depend()
    }
    

    也就是说,如果当前有在收集依赖的 watcher,那么当前计算属性的 watcher 会间接地通过 watcher.depend() 将依赖关系“继承”给这个 watcher(watcher.depend() 内部是对每个 watcher.deps 记录的 dep 执行 dep.depend() 从而让依赖数据与当前的 watcher 建立依赖关系)。

    所以,依赖数据改变,依赖计算属性的 watcher 会直接得到通知,再来获取计算属性的值的时候,计算属性才进行计算求值。

    所以,依赖计算属性的 watcher 可以视为依赖 watcher 的 watcher。这样的 watcher 在 Vue 中最常见不过,那就是 RenderWatcher。

    RenderWatcher 及异步更新

    相信读过前文,你应该对 Vue 响应式原理有基本的认识。那么 Vue 是如何将其运用到视图更新中的呢?答案就是这里要讲的 RenderWatcher。

    RenderWatcher 首先是 watcher,只不过和计算属性对应的 ComputedWatcher 类似,它也有些特殊的行为。

    RenderWatcher 的创建,在函数 mountComponent 中:

    // Vue.prototype.$mount() -> mountComponent()
    let updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
    new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
    

    核心代码就在这里了。这个 watcher 就是 Vue 实例对象唯一的 RenderWatcher,在 watcher 构造函数中,会记录到 vm._watcher 上(普通 watcher 只会记录到 vm._watchers 数组中)。

    这个 watcher 也会在创建的最后执行 watcher.get(),也就是执行 getter 收集依赖的过程。而在这里,getter 就是 updateComponent,也就是说,执行了渲染+更新 DOM!并且,这个过程中使用到的数据也被收集了依赖关系。

    那么,理所当然地,在 render() 中使用到数据,发生改变,自然会通知到 RenderWatcher,从而最终更新视图!

    不过,这里会有个疑问:如果进行多次数据修改,那么岂不是要频繁执行 DOM 更新?

    这里就涉及到 RenderWatcher 的特殊功能了:异步更新

    结合前面内容,我们知道数据更新后,依赖该数据的 watcher 会执行 watcher.update(),这个在前文中没有展开,现在我们来看下这个方法:

    observer/watcher.js#L161

    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
    

    第一种情况,lazy 为 true,也就是计算属性,只是标记 dirty 为 true,并不立即计算。sync 为 true 的情况,这里也不管,不过看起来也很简单,就是立即执行计算嘛。

    最后的情况,就是这里 RenderWatcher 的场景,并不立即执行,也不是像计算属性那样标记为 dirty 就完了,而是放到了一个队列中。

    这个队列是干什么的呢?

    相关代码在 observer/scheduler.js 中,简单来说,就是实现了异步更新。

    理解其实现,首先要对浏览器的事件循环(Event Loop)机制有一定了解。如果你对事件循环机制不是很了解,可以看下面这篇文章:

    JavaScript 运行机制详解:再谈Event Loop - 阮一峰

    事件循环机制其实有点复杂,但只有理解事件循环,才能对这里 Vue 异步更新的方案有深入的认识。

    基于事件循环机制,RenderWatcher 将其 getter,也就是 updateComponent 函数异步执行,并且,多次触发
    RenderWatcher 的 update(),最终也只会执行一次 updateComponent,这样也就解决了性能问题。

    不过,随之而来的新问题是,修改完数据,不能直接反应到 DOM 上,而是要等异步更新执行过后才可以,这也是为什么 Vue 提供了 nextTick() 接口,并且要求开发者将对 DOM 的操作放到 nextTick() 回调中执行的原因。

    Vuex、Vue-Router

    再来看 Vue 套装中的 Vuex、Vue-Router,它们也是基于 Vue 的响应式机制实现功能。

    先来看 Vuex,代码版本 v3.0.1。

    Vuex

    在应用了 Vuex 的应用中,所有组件都可以通过 this.$store 来引用到全局的 store,并且在使用了 store 的数据后,还能在数据改变后得到同步,这其实就是响应式的应用了。

    首先看 this.$store 的实现,这个其实是通过全局 mixin 实现,代码在:

    src/mixin.js#L26

    this.$store = options.store || options.parent.$store
    

    这样在每个组件的 beforeCreate 时,会执行 $store 属性的初始化。

    而 store 数据的响应式处理,则是通过实例化一个 Vue 对象实现:

    src/store.js#L251

    // new Store() -> resetStoreVM()
    store._vm = new Vue({
      data: {
        $$state: state
      },
      computed // 对应 store.getters
    })
    

    结合前文的介绍,这里就很好理解了。因为 state 以及处理为响应式数据,而 getters 也创建为计算属性,所以对这些数据的使用,就建立依赖关系,从而可以响应数据改变了。

    Vue-Router

    Vue-Router 中,比较重要的数据是 $route,即当前的页面路由数据,在路由改变的时候,需要替换展示不同组件(router-view 组件实现)。

    vm.$route 实践上是来自 Vue.prototype,但其对应的值,最终对应到的是 router.history.current

    结合前面的分析,这里的 history.current 肯定得是响应式数据,所以,来找下对其进行初始化的地方,其实是在全局 mixin 的 beforeCreate 这里:

    v2.8.1/src/install.js#L27

    // beforeCreate
    Vue.util.defineReactive(this, '_route', this._router.history.current)

    这样 this._route 就是响应式的了,那么如果页面路由改变,又是如何修改这里的 _route 的呢?

    答案在 VueRouter 的 init() 这里:

    history.listen(route => {
      this.apps.forEach((app) => {
        app._route = route
      })
    })
    

    一个 router 对象可能和多个 vue 实例对象(这里叫作 app)关联,每次路由改变会通知所有的实例对象。

    再来看使用 vm.$route 的地方,也就是 VueRouter 的两个组件:

    • <router-link>
    • <router-view>

    两个组件都是在 render() 中,与 $route 建立了依赖关系,根据 route 的值进行渲染。这里具体过程就不展开了,感兴趣可以看下相关源码(v2.8.1/src/components),原理方面在 RenderWatcher 一节已经介绍过。

    实践:watch-it

    了解了以上这么多,也想自己试试,把 Vue 响应式相关的核心逻辑剥离出来,做一个单纯的数据响应式的库。由于只关注数据,所以在剥离过程中,将与 Vue 组件/实例对象相关的部分都移除了,包括 watcher.vm 也不再需要,这样 watcher.getter 计算时不再指定上下文对象。

    watch-it 只包括数据响应式相关的功能,暴露了4个接口:

    • defineReactive(obj, key, val):为对象配置一个响应式数据属性
    • observe(obj):将一个数据对象配置为响应式,内部对所有的属性执行 defineReactive
    • defineComputed(target, key, userDef):为对象配置一个计算属性,内部创建了 watcher
    • watch(fn, cb, options):监听求值函数中数据改变,变化时调用 cb,内部创建了 watcher

    来看一个使用示例:

    const { observe, watch } = require('@qmzm/watch-it')
    
    const data = {
      name: 'zhangsan',
      age: 18
    }
    
    observe(data)
    
    const userInfo = function() {
      return data.name + ' - ' + data.age
    }
    
    watch(userInfo, (value) => console.log(value))

    这样,当数据修改时,通过会打印出新的 userInfo 的值。

    展开全文
  • 作者:守望,Linux应用开发者,目前在公众号【编程珠玑】分享Linux/C/C++/数据结构与算法/工具等...什么是初始化初始化的是对数据对象或者变量赋予初始值。例如:intvalue=8;//声明整型变量并初始化为8intarr[]...
    作者:守望,Linux应用开发者,目前在公众号【编程珠玑】 分享Linux/C/C++/数据结构与算法/工具等原创技术文章和学习资源。

    前言

    什么是初始化?为什么要初始化?静态变量和局部变量的初始化又有什么区别?实际应用中应该怎么做?本文将一一回答这些问题。

    什么是初始化

    初始化指的是对数据对象或者变量赋予初始值。例如:

    int value = 8//声明整型变量并初始化为8
    int arr[] = {1,2,3}; //声明整型数组arr,并初始化其值为1,2,3

    为什么要初始化

    我们来看一个示例程序。
    test0.c程序清单如下:

    #include 
    #include 
    int main(void){
        int sum;
        int randNum;
        while(10 > sum)
        {
            randNum =  rand() % 10;

            sum += randNum;
            printf("rand num is %d,sum is %d
    "
    ,randNum,sum);
        }
        printf("the final sum is %d
    "
    ,sum);
        return 0;
    }

    程序随机产生0到9的数字,使得sum的值大于或等于10时,退出程序。
    编译并运行:

    gcc  -o test0 test0.c
    ./test0

    运行结果如下(每次运行结果可能不同):

    rand num is 3,sum is -4040865
    rand num is 6,sum is -4040859
    rand num is 7,sum is -4040852
    rand num is 5,sum is -4040847
    rand num is 3,sum is -4040844
    rand num is 5,sum is -4040839
    (省略其他内容)

    从运行结果来看,程序并没有达到我们的预期,这是为什么呢?

    很多读者可能已经知道,问题在于声明sum之后,没有为其赋初始值,在这样的情况下,sum的值是随机的,因此在一开始sum可能是一个很小的负数,导致多次循环出现。很显然,初始化避免使用了变量的“脏值”。而将sum的声明改成如下定义即可:

    int sum = 0

    如果将sum声明为静态变量,情况又会如何呢?

    //test1.c
    #include 
    #include 
    int main(void){
        static int sum;
        int randNum;
        while(10 > sum)
        {
            randNum =  rand() % 10;

            sum += randNum;
            printf("rand num is %d,sum is %d
    "
    ,randNum,sum);
        }
        printf("the final sum is %d
    "
    ,sum);
        return 0;
    }

    编译并运行:

    rand num is 3,sum is 3
    rand num is 6,sum is 9
    rand num is 7,sum is 16
    the final sum is 16

    在这种情况下,程序是能够符合我们预期的结果,这又是为什么呢?原因在于静态变量会被默认初始化。例如,int类型会被初始化为0。那么问题来了:

    • 为什么局部变量未初始化的时候的值是“脏值”?

    • 静态变量和局部变量为什么又不一样呢?

    在解答上面这两个问题之前,我们需要简单了解一下程序的存储空间布局。

    程序的存储空间布局

    C程序主要由以下几部分组成:

    • 正文段。即机器指令部分,为防止意外被修改,设为只读。

    • 初始化数据段。它包含了程序中需要明确赋初值的静态变量。

    • 未初始化数据段。它包含了程序中未赋初值的或初始化为0的静态变量,在程序开始执行之前,内核将此段中的数据初始化为0。

    • 栈。它保存了自动(局部)变量以及函数调用所要的信息。

    • 堆。用于动态内存分配。例如使用malloc函数进行内存分配。

    其中,正文段和数据段的内容是“静态”的,因为在程序被编译出来之后,在整个程序地址就确定了,而堆栈中的内容是”动态”变化的,它随着进行的运行而不断变化着,再加上栈随机化的策略,使得程序每次运行时,栈的地址也是不确定的。

    局部变量和静态变量的初始化有何不同

    有了前面的铺垫,就很好理解两者的差别了。
    未初始化的局部变量位于栈中,它的位置是不确定的,因此其值也是不确定的。当然,在windows下它的值是0xcccccccc,而“烫”字在MBCS字符集中的值为0xcccccccc,你说巧不巧?

    7de517bfb2efd001aa0e91556b2adfdb.png

    而静态变量就不一样的,它的地址是确定的,并且存放在了数据段,而程序在运行之前,未初始化数据段的内容可以很方便地统一被初始化为0。这也就解释了前面的两个示例程序的结果为什么会不一样。我们加上一些打印,来看一看是否真的如此?

    //test2.c
    #include 
    #include 
    int main(void){
        static int sum;
        int randNum;
        while(10 > sum)
        {
            randNum =  rand() % 10;

            sum += randNum;
            printf("rand num is %d,sum is %d
    "
    ,randNum,sum);
        }
        printf("the final sum is %d
    "
    ,sum);
        printf("sum addr %p,randNum addr %p
    "
    ,&sum,&randNum);
        return 0;
    }

    编译并运行:

    gcc -o test2 test2.c

    运行结果1:

    rand num is 3,sum is 3
    rand num is 6,sum is 9
    rand num is 7,sum is 16
    the final sum is 16
    sum addr 0x60104c,randNum addr 0x7ffd0ea8cf54

    运行结果2:

    rand num is 3,sum is 3
    rand num is 6,sum is 9
    rand num is 7,sum is 16
    the final sum is 16
    sum addr 0x60104c,randNum addr 0x7ffff5e3ddb4

    在这里,sum是静态局部变量,而randNun是局部变量(自动变量),因此可以发现,sum的地址值总是不变的,而randNum的值却不断变化着。我们也可以通过nm命令查看sum的地址:

    nm test2 |grep sum
    000000000060104c b sum.2805

    总结

    我们来总结一下本文的主要内容:

    • 如果变量是静态的,它会被初始化为0;如果变量是自动的,它不会被初始化。

    • 静态的变量包括全局变量、静态全局变量、静态局部变量。

    • 使用局部变量之前对其进行初始化,避免使用“脏值”。

    • 从可读性考虑,静态变量也建议显示初始化。

    • 初始化为0的静态变量仍然存在未初始化数据段中(BSS段)。

    送几句熟悉的话给大家:

    手持两把锟斤拷,
    口中疾呼烫烫烫。
    脚踏千朵屯屯屯,
    笑看万物锘锘锘。

    思考

    test1.c的代码运行结果每次都一样吗?为什么?该如何修改才能使得每次的运行结果不一样?

    栈随机化的作用是什么?


    ●编号510,输入编号直达本文

    ●输入m获取文章

    C语言与C++编程

    fc89f88bc348f0ee44ff55909d44d5c3.png

    分享C/C++技术文章

    展开全文
  • 什么初始化样式

    2019-10-24 17:14:28
    CSS初始化是重新设置浏览器的样式,但是因浏览器兼容的问题,不同的浏览器对标签的默认样式不同,如果不初始化会造成不同浏览器之间的显示差异。 所以开发一个项目时,第一件事应该是如何把他们统一。但是初始化...
  • 缺省什么

    2019-03-13 14:59:00
    一个属性、参数在被修改前的初始值。 计算机软件系统要求用户输入某些值而用户未给定时,系统自动赋予的事先设定的数值。 缺省,即系统默认状态。 转载于:...
  • 1、什么是类的加载类的加载的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是...
  • c中为什么整型数组自动初始化不是0

    千次阅读 2018-01-24 19:23:15
    值初始化是内置类型在函数体以外定义,而初始化为0;其他的则采用默认初始化,取决于编译器赋给它一个不确定的。 因此如果整型数组定义在函数体内(main函数也在此列),则采用默认初始化,不能确定。 ps:...
  • 作者:守望,Linux应用开发者,目前在公众号【编程珠玑】分享Linux/C/C++/数据结构与算法/工具等...什么是初始化初始化的是对数据对象或者变量赋予初始值。例如:intvalue=8;//声明整型变量并初始化为8intarr[]...
  • 那么对于上市公司股权结构来说,有哪些类型,不同类型的股票结构各有什么利弊呢?股权结构有不同的分类。一般来讲,股权结构有两层含义:第一个含义是股权集中度,即前五大股东持股比例。从这个意义上讲,股...
  • 与向量对应的只有大小,没有方向的量叫做数量(物理学中称标量)在这里,向量即一维数组,用 arange 函数创建向量是最简单的方式之一:arange函数也可以指定初始值、终止值和步长来创建一维数组: 向量...
  • 初始化的是对数据对象或者变量赋予初始值。例如: int value = 8; //声明整型变量并初始化为8 int arr[] = {1,2,3}; //声明整型数组arr,并初始化其值为1,2,3 为什么要初始化 我们来看一个示例程序。...
  • 如果用一级指针,则只能修改指针所内容,却无法修改指针的,也就是无法修改指针所在的(指向) 内存块。 所以创建链表和销毁链表需要二级指针或者一级指针引用 typedef struct Node{ Ele...
  • 一说到java中的变量,我只想表达的是,这应该是所有java初级入门的小伙伴们上的第一课吧,那么你还记得Java变量具体是指什么吗?其中的静态变量呢?首先我们先来了解一下,变量是什么?变量是用来存数的,代词,指代的...
  • 如题,什么是内置,或者复合类型??如果是 一般的float,int,或者一个结构体的引用,是不是不初始化会出问题?? 新手求解。 内置类型(built-in):像int ...初始值可能是0(静态对象),可能是随机数(堆,栈对象) ...
  • 这里说的 变量初始值: int a=3; 这种人为的 不是jvm自动给变量赋初值那种 静态部分: 顺序:静态代码块 >静态变量初始化值 非静态部分: 顺序: 非静态代码块> 非静态变量初始化值 系统默认初始...
  • 权重初始

    千次阅读 2018-01-06 21:12:12
    权重初始的是在神经网络开始训练之前预先设定的过程,那么问题来了 为什么权重需要初始化呢? 如果权重不初始化,全部为0,那么每个神经节点的均相同,因此在反向传播时,每个权重的梯度为输入该节点的...
  • CLR支持两种类型:引用类型和类型 关于引用类型,你必须要知道的: 内存必须从托管堆分配 堆上分配的每个对象都有一些额外成员,这些成员必须初始化 ...类型实例变量中不包含指向实例的...
  • 初始化的是对数据对象或者变量赋予初始值。例如: int value = 8; //声明整型变量并初始化为8 int arr[] = {1,2,3}; //声明整型数组arr,并初始化其值为1,2,3 为什么要初始化 我们来看一个示例程序。 te...
  • java中的类型有基本数据类型和复合类型。...引用必须进行初始化定义了一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的。为引用赋值实际上是把赋给了与引用绑定的对象;获取引用的实际上是获...
  • java 动态初始化和静态初始化的区别

    千次阅读 2014-04-11 23:18:03
    而动态的话,声明时候只是给数组分配了内存大小空间,空间里除了初始值(数值类型是0,引用类型为NULL)什么也没有,实际放什么值是要由后面的程序来指定的,因为一开始放什么,我们也许也并不清楚
  • 初始C语言

    2018-10-25 10:48:58
    通俗而言,算法是一个明确的计算过程,可以以一些或一组作为输入并产生一些或一组作为输出。因此算法就是输入转为输出的一系列计算步骤。 枚举法(穷举法) 迭代法 递归法 流程图 流程图是算法的一种图形表示...
  • 口令中的突出主音是()。偏心受力构件进行钢筋代换时应按()分别代换采用提净法炮制的药物是()可能导致锅炉爆炸的主要原因是什么?()一个成年人的全身约有的细胞数为:()每个神经元都包含细胞体和()两部分。远源杂种...
  • C++初始化总览

    2019-08-25 13:18:51
    C++的初始化有很多方式:默认初始化,值初始化,直接初始化,拷贝初始化,列表初始化。这些方式之间有什么区别与联系呢?我们一一来看。 默认初始化 默认初始化是定义变量时没有指定初值时进行的初始化操作。例如...
  • C++之初始化问题

    2014-03-11 12:36:00
    初始创建变量并且给它赋初值,而赋值则是擦除对象的当前并用新代替。C++支持两种初始化变量的方式:复制初始化和直接初始化: int ival(1000);//直接初始化是将初始化式放在括号里 int ival=1000;//复制...
  • 与向量对应的只有大小,没有方向的量叫做数量(物理学中称标量)在这里,向量即一维数组,用 arange 函数创建向量是最简单的方式之一:arange函数也可以指定初始值、终止值和步长来创建一维数组:向量还...
  • C++的各种初始

    2017-12-28 16:35:15
    C++的初始化有很多方式:默认初始化,值初始化,直接初始化,拷贝初始化,列表初始化。这些方式之间有什么区别与联系呢?我们一一来看。 1.默认初始化默认初始化是定义变量时没有指定初值时进行的初始化操作。例如...
  • 初始化是对数据对象或者变量赋予初始值。例如: int a=3; //声明整型变量并初始化为3 int arr[ ]={0};//声明整型数组arr,并初始化其值为0 为什么要初始化 例: #include“stdio.h" #include"stdlib.h" ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 740
精华内容 296
关键字:

初始值指什么