周元-JS高级用法

# 周元-JS高级用法

# this指针/闭包/作用域

# 前端原型&原型链

// 构造函数
function Person() {}
// 构造函数的实例
const person = new Person(); // 实例化
person.name = 'zhou'
console.log(person.name); // zhou
// 构造函数
function Person() {}
// 实例的原型
Person.prototype.name = 'zhou2';
// 构造函数的实例1
const person1 = new Person();
// 构造函数的实例1
const person2 = new Person();
console.log(person1.name, person2.name); // zhou2 zhou2

Person的prototype属性指向是一个对象 <==> person1/person2创建实例的原型;

// 构造函数
function Person() {}
// 构造函数的实例
const person = new Person();


// __proto__用于获取实例的原型
console.log(person.__proto__ === Person.prototype) // true


// constructor用于返回实例的构造函数
console.log(Person.prototype.constructor === Person); // true
console.log(person.constructor === Person) // true
console.log(person.constructor === Person.prototype.constructor) // true


// getPrototypeOf用于获取实例的原型
console.log(Object.getPrototypeOf(person) === person.__proto__); // true
console.log(Object.getPrototypeOf(person) === Person.prototype); // true


// 构造函数的原型对象的原型 等于 Object的原型
console.log(Person.prototype.__proto__ === Object.prototype);


// Object.prototype.__proto__ 的值为 null,即 Object.prototype 没有原型
console.log(Object.prototype.__proto__ === null) // true
  1. 所有的函数都有prototype属性,这里的Person是作为构造函数来使用;

  2. 每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型;

绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.__proto__ 时,可以理解成返回了 Object.getPrototypeOf(obj)

  1. 原型指向构造函数用constructor,每个原型都有一个 constructor 属性指向关联的构造函数;

其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性。

  • 原型

某一个对象,在它的原型链上的上一个节点。比如person.__proto__就是person的原型。

  • 原型链

原型上的原型,这样一个链式结构,就是原型链。

person 原型链查找:person => Person.prototype => Object.prototype => null;查找属性的时候查到 Object.prototype 就可以停止查找了

  • 继承

每一个对象都会从原型‘继承’属性,继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。

# 作用域

定义变量的区域。

  • 静态作用域

JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。

函数的作用域是在函数定义时确定的。

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar(); // 1

执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。

  • 动态作用域

函数的作用域是在函数调用的时候才决定的。

// case 1
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope(); // local scope

// case 2
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()(); // local scope

JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。

# 执行上下文

console.log(add2(1,1)); //输出2
function add2(a,b){
    return a+b;
}
console.log(add1(1,1));  //报错:add1 is not a function
var add1 = function(a,b){
    return a+b;
}

用函数语句创建的函数add2,函数名称和函数体均被提前,在声明它之前就使用它。

但是使用var表达式定义函数add1,只有变量声明提前了,变量初始化代码仍然在原来的位置,没法提前执行。

# 可执行代码

executable code

  1. 全局代码
  2. 函数代码
  3. eval

# 执行上下文栈

ECS: execution context stack

FILO 先进后出: first in last out

  • case 1
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();

// 模拟执行上下文栈顺序:
ECStack = [
    globalContext
]
1. ECStack.push(<checkscope>functionContext)
2. ECStack.push(<f>functionContext)
3. ECStack.pop();
4. ECStack.pop();
  • case 2
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();

// 模拟执行上下文栈顺序:
ECStack = [
    globalContext
]
1. ECStack.push(<checkscope> functionContext);
2. ECStack.pop();
3. ECStack.push(<f> functionContext);
4. ECStack.pop();

# 变量对象

当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。 对于每个执行上下文,都有三个重要属性:

  1. 变量对象 varible object(VO)
  2. 作用域链 scope chain
  3. this

变量对象:上下文中定义的变量和函数的声明

global、function

# 全局上下文

this => 全局对象

全局对象是作用域链的头,还意味着在顶层 JavaScript 代码中声明的所有变量都将成为全局对象的属性。

JS 全局上下文的VO => 全局对象

对JS而言,全局上下文中的变量对象就是全局对象。

console.log(this); // window对象
console.log(this instanceof Object); // true
console.log(Math.random());
console.log(this.Math.random());

# 函数上下文

在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。

活动对象:activiation object(AO)

活动对象和变量对象其实是一个东西,只是变量对象不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。

# 执行代码

执行上下文的代码会分成两个阶段进行处理:分析和执行

  1. 分析:进入执行上下文
  2. 执行:代码执行
  • 进入执行上下文

当进入执行上下文时,这时候还没有执行代码,变量对象会包括:

  1. 函数的所有形参
  2. 函数声明
  3. 变量声明
function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};

  b = 3;

}

foo(1);

// 进入执行上下文后,AO是:
AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}
  • 代码执行

在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值

// 当代码执行完后,这时候的 AO 是:
AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){},
    d: reference to FunctionExpression "d"
}

# 变量对象的创建过程

  1. 全局上下文的变量对象初始化是全局对象;
  2. 函数上下文的变量对象初始化只包括 Arguments 对象;
  3. 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值;
  4. 在代码执行阶段,会再次修改变量对象的属性值;
  • 例1:
function foo() {
    console.log(a);
    a = 1;
}

foo(); // Uncaught ReferenceError: a is not defined

// 函数中的 "a" 并没有通过 var 关键字声明,所有不会被存放在 AO 中。
// 第一段执行 console 的时候, AO 的值是:
AO = {
    arguments: {
        length: 0
    }
}
// 没有 a 的值,然后就会到全局去找,全局也没有,所以会报错。


function bar() {
    a = 1;
    console.log(a);
}
bar(); // 1
// 这里执行 console 的时候,全局对象已经被赋予了 a 属性,这时候就可以从全局找到 a 的值,所以会打印
  • 例2:
console.log(foo); // 会打印函数
function foo(){
    console.log("foo");
}
var foo = 1;
// 这是因为在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

var foo = 1;
console.log(foo); // 1
function foo(){
    console.log("foo");
}
console.log(foo); // 1

先处理函数声明,再处理函数赋值,然后执行,看当前位置foo是否有被重新赋值,有则打印1,无则打印函数

# 作用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

  • 函数创建

函数的作用域在函数定义的时候就决定了。

函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!

function foo() {
    function bar() {
        ...
    }
}

// 函数创建时,各自的[[scope]]为:
foo.[[scope]] = [
  globalContext.VO
];

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];

  • 函数激活

当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。

这时候执行上下文的作用域链,我们命名为 Scope:

Scope = [AO].concat([[Scope]]);
var scope = "global scope";
function checkscope(){
    var scope2 = 'local scope';
    return scope2;
}
checkscope();

// 执行过程如下:
// 1. checkscope 函数被创建,保存作用域链到 内部属性[[scope]]
checkscope.[[scope]] = [
    globalContext.VO
];

// 2.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
ECStack = [
    checkscopeContext,
    globalContext
];

// 3.checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链
checkscopeContext = {
    Scope: checkscope.[[scope]],
}

// 4.第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    }Scope: checkscope.[[scope]],
}

// 5.第三步:将活动对象压入 checkscope 作用域链顶端
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: [AO, [[Scope]]]
}

// 6.准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: 'local scope'
    },
    Scope: [AO, [[Scope]]]
}

// 7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
  globalContext
];

# this

ECMAScript 类型

  • 语言类型:String / bool / Null
  • 规范类型:Reference, Property Descriptor, Environment Record, ...

用来描述语言底层行为逻辑~

# Reference 类型

Reference 类型就是用来解释诸如 delete、typeof 以及赋值等操作行为的。

Reference 是一个 Specification Type,也就是 “只存在于规范里的抽象类型”。它们是为了更好地描述语言的底层行为逻辑才存在的,但并不存在于实际的 js 代码中。

Reference 的构成,由三个组成部分,分别是:

  • base value:base value 就是属性所在的对象
  • referenced name:属性的名称
  • strict reference
var foo = 1;
// 对应的Reference是:
var fooReference = {
    base: EnvironmentRecord,
    referenced name: 'foo',
    strict reference: false
};
// GetBase
GetBase(fooReference) // base
// GetValue 返回对象属性真正的值
GetValue(fooReference) // 1;


var foo = {
    bar: function () {
        return this;
    }
};
foo.bar(); // foo
// bar对应的Reference是:
var BarReference = {
    base: foo,
    referenced name: 'bar',
    strict reference: false
};

var value = 1;

var foo = {
  value: 2,
  bar: function () {
    return this.value;
  }
}

//示例1
console.log(foo.bar()); // 2
//示例2
console.log((foo.bar)()); // 2
//示例3
console.log((foo.bar = foo.bar)()); // 1
//示例4
console.log((false || foo.bar)()); // 1
//示例5
console.log((foo.bar, foo.bar)()); // 1

# 闭包

闭包:能够访问自由变量的函数。

自由变量:能在函数中使用,但既不是函数参数也不是函数的局部变量的变量。

函数 + 函数里能够访问非自身的变量

var a = 1;
function foo() {
    console.log(a);
}
foo();

foo 函数可以访问变量 a,但是 a 既不是 foo 函数的局部变量,也不是 foo 函数的参数,所以 a 就是自由变量。

在《JavaScript权威指南》中就讲到:从技术的角度讲,所有的JavaScript函数都是闭包

ECMAScript中,闭包指的是:

  1. 从理论角度:所有的函数。

因为它们都在创建的时候就将上层上下文的数据保存起来了。函数中访问全局变量就相当于是在访问自由变量;

  1. 从实践角度:以下函数才算是闭包:
    • 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回);
    • 在代码中引用了自由变量;
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        console.log(this); // Window
        return scope;
    }
    return f;
}

var foo = checkscope();
foo(); // local scope


// 全局上下文
VO = {
    scope: 'global scope',
    checkscope: reference checkscope function
}
作用域链:[VO]
this: window


// checkscope上下文
AO = {
    arguments: {
        length: 0
    },
    scope: 'local scope',
    f: reference f() {}
}
作用域链:[AO, VO]
// this
ECStack = [
    <f>functionContext,
    <checkscope>functionContext,
    globalContext
]


{/* f上下文 */}
AO = {
    arguments: {
        length: 0
    },
}
作用域链:[<f>AO,<checkscope>AO, VO]
ECStack = [
    <f>functionContext,
    globalContext
]

Q: 当 f 函数执行的时候,checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢?

具体的执行过程后,我们知道 f 执行上下文维护了一个作用域链:

fContext = {
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
}

因为这个作用域链,f 函数依然可以读取到 checkscopeContext.AO 的值,说明当 f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中,f 函数依然可以通过 f 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。

# 传递参数

  • 按值传递

ECMAScript中所有函数的参数都是按值传递的。

把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。

var value = 1;
function foo(v) {
    v = 2;
    console.log(v); //2
}
foo(value);
console.log(value) // 1
  • 按引用传递

所谓按引用传递,就是传递对象的引用,函数内部对参数的任何改变都会影响该对象的值,因为两者引用的是同一个对象。

var obj = {
    value: 1
};
function foo(o) {
    o.value = 2;
    console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2

又叫共享传递,在传递对象的时候,传递的是地址索引。

参数如果是基本类型是按值传递,如果是引用类型按共享传递。

Q:为什么《JavaScript高级程序设计》都说了 ECMAScript 中所有函数的参数都是按值传递的,那为什么能按"引用传递"成功呢?

参数如果是基本类型是按值传递,如果是引用类型按共享传递。但是因为拷贝副本也是一种值的拷贝,所以在高程中也直接认为是按值传递了。

函数传递参数 ,传递的是参数的拷贝:

  1. 指针拷贝,拷贝的是地址索引;
  2. 常规类型拷贝,拷贝的是值 ;

javascript中数据类型分为基本类型与引用类型:

  1. 基本类型值存储于栈内存中,传递的就是当前值,修改不会影响原有变量的值;
  2. 引用类型值其实也存于栈内存中,只是它的值是指向堆内存当中实际值的一个地址;索引引用传递传的值是栈内存当中的引用地址,当改变时,改变了堆内存当中的实际值;

# call 和 apply

# call实现

在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

call改变了this指向。

模拟的步骤可以分为:

  1. 将函数设为对象的属性;
  2. 执行该函数;
  3. 删除该函数;
// 第一步
// fn 是对象的属性名,反正最后也要删除它,所以起什么都可以。
foo.fn = bar
// 第二步
foo.fn()
// 第三步
delete foo.fn
  • 完整版:
Function.prototype.call2 = function(context) {
    var context = context || window; // 兼容this参数传 null的情况
    context.fn = this; // 即:foo.fn = bar;
    let arg = [...arguments].slice(1) // 指定参数
    let result = context.fn(...arg)

    delete context.fn
  	return result // 实现返回值
}

// 测试一下
var foo = {
    value: 1
};

function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}

bar.call2(foo, 'kevin', 18); 
// kevin
// 18
// 1

Symbol写法:

Function.prototype.call2 = function(context, ...args) {
  // 判断是否是undefined和null
  if (typeof context === 'undefined' || context === null) {
    context = window
  }
  let fnSymbol = Symbol()
  context[fnSymbol] = this
  let fn = context[fnSymbol](...args)
  delete context[fnSymbol] 
  return fn
}

# apply实现

apply 的实现跟 call 类似,只是入参不一样,apply为数组

Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
				result = context.fn(...arr)
    }

    delete context.fn
    return result;
}

// Symbol写法
Function.prototype.apply2 = function(context, args) {
  // 判断是否是undefined和null
  if (typeof context === 'undefined' || context === null) {
    context = window
  }
  let fnSymbol = Symbol()
  context[fnSymbol] = this
  let fn = context[fnSymbol](...args)
  delete context[fnSymbol] 
  return fn
}

# bind的实现

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

bind 函数的两个特点:

  1. 返回一个函数;
  2. 可以传入参数;
  • 版本1
Function.prototype.bind2 = function (context) {

    var self = this;
    // 获取bind2函数从第二个参数到最后一个参数
    var args = Array.prototype.slice.call(arguments, 1);

    return function () {
        // 这个时候的arguments是指bind返回的函数传入的参数
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(context, args.concat(bindArgs));
    }
}


/**
 * 调用
 */
var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}

bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'daisy');

bindFoo('18');
// 1
// daisy
// 18

// new调用
var obj = new bindFoo('18');
// undefined (绑定的 this 失效, 已经指向obj了)
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin
  • 版本2,兼容使用new时this失效问题:
// 第三版
Function.prototype.bind2 = function (context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
        // 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
        // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
        return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
    }
    // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
    fBound.prototype = this.prototype;
    return fBound;
}
  • this: 新创建出来的obj实例,这个实例是通过 new fBound 创建
  • obj instanceof fBound

# 手写new

因为 new 是关键字,所以无法像 bind 函数一样直接覆盖,所以我们写一个函数,命名为 objectFactory,来模拟 new 的效果。用的时候是这样的:

function Person () {
    ……
}

// 使用 new
var person = new Person(……);
// 使用 objectFactory
var person = objectFactory(Person, ……)
  • 实现:
function Person (name, age) {
    this.name = name;
    this.age = age;

    this.habit = 'Games';
}

Person.prototype.strength = 60;

Person.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}

function objectFactory() {
    var obj = new Object(),
    Constructor = [].shift.call(arguments); // 获取传入的第一个参数作为构造函数
    obj.__proto__ = Constructor.prototype; // 获取原型对象
    Constructor.apply(obj, arguments); // 传入其他参数

    // return obj; // 返回obj
    // 优化:
    /**
     * 构造函数返回了一个对象,在实例 person 中只能访问返回的对象中的属性;
     * 返回基本数据类型,则不对返回值进行处理
     */
    return typeof ret === 'object' ? ret : obj;
};

var person = objectFactory(Person, 'Kevin', '18')

console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60

person.sayYourName(); // I am Kevin

# 类数组对象与arguments

# 类数组对象

拥有一个 length 属性和若干索引属性的对象

var array = ['name', 'age', 'sex'];
var arrayLike = {
    0: 'name',
    1: 'age',
    2: 'sex',
    length: 3
}

// 读写
console.log(array[0]); // name
console.log(arrayLike[0]); // name
array[0] = 'new name';
arrayLike[0] = 'new name';
// 长度
console.log(array.length); // 3
console.log(arrayLike.length); // 3
// 遍历
for(var i = 0, len = array.length; i < len; i++) {
   ……
}
for(var i = 0, len = arrayLike.length; i < len; i++) {
    ……
}

// 但是调用原生的数组方法会报错,如push:
// arrayLike.push is not a function
  • 调用数组方法

只能通过 Function.call 间接调用~

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }

Array.prototype.join.call(arrayLike, '&'); // name&age&sex
Array.prototype.map.call(arrayLike, function(item){
    return item.toUpperCase();
}); 
// ["NAME", "AGE", "SEX"]


/**
 * 类数组转数组
 */
// 1.slice
Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] 
// 2. splice
Array.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"] 
// 3. ES6 Array.from
Array.from(arrayLike); // ["name", "age", "sex"] 
// 4. apply
Array.prototype.concat.apply([], arrayLike)

# Arguments对象

Arguments 对象只定义在函数体中,包括了函数的参数和其他属性。在函数体中,arguments 指代该函数的 Arguments 对象。

function foo(b, c, d){
    console.log(arguments) // 除了类数组的索引属性和length属性之外,还有一个callee属性
    console.log("实参的长度为:" + arguments.length) // 实参的长度为:1
}
console.log("形参的长度为:" + foo.length) // 形参的长度为:3
foo(1)


/**
 * callee
 */
// 闭包经典面试题使用 callee 的解决方法:
var data = [];
for (var i = 0; i < 3; i++) {
    (data[i] = function () {
       console.log(arguments.callee.i) // Arguments 对象的 callee 属性,通过它可以调用函数自身。
    }).i = i;
}
data[0](); // 0
data[1](); // 1
data[2](); // 2
  • 传入的参数,实参和 arguments 的值会共享,当没有传入时,实参与 arguments 值不会共享

# 讲义

# 收藏

上次更新: 7/6/2023, 7:44:05 PM
最近更新
01
taro开发实操笔记
09-29
02
前端跨端技术调研报告
07-28
03
Flutter学习笔记
07-15
更多文章>