ES6-变量的解构赋值(3)

1、解构赋值简介官方解释:按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。举个例子,想获取数组中的前三个元素,通常会这么写:var arr =[111,222,333];var first = arr[0];var second = arr[1];var third = arr[2];如果使用解构赋值的特性,将会使等效的代码变得更加简洁并且可读性更高:let [first, second, third] = arr;本质上,这种写法属于“模式匹配”、“映射关系”。只要等号两边的模式相同,一一对应,左边的变量就会被赋予右边对应的值。这种赋值语法极度简洁,同时还比传统的属性访问方法更为条理清晰。当然,世间万物并不是完美的,例如下面的例子:let [x, y] = [‘a’];x // “a"y // undefined注意:左边数组中的 y 没有找到右边数组中对应值,解构不成功,变量 y 的值就等于undefined。我们也可以给解构的对象设置一个默认值let [x, y=‘b’] = [‘a’];x // “a"y // “b"左边数组中的 y 的有了默认值 “b”。把解构赋值说的更通俗点,有点类似于“庖丁解牛” 。庖丁心里先设想把牛(Array、Object等)分解成很多块,然后按照规划好的想法,一刀刀对应起来,就把牛分解了。2、数组的解构赋值2.1 数组解构赋值特点// ES6 之前var a=1; var b=2; var c=3;// ES6 之后let [a,b,c] = [1,2,3];这样可以用简单的方式将数组的值分别赋值到多个变量中。数组的解构赋值特点:根据数据的下标来赋值的,有次序。本质上,只要等号两边模式一致,左边变量即可获取右边对应位置的值。2.2 可以对任意深度的嵌套数组进行解构能够非常迅速的获取二维数组、三维数组,甚至多维数组中的值let [foo, [[bar], baz]] = [1, [[2], 3]];console.log(foo); // 1console.log(bar); // 2console.log(baz); // 32.3 不需要匹配的位置可以置空[,,third] = [1, 2, 3]; console.log(third); // 32.4 使用…扩展运算符,匹配余下的所以值,形成一个数组var [head, …body] = [1, 2, 3, 4]; console.log(body); // [2, 3, 4]这个’三点式’运算符我们用的比较多,比如用在数组合并上,//ES5var arr1 = [8]var arr2 = [9,11,12,13]arr1.push(arr2);//[8,[9,11,12,13]]Array.prototype.push.apply(arr1,arr2);//[8,9,11,12,13]// ES6arr1.push(…arr2);console.log(arr1)//[8,9,11,12,13]大家可以看到ES6明显写起来简洁很多。3、对象的解构赋值3.1 对象的解构赋值特点数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。let { a, b } = { a: “111”, z: “222” };a // “111"b // undefined上面的例子中,变量名与属性名不一致,可以改写成下面这样:let { a, z:b } = { a: “111”, z: “222” };a // “111"b // “222"对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。3.2 可以对任意深度的嵌套对象进行解构let itemObj = { arr: [ “aaa”, { secondLevel: “bbb” } ] }; let { arr: [firstLevel, { secondLevel }] } = itemObj; console.log(firstLevel); // “aaa” console.log(secondLevel); // “bbb"3.3 可以自定义属性名称var {name, id: ID} = { name: ‘jack’, id: 1 };ID // 1id // Uncaught ReferenceError: id is not defined但要注意的是被赋值的只是我们自定义的属性名称,匹配的模式(项)并未被赋值4、字符串解构字符串也可以解构赋值,字符串被转换成了一个类似数组的对象。模式能够匹配起来,如:const [a, b, c, d, e] = ‘hello’;a // “h"b // “e"c // “l"d // “l"e // “o"let { length:len } = ‘hello’;console.log(len); //5 (长度为5)5、数值和布尔值的解构赋值解构赋值的规则是:只要等号右边的值不是对象或数组,就先将其转为对象。如果转换之后的对象或原对象拥有Iterator接口,则可以进行解构赋值,否则会报错。// 数值和布尔值的包装对象都有toString属性let {toString: str} = 111;str === Number.prototype.toString // truelet {toString: str} = true;str === Boolean.prototype.toString // truelet { prop: x } = undefined; // TypeErrorlet { prop: y } = null; // TypeError以上的数组和布尔值会转换成对象,toString模式匹配上了对象的toString属性,所以解构成功。而null或undefined却不能转换成此类对象,所以报错。ES6中引入了Iterator迭代器,集合Set或Generator生成器函数等都部署了这个Iterator接口,所以也可以用来进行解构赋值。Set的解构赋值例子如下:var [a, b, c] = new Set([1, 2, 3]);a // 1b // 2c // 36、圆括号的用法如果在解构之前就已经定义了对象let obj;{obj}={obj:‘James’};console.log(‘James’); //报错原因:大括号{位于行首,匹配了}之后 JS引擎 就会认为 { obj } 是一个代码块,所以等号就出问题了,解决方式是在行首放个括号(,即外包裹一层括号()let obj;({obj}={obj:‘James’});console.log(‘James’); //James括号的出现,让整个解构赋值的结构被看做一个代码块,而内部的 { obj } 模式则可以正常匹配到。7、实际用途7.1 交换变量的值let x = 1;let y = 2;[x, y] = [y, x];console.log(x); //2console.log(y); //17.2 函数参数定义// 参数是一组有次序的值function foo([width,height,left,right]) { //… }foo([100, 200, 300, 300])// 参数是一组无次序的值function foo({width,height,left,right}){ // …}foo([left:300, width:100, right:300, height:200,])为了实现设计良好的 API,通常的做法是为函数为函数设计一个对象作为参数,然后将不同的实际参数作为对象属性,以避免让 API 使用者记住多个参数的使用顺序。我们可以使用解构特性来避免这种问题,当我们想要引用它的其中一个属性时,大可不必反复使用这种单一参数对象。7.3 配置对象参数jQuery.ajax = function (url, { async = true, beforeSend = noop, cache = true, complete = noop, crossDomain = false, global = true, // … 更多配置 }) { // … };如此一来,我们可以避免对配置对象的每个属性都重复var foo = config.foo || theDefaultFoo;这样的操作。7.4从函数返回多个值// 返回一个数组function foo() { return [1, 2, 3];}let [a, b, c] = foo();// 返回一个对象function foo2() { return { a: 1, b: 2 };}let { a, b } = foo2();7.5 引入模块的指定方法加载模块时,往往需要指定输入那些方法。解构赋值使得输入语句非常清晰。如果项目中只用到了element-ui中的Loading模块,可以这么写:import { Loading} from ’element-ui'8、总结8.1 解构赋值的定义解构赋值,即对某种结构进行解析,然后将解析出来的值赋值给相关的变量,常见的有数组、对象、字符串的解构赋值等。在语法上,就是赋值的作用,解构为(左边一种解构。右边一种解构,左右一一对应进入赋值)。8.2 解构赋值的分类1.左右为数组即为数组解构赋值。2.左右为对象即为对象解构赋值。3.左边是数组,右边是字符串,为字符串解构赋值。4.布尔值解构赋值为字符串的一种。5.函数参数解构赋值即为数组解构赋值在函数参数的一种应用。6.数值解构赋值为字符串解构赋值的一种。8.3 解构赋值的优势变量的解构赋值就是一种写法,掌握了这种写法可以让我们在书写 javascript 代码时可以更加的简单,迅捷。在很多独立细小的方面,解构赋值都非常有用。这些新的特性和微小的改进结合起来,它终将会影响你工作中的每一个项目。 ...

April 5, 2019 · 2 min · jiezi

PHP变量的引用赋值与传值赋值

一、使用 memory_get_usage() 查看PHP内存使用量1. 传值赋值// 定义一个变量$a = range(0, 10000);var_dump(memory_get_usage());// 定义变量b,将a变量的值赋值给b$b = $a;var_dump(memory_get_usage());// 对a进行修改// COW: Copy-On-Write$a = range(0, 10000);var_dump(memory_get_usage());输出结果:int(989768)int(989856)int(1855608)定义一个变量 $a = range(0, 10000);$b = $a;对a进行修改 $a = range(0, 10000);PHP写时复制机制(Copy-on-Write,也缩写为COW)顾名思义,就是在写入时才真正复制一份内存进行修改。COW最早应用在Unix系统中对线程与内存使用的优化,后面广泛的被使用在各种编程语言中,如C++的STL等。 在PHP内核中,COW也是主要的内存优化手段。在通过变量赋值的方式赋值给变量时,不会申请新内存来存放新变量的值,而是简单的通过一个计数器来共用内存。只有在其中的一个引用指向变量的值发生变化时,才申请新空间来保存值内容,以减少对内存的占用。在很多场景下PHP都使用COW进行内存的优化。比如:变量的多次赋值、函数参数传递,并在函数体内修改实参等。2. 引用赋值// 定义一个变量$a = range(0, 10000);var_dump(memory_get_usage());// 定义变量b,将a变量的引用赋给b$b = &$a;var_dump(memory_get_usage());// 对a进行修改$a = range(0, 10000);var_dump(memory_get_usage());输出结果:int(989760)int(989848)int(989840)定义一个变量 $a = range(0, 10000);定义变量b,将a变量的引用赋给b $b = &$a;对a进行修改 $a = range(0, 10000);二、使用 xdebug_debug_zval() 查看变量的引用情况xdebug_debug_zval() 用于显示变量的信息。需要安装xdebug扩展。1. 传值赋值$a = 1;xdebug_debug_zval(‘a’);// 定义变量b,把a的值赋值给b$b = $a;xdebug_debug_zval(‘a’);xdebug_debug_zval(‘b’);// a进行写操作$a = 2;xdebug_debug_zval(‘a’);xdebug_debug_zval(‘b’);输出结果:a: (refcount=1, is_ref=0)=1a: (refcount=2, is_ref=0)=1b: (refcount=2, is_ref=0)=1a: (refcount=1, is_ref=0)=2b: (refcount=1, is_ref=0)=1定义变量 $a = 1;$a = 1;xdebug_debug_zval(‘a’);输出a: (refcount=1, is_ref=0)=1refcount=1 表示该变量指向的内存地址的引用个数变为1is_ref=0 表示该变量不是引用定义变量 $b ,把 $a 的值赋给 $b, $b = $a;$b = $a;xdebug_debug_zval(‘a’);xdebug_debug_zval(‘b’);输出a: (refcount=2, is_ref=0)=1b: (refcount=2, is_ref=0)=1refcount=2 表示该变量指向的内存地址的引用个数变为2is_ref=0 表示该变量不是引用对变量 $a 进行写操作 $a = 2;$a = 2;xdebug_debug_zval(‘a’);xdebug_debug_zval(‘b’);输出a: (refcount=1, is_ref=0)=2b: (refcount=1, is_ref=0)=1因为COW机制,对变量 $a 进行写操作时,会为变量 $a 新分配一块内存空间,用于存储变量 $a 的值。此时 $a 和 $b 指向的内存地址的引用个数都变为1。2. 引用赋值$a = 1;xdebug_debug_zval(‘a’);// 定义变量b,把a的引用赋给b$b = &$a;xdebug_debug_zval(‘a’);xdebug_debug_zval(‘b’);// a进行写操作$a = 2;xdebug_debug_zval(‘a’);xdebug_debug_zval(‘b’);a: (refcount=1, is_ref=0)=1a: (refcount=2, is_ref=1)=1b: (refcount=2, is_ref=1)=1a: (refcount=2, is_ref=1)=2b: (refcount=2, is_ref=1)=2定义变量 $a = 1;$a = 1;xdebug_debug_zval(‘a’);输出a: (refcount=1, is_ref=0)=1refcount=1 表示该变量指向的内存地址的引用个数变为1is_ref=0 表示该变量不是引用定义变量 $b ,把 $a 的引用赋给 $b, $b = &$a;$b = &$a;xdebug_debug_zval(‘a’);xdebug_debug_zval(‘b’);输出a: (refcount=2, is_ref=1)=1b: (refcount=2, is_ref=1)=1refcount=2 表示该变量指向的内存地址的引用个数变为2is_ref=1 表示该变量是引用对变量 $a 进行写操作 $a = 2;$a = 2;xdebug_debug_zval(‘a’);xdebug_debug_zval(‘b’);输出a: (refcount=2, is_ref=1)=2b: (refcount=2, is_ref=1)=2因为变量 $a 和变量 $b 指向相同的内存地址,其实引用。对变量 $a 进行写操作时,会直接修改指向的内存空间的值,因此变量 $b 的值会跟着一起改变。三、当变量时引用时,unset()只会取消引用,不会销毁内存空间$a = 1;$b = &$a;// unset 只会取消引用,不会销毁内存空间unset($b);echo $a;输出1定义变量 $a ,并将 $a 的引用赋给变量 $b$a = 1;$b = &$a;销毁 $bunset($b);输出 $a虽然销毁的 $b,但是 $a 的引用和内存空间依旧存在。echo $a;输出1四、php中对象本身就是引用赋值class Person{ public $age = 1;}$p1 = new Person;xdebug_debug_zval(‘p1’);$p2 = $p1;xdebug_debug_zval(‘p1’);xdebug_debug_zval(‘p2’);$p2->age = 2;xdebug_debug_zval(‘p1’);xdebug_debug_zval(‘p2’);p1: (refcount=1, is_ref=0)=class Person { public $age = (refcount=2, is_ref=0)=1 }p1: (refcount=2, is_ref=0)=class Person { public $age = (refcount=2, is_ref=0)=1 }p2: (refcount=2, is_ref=0)=class Person { public $age = (refcount=2, is_ref=0)=1 }p1: (refcount=2, is_ref=0)=class Person { public $age = (refcount=1, is_ref=0)=2 }p2: (refcount=2, is_ref=0)=class Person { public $age = (refcount=1, is_ref=0)=2 }实例化对象 $p1 = new Person;$p1 = new Person;xdebug_debug_zval(‘p1’);输出p1: (refcount=1, is_ref=0)=class Person { public $age = (refcount=2, is_ref=0)=1 }refcount=1 表示该变量指向的内存地址的引用个数变为1is_ref=0 表示该变量不是引用把 $p1 赋给 $p2$p2 = $p1;xdebug_debug_zval(‘p1’);xdebug_debug_zval(‘p2’);输出p1: (refcount=2, is_ref=0)=class Person { public $age = (refcount=2, is_ref=0)=1 }p2: (refcount=2, is_ref=0)=class Person { public $age = (refcount=2, is_ref=0)=1 }refcount=2 表示该变量指向的内存地址的引用个数变为2对 $p2 中的属性 age 进行写操作$p2->age = 2;xdebug_debug_zval(‘p1’);xdebug_debug_zval(‘p2’);输出p1: (refcount=2, is_ref=0)=class Person { public $age = (refcount=1, is_ref=0)=2 }p2: (refcount=2, is_ref=0)=class Person { public $age = (refcount=1, is_ref=0)=2 }因为php中对象本身就是引用赋值。对 $p2 中的属性 age 进行写操作时,会直接修改指向的内存空间的值,因此变量 $p1 的 age 属性的值会跟着一起改变。五、实战例题分析/** * 写出如下程序的输出结果 * * $d = [‘a’, ‘b’, ‘c’]; * * foreach($d as $k => $v) * { * $v = &$d[$k]; * } * * 程序运行时,每一次循环结束后变量 $d 的值是什么?请解释。 * 程序执行完成后,变量 $d 的值是什么?请解释。 */1. 第一次循环推算出进入 foreach 时 $v、$d[$k] 的值$k = 0$v = ‘a’$d[$k] = $d[0] = ‘a’此时,$v 和 $d[0] 在内存中分别开辟了一块空间![$v 和 $d[0] 在内存中分别开辟了一块空间](http://md.ws65535.top/xsj/201...$v = &$d[0] 改变了 $v 指向的内存地址$v = &$d[0]![$v = &$d[0] 改变了 $val 指向的内存地址](http://md.ws65535.top/xsj/201…第一次循环后 $d 的值:[‘a’, ‘b’, ‘c’]2. 第二次循环进入 foreach 时 $v 被赋值为 ‘b’,此时$v指向的内存地址与 $d[0] 相同,且为引用,因此 $d[0] 的值被修改为 ‘b’$v = ‘b’ => $d[0] = ‘b’![$v = ‘b’ => $d[0] = ‘b’](http://md.ws65535.top/xsj/201…推算出进入 foreach 时 $d[$k] 的值$k = 1$d[$k] = $d[1] = ‘b’![$d[2] = ‘b’](http://md.ws65535.top/xsj/201...$v = &$d[1] 改变了 $v 指向的内存地址$v = &$d[1]![$v = &$d[1]](http://md.ws65535.top/xsj/201…第二次循环后 $d 的值[‘b’, ‘b’, ‘c’]3. 第三次循环进入 foreach 时 $v 被赋值为 ‘c’,此时$v指向的内存地址与 $d[1] 相同,且为引用,因此 $d[1] 的值被修改为 ‘c’$v = ‘c’ => $d[1] = ‘c’![$v = ‘c’ => $d[1] = ‘c’](http://md.ws65535.top/xsj/201…推算出进入 foreach 时 $d[$k] 的值$k = 2$d[2] = ‘c’![$d[2] = ‘c’](http://md.ws65535.top/xsj/201...$v = &$d[2] 改变了 $v 指向的内存地址$v = &$d[2]![$v = &$d[2]](http://md.ws65535.top/xsj/201…第三次循环后 $d 的值[‘b’, ‘c’, ‘c’]4. 实测$d = [‘a’, ‘b’, ‘c’];foreach ($d as $k=>$v){ $v = &$d[$k]; print_r($d);}print_r($d);输出:Array( [0] => a [1] => b [2] => c)Array( [0] => b [1] => b [2] => c)Array( [0] => b [1] => c [2] => c)Array( [0] => b [1] => c [2] => c) ...

September 2, 2018 · 3 min · jiezi