JavaScript 你必须掌握的概念(上)

南楼画角 提交于 2021-02-12 02:05:31

不可变的原始值和可变的对象引用

当JavaScript中指定的值是原始值(基本类型)即 Boolean,null,undefined,String 和 Number 时,将分配实际值。原始值是不可更改的,

任何方法都无法更改或突变一个原始值。

  
    
  
  
  
  1. let str1 = 'My string';

  2. let str2 = str1;

  3. str2 = 'My new string';

  4. console.log(str1);

  5. // 'My string'

  6. console.log(str2);

  7. // 'My new string'

但是,当指定的值是Array,Function 或 Object 时,将分配内存中对象的引用。对象和原始值不同,首先它们是可变的,它们的值是可修改的:

  
    
  
  
  
  1. let obj1 = { name: 'Jim' }

  2. let obj2 = obj1;

  3. obj2.name = 'John';

  4. console.log(obj1); // { name: 'John' }

  5. console.log(obj2); // { name: 'John' }

原始值的比较是值的比较,只有在它们的值相等时它们才相等。而对象的比较均是引用的比较,对象值都是引用,当且仅当它们引用同一个基对象时,它们才相等。

  
    
  
  
  
  1. //原始值的比较

  2. let str1 = "hello";

  3. let str2 = "hello";

  4. console.log(str1 === str2); // true 值的比较


  5. //引用不同的两个对象

  6. let o = {x: 1}, p = {x: 1} //具有相同属性的两个对象

  7. console.log(o === p); //false 两个单独的对象永不相等

  8. console.log([] === []); //;两个单独的数组永不相等

  9. //引用相同的两个对象

  10. let a = [];

  11. let b = a;

  12. b[0] = 1;

  13. console.log(a[0]); //1 变量a也会修改

  14. console.log(a === b); // true a 和 b 引用同一个数组

闭包(Closure)

闭包是一种用于访问私有变量的重要JavaScript模式,可以访问当前作用域链上的变量。先看一个简单的例子:

  
    
  
  
  
  1. function createGreeter(greeting) {

  2. return function(name) { //返回的匿名函数可以访问 greeting

  3. console.log(greeting + ', ' + name);

  4. }

  5. }

  6. const sayHello = createGreeter('Hello');

  7. sayHello('Joe');

  8. // Hello, Joe

提到闭包,就不得不提到变量作用域这个概念,函数的执行依赖于变量作用域,要注意的是,这个作用域是在函数定义时决定的,而不是函数调用时决定的。看下边这个例子:

  
    
  
  
  
  1. var scope = "global scope"; //全局变量

  2. function fn() {

  3. var scope = "local scope"; //局部变量

  4. function f() { return scope; } //在作用域中返回这个值

  5. return f();

  6. }


  7. fn(); // 输出 local scope


上个例子中,沿着作用域链向上查找,你应该很轻松得知输出的结果是 local scope。现在将这段代码稍作改动,你知道会返回什么吗?

  
    
  
  
  
  1. var scope = "global scope"; //全局变量

  2. function fn() {

  3. var scope = "local scope"; //局部变量

  4. function f() { return scope; } //在作用域中返回这个值

  5. return f;

  6. }


  7. fn()(); // 输出 ?

在这段代码中,我们将函数内的一对圆括号移动到了 fn() 之后,fn() 现在仅仅返回函数内嵌套的一个函数对象,而不是返回结果,在定义函数作用域外面调用这个闭包会发生什么呢?上文提到,作用域链上函数定义时创建的,这个闭包定义在这个作用域里,所以不管在何时执行这个函数 f(), 返回的结果一定上定义的局部变量 local scope。

解构赋值(Destructuring

解构赋值中,右侧的数组或对象中的一个或多个值会被提取出来(解构),并赋值给左侧相应的变量名,这是一种从对象中干净地提取属性的常用方法。

  
    
  
  
  
  1. const obj = {

  2. name: 'lili',

  3. age: 23

  4. }

  5. const { name, age } = obj;


  6. console.log(name, age); // 'lili' 23

如果要以指定的名称提取属性,可以使用以下格式指定它们。

  
    
  
  
  
  1. const obj = {

  2. name: 'Lili',

  3. age: 23

  4. }

  5. const { name: myName, age: myAge } = obj;

  6. console.log(myName, myAge); // 'Lili' 23

解构赋值还可以直接用于提取函数参数(React 经常会使用)。

  
    
  
  
  
  1. const person = {

  2. name: 'Lili',

  3. age: 23

  4. }

  5. function introduce({ name, age }) {

  6. console.log(`我叫 ${name}, 我今年 ${age} !`);

  7. }

  8. console.log(introduce(person));

  9. // "我叫 Lili 我今年 24!"

扩展运算符(Spread Syntax

扩展运算符可以在函数调用/数组构造时, 将数组表达式在语法层面展开,还可以在构造字面量对象时, 将对象表达式按key-value的方式展开。

等价于apply的方式:

  
    
  
  
  
  1. function myFunction(x, y, z) { }

  2. var args = [0, 1, 2];

  3. myFunction.apply(null, args);


  4. const path = require('path'

有了展开语法,可以这样写:

  
    
  
  
  
  1. function myFunction(x, y, z) { }

  2. var args = [0, 1, 2];

  3. myFunction(...args);

所有参数都可以通过展开语法来传值,也不限制多次使用展开语法。

  
    
  
  
  
  1. function myFunction(v, w, x, y, z) { }

  2. var args = [0, 1];

  3. myFunction(-1, ...args, 2, ...[3]);

数组拷贝(copy):

  
    
  
  
  
  1. var arr = [1, 2, 3];

  2. var arr2 = [...arr]; //类似 arr.slice()

  3. arr2.push(4);


  4. // arr2 此时变成 [1, 2, 3, 4]

  5. // arr 不受影响

不使用展开语法,连接多个数组:

  
    
  
  
  
  1. var arr1 = [0, 1, 2];

  2. var arr2 = [3, 4, 5];

  3. // 将 arr2 中所有元素附加到 arr1 后面并返回

  4. var arr3 = arr1.concat(arr2);

使用展开语法:

  
    
  
  
  
  1. var arr1 = [0, 1, 2];

  2. var arr2 = [3, 4, 5];

  3. var arr3 = [...arr1, ...arr2];

Array.unshift 方法常用于在数组的开头插入新元素/数组.  不使用展开语法, 示例如下:

  
    
  
  
  
  1. var arr1 = [0, 1, 2];

  2. var arr2 = [3, 4, 5];

  3. // 将 arr2 中的元素插入到 arr1 的开头

  4. Array.prototype.unshift.apply(arr1, arr2)

  5. // arr1 现在是 [3, 4, 5, 0, 1, 2]

如果使用展开语法, 代码如下: (请注意, 这里使用展开语法创建了一个新的 arr1 数组,  Array.unshift 方法则是修改了原本存在的 arr1 数组):

  
    
  
  
  
  1. var arr1 = [0, 1, 2];

  2. var arr2 = [3, 4, 5];

  3. arr1 = [...arr2, ...arr1];

  4. // arr1 现在为 [3, 4, 5, 0, 1, 2]

Rest 运算符(Rest Syntax)

用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

  
    
  
  
  
  1. function myFunc(...args) {

  2. console.log(args[0] + args[1]);

  3. }

  4. myFunc(1, 2, 3, 4); // 3

生成器函数(Generators)

ES6 中的生成器函数,在yield关键字的帮助下可以返回多个值的函数。

注意在 function 关键字旁边使用*字符来表示它是一个生成器函数。现在让我们创建一个生成器的实例,并通过调用生成器上的next()并获取值:

  
    
  
  
  
  1. function* greeter() {

  2. yield 'Hi';

  3. yield 'How are you?';

  4. yield 'Bye';

  5. }

  6. const greet = greeter();

  7. console.log(greet.next().value);

  8. // 'Hi'

  9. console.log(greet.next().value);

  10. // 'How are you?'

  11. console.log(greet.next().value);

  12. // 'Bye'

  13. console.log(greet.next().value);

  14. // undefined

next() 返回一个带有值的对象 调用 done 属性返回布尔值,如果生成器超出值,则返回 true:

  
    
  
  
  
  1. function* someGenerator(){

  2. yield 'Cats';

  3. yield 'Dogs';

  4. yield 'Birds';

  5. }

  6. const gen = someGenerator();


  7. console.log(gen2.next().done); // false

  8. console.log(gen2.next().done); // false

  9. console.log(gen2.next().done); // false

  10. console.log(gen2.next().done); // true

未完待续 ...

码字辛苦关注分享走一波

一扫


注我


本文分享自微信公众号 - 像素摇摆(pxDance)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!