Go基础12-理解Go语言表达式的求值顺序
Go语言在变量声明、初始化以及赋值语句上相比其先祖C语言做了一些改进,诸如:
● 支持在同一行声明和初始化多个变量(不同类型也可以)
var a, b, c = 5, "hello", 3.45
a, b, c := 5, "hello", 3.45 // 短变量声明形式
● 支持在同一行对多个变量进行赋值
a, b, c = 5, "hello", 3.45
这种语法糖在给我们带来便利的同时,也可能带来一些令人困惑的问题。
Go语言之父Rob Pike在Go语言早期(r60版本,2011年)曾经讲过一门名为“The GoProgramming Language” [1] 的课程,虽然距今年代有些久远,但该课程仍然是笔者心中的经典,强烈推荐Gopher学习一下。
在该门课程第二天 [2] 的内容中,Rob Pike出了这样一道练习题:下面语句执行完毕后,n0和n1的值分别是多少?
n0, n1 = n0+n1, n0
或者
n0, n1 = op(n0, n1), n0
对于这个问题,很多Go语言初学者无法给出答案;一些Go语言老手虽然能给出正确答案,但也说不出个所以然。显然这个问题涉及Go语言的表达式求值顺序(evaluationorder)。
上面问题中赋值语句中的表达式求值仅仅是表达式求值的众多应用场景中的一个。表达式的求值顺序在任何一门编程语言中都是比较“难缠的”。很多情形下,语言规范给出的 答 案 可 能 是“undefined”(未 定 义)、“not specified”(未 明 确 说 明)或“implementation-dependent”(实现相关)。
理解表达式求值顺序的机制,对于编写出正确、逻辑清晰的Go代码很有必要,因此在这一条中,我们一起结合直观的实例来深入理解Go语言的表达式求值顺序。
包级别变量声明语句中的表达式求值顺序
在一个Go包内部,包级别变量声明语句的表达式求值顺序是由初始化依赖(initialization dependencies)规则决定的。那初始化依赖规则是什么呢?根据Go语言规范中的说明,这里将该规则总结为如下几点。
● 在Go包中,包级别变量的初始化按照变量声明的先后顺序进行。
● 如果某个变量(如变量a)的初始化表达式中直接或间接依赖其他变量(如变量b),那么变量a的初始化顺序排在变量b后面。
● 未初始化的且不含有对应初始化表达式或初始化表达式不依赖任何未初始化变量的变量,我们称之为“ready for initialization”变量。
● 包级别变量的初始化是逐步进行的,每一步就是按照变量声明顺序找到下一个“ready for initialization”变量并对其进行初始化的过程。反复重复这一步骤,直到没有“ready for initialization”变量为止。
● 位于同一包内但不同文件中的变量的声明顺序依赖编译器处理文件的顺序:先处理的文件中的变量的声明顺序先于后处理的文件中的所有变量。规则往往抽象难懂,例子则更直观易理解。
我们看一个Go语言规范中的例子,并使用
上述规则进行分析(Go编译器版本1.13):
package mainimport "fmt"var (a = c + bb = f()c = f()d = 3
)func f() int {d++return d
}
func main() {fmt.Println(a, b, c, d)
}
运行结果:
9 4 5 5
对于上面的代码,不同的包变量初始化顺序会导致变量值不同,因此明确四个变量的初始化顺序至关重要。我们结合上面的初始化依赖规则来分析一下该程序执行后的a、b、c、d四个变量的结果值。
1)根据规则,包级变量初始化按照变量声明先后顺序进行,因此每一轮寻找“readyfor initialization”变量的过程都会按照a -> b -> c -> d的顺序依次进行。
2)我们先来进行第一轮选择“ready for initialization”变量的过程。我们从变量a开始。变量a的初始化表达式为c + b,这使得a的初始化依赖b和c,而b、c通过函数f间接依赖未初始化变量d,因此a并不是“ready for initialization”变量。
3)按照声明顺序,接下来是b。b的初始化表达式依赖函数f,而函数f依赖未初始化变量d,因此b也不是“ready for initialization”变量。
4)按照声明顺序,接下来是c。c的初始化表达式依赖函数f,而函数f依赖未初始化变量d,因此c也不是“ready for initialization”变量。
5)按照声明顺序,接下来是d。d没有需要求值的初始化表达式,而是直接被赋予了初值,因此d是我们第一轮找到的“ready for initialization”变量,我们对其进行初始化:d = 3。当前已初始化变量集合为[d=3]。
6)接下来进行第二轮“ready for initialization”变量的寻找。我们依然从a开始,和第一轮一样,b、c依旧是未初始化变量,a不符合条件;我们继续看b。b依赖函数f,函数f依赖d,但d已经是已初始化变量集合中的元素了,因此b具备了成为“ready forinitialization”的条件,于是第二轮我们选出了b,并对b进行初始化:b = d + 1 = 4。
此时已初始化变量集合为[d=4, b=4]。
7)接下来进行第三轮“ready for initialization”变量的寻找。我们依然从a开始,和前两轮一样,c依旧是未初始化变量,a不符合条件;我们继续看c。c依赖函数f,函数f依赖d,但d已经是已初始化变量集合中的元素了,因此c具备了成为“ready forinitialization”的条件,于是第三轮我们选出了c,并对c进行初始化:c = d + 1 = 5。
此时已初始化变量集合为[d=5, b=4, c=5]。
8)接下来进行最后一轮“ready for initialization”变量的寻找。此时只剩下变量a了,并且a依赖的b、c都是已初始变量集合中的元素了,因此a符合“ready forinitialization”的条件,于是最后一轮我们选出a,并对a进行初始化:a = 4 + 5 = 9。
此时已初始化变量集合为[d = 5, b = 4, c = 5, a = 9]。
9)初始化结束,根据上述分析,程序应该输出9 4 5 5。
如果在包级变量声明中使用了空变量_,空变量也会得到Go编译器一视同仁的对待。我们看下面的例子:
package mainimport "fmt"var (a = c + bb = f()_ = f()c = f()d = 3
)func f() int {d++return d
}
func main() {fmt.Println(a,b,c,d)
}
有了第一个例子中详细的分析,这里我们的分析从简。
1) 初 始 化 过 程 按 照 a - > b - > _ - > c - > d 的 顺 序 进 行“ready forinitialization”变量的查找。
2)第一轮:变量a、b、_、c都不符合条件,d被选出并初始化,已初始化变量集合为[d=3]。
3)第二轮:变量b符合条件被选出并初始化,已初始化变量集合为[d=4, b=4]
4)第三轮:空变量符合条件被选出并初始化,但空变量忽略了初始值,这一过程的副作用是使得变量d增加1,已初始化变量集合为[d=5, b=4]。
5)第四轮:变量c符合条件被选出并初始化,已初始化变量集合为[d=6, b=4, c=6]。
6)第五轮:变量a符合条件被选出并初始化,已初始化变量集合为[d=6, b=4, c=6,a=10]。
7)包变量初始化结束,分析输出结果应为10 4 6 6。
还有一种比较特殊的情况值得我们在这里一并分析,那就是当多个变量在声明语句左侧且右侧为单一表达式时的表达式求值情况。在这种情况下,无论左侧哪个变量被初始化,同一行的其他变量也会被一并初始化。
我们来看下面这个例子:
package mainimport "fmt"var (a = cb, c = f()d = 3
)func f() (int, int) {d++return d, d + 1
}
func main() {fmt.Println(a, b, c, d)
}
1)根据包级变量初始化规则,初始化过程将按照a -> b&c -> d顺序进行“ready forinitialization”变量的查找。
2)第一轮:变量a、b、c都不符合条件,d被选出并初始化,已初始化变量集合为[d=3]。
3)第二轮:变量b和c一起符合条件,以b被选出为例,b被初始化的同时,c也得到了
初始化,因此已初始化变量集合为[d=4, b=4, c=5]。
4)第三轮:变量a符合条件被选出并初始化,已初始化变量集合为[d=4, b=4, c=5,a=5]。
5)包变量初始化结束,分析输出结果应为5 4 5 4。
运行上述代码:
5 4 5 4
输出结果也与我们分析的一致。
普通求值顺序
除了包级变量由初始化依赖决定的求值顺序,Go还定义了普通求值顺序(usualorder),用于规定表达式操作数中的函数、方法及channel操作的求值顺序。Go规定表达式操作数中的所有函数、方法以及channel操作按照从左到右的次序进行求值。
同样来看一个改编自Go语言规范中的例子:
package mainimport "fmt"func f() int {fmt.Println("calling f")return 1
}
func g(a, b, c int) int {fmt.Println("calling g")return 2
}
func h() int {fmt.Println("calling h")return 3
}
func i() int {fmt.Println("calling i")return 1
}
func j() int {fmt.Println("calling j")return 1
}
func k() bool {fmt.Println("calling k")return true
}
func main() {var y = []int{11, 12, 13}var x = []int{21, 22, 23}var c chan int = make(chan int)go func() {c <- 1}()y[f()], _ = g(h(), i()+x[j()], <-c), k()
}
y[f()], _ = g(h(), i()+x[j()], <-c), k()这行语句是赋值语句,但赋值语句的表
达式操作数中包含函数调用、channel操作。按照普通求值规则,这些函数调用、channel操作按从左到右的顺序进行求值。
● 按照从左到右的顺序,先对等号左侧表达式操作数中的函数进行调用求值,因此第一个是y[f()]中的f()。
● 接下来是等号右侧的表达式。第一个函数是g(),但g()依赖其参数的求值,其参数列表依然可以看成是一个多值赋值操作,其涉及的函数调用顺序从左到右依次为h()、i()、j()、<-c,这样该表达式操作数函数的求值顺序即为h() -> i() -> j() -> c取值操作 ->g()。
● 最后还剩下末尾的k(),因此该语句中函数以及channel操作的完整求值顺序是:f() ->h() -> i() -> j() -> c取值操作 -> g() -> k()。
例子的实际运行结果如下:
calling f
calling h
calling i
calling j
calling g
calling k
输出结果与我们分析的一致。
赋值语句的求值
package mainimport "fmt"func example() {n0, n1 := 1, 2n0, n1 = n0+n1, n0fmt.Println(n0, n1)
}
func main() {example()
}
这是一个赋值语句。Go语言规定,赋值语句求值分为两个阶段:、
1)第一阶段,对于等号左边的下标表达式、指针解引用表达式和等号右边表达式中的操作数,按照普通求值规则从左到右进行求值
2)第二阶段,按从左到右的顺序对变量进行赋值。
根据上述规则,我们对这个问题等号两端的表达式的操作数采用从左到右的求值顺序。
假定n0和n1的初值如下:n0, n1 = 1, 2
第一阶段:等号两端表达式求值。上述问题中,等号左边没有需要求值的下标表达式、指针解引用表达式等,只有右端有n0+n1和n0两个表达式,但表达式的操作数(n0,n1)
都是已初始化了的,因此直接将值代入,得到求值结果。
求值后,语句可以看成n0, n1 =3, 1。
第二阶段:从左到右赋值,即n0 =3,n1 = 1。
switch/select语句中的表达式求值
上面的三类求值顺序原则已经可以覆盖大部分Go代码中的场景了,如果说在表达式求值方面还有值得重点关注的,那肯定非switch/select语句中的表达式求值莫属了。
我们先来看switch-case语句中的表达式求值,这类求值属于“惰性求值”范畴。惰性求值指的就是需要进行求值时才会对表达值进行求值,这样做的目的是让计算机少做事,从而降低程序的消耗,对性能提升有一定帮助。
package mainimport "fmt"func Expr(n int) int {fmt.Println(n)return n
}
func main() {switch Expr(2) {case Expr(1), Expr(2), Expr(3):fmt.Println("enter into case1")fallthroughcase Expr(4):fmt.Println("enter into case2")}
}
运行结果:
2
1
2
enter into case1
enter into case2
从例子的输出结果我们看到:
1)对于switch-case语句而言,首先进行求值的是switch后面的表达式Expr(2),这个表达式在求值时输出2。
2)接下来将按照从上到下、从左到右的顺序对case语句中的表达式进行求值。如果某个表达式的结果与switch表达式结果一致,那么求值停止,后面未求值的case表达式将被
忽略。结合上述例子,这里对第一个case中的Expr(1)和Expr(2)进行了求值,由于Expr(2)
求值结果与switch表达式的一致,所以后续Expr(3)并未进行求值。
3)fallthrough将执行权直接转移到下一个case执行语句中了,略过了case表达式Expr(4)的求值。
我们再来看看select-case语句的求值。Go语言中的select为我们提供了一种在多个channel间实现“多路复用”的机制,是编写Go并发程序最常用的并发原语之一。
我们通过一个例子直观看一下select-case语句中表达式的求值规则:
package mainimport ("fmt""time"
)func getAReadOnlyChannel() <-chan int {fmt.Println("invoke getAReadOnlyChannel")c := make(chan int)go func() {time.Sleep(3 * time.Second)c <- 1}()return c
}
func getASlice() *[5]int {fmt.Println("invoke getASlice")var a [5]intreturn &a
}
func getAWriteOnlyChannel() chan<- int {fmt.Println("invoke getAWriteOnlyChannel")return make(chan int)
}
func getANumToChannel() int {fmt.Println("invoke getANumToChannel")return 2
}
func main() {select {// 从channel接收数据case (getASlice())[0] = <-getAReadOnlyChannel():fmt.Println("recv something from a readonly channel")// 将数据发送到channelcase getAWriteOnlyChannel() <- getANumToChannel():fmt.Println("send something to a writeonly channel")}
}
运行结果:
invoke getAReadOnlyChannel
invoke getAWriteOnlyChannel
invoke getANumToChannel
invoke getASlice
recv something from a readonly channel
从上述例子可以看出以下两点。
1)select执行开始时,首先所有case表达式都会被按出现的先后顺序求值一遍。
invoke getAReadOnlyChannel
invoke getAWriteOnlyChannel
invoke getANumToChannel
有一个例外,位于case等号左边的从channel接收数据的表达式(RecvStmt)不会被求值,这里对应的是getASlice()。
2)如果选择要执行的是一个从channel接收数据的case,那么该case等号左边的表达式在接收前才会被求值。比如在上面的例子中,在getAReadOnlyChannel创建的goroutine
在3s后向channel中写入一个int值后,select选择了第一个case执行,此时对等号左侧的
表达式(getASlice())[0]进行求值,输出“invoke getASlice”,这也算是一种惰性求
值。
表达式本质上就是一个值,表达式求值顺序影响着程序的计算结果。
Gopher应牢记以下几点规则。
● 包级别变量声明语句中的表达式求值顺序由变量的声明顺序和初始化依赖关系决定,
并且包级变量表达式求值顺序优先级最高。
● 表达式操作数中的函数、方法及channel操作按普通求值顺序,即从左到右的次序进
行求值。
● 赋值语句求值分为两个阶段:先按照普通求值规则对等号左边的下标表达式、指针解
引用表达式和等号右边的表达式中的操作数进行求值,然后按从左到右的顺序对变量
进行赋值。
● 重点关注switch-case和select-case语句中的表达式“惰性求值”规则。
相关文章:
Go基础12-理解Go语言表达式的求值顺序
Go语言在变量声明、初始化以及赋值语句上相比其先祖C语言做了一些改进,诸如: ● 支持在同一行声明和初始化多个变量(不同类型也可以) var a, b, c 5, "hello", 3.45 a, b, c : 5, "hello", 3.45 // 短变量…...
OJ练习第165题——修车的最少时间
修车的最少时间 力扣链接:2594. 修车的最少时间 题目描述 给你一个整数数组 ranks ,表示一些机械工的 能力值 。ranksi 是第 i 位机械工的能力值。能力值为 r 的机械工可以在 r * n2 分钟内修好 n 辆车。 同时给你一个整数 cars ,表示总…...
纯前端实现 导入 与 导出 Excel
最近经常在做 不规则Excel的导入,或者一些普通Excel的导出,当前以上说的都是纯前端来实现;下面我们来聊聊经常用到的Excel导出与导入的实现方案,本文实现技术栈以 Vue2 JS 为例 导入分类: 调用 API 完全由后端来解析数…...
关于一次两段式提交和数据库恢复数据我的一些想法
binlog是服务层的功能,而redolog是innodb引擎的功能,binlog主要用于主从复制,redolog主要用做数据的恢复,我们必须保证binlog和redolog日志数据的一致性。恢复数据时也必须遵守此一致性。 1.如果只写一次redolog会出现什么问题&a…...
阿里巴巴springcloud的gateway网关如何用继承接口WebExceptionHandler定义一个json格式的404错误页面实例
如果你想通过实现 WebExceptionHandler 接口来定义一个返回 JSON 格式的 404 错误页面的实例,可以按照以下方式操作: import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.ster…...
『力扣每日一题07』字符串最后一个单词的长度
气死我啦,今天这道题花了快一个小时,我学完了答案的解法,放上去在线 OJ ,一直报错,找来找去都找不到自己错在哪,明明跟答案一模一样。后来还是学了另一种解法,才跑出来的(̥̥̥̥̥̥̥̥o̥̥…...
成都睿趣科技:抖音开店初期要注意什么
随着社交媒体和短视频平台的崛起,抖音已经成为了一个风靡全球的短视频应用,拥有着庞大的用户群体。因此,越来越多的创业者开始在抖音上开设自己的线上店铺,希望借助这个平台赚取丰厚的利润。然而,在抖音开店初期&#…...
QT 5.13保姆级安装教程
辨清关系 要想学习一个新的东西,我们必须知其事,达其理,悟其道,然后才能无往而不利也! 我们常听到QT、Qt Creator 和 Qt SDK ,这三者究竟是什么,他们之间的关系又是如何的?在安装QT之前我们先来了解一下他们之间的关系: Qt:Qt 是一个跨平台的 C++ 应用程序开发框架,…...
js 创建DOM,并添加父DOM上,移除某个DOM的所有子节点
在sectionIdDiv上,添加子DOM <div ref"sectionIdDiv" class"sectionIdDiv"> </div>创建你要添加的子DOM ## 创建DOM let elementDom document.createElement(div)## 设置DOM的样式 elementDom.style.height "15px" e…...
element el-input 二次封装
说明:为实现输入限制,不可输入空格,长度限制。 inputView.vue <template><!-- 输入框 --><el-input:type"type":placeholder"placeholder"v-model"input"input"inputChange":maxle…...
[源码系列:手写spring] IOC第十三节:Bean作用域,增加prototype的支持
为了帮助大家更深入的理解bean的作用域,特意将BeanDefinition的双例支持留到本章节中,创建Bean,相关Reader读取等逻辑都有所改动。 内容介绍 在Spring中,Bean的作用域(Scope)定义了Bean的生命周期和可见性。包括单例和…...
【性能优化】事件委托
一、为什么要用事件委托 当 dom 有事件处理程序时,我们一般都会直接给它设置事件处理程序,设想一下,如果在一个父元素中有很多个 dom 需要添加事件处理呢?比如 ul 中处在100个 li,每个 li 都有相同的 click 事件&…...
C 风格文件输入/输出---无格式输入/输出---(std::fputc,std::putc,std::fputs)
C 标准库的 C I/O 子集实现 C 风格流输入/输出操作。 <cstdio> 头文件提供通用文件支持并提供有窄和多字节字符输入/输出能力的函数,而 <cwchar>头文件提供有宽字符输入/输出能力的函数。 无格式输入/输出 写字符到文件流 std::fputc, std::putc in…...
建议收藏!Harmony应用配置文件概述(Stage模型)
一. 应用配置文件 每个应用项目必须在项目的代码目录下加入配置文件,这些配置文件会向编译工具、操作系统和应用市场提供应用的基本信息。 在基于Stage模型开发的应用项目代码下,都存在一个app.json5及一个或多个module.json5这两种配置文件。 app.json5…...
金蝶云星空和四化智造MES(WEB)单据接口对接
金蝶云星空和四化智造MES(WEB)单据接口对接 接入系统:四化智造MES(WEB) MES建立统一平台上通过物料防错防错、流程防错、生产统计、异常处理、信息采集和全流程追溯等精益生产和精细化管理,帮助企业合理安排…...
Shell命令切换root用户、管理配置文件、检查硬件
Shell命令切换root用户、管理配置文件、检查硬件 切换root用户 两种方法 su命令详细介绍 sudo命令详细介绍 /etc/passwd文件 /etc/passwd文件里为什么有乱七八糟的用户? /etc/shadow文件 管理配置文件 检查硬件命令 查看CPU 查看GPU 与其他基于UNIX的系统…...
DataX(MySQL同步数据到Doris)
1.场景 这里演示介绍的使用 Doris 的 Datax 扩展 DorisWriter实现从Mysql数据定时抽取数据导入到Doris数仓表里 2.编译 DorisWriter 这个的扩展的编译可以不在 doris 的 docker 编译环境下进行,本文是在 windows 下的 WLS 下进行编译的 首先从github上拉取源码 …...
sql server服务无法启动怎么办?如何正常启动?
sql server软件是一款关系型数据库管理系统。具有使用方便可伸缩性好与相关软件集成程度高等优点。并且有些应用软件使用过程中是需要sql server数据库的后台支持的,我们在数据编程操作时经常会使用这款编程软件,在编程时系统有时会提示sql server服务无…...
SpringMVC实现文件上传和下载
目录 前言 一. SpringMVC文件上传 1. 配置多功能视图解析器 2. 前端代码中,将表单标记为多功能表单 3. 后端利用MultipartFile 接口,接收前端传递到后台的文件 4. 文件上传示例 1. 相关依赖: 2. 逆向生成对应的类 3. 后端代码…...
Your build is currently configured to use Java 20.0.2 and Gradle 8.0
jdk 版本不适配 下载jdk17 https://www.oracle.com/java/technologies/downloads/#jdk17 参考 JDK17的下载安装与配置(详细教程)_keyila798的博客-CSDN博客...
栈 之 如何实现一个栈
前言 栈最鲜明的特点就是后进先出,一碟盘子就是类似这样的结构,最晚放上去的,可以最先拿出来。本文将介绍的是如何自己实现一个栈结构。 栈的操作 栈是一种先进后出(Last-In-First-Out, LIFO)的数据结构,…...
uni-app:自带的消息提示被遮挡的解决办法(自定义消息提示框)
效果: 代码: 1、在最外层或者根组件的模板中添加一个容器元素,用于显示提示消息。例如: <div class"toast-container" v-if"toastMessage"><div class"toast-content">{{ toastMessa…...
PHP设备检验系统Dreamweaver开发mysql数据库web结构php编程计算机网页代码
一、源码特点 PHP设备检验系统是一套完善的web设计系统,对理解php编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。 下载地址 https://download.csdn.net/download/qq_41221322/88306259 php设备检验系统1 …...
Windows 可以使用以下快捷键打开终端(命令提示符)
Windows 可以使用以下快捷键打开终端(命令提示符) 使用快捷键 Win R 打开 “运行” 对话框,然后输入 “cmd” 并按下 Enter 键。这将打开默认的命令提示符窗口。 使用快捷键 Ctrl Shift Esc 打开任务管理器,然后在 “文件” …...
Netty编程面试题
1.Netty 是什么? Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。Netty是基于nio的,它封装了jdk的nio,让我们使用起来更加方法灵活。 2.Netty 的特点是什么? 高并发&a…...
math_review
topics mathmatics supreme and optimumNorm and Linear producttopology of R*Continuious Function supreme and optimum Def 1: 非空有界集合必有上确界 common norm (1) x ∈ \in ∈ Rn, ||x||2 x 1 2 x 2 2 . . . x n 2 \sqrt {x_1^2x_2^2...x_n^2} x12x22.…...
肖sir__设计测试用例方法之场景法04_(黑盒测试)
设计测试用例方法之场景法 1、场景法主要是针对测试场景类型的,顾也称场景流程分析法。 2、流程分析是将软件系统的某个流程看成路径,用路径分析的方法来设计测试用例。根据流程的顺序依次进行组合,使得流程的各个分支能走到。 举例说明&…...
plt函数显示图片 在图片上画边界框 边界框坐标转换
一.读取图片并显示图片 %matplotlib inline import torch from d2l import torch as d2l读取图片 image_path ../data/images/cat_dog_new.jpg # 创建画板 figure d2l.set_figsize() image d2l.plt.imread(image_path) d2l.plt.imshow(image);二.给出一个(x左上角,y左上角,…...
运行期获得文件名和行号
探索动态日志模块的实现 最初的目标是创建一个通用的日志模块, 它具有基本的日志输出功能并支持重定向. 这样, 如果需要更换日志模块, 可以轻松实现. 最初的构想是通过函数重定向, 即使用 dlsym 来重定向所有函数以实现打印功能. 然而, 这种方法引发了一个问题, 即无法正确获…...
数组操作UNIAPP
字符串转数组 let string "12345,56789" string.split(,) // [12345,56789] 数组转字符串 let array ["123","456"] array.join(",") // "123,456" 数组元素删除 let array [123,456] // 删除起始下标为1࿰…...
东营做网站优化的公司/网络营销课程
欢迎来到我的《从源码中学Vue》专题系列文章,更多精彩内容持续更新中,欢迎关注 :)上一章节我们通过源码分析了Vue中的methods对象下的方法是如何挂载到vm下,以及各方法内部的this为何是指向了vm对象。其实在Vue中,还有…...
一般做外贸上什么网站好/seo服务商排名
2019独角兽企业重金招聘Python工程师标准>>> java 类型不匹配! 转载于:https://my.oschina.net/u/242853/blog/176058...
网站的建设原始代码/重庆网站建设外包
一、 ng-class ng-class 指令用于给 HTML 元素动态绑定一个或多个 CSS 类。 ng-class 指令的值可以是字符串,对象,或一个数组。 如果是字符串,多个类名使用空格分隔。如果是对象,需要使用 key-value 对,key 为你想要添…...
wordpress导出主题/最近一周的重大热点新闻
前台 后台...
购物网站建设机构/深圳百度关键
1 概述 函数式编程是一种抽象程序很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数称之为没有副作用。而允许使用变量的程序设计语…...
网站备案 网址/吴江网站制作
2019独角兽企业重金招聘Python工程师标准>>> 有高人把CSS BUG编成了顺口溜了!大家可以看看,可以帮助大家解决很多问题! 一、IE边框若显若无,须注意,定是高度设置已忘记; 二、浮动产生有缘故&am…...