闭包(closure)的概念

闭包是函数和声明该函数的词法环境的组合

在 js 中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,产生闭包

产生闭包的条件:有两个函数,是嵌套关系,内部函数引用了外部函数的变量

闭包的作用:

  • 私有变量,保护数据安全
  • 持久化数据
// 闭包的基本模型
function outer() {
var num = 10
function inner () {
num++
console.log(num)
}
return inner // 把inner函数给返回出去,让外部能够调用inner函数
}

// 并不一定是有返回函数才算是产生了闭包
var f3
function f1() {
var a = 2
f3 = function() {
console.log(a)
}
}
f1()
f3() // 2

闭包的应用

计数器

需求:统计一个函数的调用次数

var count = 0
function fn() {
count++
console.log('我被调用了,调用次数是' + count)
}
fn()
fn()
fn()
// 缺点:count是全局变量,不安全

使用闭包解决这个问题

function outer() {
var count = 0 // 私有变量, 将 count 保护起来了
function add() {
count++
console.log('当前count' + count)
}
return add
}
var result = outer()
result()

缓存的私有化

计算斐波那契数列,会有很大的性能问题,因为重复的计算了很多次,因此我们可以使用缓存来解决这个性能问题。

缺点:既然使用缓存,就需要保证缓存的数据的安全,不能被别人修改,因此,需要使用闭包来实现缓存的私有化。

function outer() {
// 缓存
var arr = []

var fbi = function(n) {
if (n == 1 || n == 2) {
return 1
}
if (arr[n]) {
return arr[n]
} else {
var temp = fbi(n - 1) + fbi(n - 2)
arr[n] = temp //存入缓存
return temp
}
}
return fbi
}
var fbi = outer()
console.log(fbi(40))

闭包存在的问题

正常情况下:函数在调用的时候,去开辟一块内存空间用来执行内部的代码,当函数调用结束的时候,要销毁开辟的空间,节省内存
闭包占用的内存是不会被释放的,因此,如果滥用闭包,会造成内存泄漏的问题。闭包很强大,但是只有在必须使用闭包的时候才使用

js 的垃圾回收机制(了解)

  • 内存:计算机中所有程序的运行都是在内存 中进行的,因此内存的性能对计算机的影响非常大,运行程序需要消耗内存,当程序结束时,内存会得到释放。
  • javascript 分配内存:当我们定义变量,javascript 自动分配内存存储数据。无论是值类型或者是引用类型,都需要存储在内存中。
  • 垃圾回收:当代码执行结束,分配的内存已经不需要了,这时候需要将内存进行回收,在 javascript 语言中,垃圾回收机器会帮我们回收不再需要使用的内存。
引用记数法清除

引用记数垃圾收集:如果没有引用指向某个对象(或者是函数作用域),那么这个对象或者函数作用域就会被垃圾回收机制回收。

var o = {
name: 'zs'
}
// 对象被 o 变量引用,引用记数 1
var obj = o // 变量被 o 和 obj 引用,引用记数 2
o = 1 // o 不在引用对象了,引用记数 1
obj = null // obj 不在引用对象了,引用记数 0,可以被垃圾回收了

引用计数法无法解决循环引用导致的内存泄露

function fn() {
var obj1 = {} // 引用计数为 2
var obj2 = {} // 引用计数为 2
obj1.a = obj2
obj2.b = obj1
}
fn() // 销毁fn调用开辟的空间, 但是由于引用计数考虑到两个对象都不是零引用的对象,就不能够被垃圾回收机制给回收掉
标记清除法清除

使用引用计数法进行垃圾回收的时候,会出现循环引用导致内存泄漏的问题。因此现代的浏览器都采用标记清除法来进行垃圾回收。

这个算法假定设置一个叫做根(root)的对象(在 Javascript 里,根是全局对象 Window)。定期的,垃圾回收器将从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象。

从 2012 年起,所有现代浏览器都使用了标记 - 清除垃圾回收算法。

闭包占用内存释放

当闭包的功能不在需要使用了,将这个变量指向 null, 这样闭包占用的内存就可以被回收掉了

function outer() {
var count = 0
function fn() {
count++
console.log('执行次数' + count)
}
return fn
}
var result = outer()
result()
result = null // 当函数 fn 没有被变量引用了,那么函数 fn 就会被回收,函数 fn 一旦被回收,那么 outer调用形成的作用域也就得到了释放

语法

parseInt() 函数解析一个字符串参数,指定该字符串为指定基数的进制值,并返回一个 10 进制的整数,如果被解析参数的第一个字符无法被转化成数值类型,则返回 NaN

参考 parseInt

parseInt(string, radix)

string 要被解析的值,如果参数不是一个字符串,则将其转换为字符串

radix 基数,表示进制,介于 2 和 36 之间的整数,参数 radix 的值为undefined、0 或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数。如果输入的 string 以 “0x”或 “0x”(一个0,后面是小写或大写的X)开头,那么radix被假定为16,字符串的其余部分被解析为十六进制数。如果输入的 string以 “0”(0)开头, radix被假定为10(十进制)。如果输入的 string 以任何其他值开头, radix 是 10 (十进制)。

parseInt('123', 5) // 将 '123' 看作 5 进制数,返回十进制数 38
parseInt('4215213', 5) // 4 * 5^2 + 2 * 5^1 + 1 * 5^0 = 111 返回 111
parseInt('0x123') // 291
[1, 2, 3].map(parseInt) // [1, NaN, NaN]
// [1, 2, 3].map(parseInt(item, index))

parseFloat() 函数解析一个字符串参数并返回一个浮点数,如果给定值不能被转换成数值,则会返回 NaN

parseFloat('3.14') // 3.14
parseFloat('0x123') // 0
// [1, 2, 3].map(parseFloat) // [1, 2, 3]

问题

parseInt(0.0000005) === 5
// 如果 parseInt 第一个参数不是字符串,会将其转换成字符串
// 小于 10-6 的浮点数以指数表示
// parseint 从 float 的指数法中提取整数
String(0.0000005) // => '5e-7'
parseInt(5e-7) // => 5
parseInt('5e-7') // => 5

parseInt(1111111111111111111111) // => 1
parseInt(999999999999999999999) // => 1

parseInt((5e-7).toFixed()) // => 0

parseFloat(9999999999999999) // 10000000000000000

typeof // 只能查看基本数据类型的类型
instanceof // 判断对象的具体类型
constructor.name // 获取对象的具体类型 适用于任何类型的检测
Object.prototype.toString.call('str') // '[object String]' 适用于任何类型的检测

typeof

用于查看基本数据的数据类型, number string boolean undefined

null 比较特殊,结果是 object

如果查看复杂数据类型,返回的都是 object 类型

函数的结果是 function

// typeof 判断
// 简单类型
typeof 12 // 'number'
typeof 'abc' // 'string'
typeof true // 'boolean'
typeof undefined // 'underfined'
typeof null // 'object'

// 复杂类型 (引用类型)
typeof function() {} // 'function'
typeof [] // 'object'
typeof {} // 'object'

instanceof 判断

// 语法
object instanceof constructor

用来检测 constructor.prototype 是否存在于参数 object 的原型链中

不能用于类型识别

// instanceof 判断
var simpleStr = 'This is a simple string'
var myString = new String()
var newStr = new String('String created with constructor')
var myObj = {}
var myNonObj = Object.create(null)
var myArr = []
var myFn = function() {}

simpleStr instanceof String // 返回 false, 检查原型链会找到 undefined
myString instanceof String // 返回 true
newStr instanceof String // 返回 true
myString instanceof Object // 返回 true

myObj instanceof Object // 返回 true, 尽管原型没有定义
;({} instanceof Object) // 返回 true, 同上
myNonObj instanceof Object // 返回 false, 一种创建对象的方法,这种方法创建的对象不

myArr instanceof Array // true
myArr instanceof Object // true
myFn instanceof Object // true
myFn instanceof Function // true

constructor.name

Undefined/Null 没有 constructor 属性

var myArr = []
var myFn = function() {}
var myObj = {}
let myDate = new Date()

// 原型的构造函数
myArr.constructor.name // Array
myFn.constructor.name // Object
myObj.constructor.name // Function
myDate.constructor.name // Date

// 自定义构造函数
function Teacher(name, age) {
this.name = name
this.age = age
}
var tea = new Teacher('zs', 18)
tea.constructor.name // Teacher

Object.prototype.toString

适用于任何类型的检测,不能识别自定义对象类型

Object.prototype.toString.call('str').slice(8, -1) // String
// 正则 => RegExp
// 时间对象 => Date
// 字符串 => String
// 对象 => Object
// 数组 => Array

// 自定义构造函数
function Teacher(name, age) {
this.name = name
this.age = age
}
var tea = new Teacher('zs', 18)
Object.prototype.toString.call(tea) // '[object Object]'

原生 js 中 for 语句

可使用 continue 跳出当前循环, break 跳出整个循环

如果 for 语句在函数中,使用 return 可以结束 for 循环,同时也会结束函数后续代码的执行

for (var i = 0; i < arr.length; i++) {
console.log(arr[i])
}

原生 js 中数组的 forEach 方法

遍历数组

不能用 break continue 语句跳出整个循环
不支持 return 操作输出,return 只用于跳出当前循环

arr.forEach(function(item, index, arr) {
console.log(item)
console.log(this)
})
// 返回值: undefined

原生 js 中 for…in 语句

遍历对象

支持 break, continue 跳出循环

会枚举原型链中的属性

for (var key in obj) {
console.log(key) // 键
console.log(obj[key]) // 值
}

如果使用 for in 遍历数组,会产生一些问题

var arr = ['a', 'b', 'c']
a.name = 'd'
for (var index in arr) {
console.log(index) // '0', '1', '2', 'name'
}
  1. 数组的索引值 index 是 String 类型
  2. 会将 expando 属性也遍历出来
  3. 在某些情况下,在遍历数组元素时顺序是任意的

es6 for…of 方法

遍历类数组集合(Array, Map, Set, String, Arguments)

支持 break, continuethrow

let arr = [1, 2, 3, 4]
for (const item of arr) {
console.log(item)
}

for…of 与 for…in 的区别

参考 MDN

无论是 for…in 还是 for…of 语句都是迭代一些东西。它们之间的主要区别在于它们的迭代方式。

for…in 语句以任意顺序迭代对象的可枚举属性。

for…of 语句遍历可迭代对象定义要迭代的数据。

jquery 中的 each 方法

遍历 jQuery 对象集合,为每个匹配的元素执行一个函数

$(selector).each(function(index, element) {
// index 表示当前元素在所有匹配元素中的索引号
// element 表示当前元素 dom 对象
// this 在函数内部,this指向了element
})

$('li').each(function(index, ele) {
// $(ele).css("backgroundColor", arr[index])
$(this).css('backgroundColor', arr[index])
})

php 中 foreach 语句

用来遍历数组(关联数组和索引数组均可)。

foreach($arr as $key => $value) {
// $arr: 要遍历的数组
// $key: 键,可以是任意变量名
// $value: 值,可以是任意变量名
}
foreach($arr as $value) {

}
// 遍历关联数组
$arr = array(
"name"=>"zs",
"age"=>18,
"sex"=>20
);
foreach($arr as $k => $v) {
echo $k . "=" . $v . "<br>";
}

作用域

作用域:变量起作用的区域,也就是说:变量定义后,可以在哪个范围内使用该变量

全局作用域 :在 script 标签内,函数外的区域就是全局作用域,在全局作用内声明的变量叫做全局变量 。全局变量可以在任意地方访问。(if/while/for 语句中声明的变量也是全局变量)

函数作用域 :在函数内的区域叫做函数作用域,在函数作用域内声明的变量叫做局部变量 ,局部变量只有在当前函数内才能访问到。

自由变量:对于一个函数来说,函数内部没有声明该变量,但在函数内部有访问该变量。对于这个函数来说, 该变量就是一个自由变量。

隐式全局变量:没有使用 var 定义的变量也是全局变量,叫做隐式全局变量。

var num = 11
function fn() {
var num1 = 22
num2 = 33
num = 33
console.log(num1)
}
fn()
console.log(num)
// console.log(num1)
console.log(num2)

变量的查找规则:

  • 函数内部可以使用函数外部的变量
  • 有局部变量就用局部变量,没有局部变量就用全局变量。

函数作用域是在函数定义的时候作用域就确定下来了,和函数在哪调用无关

var num = 123
function f1() {
console.log(num)
}

function f2() {
var num = 456
f1()
}
f2() // 123

作用域链

作用域链:只要是函数,就会形成一个作用域,如果这个函数被嵌套在其他函数中,那么外部函数也有自己的作用域,这个一直往上到全局环境,就形成了一个作用域链

变量的搜索原则:从当前作用域开始查找,一直查询到全局作用域,如果存在,就返回。如果在全局中也没有找到该变量会报错

// 1.
var num = 10
fn1()
function fn1() {
console.log(num) // undefined
var num = 20
console.log(num) // 20
}
console.log(num) // 10

// 2
var num = 10
fn1()
function fn1() {
console.log(num) // 10
num = 20
console.log(num) // 20
}
console.log(num) // 20

// 3
var num = 123
function f1(num) {
console.log(num) // 456 undefined
}
function f2() {
var num = 456
f1(num)
f1()
}
f2()

// 4
var num1 = 10
var num2 = 20
function fn(num1) {
num1 = 100
num2 = 200
num3 = 300
console.log(num1) // 100
console.log(num2) // 200
console.log(num3) // 300
var num3
}
fn()
console.log(num1) // 10
console.log(num2) // 200
console.log(num3) // error

预解析

预解析过程:js 解析器在执行代码前,会把所有变量的声明和函数的声明提升到当前作用域的顶部。例如var a = 11其实会分为var aa = 11两部分,其中var a会被提升

预解析规则:

  1. var 声明的变量:只提升声明,不会提升赋值
  2. 函数声明:整体提升
  3. 先提升 var 声明的变量,后提升函数声明
  4. 遇到重名的 var 声明, var 声明会被忽略,值会保留
  5. 遇到重名的函数声明,后者会覆盖前者
  6. 如果 var 声明和函数声明同名,函数声明会把 var 声明覆盖
// 函数预解析
// 1.
function fn() {
console.log(a) // undefined
}
fn()
var a = 1

// 2.
var n = 45
function fn5() {
console.log(n) // undefined
n = 20
console.log(n) // 20
var n = 0
console.log(n) // 0
}
fn5()
console.log(n) // 45

// 3.
console.log(b) // 函数体
var b = 23
function b() {
console.log(b)
}
console.log(b) // 23
// b() // 报错

// 4.
console.log(c) // 函数体
c() // 嘿嘿
var c = function() {
comsole.log('哈哈')
}

function c() {
console.log('嘿嘿')
}

// 5.
console.log(fn1) // 函数体
fn1()
function fn1() {
console.log('哈哈') // 哈哈
}
console.log(fn2) // undefined
fn2() // 报错
var fn2 = function() {
console.log('嘿嘿')
}
// 对于函数表达式,函数的调用必须在表达式声明之后
fn2() // 嘿嘿

// 6.
// 只有用 var 声明的变量才会预解析
console.log(d) // 报错
d = 5

// 7.
console.log(e)
console.log(f) // 报错 f is not defined
var e = (f = 10)
console.log(f) // 10

// 8.
if ('a' in window) {
var a = 'abc'
}
console.log(a) // abc

不要在一个作用域内重复的声明相同的变量和函数

函数的参数

arguments 对象里保存了所有的实参,是一个伪数组

定义函数的三种方式

  • 函数声明
fn() // 函数声明可以先调用,在声明
function fn() {
console.log('这是函数声明')
}
  • 函数表达式
var fn = function() {
console.log('这是函数表达式')
}
fn() // 函数表达式必须先声明,再调用
  • 构造函数 Function
// 函数也是对象,可以使用 Function 构造函数 new 出来
// 相当于var fn = function () {}
var fn = new Function()

// 语法:new Function(arg1,arg2,arg3..,body)
// 所有的参数都是字符串类型
// 前面可以定义任意多个形参,最后一个参数是代码体
var fn = new Function('alert(1)')
fn()

var fn1 = new Function('a1', 'a2', 'alert(a1 + a2)')
fn1(1, 2)

Function 属性

  • length:获取形参的长度
  • name:获取函数的名字,此属性不允许修改
Function.length // 1
Function.prototype.length // 0
(function() {}).length // 0
(function(a) {}).length // 1
(function(...args) {}).length // 0
(function(a, b = 1, c) {}).length // 1

Function.prototype 成员

  • arguments:已废弃,获取函数的实参,现在推荐的做法是使用函数内部可用的  arguments 对象来访问函数的实参
  • caller: 已废弃,用于获取当前函数是在哪个函数中调用的
  • constructor:指向当前构造函数,Function
  • call:调用函数,重新指定 this
  • apply:调用函数,重新指定 this
  • bind:重新指向 this,返回一个新的函数,不调用
  • toString : 得到函数的字符串格式
function a() {}
a.toString() // 'function a() {}'

// 获取数据类型
return Object.prototype.toString.call(obj).slice(8, -1) // '[object 构造函数]'

函数的四种调用模式

分析 this 指向问题

  1. 任何函数都有属于自己的 this
  2. this 是动态的,this 在函数声明的时候是确定不了的,只有当函数被调用了才能够确定 this 的指向,this 的指向和函数在哪被调用没有关系

分析 this 的问题的思路: 1. this 是属于哪个函数 2. 这个函数是何种调用模式

函数调用模式

如果一个函数不是一个对象的属性时,就是被当做一个函数来进行调用的。此时 this 指向了 window

function fn() {
console.log(this) // 指向 window
}
fn()

方法调用模式

当一个函数被保存为对象的一个属性时,我们称之为一个方法。当一个方法被调用时,this 被绑定到当前对象
通过点语法或者中括号语法来访问方法,都是属于方法调用模式

var f = function() {
console.log(this)
}
var obj = { fn: f }
var arr = [f]

obj.fn() // obj
obj['fn']() // obj
arr[0]() // arr 也是方法调用模式

构造函数调用模式

如果函数是通过 new 关键字进行调用的,此时 this 被绑定到创建出来的新对象上

function Person() {
console.log(this)
}
var p = new Person() // this 指向 p

总结:分析 this 的问题,主要就是区分函数的调用模式,看函数是怎么被调用的

// 1.
var age = 38
var obj = {
age: 18,
getAge: function() {
console.log(this.age)
}
}
var f = obj.getAge
f() // window ==> 38

// 2.
var age = 38
var obj = {
age: 18,
getAge: function() {
console.log(this.age) // obj ==> 18
function foo() {
console.log(this.age) // window ==> 38
}
foo()
}
}
obj.getAge()

// 3.
var length = 10
var age = 18
function fn() {
console.log(this.length)
}
var arr = [fn, '222']
fn() // 10
arr[0]() // 2

// 4.
var length = 10
function fn() {
console.log(this.length)
}
var obj = {
length: 5,
method: function(fn) {
fn() // window ==> 10
arguments[0]() // argument ==> 3
}
}
obj.method(fn, 10, 5)

// 5.
let len = 10
function fn() {
console.log(this.len)
}
fn() // window ==> undefined

let Person = {
len: 5,
say: function() {
fn() // window ==> undefined
arguments[0]() // arguments ==> undefined
}
}
Person.say(fn)

// 6.
var obj = {
bar: function() {
var x = () => this
return x
}
}

// 作为obj对象的一个方法来调用bar,把它的this绑定到obj。
// 将返回的函数的引用赋值给fn。
var fn = obj.bar()

// 直接调用fn而不设置this,
// 通常(即不使用箭头函数的情况)默认为全局对象
// 若在严格模式则为undefined
console.log(fn() === obj) // true

// 但是注意,如果你只是引用obj的方法,而没有调用它
var fn2 = obj.bar
// 那么调用箭头函数后,this指向window,因为它从 bar 继承了this。
console.log(fn2()() == window) // true

方法借用模式

上下文调用模式也叫方法借用模式,分为 apply,call ,bind
任何函数都可以调用 apply,call ,bind 这三个方法

call 方法

call 方法可以调用一个函数,并且可以指定这个函数的 this 指向
call 方法也可以和 () 一样,进行函数调用
第一个参数:指定函数的 this,如果不传,则 this 指向 window;其余参数:和函数的参数列表一模一样

// 调用函数
function fn() {
console.log(1)
}
fn.call() // 1

// 改变 this 指向
function fn() {
console.log(this)
}
fn.call({ name: 'zs' }) // { name: 'zs' }

apply 方法

apply()方法的作用和 call()方法类似,只有一个区别,就是apply()方法接受的是一个包含多个参数的数组。而call()方法接受的是若干个参数

function fn(n1, n2) {
console.log(this)
console.log(n1 + n2)
}
fn.apply({ name: 'zs' }, [10, 20]) // {name: 'zs'}, 30
// apply 的特性:平铺性,把数组中的每一项取出来作为函数的实参

bind 方法

bind() 方法创建一个新的函数、可以绑定新的函数的 this 指向

返回值:新的函数(不会被调用)
参数:新函数的 this 指向,绑定后,无论使用何种调用模式,this 都不会改变

var fn = function() {
console.log(this)
}

var newFn = fn.bind([1, 2, 3])
// newFn 是 bind 创建并返回出来的
console.log(newFn)
newFn() // this ==> [1,2,3]

如果对一个函数进行多次 bind,那么上下文会是什么呢

let a = {}
let fn = function() {
console.log(this)
}
fn.bind().bind(a)() // => ?

如果你认为输出结果是 a,那么你就错了,其实我们可以把上述代码转换成另一种形式

// fn.bind().bind(a) 等于
let fn2 = function fn1() {
return function() {
return fn.apply()
}.apply(a)
}
fn2()

可以从上述代码中发现,不管我们给函数 bind 几次,fn 中的 this 永远由第一次 bind 决定,所以结果永远是 window

特殊的 this 指向

  • 定时器中的 this 指向了 window,因为定时器的 function 最终是由 window 来调用的
  • 事件中的 this 指向的是当前的元素,在事件触发的时候,浏览器让当前元素调用了 function
  • call apply bind 第一个参数表示要绑定的 this,不传、传 null或者 undefined,this 均指向 window,但在严格模式下,不传指向 undefined,传 null 指向 null,传 undefined 指向 undefined

递归函数

递归的要求:1. 自己调用自己 2. 要有结束条件(出口)

// 计算斐波那契数列
function fn(n) {
if (n == 1 || n == 2) {
return 1
}
return fn(n - 1) + fn(n - 2)
}
console.log(fn(12))

原型

Javascript 规定,每一个函数都有一个 prototype 属性,属性值是一个对象,这个对象就叫做原型(原型对象),这个对象的所有属性和方法,都会被构造函数的实例继承

这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上

function Person (name, age) {
this.name = name
this.age = age
}

console.log(Person.prototype)
Person.prototype.sayName = function () {
console.log(this.name)
}
var p1 = new Person(...)
var p2 = new Person(...)
console.log(p1.sayName === p2.sayName) // true

这时所有实例的 sayName() 方法,其实都指向同一个内存地址

__proto__

任意对象都有 __proto__ 属性,这个属性指向了构造函数的 prototype 属性,也就是原型对象

获取原型对象:

  • 通过 构造函数.prototype 可以获取
  • 通过 实例.__proto__ 可以获取(隐式原型)
  • 它们指向了同一个对象 构造函数.prototype === 实例.__proto__

注意:__proto__是浏览器的一个隐藏(私有)属性,IE 浏览器不支持,不要通过它来修改原型里的内容,如果要修改原型中的内容,使用 构造函数.prototype 去修改

constructor 属性

默认情况下,原型对象中只包含了一个属性:constructor,constructor 属性指向了当前原型对象的构造函数

function Person() {}

console.log(Person.prototype)
console.log(Person.prototype.constructor) // 构造函数本身

var p = new Person()
console.log(p)
// p 实例对象没有constructor 属性, 该属性来源于原型上
console.log(p.constructor == Person.prototype.constructor) // true

构造函数、实例、原型三者之间的关系

构造函数:构造函数就是一个函数,配合 new 可以新建对象

实例:通过构造函数实例化出来的对象我们把它叫做构造函数的实例。一个构造函数可以有很多实例

原型:每一个构造函数都有一个属性prototype,函数的 prototype 属性值就是原型。通过构造函数创建出来的实例能够直接使用原型上的属性和方法

原型三角关系:

  • 构造函数和原型

    • 构造函数,通过 prototype 属性访问原型
    • 原型通过 constructor 属性访问到构造函数
  • 构造函数 和 实例对象

    • 构造函数可以创建实例对象
    • 实例对象不能直接访问到构造函数
  • 原型 和 实例对象关系

    • 实例对象可以直接访问到原型上的所有成员
    • 实例对象可以间接的访问到构造函数(通过原型上的 constructor 属性)

原型链

任何一个对象,都有原型对象,原型对象本身又是一个对象,所以原型对象也有自己的原型对象,这样形成的链式结构,就是原型链

绘制对象的原型链结构:

var p = new Person()
// p ==> Person.prototype ==> Object.prototype ==> null
var o = new Object()
// o ==> Object.prototype ==> null
var arr = new Array()
// arr ==> Array.prototype ==> Object.prototype ==> null
var date = new Date()
// date ==> Date.prototype ==> Object.prototype ==> null

// Math 是个内置对象,不是个构造函数
// Math ==> Object.prototype ==> null

总结:Object.prototype 是原型链的尽头,Object.prototype 的原型是 null

函数的原型链结构

函数是由 new Function 创建出来的,因此函数也是一个对象,所有的函数都是 Function 的实例

Person ==> Function.prototype ==> Object.prototype ==> null

Function.prototype 类型是个函数

完整版原型链

图一

图二

  1. 所有函数都是 new Function 创建出来的,因此 所有函数.__proto__ 都是 Function.prototype
  2. 所有对象都是 new Object 创建出来的,因此 所有对象.__proto__ 都是 Object.prototype

参考链接

ES5实现继承的六种方式

原型链

利用原型链让一个引用类型继承另一个引用类型的属性和方法。

function SuperType () {
this.property = true;
}

SuperType.prototype.getSuperValue = function () {
return this.property;
};

// 子类 SubType
function SubType () {
this.subProperty = false;
}

SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subProperty;
};

// 实例
var instance = new SubType();
console.log(instance);
console.log(instance.getSuperValue()); // true
console.log(instance instanceof SubType); // true
console.log(instance instanceof SuperType); // true
console.log(instance instanceof Object); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(Object.prototype.isPrototypeOf(instance)); // true

缺点:

  1. 来自原型对象的引用属性是所有实例共享的。
  2. 创建子类实例时,无法向父类构造函数传参。

举例如下:

// 1. 来自原型对象的引用属性是所有实例共享的

// 父类
function SuperType () {
this.colors = ['red', 'blue', 'green'];
}

// 子类
function SubType () {

}
SubType.prototype = new SuperType();

// 实例
var instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
var instance2 = new SubType();
console.log(instance2.colors); // ['red', 'blue', 'green', 'black']

// 因为修改colors是修改的SubType.prototype.colors,所以所有的实例都会更新
// 2. 创建子类实例时,无法向父类构造函数传参

// 调用父类是在 SubType.prototype = new SuperType()
// 新建子类实例调用 new SubType()
// 所以无法再new SubType() 的时候给父类 SuperType() 传参

借用构造函数

在子类构造函数的内部通过call()以及apply()调用父类构造函数。

// 父类 SuperType
function SuperType (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];

this.getName = function () {
return this.name;
}
}

// 子类
function SubType (name) {
// 继承了SuperType,同时还传递了参数
SuperType.call(this, name);

// 实例属性
this.age = 20;
}

// 实例
var instance1 = new SubType('Tom');
instance1.colors.push('black');
console.log(instance1.name); // "Tom"
console.log(instance1.getName()); // "Tom"
console.log(instance1.age); // 20
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
var instance2 = new SubType('Peter');
console.log(instance2.name); // "Peter"
console.log(instance2.getName()); // "Peter"
console.log(instance2.age); // 20
console.log(instance2.colors); // ['red', 'blue', 'green']

可以看到,借用构造函数实现继承,解决了原型链继承的两个问题,既可以在新建子类实例的时候给父类构造函数传递参数,也不会造成子类实例共享父类引用变量。

但是你注意到了吗,这里我们把父类方法也写在了SuperType()构造函数里面,可以像前面一样写在SuperType.prototype上吗?

答案是不可以,必须写在SuperType()构造函数里面。因为这里是通过调用SuperType.call(this)来实现继承的,并没有通过new生成一个父类实例,所以如果写在prototype上,子类是无法拿到的。

缺点:如果方法都在构造函数中定义,那么就无法复用函数。每次构建实例时都会在实例中保留方法函数,造成了内存的浪费,同时也无法实现同步更新,因为每个实例都是单独的方法函数。如果方法写在prototype上,就只会有一份,更新时候会做到同步更新。

组合继承

将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。

使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

// 父类
function SuperType (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}

SuperType.prototype.sayName = function () {
console.log(this.name);
}

// 子类
function SubType (name, age) {
// 继承父类实例属性
SuperType.call(this, name);

// 子类实例属性
this.age = age;
}

SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
console.log(this.age);
};

// 实例
var instance1 = new SubType('Tom', 20);
instance1.colors.push('black');
console.log(instance1.colors); // ['red', 'blue', 'green', 'black']
instance1.sayName(); // "Tom"
instance1.sayAge(); // 20

var instance2 = new SubType('Peter', 30);
console.log(instance2.colors); // ['red', 'blue', 'green']
instance2.sayName(); // "Peter"
instance2.sayAge(); // 30

缺点: 调用了两次父类构造函数,一次通过SuperType.call(this)调用,一次通过new SuperType()调用。

原型式继承

不使用严格意义上的构造函数,借助原型可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型。

// 在object函数内部,先创建了一个临时的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。
// 从本质上讲,object()对传入其中的对象执行了一次浅复制。

function object (o) {
function F() {}
F.prototype = o;
return new F();
}


var person = {
name: 'Tom',
friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');

console.log(anotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie']
console.log(yetAnotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie']
console.log(person.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie']

Object.create()在传入一个参数的情况下与前面写的object()方法的行为相同

var person = {
name: 'Tom',
friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = Object.create(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = Object.create(person, {
name: {
value: 'Linda',
enumerable: true
}
});
yetAnotherPerson.friends.push('Barbie');

console.log(anotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie']
console.log(yetAnotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie']
console.log(person.friends); // ['Shelby', 'Court', 'Van', 'Rob', 'Barbie']

缺点:和原型链继承一样,所有子类实例共享父类的引用类型。

寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路,创建一个仅用于封装继承过程的函数,该函数内部以某种形式来做增强对象,最后返回对象

function object (o) {
function F() {}
F.prototype = o;
return new F();
}

function createAnother (o) {
var clone = object(o);
clone.sayHi = function () {
console.log('Hi');
}
return clone;
}

var person = {
name: 'Tom',
friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi(); // "Hi"
anotherPerson.friends.push('Rob');
console.log(anotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob']
var yerAnotherPerson = createAnother(person);
console.log(yerAnotherPerson.friends); // ['Shelby', 'Court', 'Van', 'Rob']

缺点: 和原型链式继承一样,所有子类实例共享父类引用类型。 和借用构造函数继承一样,每次创建对象都会创建一次方法。

寄生组合式继承

将寄生式继承和组合继承相结合,解决了组合式继承中会调用两次父类构造函数的缺点。

组合继承在第一次调用SuperType构造函数时,SubType.prototype会得到两个属性:name和colors;它们都是 SuperType 的实例属性,只不过现在位于 SubType的原型中。当调用SubType构造函数时,又会调用一次SuperType构造函数,这一次又在新对象上创建了实例属性name和colors。于是,这两个属性就屏蔽了原型中的两个同名属性。

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

其背后的基本思路是:不必为了指定子类型的原型而调用父类的构造函数,我们需要的无非就是父类原型的一个副本而已。本质上,就是使用寄生式继承来继承父类的prototype,然后再将结果指定给子类的prototype。

function object(o) {
function F() { }
F.prototype = o;
return new F();
}

function inheritPrototype(SubType, SuperType) {
var prototype = object(SuperType.prototype); // 创建对象
prototype.constructor = SubType; // 增强对象
SubType.prototype = prototype; // 指定对象
}

// 父类
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function () {
console.log(this.name);
};

// 子类
function SubType(name, age) {
// 继承父类实例属性
SuperType.call(this, name);

// 子类实例属性
this.age = age;
}

// 继承父类方法
inheritPrototype(SubType, SuperType);

// 子类方法
SubType.prototype.sayAge = function () {
console.log(this.age);
};

// 实例
var instance1 = new SubType('Tom', 20);
instance1.colors.push('black');
instance1.sayAge(); // 20
instance1.sayName(); // "Tom"
console.log(instance1.colors); // ["red", "blue", "green", "black"]

var instance2 = new SubType('Peter', 30);
instance2.sayAge(); // 30
instance2.sayName(); // "Peter"
console.log(instance2.colors); // ["red", "blue", "green"]

寄生组合式继承的高效率体现在它只调用了一次SuperType构造函数,并且因此避免了再SubType.prototype上面创建不必要的、多余的属性。与此同时,原型链还能保持不变。因此,还能够正常使用instanceof和isPrototypeOf()。

ES6实现继承

// 父类
class SuperType {
constructor(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}

sayName() {
console.log(this.name);
};
}

// 子类
class SubType extends SuperType {
constructor(name, age) {
// 继承父类实例属性和prototype上的方法
super(name);

// 子类实例属性
this.age = age;
}

// 子类方法
sayAge() {
console.log(this.age);
}
}

// 实例
var instance1 = new SubType('Tom', 20);
instance1.colors.push('black');
instance1.sayAge(); // 20
instance1.sayName(); // "Tom"
console.log(instance1.colors); // ["red", "blue", "green", "black"]

var instance2 = new SubType('Peter', 30);
instance2.sayAge(); // 30
instance2.sayName(); // "Peter"
console.log(instance2.colors); // ["red", "blue", "green"]

删除对象属性

如果删除成功,返回true,删除失败,返回false
var 声明的全局变量不能被删除

delete obj.name // 删除obj的name属性

// var 声明的全局变量不能被删除
var num = 12
str = 'hello'
delete window.num // false 删除失败
delete window.str // true 删除成功
console.log(num) // 12
console.log(str) // 报错 str is not undefined

判断一个属性是否是对象的一个属性

key in obj // 返回布尔值 从原型链继承的属性会返回 true

obj.hasOwnProperty(key) // 判断某个key是否是这个对象本身的属性

for..in 遍历对象

会枚举原型链中的属性

// 遍历对象
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key) // 键
console.log(obj[key]) // 值
}
}

Object.prototype 成员

constructor: 指向了构造函数 Object
hasOwnProperty(): 返回一个布尔值,判断对象自身是否具有该属性
isPrototypeOf(): 返回一个布尔值,用于测试一个对象是否存在于另一个对象的原型链上
propertyIsEnumerable(): 返回一个布尔值,表明指定的属性名是否是当前对象可枚举的自身属性
toString()/toLocaleString(): 返回对象的字符串格式
valueOf(): 返回对象的原始值

hasOwnProperty

var obj = { name: 'zs' }
// 判断name属性是不是obj自己提供的
obj.hasOwnProperty('name') // true
obj.hasOwnProperty('toString') // false

hasOwnPropertyin 的区别

  1. in 操作符:判断对象能否访问到该属性(不管这个属性是自己提供的,还是从原型上继承来的),如果可以访问到,都会返回 true

  2. hasOwnProperty:该属性必须是自己提供,才返回 true,否则返回 false

isPrototypeOf

// 判断 A 对象是否在 B 对象的原型链上 ,回一个布尔值
A.isPrototetypeOf(B)

function Person() {}
var p = new Person()

// p 的原型链:
// p ==> Person.prototype ==> Object.prototype ==> null

Person.isPrototypeOf(p) // false Person 是构造函数
Person.prototype.isPrototypeOf(p) // true
Object.prototype.isPrototypeOf(p) // true

isPropertyOfinstanceof 运算符的区别

instanceof 运算符用来测试一个对象的原型链中是否存在一个构造函数的 prototype 属性。作用和 isPrototypeOf 类似

语法: 实例对象 instanceof 构造函数

作用:构造函数的 prototype 属性是否在实例对象的原型链上

  • A.isPrototypeOf(B) 判断 A 是否在 B 的原型链上 A: 是一个原型对象
  • B instanceof A 判断 A 的 prototype 是否在 B 的原型链上 A:是一个构造函数
Array.isPrototypeOf([]) // false
Array.prototype.isPrototypeOf([]) // true

[] instanceof Array // true
[] instanceof Array.prototype // 语法错误

propertyIsEnumerable

function Person(name) {
this.name = name
}
Person.prototype.age = 19
var p = new Person('lw')

p.propertyIsEnumerable('name') // true
p.propertyIsEnumerable('age') // false

toString/toLocaleString

返回对象的字符串格式

每个内置对象的原型上都有属于自己的 toString 方法

var obj = { name: 'zs' }
// obj ==> Object.prototype ==> null
obj.toString() // '[object Object]'
obj.toLocaleString() // '[object Object]'

var arr = [1, 2, 3]
// arr ==> Array.prototype ==> Object.prototype ==> null
// toString() toString()
// Array.prototype 含有自己的 toString 方法
arr.toString() // '1,2,3'
arr.toLocaleString() // '1,2,3'

var date = new Date()
// date ==> Date.prototype ==> Object.prototype ==> null
// toString() toString()
// Date.prototype 含有自己的 toString 方法
date.toString() // Wed Oct 10 2018 16:00:51 GMT+0800 (中国标准时间)
date.toLocaleString() // 2018/10/10 下午4:00:51 得到的是本地时间格式

valueOf()

返回值为该对象的原始值,如果对象没有原始值,则 valueOf 将返回对象本身

对象 返回值
Array 返回数组对象本身
Boolean 布尔值
Date 时间戳
Function 函数本身
Number 数字值
Object 对象本身 (这是默认情况)
String 字符串值
var obj = { name: 'zs' }
// obj ==> Object.prototype ==> null
obj.valueOf() // { name: 'zs' }

var arr = [1, 2, 3]
// arr ==> Array.prototype ==> Object.prototype ==> null
arr.valueOf() // [1, 2, 3]

var date = new Date()
// date ==> Date.prototype ==> Object.prototype ==> null
// Date.prototype 含有 valueOf 方法
date.valueOf() // 时间戳

valueOf 和 toString 的应用

当对象在参与运算和比较的时候,js 内部会自动的调用 valueOf 和 toString 方法

调用规则:

  1. 默认先调用 vauleOf, 尝试将对象转成简单数据类型, 如果没有转成简单数据类型, 会继续在调用 toString 方法

  2. 如果 valueOf 和 toString 方法都没有转成简单数据类型,会报错

Object 静态方法

Object.assign()
Object.create()
Object.defineProperty()
Object.entries()
Object.freeze()
Object.getPrototypeOf()
Object.is()
Object.isFrozen()
Object.keys()
Object.values()

Object.assign(target, ...sources)

用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象

const o1 = { a: 1 }
const o2 = { b: 2 }
const o3 = { c: 3 }

const obj = Object.assign(o1, o2, o3)
console.log(obj) // { a: 1, b: 2, c: 3 }
console.log(o1) // { a: 1, b: 2, c: 3 }
obj === o1 // true
const v1 = "abc"
const v2 = true
const v3 = 10
const v4 = Symbol("foo")

const obj = Object.assign({}, v1, null, v2, undefined, v3, v4)
// 原始类型会被包装,null 和 undefined 会被忽略。
// 注意,只有字符串的包装对象才可能有自身可枚举属性。
console.log(obj) // { "0": "a", "1": "b", "2": "c" }

Object.create(proto, [propertiesObject])

创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

// 语法
Object.create(proto[, propertiesObject])
// 参数:proto 一个对象,新创建对象的原型对象
// 参数:propertiesObject 要添加到新创建对象的可枚举属性
// 返回值:一个新对象,带着指定的原型对象和属性

var obj = Object.create(proto)
console.log(obj)

Object.defineProperty(obj, prop, descriptor)

在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

let obj = {}
Object.defineProperty(obj, 'name', {
configurable: true, // 表示对象的属性是否可以被删除,以及除 value 和 writable 特性外的其他特性是否可以被修改。默认为 false
value: 'c', // 配置该属性的默认值 默认为 undefined
writable: true, // 配置该属性是否可以被修改, 默认值是false, 不可修改
enumerable: true // 配置该属性是否可枚举, 默认值是false, 不可枚举
// 默认为 undefined
// set: function (newVal) {
// console.log('赋值了', newVal)
// },
// 默认为 undefined
// get: function () {
// console.log('取值了')
// }
})

如果一个描述符同时拥有 value 或 writable 和 get 或 set 键,则会产生一个异常

Object.entries(obj)

返回一个给定对象自身可枚举属性的键值对数组

将Object转换为Map new Map(Object.entries(obj))

Object.freeze()

接受一个对象作为参数,并返回一个相同的不可变的对象,冻结一个对象后该对象的原型也不能被修改

可以阻止修改对象的值,但是不能阻止引用的修改

只是做了层浅冻结,具有嵌套属性的对象实际上并未冻结

Object.getPrototypeOf

返回指定对象的原型

Object.is()

Object.is() 方法判断两个值是否是相同的值。比较时不会做类型转换,这与 == === 运算符的判定方式都不一样

Object.is(+0, -0) // false
Object.is(0, -0) // false
Object.is(0, +0) // true
Object.is(-0, -0) // true

Object.is(undefined, undefined) // true
Object.is(null, null) // true
Object.is(Number.NaN, Number.NaN) // true
Object.is(Number.NaN, NaN) // true
Object.is(NaN, NaN) // true
Object.is(NaN, 0 / 0) // true

Object.isFrozen()

Object.keys(obj)

返回一个由一个给定对象的自身可枚举属性组成的数组

Object.values(obj)

返回一个给定对象自身的所有可枚举属性值的数组

Array

new Array(4) // [empty × 4]
new Array('4') // ['4']

基本方法

arr.join()

将数组的值拼接成字符串 不传参数,默认用逗号进行拼接,返回拼接好的字符串

arr.push()

从后面添加一个或多个元素,多个参数逗号隔开,返回新数组的 length

arr.push(arr1) // 把 arr1 当成一个整体放到 arr 里

arr.pop()

从数组的后面删除元素,返回删除的那个元素

arr.unshift()

从数组的前面的添加元素,,多个参数逗号隔开,返回新数组的 length

arr.shift()

从数组的最前面删除元素,返回删除的那个元素

arr.reverse()

翻转数组

arr.sort()

排序

sort 方法可以传递一个函数作为参数,这个参数用来控制数组如何进行排序

arr.sort(function(a, b) {
// 参数为 true 时,即返回值 > 0 时,交换位置
// return a - b // 从小到大排序
return b - a // 从大到小排序
})

arr.concat()

数组合并,返回一个新数组,原数组不受影响

a.concat(b) // [...a, ...b]

arr.slice()

数组切分,复制数组的一部分到一个新数组,并返回这个新数组,原数组不受影响

// slice(begin, end) 包含 begin,不包含 end, begin 和 end 为下标
// slice(begin) 只有一个参数时,为开始参数,截取到末尾
// slice() 没有参数,全部截取
// 可以为负数,会将字符串的长度与对应的负数相加,结果作为参数,-1 表示从后数第一个
var newArr = arr.slice(begin, end)

arr.splice()

删除或者增加数组元素,修改原数组,返回删除的内容(数组形式)

// start: 开始位置  deletedCount: 删除的个数(如果不删除为 0)items: 替换的内容, 可为多个
arr.splice(start) // 删除原数组 start 位置之后的项(包含 start),返回删除的内容
arr.splice(start, deletedCount, [items]) // items 将作为 arr 的一项

arr.indexOf()

返回数组中某个元素第一次出现的位置,如果找不到,返回 -1

// fromIndex 表示从 fromIndex 下标开始查找
arr.indexOf('zs'[, fromIndex])

arr.lastIndexOf()

从后面开始查找数组中元素出现位置,即查找某元素最后一次出现的位置,如果找不到,返回 -1

arr.lastIndexOf('zs'[, fromIndex])

arr.forEach()

返回值: undefined
除了抛出异常以外,没有办法中止或跳出 forEach() 循环
不支持 return 操作输出,return 只用于控制循环是否跳出当前循环

遍历时会自动忽略 empty 值

arr.forEach(function(item, index, arr) {}, thisArg)
// item 必需。数组中正在处理的当前元素
// index 可选。数组中正在处理的当前元素的索引
// arr 可选。当前数组
// thisArg 可选。当执行回调函数时用作this的值
var arr = ['zs', 'ls', 'ww']
arr.forEach(function(item, index, arr) {
console.log(item)
console.log(this)
})

arr.map()

var newArr = arr.map(function(item, index) {
// item 必需。数组中正在处理的当前元素
// index 可选。数组中正在处理的当前元素的索引
// arr 可选。当前数组
// 使用 return 操作输出,会循环数组每一项,并返回新的每一项组成的数组
return item * 2
})
// 不修改原数组
// 返回一个新数组,新数组的每一项乘以 2

arr.filter()

var newArr = arr.filter(function(item, index) {
// 参数同 map
// 使用 return 操作输出,会循环数组每一项,并返回判断为 true 的每一项组成的数组
return item > 2 && item < 5 // return 后是判断条件
})
// 不修改原数组
// 返回一个新数组,新数组每一项满足 2 < item < 5

arr.some()

var newArr = arr.some(function(item, index) {
// 参数同 map
// 返回布尔值,只要有一项满足条件就返回 true,否则返回 false
return item > 2 // return 后是判断条件
})
// 不修改原数组

arr.every()

var newArr = arr.every(function(item, index) {
// 参数同 map
// 返回布尔值,只有所有项都满足条件才返回 true,否则返回f alse
return item > 2 // return 后是判断条件
})
// 不修改原数组

arr.includes()

判断数组是否含有某值,输出 true 或 false

var flag = arr.includes(5)

arr.find()

使用 return 操作输出,会循环数组每一项,当循环到满足条件时则跳出循环,输出当前数组元素
如果全不满足返回 undefined

var newArr = arr.find(function(item, index) {
return item > 2
})
// 不修改原数组

arr.findIndex()

使用 return 操作输出,会循环数组每一项,当循环到满足条件时则跳出循环,输出当前数组元素的下标
如果全不满足返回 -1

var index = arr.findIndex(function(item, index) {
return item > 2
})
// 不修改原数组

arr.reduce()

var new1 = arr.reduce(function(accumulator, current, index, array) {
// accumulator 第一次为数组第一项,之后为上一操作的结果
// current 数组的当前项
// index 当前项的序列
// array 可选。当前数组
// initialValue 可选。作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错
// 使用 return 操作输出
return accumulator + current // 返回数组每一项的和
}[, initialValue])
// 不修改原数组
// 扁平化数组
var arr2 = [ [1, 2, 3], [4, 5], [6, 7] ]
var new2 = arr2.reduce(function(accumulator, current, index) {
return accumulator.concat(current)
})
// 对象数组叠加计算
var arr3 = [
{ price: 1, count: 1 },
{ price: 2, count: 2 }
]
var new3 = arr3.reduce(function(accumulator, current, index) {
return accumulator + current.price * current.count

// 当需要操作第一项的时候,利用 reduce(callbreak(){},往数组第一项前添加一项,如:0)
}, 0) // 在原数组第一项添加为 0,不改变原数组

arr.fill()

用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引

arr.fill(value[, start[, end]])

arr.flat()

扁平化数组
按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回

var newArray = arr.flat([depth])

// 使用 Infinity,可展开任意深度的嵌套数组
var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]]
arr4.flat(Infinity) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// 会移除数组中的空项
var arr4 = [1, 2, , 4, 5]
arr4.flat() // [1, 2, 4, 5]

伪数组

伪数组也叫类数组

  1. 伪数组其实就是一个对象,但是跟数组一样,伪数组也会有length属性,也有0, 1, 2, 3等属性
  2. 伪数组并没有数组的方法,不能使用push/pop等方法
  3. 伪数组可以跟数组一样进行遍历,通过下标操作
  4. 常见的伪数组:argumentsdocument.getElementsByTagName的返回值jQuery对象
var obj = {
0: 'zs',
1: 'ls',
2: 'ww',
length: 3
}
  • 伪数组借用数组的方法
// 给 obj 添加一项
Array.prototype.push.call(obj, 'zl')
// 把 obj 中的每一项使用 '-' 拼接起来返回一个字符串
Array.prototype.join.call(obj, '-')
  • 将伪数组转换成真数组
// 借用数组的方法
Array.prototype.slice.call(obj)
[].slice.call(obj)

// 使用es6中数组的from方法:从一个类似数组或可迭代对象中创建一个新的数组实例
Array.from(obj)

// 对于函数的arguments参数可以使用扩展运算符
[...arguments]