快速入门和详解kotlin


快速入门

1、资料

As查看反编译代码:

Tools -> Kotlin -> show kotlin bytecode -> Decompile

官方文档

https://kotlinlang.org/docs/reference/

Kotlin源码

https://github.com/JetBrains/kotlin

Kotlin官博

https://blog.jetbrains.com/kotlin/

Kotlin微信公众号

Kotlin

2、HelloWorld

第一种写法

fun main(args: Array<String>) {
    println("Hello World")
}

第二种写法

object HelloWorld {
    @JvmStatic
    fun main(args: Array<String>) {
        println("Hello")
    }
}

第三种写法

class HelloWorld {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            println("Hello")
        }
    }
}	

3、基本类型定义

var s : String = "Hello"
var stringA = "Hello"
var int = 5

基本数据类型的定义可以加类型,也可以不加。

lateinit延迟初始化成员变量,先不赋值

private lateinit var string : String

val延迟初始化用lazy,使用的时候再赋值,值只在第一次访问的时候计算

val ss : String by lazy { 
    "赋值"
}

数据类型转换

.toInt()
.toFloat()

字符串比较

==
.equals()

4、定义final

const val stringB = "Hello"
val stringC = "Hello"

var是一个可变变量,val是一个只读变量,可以省略类型。

const只能用在顶级属性,以及object对象的属性中(伴随对象也是obejct)。

const val就相当于Java中的final编译期常量

5、占位符"${}"

var stringG = "IamG"
var stringF = "${stringG}" + "dd"
println(stringF)//IamGdd

6、数组和遍历

val names: Array<String> = arrayOf("A", "B", "C")
val emptyStrings: Array<String?> = arrayOfNulls(10)


val array = arrayOf(1, 2, 3)
val arrayOfNulls = arrayOfNulls<Int>(3)
val array1 = Array(5) { i: Int -> (i * i).toString() }//i为索引0-4
val intArrayOf = intArrayOf(1, 2, 3)//[1,2,3]
val intArray = IntArray(3)//[0,0,0]
val intArray1 = IntArray(2) { 99 }//[99,99]
val intArray2 = IntArray(3) { it * 1 }//it为索引0-4

String?表示可以为null的String类型

基本数据类型定制版

val ints = intArrayOf(1, 3, 5)

基本操作

names.isNotEmpty() //判断空
names.length //数组的长度
names[i] = "Hello" //给第i个成员赋值
print array[i] //输出第i个成员

for遍历(注意..until)

val ints = intArrayOf(1, 3, 5, 7, 9)

for (int in ints) {
    println(int)
}

for (i in ints.indices){//i是角标
    print(ints[i])
}

for ((index,value) in ints.withIndex()){
    println("$index -> $value")
}

for (indexedValue in strArrays.withIndex()){
    println("${indexedValue.index} -> ${indexedValue.value}")
}

for (i in IntRange(0, ints.size - 1)) {
    print(ints[i])
}

for (i in 0..ints.size - 1) {
    print(ints[i])
}

for (i in 0 until ints.size) {
    print(ints[i])
}

//Lambda方式遍历
ints.forEach{
    println(it)
}
ints.forEach(::println)

ints.forEachIndexed { index: Int, i: Int -> println("$index:$i") }
ints.forEachIndexed { index, i -> println("$index:$i") }

7、list


List:

var lists = listOf <String>("张三", "李四", "王五")
for (list in lists) {
    println(list)
}
for ((index, valu) in lists.withIndex()) {//角标从零开始,角标和值
    println(index.toString() + "-----" + valu)
}

val list = listOf("one", "two", "one")
println(list)
val set = setOf("one", "two", "one")//不可重复
println(set)

//可变集合List
val mutableListOf1 = mutableListOf(1, 2, 3, 4)
val mutableListOf2 = mutableListOf<String>()

//可变集合Set
val hello = mutableSetOf("H", "e", "l", "l", "o")//Set自动过滤重复元素
hello.remove("o")
println(hello)
hello += setOf("w", "o", "r", "l", "d")//集合相加合并,前面的必须为可变集合
println(hello)

排序:

val number3 = mutableListOf(1, 2, 3, 4)
number3.shuffle()//洗牌,随机排序
println(number3)
number3.sort()//从小到大
println(number3)
number3.sortDescending()//从大到小
println(number3)

自定义排序:

data class Language(var name: String, var score: Int)

val languageList = mutableListOf<Language>()
languageList.add(Language("Java", 80))
languageList.add(Language("Kotlin", 90))
languageList.add(Language("Dart", 99))
languageList.add(Language("C", 80))
//使用sortBy进行排序,适合单条件排序
languageList.sortBy { it.score }
println(languageList)
languageList.sortWith(compareBy({ it.score }, { it.name }))
println(languageList)

7、map


Map:

var map = TreeMap<String, String>()
map["姓名"] = "张三"
map["性别"] = "男"
map["年龄"] = "18"
map["爱好"] = "游泳"

map不是Collection接口的继承者,但是他也是kotlin的一种集合类型:

val map = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 4, "key5" to 5)
println(map.keys)
println(map.values)
if ("key2" in map) println("Value by key key2:${map["key2"]}")
if (1 in map.values) println("1 is in the map")
if (map.containsKey("key1")) {
    println()
}
if (map.containsValue(1)) {
    println()
}

两个具有相同键值对,但顺序不同的map相等吗?为什么?

/**
 * Q1:两个具有相同键值对,但顺序不同的map相等吗?为什么?
 * 无论键值对的顺序如何,包含相同键值对的两个 Map 是相等的
 */
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key3" to 3, "key4" to 4, "key5" to 5)
println("anotherMap == numberMap:${anotherMap == map}")
anotherMap.equals(map)

8、静态变量、方法

companion object { 
}

9、方法函数

定义方法带参及返回类型

fun stringD(string : String):Boolean{
    //...
    return true
}

参数个数不固定,vararg:

fun stringE(vararg names: String){
}

stringE(*Ints)

简化写法

fun add(num1 : Int ,num2 : Int) : Int{
    return num1 + num2
}

fun add(num1 : Int ,num2 : Int) = num1 + num2

函数作为变量使用

var d = add(1,2)
val add = fun(num1: Int, num2: Int): Int {
    return num1 + num2
}

10、when表达式

when用来代替switch

fun checkScore(score: Int) : String {
    var str = when (score) {
        100 ->"满分"
        99 -> "继续加油"
        else ->"看好你哦"
    }
    return str
}
    

11、实例化类和class实例

var data = Date()

不需要new

获得class的实例的两种方式

val clazz1 = DD::class.java

val dd = DD()
val clazz2 = dd.javaClass

12、继承

class Son : Father(){
    override fun action(){
        //...
    }
}

13、AS转化

as中直接转换:两次shift后输入Convert Java File to Kotlin File

14、Lambda

无参数:

fun test() {
    println("no param")
}
//转换成Lambda
val test = { println("no param") }

有参数:

函数入参 -> 返回值

val add = fun(num1: Int, num2: Int): Int {
    return num1 + num2
}

//Lambda可以写成
val add: (Int, Int) -> Int = { a, b -> a + b }
//或者
val add = { num1: Int, num2: Int -> num1 + num2 }

过滤空后的输出结果

var strArrays = arrayOf("1", "2", "", "", "3", "4")
strArrays.filter { it.isNotEmpty() }.forEach{ println(it)}

下划线,用不到的参数可以用下划线代替:

val map = mapOf("key1" to "value1", "key2" to "value2", "key3" to "value3", "key4" to "value4", "key5" to "value5")
map.forEach { (k, _) -> println(k) }
map.forEach { (_, v) -> println(v) }

15、空安全

String?表示可以为null的String类型!!表示自己判断不会出现null,但如果出现了null会崩溃。表示可能会返回null,不会崩溃。

var string: String? = null
var len1 = string?.length
var len2 = string!!.length//最终会崩溃

16、类


类的创建

class Person(val name:String,val age:Int)

构造方法

class Person(var name: String?, var age:Int) {
    constructor(name: String):this(name,0)    
    constructor():this(null,0)
}

get和set

var name: String? = null        
    private set// 私有掉set方法    
var age: Int = 0        
    get() { return if (field < 0) 0 else field }//field就是age

类的继承

class QQStepView : View {    
    constructor(context:Context):this(context,null)    
    constructor(context:Context,attrs: AttributeSet?):this(context,attrs,0)    
    constructor(context:Context,attrs: AttributeSet?,defStyleAttr:Int):super(context,attrs,defStyleAttr{        
        // 写内容 获取自定义属性的内容    
    }
}

或者

class QQStepView(context: Context?, attrs: AttributeSet?) : View(context, attrs)

匿名内部类

 //匿名内部类 object:xxx        
httpUtils.get(object: HttpCallback(){
        override fun onError(e: IOException) {  }            
        override fun onSuccess() { Log.e("TAG","onSuccess")} 
})

类内声名变量三种方式:

//1声明为空 
val simple:Int?=null
//2设置getter
val simple: Int?
    get() {
        return 0
    }
//3在构造方法内初始化

类内成员变量:

class Shop {
    val name: String = "Android"
    var address: String? = null
    val isClose: Boolean
        get() = Calendar.getInstance().get(Calendar.HOUR_OF_DAY) > 11
    var score: Float = 0.0f
        //val不允许set var才可以
        get() = if (field < 0.2f) 0.2f else field * 1.5f
        set(value) = println(value)
    var counter = 0
        set(value) {
            if (value > 0) field = value
        }

}

/**
 * lateinit 对属性的延迟初始化 常用于注入
 */
class Test {
    lateinit var shop: Shop
    fun setup() {
        shop = Shop()
        shop.address = "Beijing"
    }

    fun test() {
        //::表示创建成员或者类的引用
        if (::shop.isInitialized) println(shop.address)
    }
}

17、运算符重载


class Counter(val dayIndex: Int){    
    // 操作符重载 +    
    operator fun plus(counter: Counter):Counter{        
        return Counter(dayIndex+counter.dayIndex)    
    }    
    // 操作符重载 -    
    operator fun minus(counter: Counter):Counter{        
        return Counter(dayIndex-counter.dayIndex)    
    }
}

18、方法扩展


 // 对类方法进行扩展        
val str = "abc"       
var strMulit = str.mulit(3)
Log.e("TAG","strMulit = $strMulit")
// 对一个类的方法进行扩展    
operator fun  String.mulit(number: Int): String {         
    val stringBuilder = StringBuilder()         
    for (num in 1..number){             
    stringBuilder.append(this)         
    }        
    return stringBuilder.toString()    
}

详解kotlin

1、数据类型和函数

=====

==表示比较内容,类似于java的equals;===表示比较对象是否相同。

open继承

如果一个类允许被继承,那么需要使用open声明。方法想被重写也是要加open。

open class People(name: String, sex: String) {
}

class Man(name: String, sex: String) : People(name, sex) {
}

init

init代码比构造函数先执行。

open class People(name: String, sex: String) {
    init {

    }
}

is

is类似于java中的instance of

as父类向下转为子类

as类型转换,失败则抛异常,as?如果转换失败,则返回null,不抛异常。

val child : Child? = parent as? Child

只能类型转化:父类对象直接引用子类的成员。

Any父类和Unit返回值

Any是Kotlin中的父类,类似于java中的Object。

没有返回值时候,其实返回的是Unit,相当于java中的void。

引用包名

导包时候替换其他名字

import 包名...类名1 as xxx
val name : xxx = xxx() 

range

range表示范围,ClosedRange的子类

0..100 表示 [0,100]
0 until 100 表示 [0,100)
i in 0..100 判断i是否在[0,100]中

类中的toString

open class People(var name: String, var sex: String) {
    override fun toString(): String {
        return name + sex
    }
}

get和set方法

默认已经实现了get/set方法,如果要做处理

class Woman{
    var nameA : String = "翠花"
        get() {
            print("get方法")
            return field
        }
        set(value) {
            print("set方法")
            field = value
        }
}

if可以作为表达式

if可以直接作为表达式

val nameB = if (true){
    "NameB"
} else {
    "NameBB"
}

try...catchwhen等也可以作为表达式。

标签

Outter@for ((index,value) in strArrays.withIndex()){
    println("$index -> $value")
    Inner@for (indexedValue in strArrays.withIndex()){
        if (...){
            break@Outter
        }
    }
}

接口代理

interface Child {
    fun play()
}

interface Parent {
    fun eat()
}

class People : Child, Parent {
    override fun play() {
    }
    override fun eat() {
    }
}

class People1(child: Child,parent: Parent) : Child by child, Parent by parent

object类

只有一个实例的类;不能自定义构造方法;可以实现接口,继承父类;本质上就是单例模式的最基本实现。object类里面的方法都是静态方法。

object FF {
    val ffv : String = "ff"
}

静态方法和成员

companion object包裹起来静态方法;@JvmStatic@JvmField加上后,java去调用更方便。

class HelloWorld {

    @JvmField
    val TAG : String = "TAG"

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            println("Hello")
        }
    }
}

方法重载


方法重载可以用方法上的默认参数代替,想给java用可以加上@JvmOverloads

@JvmOverloads
fun sum(a: Int = 2, b: Int): Int {
    return a + b
}

data class


可以定义javabean,自动有了 toString()hashCode()equals()copy()componentN()这些方法。


填坑:

apply plugin: 'kotlin-noarg'
apply plugin: 'kotlin-allopen'

classpath "org.jetbrains.kotlin:kotlin-gradle-noarg:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-allopen:$kotlin_version"

noArg{
    annotation("xxxx")
}

内部类

java定义内部类,默认持有外部类的引用,除非定义为静态的内部类。

Kotlin中内部类默认是静态内部类,inner定义的是非静态内部类

inner class Inner{
}

匿名内部类

可以继承父类或者实现多个接口

interface OnClickListener {
    fun onClick()
}

class View {
    var onclick: OnClickListener? = null
}

val view = View()
view.onclick = object : OnClickListener{
    override fun onClick() {
    }
}

对象表达式


open class Address2(name: String) {
    open fun print() {

    }
}

class Shop2 {
    var address: Address2? = null
    fun addAddress(address2: Address2) {
        this.address = address2
    }
}

/**
 * 对象表达式
 */
fun test3() {
    //如果超类型有一个构造方法,则必须传递适当的构造方法参数给它
    Shop2().addAddress(object : Address2("Android") {
        //对象表达式
        override fun print() {
            super.print()
        }
    })
    //只需要一个对象 不需要一个新的类
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    println(adHoc.x + adHoc.y)
}

2、高阶函数


对每个元素操作返回新的list

val list = listOf(1, 3, 5, 7, 9, 11)
val newList = list.map {
    it * 2 + 3
}

还有

forEach、map、flatMap、reduce、fold、filter、takeWhile、
let、apply、with、use、
joinToString

尾递归

tailrec

高阶函数:如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数的话,那么该函数就称为高阶函数。

扩展函数->参考上文,18、方法扩展。

定义函数变量, ->左边是参数类型,右边是返回值,Unit相当于void,无返回值:

val a: (String, Int) -> Unit

定义函数

fun num1Andnum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
    return operation(num1, num2)
}

函数作为参数


/**
 * .sum扩展
 */
fun List<Int>.sum(callback: (Int) -> Unit): Int {
    var result = 0
    for (v in this) {
        result += v
        callback(v)
    }
    return result
}

//使用
val list = listOf(1, 2, 3, 4, 5)
var result = list.sum { println(it) }//当lambda作为参数,且只有一个参数时,可用it代替此参数
print(result)

函数作为返回值


实现一个能够对集合进行求和的高阶函数,并且返回一个(scale: Int) -> Float的函数:

/**
 * 函数作为返回值
 */
fun List<String>.toIntSum(): (scale: Int) -> Float {
    println("第一层函数")
    return fun(scale): Float {
        var result = 0f
        for (v in this) {
            result += v.toInt() * scale
        }
        return result
    }
}

//使用
val listString = listOf("1", "2", "3")
val toIntSum = listString.toIntSum()(2)

闭包


实现一个接收一个testClosure方法,该方法要接收一个Int类型的v1参数,同时能够返回一个声名为v2: Int, (Int) -> Unit的函数,并且这个函数能够计算v1和v2的和。

fun testClosure(v1: Int): (v2: Int, (Int) -> Unit) -> Unit {
    return fun(v2, printer: (Int) -> Unit) {
        printer(v1 + v2)
    }
}

//使用
testClosure(1)(2){
    println(it)
}

解构声明


data class Result(val msg: String, val code: Int)

fun test11() {
    val result = Result("success", 200)
    val (msg, code) = result
    println(msg + code)
}

方法字面值


var temp: ((Int) -> Boolean)? = null //变量temp为(Int)->Boolean类型
//{num->(num>10)}方法字面值
temp = { num -> (num > 10) }
println(temp(11))

3、协程

协作程序,解决异步问题。

4、泛型


fun main() {
    Coke().taste()
    Coke().price(Sweet())
    BlueColor(Blue()).printColor()
    test12()
}

//范型接口
interface Drinks<T> {
    fun taste(): T
    fun price(t: T)
}

class Sweet {
    val price = 5
}

class Coke : Drinks<Sweet> {
    override fun taste(): Sweet {
        println("sweet")
        return Sweet()
    }

    override fun price(t: Sweet) {
        println("coke price=${t.price}")
    }
}

//范型类
abstract class Color<T>(var t: T/*范型字段*/) {
    abstract fun printColor()
}

class Blue {
    val color = "blue"
}

class BlueColor(b: Blue) : Color<Blue>(b) {
    override fun printColor() {
        println("color:${t.color}")
    }
}

//范型方法
fun <T> fromJson(json: String, tClass: Class<T>): T? {
    val t: T? = tClass.newInstance()
    return t
}

//范型约束
//extends
fun <T : Comparable<T>?> sort(list: List<T>?): Unit {}

fun test12() {
    sort(listOf(1, 2, 3))
    val listString = listOf("A", "B", "C")
    val list = test(listString, "B")
    println(list)
}

//多个上界
fun <T> test(list: List<T>, threshold: T): List<T>
        where T : CharSequence, T : Comparable<T> {
    return list.filter { it > threshold }.map { it }
}

//范型out(?extends) in(?super)
fun sumOfList(list: List<out Number>): Number {
    var result = 0f;
    for (number in list) {
        result += number.toFloat()
    }
    return result
}

5、注解


fun main() {
    fire(ApiGetArticles())
}

//kotlin注解
annotation class ApiDoc(val value: String)

@ApiDoc("modifier class")
class Box {
    @ApiDoc("modifier field")
    var size = 6

    @ApiDoc("modifier func")
    fun test() {
    }
}

public enum class Method {
    GET, POST
}

//元注解
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class HttpMethod(val method: Method)

interface Api {
    val name: String
    val version: String
        get() = "1.0"
}

@HttpMethod(Method.GET)
class ApiGetArticles : Api {
    override val name: String
        get() = "/api.articles"
}

fun fire(api: Api) {
    val annotations = api.javaClass.annotations
    val httpMethod = annotations.find { it is HttpMethod } as? HttpMethod
    println(httpMethod?.method)
}

6、扩展


fun main() {
    val mutableListOf = mutableListOf(1, 2, 3)
    mutableListOf.swap(0, 2)
    println(mutableListOf)

    val mutableListOf1 = mutableListOf("A", "B", "C")
    mutableListOf1.swap1(0, 2)
    println(mutableListOf1)

    println("hello".lastChar)
    Jump.print("dfaj")
    testApply()
}

//扩展方法
fun MutableList<Int>.swap(index1: Int, index2: Int) {
    var temp = this[index1]
    this[index1] = this[index2]
    this[index2] = temp
}

//范型
fun <T> MutableList<T>.swap1(index1: Int, index2: Int) {
    var temp = this[index1]
    this[index1] = this[index2]
    this[index2] = temp
}

//扩展属性
val String.lastChar: Char get() = this.get(this.length - 1)

//伴生对象扩展
class Jump {
    companion object {}
}

fun Jump.Companion.print(string: String) {
    println("Jump.Companion.print:$string")
}

/*    常见扩展  */
/**
 * let
 */
fun testLet(string: String?) {
    string?.let {//避免null
        println(it.length)
    }
    string?.let {//限制作用域
        val str2 = "let"
        println(it + str2)
    }
}

/**
 * run
 */
data class Room(val address: String, val price: Float, val size: Float)

fun testRun(room: Room) {
    room.run { //不再需要room.
        println("Room:$address,$price,$size")
    }
}

/**
 * apply
 * 类似与Android的builder
 */
fun testApply() {
    ArrayList<String>().apply {//返回的仍为该类型的实例
        add("1")
        add("2")
        add("3")
        println("$this")
    }.let { println(it) }
}

/**---------案例:使用Kotlin扩展为控件绑定监听器减少模板代码---------**/
//为Activity添加find扩展方法,用于通过资源id获取控件
fun <T : View> Activity.find(@IdRes id: Int): T {
    return findViewById(id)
}

//为Int添加onClick扩展方法,用于为资源id对应的控件添加onClick监听
fun Int.onClick(activity: Activity, click: () -> Unit) {
    activity.find<View>(this).apply {
        setOnClickListener {
            click()
        }
    }
}

//Activity中使用 
val v = find<TextView>(R.id.test)
R.id.test.onClick(this){
    test.text="Kotlin extension"
}

补充

代码:https://github.com/AdamRight/Android2021Code/tree/master/app/src/main/java/com/android/code/kotlin

1、静态函数和属性

1.1 顶层函数


顶层函数直接在kotlin文件中定义函数和属性,会直接生成静态的。

在其他kotlin类中可以直接使用函数名,在Java中通过 文件名Kt来使用。

如果在kotlin文件中注解 @file:JvmName("类名"),那么在Java中可以通过 类名.函数名 来使用。


1.2 object类


object类直接生成单例例对象,然后通过单例对象访问函数和属性。

在其他kotlin类中可以通过 类名.函数名 使用;在Java中通过 类名.INSTANCE.函数名来使用。


1.3 companion object


kotlin类中使用 companion object 生成单例例对象,然后通过单例对象访问函数和属性。

在其他kotlin类中可以通过 类名.函数名 使用;在Java中通过 类名.Companion.函数名来使用。

如果在 companion object 中的函数和属性通过 @JvmStatic 修饰,那么Java中可以用 类名.函数名 直接使用。


2、internal


有多个module模块情况下,可⻅性修饰符internal的作用是,只能被当前module模块访问的到。


3、主构造器


改造前

class User1 {
    var userName: String? = null
    var userPassWord: String? = null
    constructor(){

    }
    constructor(name: String, password: String){
        this.userName = name
        this.userPassWord = password
    }
}

改成主构造器:

class User1 constructor(name: String?, password: String?) {
    var userName: String? = null
    var userPassWord: String? = null
    constructor() : this(null, null){

    }
    init {
        this.userName = name
        this.userPassWord = password
    }
}

进一步修改:

class User1 constructor(name: String?, password: String?) {
    var userName: String? = name
    var userPassWord: String? = password
     constructor(): this(null, null)
}

再进一步修改:

class User1 constructor(var name: String?, var password: String?) {
    constructor(): this(null, null)
}

在主构造参数前面加上 var/val 使构造参数同时成为成员变量。

再进一步修改:

class User1 constructor(var name: String? = null, var password: String? = null) {
}

4、查看kotlin类的字节码


Tools –> Kotlin –> Show Kotlin Bytecode


5、Elvis操作符


通过 ?: 的操作来简化 if null 的操作。

var myName = user.name
if (myName == null){
    myName = "kotlin"
}

修改后:

var myName = user.name ?: "kotlin"

修改前:

if (user.name == null || user.name!!.length < 4){

}

修改后:

if (user.name?.length ?: 0 < 4){

}

6、forEach和filter


forEach:

var arrayListUser1 = ArrayList<User1>()
var arrayListUser2 = ArrayList<User1>()

arrayListUser1.forEach {
    if (it.name == "kotlin"){
        arrayListUser2.add(it)
    }
}

filter:

var arrayListUser1 = ArrayList<User1>()
var arrayListUser2: List<User1> = arrayListUser1.filter { it.name == "kotlin" }

7、let、apply、run、also


返回自身 –> 从 apply 和 also 中选

作用域中使用 this 作为参数 ----> 选择 apply

作用域中使用 it 作为参数 ----> 选择 also

不需要返回自身 -> 从 run 和 let 中选择

作用域中使用 this 作为参数 ----> 选择 run

作用域中使用 it 作为参数 ----> 选择 let


文章作者:
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 !
  目录