0507-JS 笔记

splice 和 slice

splice() 方法通过删除现有元素和 / 或添加新元素来更改数组的内容

array.splice(start)
array.splice(start, deleteCount)
array.splice(start, deleteCount, item1, item2, ...)
  • start
    指定修改的开始位置(从 0 计数)。如果超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位(从 1 计数)。
  • deleteCount (可选)
    整数,表示要移除的数组元素的个数。如果 deleteCount 是 0,则不移除元素。这种情况下,至少应添加一个新元素。如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。
    如果 deleteCount 被省略,则其相当于 (arr.length - start)。
  • item1, item2, … (可选)
    要添加进数组的元素, 从 start 位置开始。如果不指定,则 splice() 将只删除数组元素。

splice() 返回值

由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组

tips:splice() 方法与 slice() 方法的作用是不同的,splice() 方法会直接对数组进行修改

var array = ['a', 'b', 'c', 'd'];

var removed = array.splice(2, 0, 'e');
//array --> ["a","b","e","c","d"]
//removed --> []

removed = array.splice(3, 1);
//array --> ["a","b","e","d"]
//removed --> ["c"]

removed = array.splice(2, 1, 'f');
//array --> ["a","b","f","d"]
//removed --> ["e"]

slice() 方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象, 不改变原数组

array.slice();
array.slice(begin);
array.slice(begin, end);
  • begin (可选)
    从该索引处开始提取原数组中的元素(从 0 开始)。
    如果该参数为负数,则表示从原数组中的倒数第几个元素开始提取,slice(-2) 表示提取原数组中的倒数第二个元素到最后一个元素(包含最后一个元素)。
    如果省略 begin,则 slice 从索引 0 开始。
  • end (可选)
    在该索引处结束提取原数组元素(从 0 开始)。slice 会提取原数组中索引从 begin 到 end 的所有元素(包含 begin,但不包含 end)。
    slice(1,4) 提取原数组中的第二个元素开始直到第四个元素的所有元素 (索引为 1, 2, 3 的元素)。
    如果该参数为负数, 则它表示在原数组中的倒数第几个元素结束抽取。 slice(-2,-1) 表示抽取了原数组中的倒数第二个元素到最后一个元素(不包含最后一个元素,也就是只有倒数第二个元素)。
    如果 end 被省略,则 slice 会一直提取到原数组末尾。
    如果 end 大于数组长度,slice 也会一直提取到原数组末尾。

slice() 返回值

array 下标 begin <= copy < end 的数组拷贝

var array = ['a', 'b', 'c', 'd'];

var sliced = array.slice(1, 3);
//array --> ["a", "b", "c", "d"]
//sliced --> ["b", "c"]

调用 Object 属性

var object = {
  name0: 'abc',
  name1: 123
};

object.name0; // 等同于 object["name0"], 输出 "abc"

创建函数的几种方法

三种基础方法

  • function foo(arg) { statements }
  • var foo = function(arg) { statements }
  • var foo = new Function(“a”,”b”,”console.log(a+b)”) // 等同于 function foo(a,b){ console.log(a+b); }
function foo() {
  var a = 123;
  console.log(a);
}
foo(); // --> 123

// 上面的代码可以简写成下面这个
(function foo() {
  var a = 123;
  console.log(a);
})(); // --> 123

ES6 引入箭头函数

MDN 箭头函数语法

深入浅出 ES6 箭头函数

x => console.log(x*x);

// 等同于下面这个方法
function (x) {
  console.log(x*x);
}

var foo = x => console.log(x*x);
foo(5) // --> 25

参数只有一个可以省略括号,其他情况如下:

// 两个参数:
(x, y) => x * x + y * y

// 无参数:
() => 3.14

// 可变参数:
(x, y, ...rest) => {
    var i, sum = x + y;
    for (i=0; i<rest.length; i++) {
        sum += rest[i];
    }
    return sum;
}

箭头函数改变了 this 指向

原有函数写法与箭头函数写法下的 this 指向:

image

箭头函数内部的 this 是词法作用域,由上下文确定。

由于 JavaScript 函数对 this 绑定的错误处理,下面的例子无法得到预期结果:

var obj = {
  birth: 1990,
  getAge: function() {
    var b = this.birth; // 1990
    var fn = function() {
      return new Date().getFullYear() - this.birth; // this 指向 window 或 undefined
    };
    return fn();
  }
};
obj.getAge(); // --> NaN

箭头函数完全修复了 this 的指向,this 总是指向词法作用域,也就是外层调用者 obj

var obj = {
  birth: 1990,
  getAge: function() {
    var b = this.birth; // 1990
    var fn = () => new Date().getFullYear() - this.birth; // this 指向 obj 对象
    return fn();
  }
};
obj.getAge(); // 27

如果使用箭头函数,以前的那种 hack 写法:
var that = this;
就不再需要了。

由于 this 在箭头函数中已经按照词法作用域绑定了,所以,用 call() 或者 apply() 调用箭头函数时,无法对 this 进行绑定,即传入的第一个参数被忽略

var obj = {
  birth: 1990,
  getAge: function(year) {
    var b = this.birth; // 1990
    var fn = y => y - this.birth; // this.birth 仍是 1990
    return fn.call({ birth: 2000 }, year);
  }
};
obj.getAge(2017); // 27

Javascript 闭包(Closure)

变量的作用域

变量的作用域无非就两种:全局变量和局部变量。

Javascript 函数内部可以直接读取全局变量

var a = 123;
function foo() {
  console.log(a);
}
foo(); // --> 123

在函数外部无法读取函数内的局部变量

function foo() {
  var a = 123;
}
foo();
console.log(a); //ReferenceError: a is not defined

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用 var 命令。如果不用的话,你实际上声明了一个全局变量!

function foo() {
  a = 123;
}
foo();
console.log(a); // --> 123

如何从外部读取局部变量?

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。

那就是在函数的内部,再定义一个函数:

function f1() {
  var n = 123;
  function f2() {
    console.log(n); // --> 123
  }
}

在上面的代码中,函数 f2 就被包括在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来就不行,f2 内部的局部变量,对 f1 就是不可见的。这就是 Javascript 语言特有的 “ 链式作用域 “ 结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,我们不就可以在 f1 外部读取它的内部变量了吗!

function f1() {
  var n = 123;
  function f2() {
    console.log(n);
  }
  return f2;
}
var result = f1();
result(); // --> 123

// 也可以这样
function f1() {
  var n = 123;
  function f2(x) {
    console.log(x);
  }
  return f2;
}
var x = 456;
var result = f1()(x);

闭包的概念

上一节代码中的 f2 函数,就是闭包。

简单理解闭包就是能够读取其他函数内部变量的函数。

由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成 “ 定义在一个函数内部的函数 “。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

function f1() {
  var n = 999;
  nAdd = function() {
    n += 1;
  };
  function f2() {
    console.log(n);
  }
  return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000

在这段代码中,result 实际上就是闭包 f2 函数。它一共运行了两次,第一次的值是 999,第二次的值是 1000。这证明了,函数 f1 中的局部变量 n 一直保存在内存中,并没有在 f1 调用后被自动清除。

为什么会这样呢?原因就在于 f1 是 f2 的父函数,而 f2 被赋给了一个全局变量,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是 “nAdd=function(){n+=1}” 这一行,首先在 nAdd 前面没有使用 var 关键字,因此 nAdd 是一个全局变量,而不是局部变量。其次,nAdd 的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以 nAdd 相当于是一个 setter,可以在函数外部对函数内部的局部变量进行操作。

使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

文章目录
  1. splice 和 slice
    1. splice() 方法通过删除现有元素和 / 或添加新元素来更改数组的内容
    2. splice() 返回值
    3. slice() 方法返回一个从开始到结束(不包括结束)选择的数组的一部分浅拷贝到一个新数组对象, 不改变原数组
    4. slice() 返回值
  2. 调用 Object 属性
  3. 创建函数的几种方法
    1. 三种基础方法
    2. ES6 引入箭头函数
      1. 箭头函数改变了 this 指向
  4. Javascript 闭包(Closure)
    1. 变量的作用域
    2. 如何从外部读取局部变量?
    3. 闭包的概念
    4. 闭包的用途
    5. 使用闭包的注意点