杨川宝

杨川宝 查看完整档案

上海编辑山东财经大学  |  计算机 编辑七牛云存储  |  前端工程师 编辑填写个人主网站
编辑

vue react angular 七牛云存储前端工程师

个人动态

杨川宝 关注了问题 · 2016-11-08

解决请问我这是什么原因?【网络安全,nginx,php,js相关】

已解决,更换了jquery导入为 七牛云静态资源加速。然后就不出现这个问题了。
说明js文件内部有问题。也给自己提了个醒,不要乱使用外部js。谢谢回答的同学!

clipboard.png

偶尔刷新页面后,正常来说应该是都来自一个地址的。为什么会有三个从外面地址请求过来的东西?(上面画黑线的都是同一个地址,画黄色线的就是来自其他的地址)

正常的应该是这样的:

clipboard.png

我是前端,我不是很懂这是为什么?(自己猜测是不是路由器或者服务器被劫持?)

1.并没有使用cdn。

2.js引用只有jquery 和 layui 2个。

3.就是个登录页面, 并没有使用外部资源的js。

4.这是页面头部:
clipboard.png

关注 8 回答 6

杨川宝 关注了问题 · 2016-11-08

解决请问我这是什么原因?【网络安全,nginx,php,js相关】

已解决,更换了jquery导入为 七牛云静态资源加速。然后就不出现这个问题了。
说明js文件内部有问题。也给自己提了个醒,不要乱使用外部js。谢谢回答的同学!

clipboard.png

偶尔刷新页面后,正常来说应该是都来自一个地址的。为什么会有三个从外面地址请求过来的东西?(上面画黑线的都是同一个地址,画黄色线的就是来自其他的地址)

正常的应该是这样的:

clipboard.png

我是前端,我不是很懂这是为什么?(自己猜测是不是路由器或者服务器被劫持?)

1.并没有使用cdn。

2.js引用只有jquery 和 layui 2个。

3.就是个登录页面, 并没有使用外部资源的js。

4.这是页面头部:
clipboard.png

关注 8 回答 6

杨川宝 发布了文章 · 2016-10-31

vue2.0源码分析之理解响应式架构

分享前啰嗦

我之前介绍过vue1.0如何实现observerwatcher。本想继续写下去,可是vue2.0横空出世..所以
直接看vue2.0吧。这篇文章在公司分享过,终于写出来了。我们采用用最精简的代码,还原vue2.0响应式架构实现
以前写的那篇 vue 源码分析之如何实现 observer 和 watcher可以作为本次分享的参考。
不过不看也没关系,但是最好了解下Object.defineProperty

本文分享什么

理解vue2.0的响应式架构,就是下面这张图
vuedeptrace.jpg

顺带介绍他比react快的其中一个原因

本分实现什么

const demo = new Vue({
  data: {
    text: "before",
  },
  //对应的template 为 <div><span>{{text}}</span></div>
  render(h){
    return h('div', {}, [
      h('span', {}, [this.__toString__(this.text)])
    ])
  }
})
 setTimeout(function(){
   demo.text = "after"
 }, 3000)

对应的虚拟dom会从
<div><span>before</span></div> 变为 <div><span>after</span></div>
好,开始吧!!!

第一步, 讲data 下面所有属性变为observable

来来来先看代码吧

    class Vue {
      constructor(options) {
        this.$options = options
        this._data = options.data
        observer(options.data, this._update)
        this._update()
      }
      _update(){
        this.$options.render()
      }
    }


    function observer(value, cb){
      Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
    }

    function defineReactive(obj, key, val, cb) {
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: ()=>{},
        set:newVal=> {
          cb()
        }
      })
    }

    var demo = new Vue({
      el: '#demo',
      data: {
        text: 123,
      },
      render(){
        console.log("我要render了")
      }
    })

     setTimeout(function(){
       demo._data.text = 444
     }, 3000)

为了好演示我们只考虑最简单的情况,如果看了vue 源码分析之如何实现 observer 和 watcher可能就会很好理解,不过没关系,我们三言两语再说说,这段代码要实现的功能就是将

    var demo = new Vue({
      el: '#demo',
      data: {
        text: 123,
      },
      render(){
        console.log("我要render了")
      }
    })

data 里面所有的属性置于 observer,然后data里面的属性,比如 text 以改变,就引起_update()函数调用进而重新渲染,是怎样做到的呢,我们知道其实就是赋值的时候就要改变对吧,当我给data下面的text 赋值的时候 set 函数就会触发,这个时候 调用 _update 就ok了,但是

     setTimeout(function(){
       demo._data.text = 444
     }, 3000)

demo._data.text没有demo.text用着爽,没关系,我们加一个代理

      _proxy(key) {
        const self = this
        Object.defineProperty(self, key, {
          configurable: true,
          enumerable: true,
          get: function proxyGetter () {
            return self._data[key]
          },
          set: function proxySetter (val) {
            self._data[key] = val
          }
        })
      }

然后在Vueconstructor加上下面这句

    Object.keys(options.data).forEach(key => this._proxy(key))

第一步先说到这里,我们会发现一个问题,data中任何一个属性的值改变,都会引起
_update的触发进而重新渲染,属性这显然不够精准啊

第二步,详细阐述第一步为什么不够精准

比如考虑下面代码

    new Vue({
      template: `
        <div>
          <section>
            <span>name:</span> {{name}}
          </section>
          <section>
            <span>age:</span> {{age}}
          </section>
        <div>`,
      data: {
        name: 'js',
        age: 24,
        height: 180
      }
    })

    setTimeout(function(){
      demo.height = 181
    }, 3000)

template里面只用到了data上的两个属性nameage,但是当我改变height的时候,用第一步的代码,会不会触发重新渲染?会!,但其实不需要触发重新渲染,这就是问题所在!!

第三步,上述问题怎么解决

简单说说虚拟 DOM

首先,template最后都是编译成render函数的(具体怎么做,就不展开说了,以后我会说的),然后render 函数执行完就会得到一个虚拟DOM,为了好理解我们写写最简单的虚拟DOM

    function VNode(tag, data, children, text) {
      return {
        tag: tag,
        data: data,
        children: children,
        text: text
      }
    }

    class Vue {
      constructor(options) {
        this.$options = options
        const vdom = this._update()
        console.log(vdom)
      }
      _update() {
        return this._render.call(this)
      }
      _render() {
        const vnode = this.$options.render.call(this)
        return vnode
      }
      __h__(tag, attr, children) {
        return VNode(tag, attr, children.map((child)=>{
          if(typeof child === 'string'){
            return VNode(undefined, undefined, undefined, child)
          }else{
            return child
          }
        }))
      }
      __toString__(val) {
        return val == null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val);
      }
    }


    var demo = new Vue({
      el: '#demo',
      data: {
        text: "before",
      },
      render(){
        return this.__h__('div', {}, [
          this.__h__('span', {}, [this.__toString__(this.text)])
        ])
      }
    })

我们运行一下,他会输出

     {
       tag: 'div',
       data: {},
       children:[
         {
           tag: 'span',
           data: {},
           children: [
             {
               children: undefined,
               data: undefined,
               tag: undefined,
               text: '' // 正常情况为 字符串 before,因为我们为了演示就不写代理的代码,所以这里为空
             }
           ]
         }
       ]
     }

这就是 虚拟最简单虚拟DOM,taghtml 标签名,data 是包含诸如 classstyle 这些标签上的属性,childen就是子节点,关于虚拟DOM就不展开说了。
回到开始的问题,也就是说,我得知道,render 函数里面依赖了vue实例里面哪些变量(只考虑render 就可以,因为template 也会是帮你编译成render)。叙述有点拗口,还是看代码吧

    var demo = new Vue({
      el: '#demo',
      data: {
        text: "before",
        name: "123",
        age: 23
      },
      render(){
        return this.__h__('div', {}, [
          this.__h__('span', {}, [this.__toString__(this.text)])
        ])
      }
    })

就像这段代码,render 函数里其实只依赖text,并没有依赖 nameage,所以,我们只要text改变的时候
我们自动触发 render 函数 让它生成一个虚拟DOM就ok了(剩下的就是这个虚拟DOM和上个虚拟DOM做比对,然后操作真实DOM,只能以后再说了),那么我们正式考虑一下怎么做

第三步,'touch' 拿到依赖

回到最上面那张图,我们知道data上的属性设置defineReactive后,修改data 上的值会触发 set
那么我们取data上值是会触发 get了。
对,我们可以在上面做做手脚,我们先执行一下render,我们看看data上哪些属性触发了get,我们岂不是就可以知道 render 会依赖data 上哪些变量了。
然后我么把这些变量做些手脚,每次这些变量变的时候,我们就触发render
上面这些步骤简单用四个子概括就是 计算依赖。
(其实不仅是render,任何一个变量的改别,是因为别的变量改变引起,都可以用上述方法,也就是computedwatch 的原理,也是mobx的核心)

第一步,

我们写一个依赖收集的类,每一个data 上的对象都有可能被render函数依赖,所以每个属性在defineReactive
时候就初始化它,简单来说就是这个样子的

    class Dep {
      constructor() {
        this.subs = []
      }
      add(cb) {
        this.subs.push(cb)
      }
      notify() {
        console.log(this.subs);
        this.subs.forEach((cb) => cb())
      }
    }
    function defineReactive(obj, key, val, cb) {
      const dep = new Dep()
      Object.defineProperty(obj, key, {
        // 省略
      })
    }

然后,当执行render 函数去'touch'依赖的时候,依赖到的变量get就会被执行,然后我们就可以把这个 render 函数加到 subs 里面去了。
当我们,set 的时候 我们就执行 notify 将所有的subs数组里的函数执行,其中就包含render 的执行。
至此就完成了整个图,好我们将所有的代码展示出来

    function VNode(tag, data, children, text) {
      return {
        tag: tag,
        data: data,
        children: children,
        text: text
      }
    }

    class Vue {
      constructor(options) {
        this.$options = options
        this._data = options.data
        Object.keys(options.data).forEach(key => this._proxy(key))
        observer(options.data)
        const vdom = watch(this, this._render.bind(this), this._update.bind(this))
        console.log(vdom)
      }
      _proxy(key) {
        const self = this
        Object.defineProperty(self, key, {
          configurable: true,
          enumerable: true,
          get: function proxyGetter () {
            return self._data[key]
          },
          set: function proxySetter (val) {
            self._data.text = val
          }
        })
      }
      _update() {
        console.log("我需要更新");
        const vdom = this._render.call(this)
        console.log(vdom);
      }
      _render() {
        return this.$options.render.call(this)
      }
      __h__(tag, attr, children) {
        return VNode(tag, attr, children.map((child)=>{
          if(typeof child === 'string'){
            return VNode(undefined, undefined, undefined, child)
          }else{
            return child
          }
        }))
      }
      __toString__(val) {
        return val == null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val);
      }
    }

    function observer(value, cb){
      Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
    }

    function defineReactive(obj, key, val, cb) {
      const dep = new Dep()
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: ()=>{
          if(Dep.target){
            dep.add(Dep.target)
          }
          return val
        },
        set: newVal => {
          if(newVal === val)
            return
          val = newVal
          dep.notify()
        }
      })
    }
    function watch(vm, exp, cb){
      Dep.target = cb
      return exp()
    }

    class Dep {
      constructor() {
        this.subs = []
      }
      add(cb) {
        this.subs.push(cb)
      }
      notify() {
        this.subs.forEach((cb) => cb())
      }
    }
    Dep.target = null


    var demo = new Vue({
      el: '#demo',
      data: {
        text: "before",
      },
      render(){
        return this.__h__('div', {}, [
          this.__h__('span', {}, [this.__toString__(this.text)])
        ])
      }
    })


     setTimeout(function(){
       demo.text = "after"
     }, 3000)

我们看一下运行结果
D4A9A9B8-03CA-4A73-A12F-30409E08D99D.png

好我们解释一下 Dep.target 因为我们得区分是,普通的get,还是在查找依赖的时候的get
所有我们在查找依赖时候,我们将

    function watch(vm, exp, cb){
      Dep.target = cb
      return exp()
    }

Dep.target 赋值,相当于 flag 一下,然后 get 的时候

       get: () => {
          if (Dep.target) {
            dep.add(Dep.target)
          }
          return val
        },

判断一下,就好了。
到现在为止,我们再看那张图是不是就清楚很多了?

总结

我非常喜欢,vue2.0 以上代码为了好展示,都采用最简单的方式呈现。
不过整个代码执行过程,甚至是命名方式都和vue2.0一样
对比react,vue2.0 自动帮你监测依赖,自动帮你重新渲染,而
react 要实现性能最大化,要做大量工作,比如我以前分享的
react如何性能达到最大化(前传),暨react为啥非得使用immutable.js
react 实现pure render的时候,bind(this)隐患
而 vue2.0 天然帮你做到了最优,而且对于像万年不变的 如标签上静态的class属性,
vue2.0 在重新渲染后做diff 的时候是不比较的,vue2.0比 达到性能最大化的react 还要快的一个原因
然后源码在此,喜欢的记得给个 star 哦?
后续,我会简单聊聊,vue2.0的diff。
如果有疑问,可以在评论区留言哈

查看原文

赞 36 收藏 157 评论 26

杨川宝 回答了问题 · 2016-05-23

解决React使触摸事件生效,报错!!!

你react 版本号是?

关注 3 回答 2

杨川宝 回答了问题 · 2016-03-16

React Router 设置路由报错 "unexpected token <"

这是 语法错误 。<Route path=":name" component={Instrument} />//这里name参数出入名字。应该是你注释包的错。。注释不能这样写

关注 3 回答 2

杨川宝 回答了问题 · 2016-03-06

在 Redux 中进行 初始化/一次性 的操作科学的姿势是?

没太明白你的意思…初始状态直接写在reducer中的state中就好了…

关注 3 回答 1

杨川宝 回答了问题 · 2016-01-26

解决Redux如何结合React-Router?

关注 8 回答 4

杨川宝 赞了回答 · 2016-01-21

解决有没有可能出现一个不同的字符串MD5出来相同的md5值呢?

应该会~

关注 10 回答 7

杨川宝 发布了文章 · 2016-01-21

解析神奇的 Object.defineProperty

这个方法了不起啊。。vue.js是通过它实现双向绑定的。。而且Object.observe也被草案发起人撤回了。。所以defineProperty更有必要了解一下了。

几行代码看他怎么用

var a= {}
Object.defineProperty(a,"b",{
  value:123
})
console.log(a.b);//123

很简单,它接受三个参数,而且都是必填的。。

传入参数

  • 第一个参数:目标对象

  • 第二个参数:需要定义的属性或方法的名字。

  • 第三个参数:目标属性所拥有的特性。(descriptor)

前两个参数不多说了,一看代码就懂,主要看第三个参数descriptor,看看有哪些取值

descriptor

他又以下取值,我们简单认识一下,后面例子,挨个介绍。

  • value:属性的值(不用多说了)

  • writable:如果为false,属性的值就不能被重写,只能为只读了

  • configurable:总开关,一旦为false,就不能再设置他的(value,writable,configurable)

  • enumerable:是否能在for...in循环中遍历出来或在Object.keys中列举出来。

  • get:一会细说

  • set:一会细说

descriptor 默认值

我们再看看第一个例子

var a= {}
Object.defineProperty(a,"b",{
  value:123
})
console.log(a.b);//123

我们只设置了 value,别的并没有设置,但是第一次的时候 可以简单的理解为(暂时这样理解)它会默认帮我们把writable,configurable,enumerable。都设上值,而且值还都是false。。也就是说,上面代码和下面是等价的的(仅限于第一次设置的时候)。

var a= {}
Object.defineProperty(a,"b",{
  value:123,
  writable:false,
  enumerable:false,
  configurable:false
})
console.log(a.b);//123

以上非常重要哦。。并且以上理解对set 和 get 不起作用哦

configurable

总开关,第一次设置 false 之后,,第二次什么设置也不行了,比如说

var a= {}
Object.defineProperty(a,"b",{
  configurable:false
})
Object.defineProperty(a,"b",{
  configurable:true
})
//error: Uncaught TypeError: Cannot redefine property: b

就会报错了。。

注意上面讲的默认值。。。如果第一次不设置它会怎样。。会帮你设置为false。。所以。。第二次。再设置他会怎样?。。对喽,,会报错

writable

如果设置为fasle,就变成只读了。。

var a = {}; 

Object.defineProperty(o, "b", { 
    value : 123,
    writable : false });

console.log(a.b); // 打印 37
a.b = 25; // 没有错误抛出(在严格模式下会抛出,即使之前已经有相同的值)
console.log(o.a); // 打印 37, 赋值不起作用。

enumerable

属性特性 enumerable 定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。

var a= {}
Object.defineProperty(a,"b",{
  value:3445,
  enumerable:true
})
console.log(Object.keys(a));// 打印["b"]

改为false

var a= {}
Object.defineProperty(a,"b",{
  value:3445,
  enumerable:false //注意咯这里改了
})
console.log(Object.keys(a));// 打印[]

for...in 类似,不赘述了

set 和 get

在 descriptor 中不能同时设置访问器(get 和 set)和 wriable 或 value,否则会错,就是说想用 get 和 set,就不能用 writable 或 value 中的任何一个。

set 和 get,他俩干啥用的的。

var a= {}
Object.definePrope`请输入代码`rty(a,"b",{
  set:function(newValue){
    console.log("你要赋值给我,我的新值是"+newValue)
    },
  get:function(){
    console.log("你取我的值")
    return 2 //注意这里,我硬编码返回2
   }
})
a.b =1 //打印 你要赋值给我,我的新值是1
console.log(a.b)    //打印 你取我的值
                    //打印 2    注意这里,和我的硬编码相同的

简单来说,这个 “b” 赋值或者取值的时候会分别触发 set 和 get 对应的函数。

这就是实现 observe的关键啊。

下一篇,我会分析vue的observe的实现源码,聊聊自己如何一步一步实现$watch。

查看原文

赞 31 收藏 184 评论 21

杨川宝 回答了问题 · 2016-01-19

有没有用angularjs 做的网站啊?

饿了么 阿里云

关注 19 回答 10

认证与成就

  • 获得 222 次点赞
  • 获得 7 枚徽章 获得 0 枚金徽章, 获得 2 枚银徽章, 获得 5 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-08-22
个人主页被 4.8k 人浏览