Vue学习总结之Vue的模板语法、计算属性、指令(四)

1,188 阅读20分钟

寄语:自从厌倦于追寻,我已学会一觅其中。自从一股逆风袭来,我已能抵御八面来风,驾舟而行。

本文已收录至github.com/likekk/stud…欢迎大家star,共同学习,共同进步。如果文章有错误的地方,欢迎大家指出。后期将在将GitHub上规划前端学习的路线和资源分享。

写在前面

每一篇文章都希望您有所收获,每一篇文章都希望您能静下心来浏览、阅读。每一篇文章都是作者精心打磨的作品。

如果您觉得杨戬这个前端小白还有点东西的话,作者希望你可以帮忙点亮那个点赞的按钮,对于二郎神杨戬这个暖男来说,真的真的非常重要,这将是我持续写作的动力。

前言

上一篇文章的内容主要是关于vue-cli项目的搭建、vue-cli项目引入第三方插件的相关内容,插图比较多,这也可能是搭建类博客的特点,当然以后写作尽量少用插图的,我尽量将自己理解的意思表达出来。本篇博客的内容比较简单。内容包括Vue模板、computed计算属性、watch侦听属性、methods方法、事件处理等相关内容。

Vue模板

Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。

在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

插值

1、文本

文本渲染的话主要是使用{{}}进行渲染,当然这也是数据的渲染方式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <ul>
        <li v-for="(item,index) of fruit">{{item}}</li>
    </ul>
</div>
<script src="../js/vue.js"></script>
<script>
    const vm=new Vue({
        el:'#app',
        data:{
            fruit:["苹果","香蕉","雪梨"]
        }
    })
</script>
</body>
</html>

通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。

注意:使用v-once的时候,只会在数据渲染的时候第一次生效,改变它的值,那么不会重新渲染

2、原始Html

有时候我们可能有这样或那样的需求,输出真正的html代码,Vue也为我们提供了一个属性v-html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <h2 v-html="joinHtml"></h2>
</div>
<script src="../js/vue.js"></script>
<script>
    const vm=new Vue({
        el:'#app',
        data:{

        },
        computed:{
            joinHtml(){
                return "<span>我是通过v-html进行渲染的</span>"
            }
        }
    })
</script>
</body>
</html>

注意:站点上动态渲染任意 HTML 可能会非常危险,因为它很容易导致XSS 攻击。绝不要对用户提供的内容使用插值。

3、绑定属性

属性的话一般是以v-bind:[属性]的格式进行绑定的,例如我们需要绑定图片的src属性,v-bind:src,绑定其它标签的title属性,v-bind:title等等。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <a v-bind:href="href" v-bind:title="title" :data-id="id">百度一下</a>
</div>
<script src="../js/vue.js"></script>
<script>
    const vm=new Vue({
        el:'#app',
        data:{
            href:"https://www.baidu.com",
            title:"点击链接,跳转百度",
            id:"10001"
        }
    })
</script>
</body>
</html>

如果一个标签上绑定多个属性,那么我们就需要写一大堆的v-bind:, 这样的话代码的冗余度就会很高,所以我们也可以直接使用:[属性] 进行绑定。

4、使用JavaScript表达式

文本插值中不仅可以直接使用数据,还可以对数据进行处理

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div v-bind:id="'list-' + id"></div>

这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。

<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}

<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}

当然还可以使用方法、计算属性、过滤器等等。

指令

指令是带有 v- 前缀的特殊属性。指令属性的值预期是单个 JavaScript 表达式 ,指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。

就拿v-if来说

<p v-if="seen">现在你看到我了</p>

这里的p标签显示或者隐藏取决于seen的true或false。

1、参数

一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind(:) 指令可以用于响应式地更新 HTML 属性

<a v-bind:href="url">...</a>

在这里 href 是参数,告知 v-bind 指令将该元素的 href属性与表达式 url 的值绑定。

另一个例子是 v-on(@click)指令,它用于监听 DOM 事件:

<a v-on:click="alertMsg">弹出消息</a>

alertMsg表示的是方法名(事件名)

2、动态参数

可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:

<a v-bind:[attributeName]="url"> ... </a>

这里的 attributeName 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的 Vue 实例有一个 data property attributeName,其值为 "href",那么这个绑定将等价于 v-bind:href

3、修饰符

修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()

<form v-on:submit.prevent="onSubmit">...</form>
  • .stop - 调用 event.stopPropagation()。
  • .prevent - 调用 event.preventDefault()。
  • .capture - 添加事件侦听器时使用 capture 模式。
  • .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
  • .keyCode keyAlias - 只当事件是从特定键触发时才触发回调。
  • .native - 监听组件根元素的原生事件。
  • .once - 只触发一次回调。
  • .left - 只当点击鼠标左键时触发。
  • .right - 只当点击鼠标右键时触发。
  • .middle - 只当点击鼠标中键时触发。
  • .passive - 以 { passive: true } 模式添加侦听器

缩写

关于缩写上文已经提及到,减少代码的冗余。

1、v-bind的缩写

<!-- 完整语法 -->
<a v-bind:href="url">...</a>

<!-- 缩写 -->
<a :href="url">...</a>

<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>

2、v-on的缩写

<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>

<!-- 缩写 -->
<a @click="doSomething">...</a>

<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>

computed计算属性

计算属性也是我们在Vue中经常会使用到的一个特性,和下文的methods、watch有着相似之处和非相似之处。

那我们一起来看看,

引子

有这样一个需求,假设需要在花括号中反转字符串,那么我们的第一反应就是这样写

<span>{{message.spilt('').reverse.join('')}}</span>

那么问题来了,这是一个标签这样写,我觉得还很OK,但是如果有一百个,一千个呢?将这样的代码写在界面上,那么肯定维护起来十分不方便。所以我们需要将这些的逻辑处理进行拆分。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <p>原始数据{{message}}</p>
    <p>反转后的数据{{reverseMessage}}</p>
</div>
<script src="../js/vue.js"></script>
<script>
    const vm=new Vue({
        el:'#app',
        data:{
            message:"hello"
        },
        computed:{
            reverseMessage(){
                return this.message.split('').reverse().join('');
            }
        }
    })
</script>
</body>
</html>

computed计算属性需要注意的事项:

  • computed中定义的方法只允许当属性用,不能带参数,这限制它的复用性
  • 当方法中的属性发生变化时方法将重新调用
  • 不应该使用箭头函数来定义计算属性里面的函数
  • computed计算属性可以对属性进行缓存,计算属性只有当该属性发生变化时才会重新计算值
  • 如果一个属性不能完成需要的功能时可以考虑转换为计算属性

methods方法

方法也同时可以实现上述的效果,看下方法是如何实现的

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <p>原始数据{{message}}</p>
    <p>反转后的数据{{reverseMessage}}</p>
    <p>调用方法反转后的数据{{reverseMessageOfMethods()}}</p>
</div>
<script src="../js/vue.js"></script>
<script>
    const vm=new Vue({
        el:'#app',
        data:{
            message:"hello"
        },
        computed:{
            reverseMessage(){
                return this.message.split('').reverse().join('');
            }
        },
        methods:{
            reverseMessageOfMethods(){
                return this.message.split('').reverse().join('');
            }
        }
    })
</script>
</body>
</html>

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的

只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage计算属性会立即返回之前的计算结果,而不必再次执行函数。

methods方法应该需要注意的事项

methods里面定义的方法不应该使用箭头函数,原因是箭头函数绑定了父级作用域上下文,所以this不是Vue实例

我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

计算属性VS监听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="demo">
    <span>{{ fullName }}</span>
</div>
<script src="../js/vue.js"></script>
<script>
    var vm = new Vue({
        el: '#demo',
        data: {
            firstName: 'Foo',
            lastName: 'Bar',
            fullName: 'Foo Bar'
        },
        watch: {
            firstName: function (val) {
                this.fullName = val + ' ' + this.lastName
            },
            lastName: function (val) {
                this.fullName = this.firstName + ' ' + val
            }
        }
    })
</script>
</body>
</html>

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="demo">
    <span>{{ fullName }}</span>
</div>
<script src="../js/vue.js"></script>
<script>
    var vm = new Vue({
        el: '#demo',
        data: {
            firstName: 'Foo',
            lastName: 'Bar'
        },
        computed: {
            fullName: function () {
                return this.firstName + ' ' + this.lastName
            }
        }
    })
</script>
</body>
</html>

计算属性Setter

计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:

computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

watch监听属性

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

<div id="watch-example">
  <p>
    Ask a yes/no question:
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
<!-- 因为 AJAX 库和通用工具的生态已经相当丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可以让你自由选择自己更熟悉的工具。 -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
  el: '#watch-example',
  data: {
    question: '',
    answer: 'I cannot give you an answer until you ask a question!'
  },
  watch: {
    // 如果 `question` 发生改变,这个函数就会运行
    question: function (newQuestion, oldQuestion) {
      this.answer = 'Waiting for you to stop typing...'
      this.debouncedGetAnswer()
    }
  },
  created: function () {
    // `_.debounce` 是一个通过 Lodash 限制操作频率的函数。
    // 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率
    // AJAX 请求直到用户输入完毕才会发出。想要了解更多关于
    // `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,
    // 请参考:https://lodash.com/docs#debounce
    this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
  },
  methods: {
    getAnswer: function () {
      if (this.question.indexOf('?') === -1) {
        this.answer = 'Questions usually contain a question mark. ;-)'
        return
      }
      this.answer = 'Thinking...'
      var vm = this
      axios.get('https://yesno.wtf/api')
        .then(function (response) {
          vm.answer = _.capitalize(response.data.answer)
        })
        .catch(function (error) {
          vm.answer = 'Error! Could not reach the API. ' + error
        })
    }
  }
})
</script>

三者的区别:

  • 计算属性computed:计算属性只能当作属性用,不能带参数,有缓存,效率高,,可以直接与v-model绑定
  • 方法methods:方法可以直接调用,可带参数,没有缓存,每次调用都会执行,效率不如计算属性高,不可与v-model绑定
  • 监视watch:不能显式调用(被监视的对象变化时被动调用),可以对变化的控制更加具体,但应用复杂,可以间接与v-model绑定

过滤器(Filters)

过滤器也是Vue中一个非常强大的功能,可被用于一些常见的文本格式化,过滤器用在两个地方双花括号插值和v-bind表达式,过滤器应该添加被添加在JavaScript表达式的尾部,由“管道”符号指示:

<!-- 在双花括号中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

你可以在一个组件的选项中定义本地的过滤器:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <span>    {{message|capitalize}}</span>
</div>
<script src="../js/vue.js"></script>
<script>
    const vm=new Vue({
        el:'#app',
        data:{
            message:"hello"
        },
        methods:{

        },
        filters:{
            capitalize(value){
                if(!value){
                    return ""
                }
                value=value.toString();
                return value.charAt(0).toUpperCase()+value.slice(1);
            }
        }
    })
</script>
</body>
</html>

或者在创建 Vue 实例之前全局定义过滤器:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <span>    {{message|capitalize}}</span>
</div>
<script src="../js/vue.js"></script>
<script>
    Vue.filter("capitalize",function (value) {
        if(!value){
            return ""
        }
        value=value.toString();
        return value.charAt(0).toUpperCase()+value.slice(1);
    })
    const vm=new Vue({
        el:'#app',
        data:{
            message:"hello"
        },
        methods:{

        },
    })
</script>
</body>
</html>

当全局过滤器和局部过滤器重名时,会采用局部过滤器

过滤器函数总接收表达式的值 (之前的操作链的结果) 作为第一个参数。在上述例子中,capitalize 过滤器函数将会收到 message 的值作为第一个参数。

过滤器可以串联:

{{ message | filterA | filterB }}

在这个例子中,filterA 被定义为接收单个参数的过滤器函数,表达式 message 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。

过滤器是 JavaScript 函数,因此可以接收参数:

{{ message | filterA('arg1', arg2) }}

这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值作为第一个参数,普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。

Vue常用的指令和事件处理器

上文中我们提及到了指令和事件,那么现在就好好的聊聊。

常用的指令

v-text
v-html
v-show
v-if
v-else
v-eles-if
v-for
v-on
v-bind
v-model
v-slot
v-pre
v-cloak
v-once

事件处理器

通常我们都是使用v-on:[事件名]来触发事件的,例如单击事件v-on:click等

1、监听事件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <button @click="count+=1">+</button>
    <span>{{count}}</span>
</div>
<script src="../js/vue.js"></script>
<script>
    const vm=new Vue({
        el:'#app',
        data:{
            count:1
        }
    })
</script>
</body>
</html>

2、事件处理方法

然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on 指令中是不可行的(如上示例,点击按钮的时候实现数字加1)。因此 v-on 还可以接收一个需要调用的方法名称。将方法注册在methods中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <button @click="greet">问候</button>
    <button @click="sum($event)">计算</button>
</div>
<script src="../js/vue.js"></script>
<script>
    const vm=new Vue({
        el:'#app',
        data:{
            count:1
        },
        methods:{
            greet(event){
                console.log(this.count);
                if(event){
                    console.log(event.currentTarget);
                }
            },
            sum(event){
                console.log(event.currentTarget);
            }

        }
    })
</script>
</body>
</html>

这里有一点需要注意就是关于事件添加括号和没有括号的情况

当事件没有括号的时候默认传入的第一个参数是event

当事件有括号的时候,如果想要获取event这个对象,我们需要手动传入$event这个属性

3、内联处理器中的方法

除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <button @click="say('hi')">sayHi</button>
    <button @click="say('hello')">sayHello</button>
</div>
<script src="../js/vue.js"></script>
<script>
    const vm=new Vue({
        el:'#app',
        data:{
            count:1
        },
        methods:{
            say(param){
                console.log(param)
            },
        }
    })
</script>
</body>
</html>

这种是将方法进行复用,根据传入的参数不同。

4、事件修饰符

在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。

但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>

注意:

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

Vue 还对应 addEventListener中的 passive 选项提供了 .passive 修饰符。

<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成  -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>

这个 .passive 修饰符尤其能够提升移动端的性能。

注意:

不要把 .passive.prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive 会告诉浏览器你想阻止事件的默认行为。

5、按键修饰符

在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:

<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

你可以直接将 KeyboardEvent.key暴露的任意有效按键名转换为 kebab-case 来作为修饰符。

<input v-on:keyup.page-down="onPageDown">

在上述示例中,处理函数只会在 $event.key 等于 PageDown 时被调用。

按键码

keyCode 的事件用法已经被废弃了并可能不会被最新的浏览器支持。

使用 keyCode attribute 也是允许的:

<input v-on:keyup.13="submit">

为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

有一些按键 (.esc 以及所有的方向键) 在 IE9 中有不同的 key 值, 如果你想支持 IE9,这些内置的别名应该是首选。

6、系统修饰符

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器

  • .ctrl
  • .alt
  • .shift
  • .meta
<!-- Alt + C -->
<input v-on:keyup.alt.67="clear">

<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>

请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCodekeyup.17

1、.exact修饰符

.exact修饰符允许你控制由精确的系统修饰符组合触发的事件

<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>

2、鼠标按钮修饰符

  • left
  • right
  • middle

这些修饰符会限制处理函数仅响应特定的鼠标按钮

结尾

如果觉得本篇文章对您有用的话,可以麻烦您给笔者点亮那个点赞按钮。

对于杨戬这个暖男来说:真的真的非常有用,您的支持将是我继续写文章前进的动力,我们下篇文章见。

【原创】|二郎神杨戬

二郎神杨戬,一个在互联网前端苟且偷生的划水程序员,专注于前端开发,善于技术分享。 如需转载,请联系作者或者保留原文链接,微信公众号搜索二郎神杨戬或者扫描下方的二维码更加方便。

一起来见证二郎神杨戬的成长吧!更多好文、技术分享尽在下方这个公众号。欢迎关注。