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

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

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

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

let str1 = 'My string';
let str2 = str1;
str2 = 'My new string';
console.log(str1);
// 'My string'
console.log(str2);
// 'My new string'

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

let obj1 = { name: 'Jim' }
let obj2 = obj1;
obj2.name = 'John';
console.log(obj1);  // { name: 'John' }
console.log(obj2);  // { name: 'John' }

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

//原始值的比较
let str1 = "hello";
let str2 = "hello";
console.log(str1 === str2); // true 值的比较


//引用不同的两个对象
let o = {x: 1}, p = {x: 1}  //具有相同属性的两个对象
console.log(o === p);  //false 两个单独的对象永不相等
console.log([] === []);  //;两个单独的数组永不相等


//引用相同的两个对象
let a = [];
let b = a;
b[0] = 1;
console.log(a[0]);  //1 变量a也会修改
console.log(a === b); // true a 和 b 引用同一个数组

闭包(Closure)

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

function createGreeter(greeting) {
  return function(name) { //返回的匿名函数可以访问 greeting
    console.log(greeting + ', ' + name);
  }
}
const sayHello = createGreeter('Hello');
sayHello('Joe');
// Hello, Joe

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

var scope = "global scope";  //全局变量
function fn() {
  var scope = "local scope";  //局部变量
  function f() { return scope; }  //在作用域中返回这个值
  return f();
}

fn();  // 输出 local scope

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

var scope = "global scope";  //全局变量
function fn() {
  var scope = "local scope";  //局部变量
  function f() { return scope; }  //在作用域中返回这个值
  return f;
}

fn()();  // 输出 ?

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

解构赋值(Destructuring)

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

const obj = {
  name: 'lili',
  age: 23
}
const { name, age } = obj;

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

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

const obj = {
  name: 'Lili',
  age: 23
}
const { name: myName, age: myAge } = obj;
console.log(myName, myAge); // 'Lili' 23

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

const person = {
  name: 'Lili',
  age: 23
}
function introduce({ name, age }) {
  console.log(`我叫 ${name}, 我今年 ${age} !`);
}
console.log(introduce(person));
// "我叫 Lili 我今年 24!"

扩展运算符(Spread Syntax)

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

等价于apply的方式:

function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction.apply(null, args);

const path = require('path'

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

function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction(...args);

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

function myFunction(v, w, x, y, z) { }
var args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);

数组拷贝(copy):

var arr = [1, 2, 3];
var arr2 = [...arr]; //类似 arr.slice()
arr2.push(4); 

// arr2 此时变成 [1, 2, 3, 4]
// arr 不受影响

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

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
// 将 arr2 中所有元素附加到 arr1 后面并返回
var arr3 = arr1.concat(arr2);

使用展开语法:

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
var arr3 = [...arr1, ...arr2];

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

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
// 将 arr2 中的元素插入到 arr1 的开头
Array.prototype.unshift.apply(arr1, arr2) 
// arr1 现在是 [3, 4, 5, 0, 1, 2]

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

var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
arr1 = [...arr2, ...arr1]; 
// arr1 现在为 [3, 4, 5, 0, 1, 2]

Rest 运算符(Rest Syntax)

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

function myFunc(...args) {
  console.log(args[0] + args[1]);
}
myFunc(1, 2, 3, 4); // 3

生成器函数(Generators)

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

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

function* greeter() {
  yield 'Hi';
  yield 'How are you?';
  yield 'Bye';
}
const greet = greeter();
console.log(greet.next().value);
// 'Hi'
console.log(greet.next().value);
// 'How are you?'
console.log(greet.next().value);
// 'Bye'
console.log(greet.next().value);
// undefined

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

function* someGenerator(){
  yield 'Cats';
  yield 'Dogs';
  yield 'Birds';
}
const gen = someGenerator();

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

原文发布于微信公众号 - 前端infoQ(webinfoq)

原文发表时间:2019-02-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券