《四》Javascript 中的引用类型

Object 类型:

对象是用来保存多个数据的容器。

对象的组成:

  1. 属性:属性由属性名和属性值组成。
  2. 方法:方法是一种特殊的属性,它的属性值是函数。

创建 Object 类型:

  1. 使用 Object 构造函数创建,可以省略 new 关键字。
    var obj=new Object() / /创建一个对象
    obj.name=’小米’ // 创建属性
    obj.run=function(){ // 创建方法
          return “123”
    }
    
  2. 使用对象字面量方式创建,使用最多。
    var obj={ // 创建一个对象
        name:’小米’, // 创建属性
        run:function (){ // 创建方法
              rerurn ‘123’
        }
    }
    

属性:

访问对象的属性:

访问对象的属性有两种方法:

  1. 一般使用点表示法访问对象属性。
  2. 但也可以使用方括号表示法访问对象属性:属性名要以字符串的形式放在方括号中。如果属性名中包含会导致语法错误的字符,或者使用的是变量名,就可以选择使用方括号表示。
console.log(person.name)
console.log(person[“name”])

person[“first name”]

var proppertyName=”name”
console.log(person[proppertyName] === person['name'])  // true
删除对象的属性

删除对象的属性是使用 delete 操作符。

var person={
     name: ’Lee’
}
delete person.name
alert(person.name) // undefined
判断对象中是否有某属性:

判断对象中是否有某属性:

var person = {name: 'Lee'}
person.__proto__ = {age: 18}
  1. in 判断的是对象的所有属性,包括对象自身及其原型的属性。
    console.log('name' in person) // true
    console.log('age' in person) // true
    
  2. obj.hasOwnProperty(property): 判断的是对象自身是否具有某个属性,不包括原型上的属性。
    console.log(person.hasOwnProperty('name')) // true
    console.log(person.hasOwnProperty('age')) // false
    

静态方法:

  1. Object.preventExtensions() :可以使用 Object.preventExtensions() 来禁止扩展对象的属性。接收一个对象作为参数。
    let person = {
    	name: 'Lee',
    	age: 18
    }
    Object.preventExtensions(person)
    person.address = '广州' // 无效
    console.log(person)
    
    请添加图片描述
  2. Object.seal() :可以使用 Object.seal() 来密封对象,接收一个对象作为参数。相当于是既调用了 Object.preventExtensions() 来禁止扩展对象的属性,也设置了对象中所有的属性的 [[Configurable]] 特性为 false 使其不可配置。
    let person = {
    	name: 'Lee',
    	age: 18
    }
    Object.seal(person)
    person.address = '广州' // 无效
    delete person.name // 无效
    console.log(person)
    
  3. Object.freeze() :可以使用 Object.freeze() 来冻结对象,接收一个对象作为参数。相当于是既调用了 Object.preventExtensions() 来禁止扩展对象的属性,也设置了对象中所有的属性的 [[Configurable]] 特性为 false 使其不可配置,还设置了对象中所有的属性的 [[Writable]] 特性为 false 使其不可修改。
    let person = {
    	name: 'Lee',
    	age: 18
    }
    Object.freeze(person)
    person.address = '广州' // 无效
    person.name = 'Mary' // 无效
    delete person.name // 无效
    console.log(person)
    

Array 类型:

数组在 JavaScript 中是一种特殊的对象。

数组是一组数据的集合,具有数值下标的属性,且内部数据是有序的。

数组中的项可以是任意类型。

var arr=[
	'Mary',
	{
	    name: 'Lee',
	    age: 18,
	},
	[1,2,3], 	
	25+25,
]

console.log(typeof arr) // object。数组也属于 Object 类型
console.log(arr instanceof Array) // true
console.log(Array.isArray(arr)) // true

创建 Array 类型:

  1. 使用数组字面量的方式创建数组:使用中括号定义数组,项与项之间用逗号分隔。这种方式最常用。
    // 创建一个空数组
    var arr1 = []
    
    // 创建包含项的数组
    var arr2 = ['Lee', 18]
    
  2. 使用 new Array 构造函数的方式创建数组:小括号内可以直接分配数组的项;也可以定义数组长度,此时小括号内必须是一个数字。
    // 创建一个空数组
    var arr1 = new Array()
    
    // 创建一个数组并分配两项
    var arr2 = new Array('Lee', 18)
    
    // 创建一个长度为 4 的数组,但是 4 项都是 undefined
    var arr3 = new Array(4)
    

属性:

  1. 数值下标:数组的每一项都有下标,下标是从 0 开始的,使用方括号中书写下标的形式,可以获取、修改、增加数组的任一项。

    下标越界:JavaScript 规定,访问数组中不存在的项会返回 undefined,不会报错。

    var arr = new Array('Lee', 18)
    console.log(arr[0]) // Lee。获取数组的第 1 项
    arr[1] = 20 // 修改数组的第 2 项
    arr[2] = 'student' // 给数组增加第 3 项
    
  2. length:获取数组的长度。length 不是只读的。

    数组最多可包含 4294967295 个元素,超出会发生异常。
    数组最后一项的下标是数组的长度减 1。

    var arr = new Array('Lee', 18)
    console.log(arr.length) // 2
    
    var arr = new Array('Lee', 18,)
    console.log(arr.length) // 2。如果最后一项是逗号,会被忽略
    	
    arr.length = 10 // 强制改变数组个数,以逗号扩展补充,一般不会使用
    

方法:

方法:就是打点调用的函数。

会改变原数组的方法:splice()reverse()sort()unshift()shift()push()pop()

转换方法:
  1. toString():返回由数组中每个项的字符串形式拼接成的一个以逗号分隔的字符串。不会改变原数组。

  2. toLocaleString():返回转换为本地格式的字符串。不会改变原数组。

  3. valueOf():返回的数组本身。不会改变原数组。

    所有对象都具有 toString()toLocaleString()valueOf() 方法。因为所有的对象都继承自 Object,而这些方法都是 Object 的方法。

    var arr1 = [1,2,3,undefined,4]
    var arr2 = arr1.valueOf()
    console.log(arr2 instanceof Array) // true
    
  4. join():数组转换为字符串,字符串可以由不同的分隔符连接。如果不传值或者传入undefined,则使用逗号作为分隔符。不会改变原数组。

    数组的 join() 方法可以使数组转为字符串;字符串的 split() 方法可以使字符串转为数组。

    var arr = [1,2,3]
    console.log(arr.join()) // "1,2,3"
    console.log(arr.join('')) // "123"
    
    const str = "123"
    console.log(str.split()) // [123]
    console.log(str.split('')) // [1,2,3]
    

    如果数组中的某一项的值是 null 或者 undefined,那么转换方法返回的结果中以空字符串表示。

    var arr=[1,2,3,undefined,4]
    console.log(arr.join()) // 1,2,3,,4
    
连接方法:

concat():连接多个数组,多个数组之间用逗号分隔,返回新构建的数组。不会改变原数组。

var arr = [1,2,3]
console.log(arr.concat([4,5,6])) // [1,2,3,4,5,6]
console.log(arr) // [1,2,3]
截取方法:

slice(from,to):从下标为 from 的项开始,到下标为 to 但不包括 to 的项为止截取子数组,如果没有 to,则表示一直截取到数组末尾,返回被截取的子数组。不会改变原数组。

var arr=[1,2,3,4,5]
console.log(arr.slice(1,3)) // [2,3]。截取下标为【1,3)之间的元素
console.log(arr) // [1, 2, 3, 4, 5]

参数允许为负数,最后一项为 -1。

var arr=[1,2,3,4,5]
console.log(arr.slice(1,-3)) // [2]

如果结束位置小于起始位置,则返回空数组。

var arr=[1,2,3,4,5]
console.log(arr.slice(3,1)) // []
删除方法:

splice():返回被删除的项,如果没有删除任何项,则返回一个空数组。会改变原数组。

  1. 删除功能:splice(from, num),from 表示开始位置,num 表示元素个数。
    var arr = [1,2,3,4,5]
    console.log(arr.splice(0,3)) // [1,2,3]。从下标为 0 的位置删除 3 项
    console.log(arr) // [4,5]
    
  2. 替换功能:splice(from, num,string……),from 表示开始位置,num 表示元素个数,后面的参数表示要替换成的项。
    var arr=[1,2,3,4,5]
    console.log(arr.splice(0, 3, 11, 22, 33, 44)) // [1,2,3]。从下标为 0 的位置开始删除 3 项后,插入要替换的值
    console.log(arr) // [11, 22, 33, 44, 4, 5]
    
  3. 插入功能:splice(from, 0,string……),from 表示插入位置,0 表示不删除任何项,后面的参数表示要插入的项。
    var arr=[1,2,3,4,5]
    console.log(arr.splice(1,0, 22)) // [1,2,3]。在下标为 1 的位置插入 22
    console.log(arr) // [1, 22, 2, 3, 4, 5]
    
头尾操作方法:
  1. unshift():为数组前端添加任意个元素,多项用逗号分隔,返回修改后数组最新的长度。会改变原数组。
  2. shift():从数组前端移除第一个元素,返回被移除的元素。会改变原数组。
    var arr = ['Lee',18]
    
    console.log(arr.unshift('student')) // 3
    console.log(arr) // student Lee 18
    
    console.log(arr.shift()) // student
    console.log(arr) // Lee 18
    
  3. push():为数组末尾添加任意个元素,多项用逗号分隔,返回修改后数组的最新长度。会改变原数组。
  4. pop():从数组末尾移除最后一个元素,返回被移除的元素。会改变原数组。
    var arr = ['Lee',18]
    
    console.log(arr.push('student')) // 3
    console.log(arr) // Lee 18 student
    
    console.log(arr.pop()) // Lee
    console.log(arr) // Lee 18
    
位置方法:
  1. indexOf()lastIndexOf():返回元素在数组中第一次出现的索引值,如果元素不存在,则返回 -1。
    var arr=[1, 2, 3, 4, 1];
    console.log(arr.indexOf(1))// 0
    console.log(arr.lastIndexOf(1)) // 4
    
  2. arr.includes():判断一个数组是否包含某个元素,返回值是布尔值。
排序方法:
  1. reverse():数组倒转,返回倒转后的数组。会改变原数组。
    var arr = [1,2,3]
    console.log(arr.reverse()) // [3,2,1]
    console.log(arr) // [3,2,1]
    
  2. sort(sortfunc):会调用数组中每个项的 toString() 方法,然后根据得到的字符串,按 ascii 字符来进行升序排列。会改变原数组。

    即使数组中的每一项都是数值,sort() 方法比较的也是字符串。

    var arr=[32,12,111,444];
    console.log(arr.sort()) // [111,12,32,444]
    console.log(arr) // [111,12,32,444]
    
    要改变默认的 sort 行为,即按字符排序,可以自行指定排序规则函数,也就是给 sort() 方法传递一个比较函数作为参数参数,比较函数接收两个参数,该函数必须返回下列值之一: 如果函数中所传递的第一个参数比第二个参数小,那么返回负值;如果函数中所传递的第一个参数比第二个参数大,那么返回正值; 如果函数中所传递的第一个参数与第二个参数相等,那么返回 0。
    // 升序
    function compare(a,b){
        return a-b
    }
    var arr = [32,12,111,444]
    console.log(arr.sort(compare)) // [12,32,111,444]。sort() 方法内部做遍历,进行冒泡排序,如果两个数的差 > 0,那么 2 个数就交换位置
    
    // 降序
    function compare(a,b){
            return b-a
    }
    
迭代方法::

ECMAScript 为数组定义了 5 个迭代方法,每个方法都接收两个参数:要在每一项运行的函数和可选的运行该函数的作用域对象,函数接收三个参数:数组项的值、该项在数组中的位置和数组对象本身。

  1. every():对数组中的每一项运行给定函数,如果每一项都返回 true,则结果返回 true,否则返回 false。
  2. some():对数组中的每一项运行给定函数,如果任一项返回 true,则结果返回 true。
  3. filter():对数组中的每一项运行给定函数,返回结果为 true 的项所组成的数组。
  4. map():对数组中的每一项运行给定函数,返回每次函数调用的结果所组成的数组。
  5. forEach():对数组中的每一项运行给定函数,这个方法没有返回值,本质上与使用 for 循环迭代数组一样。
    var arr = [1,2,3,4,5,4,3,2,1]
    var result = arr.filter(function(item,index,array){
    	  return (item>2)
    })
    console.log(result) // [3,4,5,4,3]
    
归并方法:

reduce()reduceRight(): 迭代数组的所有项,然后构建一个最终返回的值。reduce() 方法从数组的第一项开始,逐个遍历到最后。reduceRight() 则从数组的最后一项开始,向前遍历到第一项。这两个方法都接收两个参数:在每一项上调用的函数和可选的传递给函数的初始值。

传给 reduce()reduceRight() 的函数接收四个参数:前一个值 prev、当前值 curr、项的索引 index 和数组对象 array。这个函数返回的任何值都会作为函数的第一个参数自动传给下一项。

当没有传入初始值时,prev 是从数组中第一个元素开始的,curr 是第二个元素。但是当传入初始值,第一个 prev 将是传入的初始值,curr 是数组中的第一个元素。

在这里插入图片描述
在这里插入图片描述

// 使用 reduce() 方法求数组中所有值之和
var values=[1,2,3,4,5]
var sum=values.reduce(function(pre,cur,index,array){
	rerurn pre+cur
})
console.log(sum) // 15

二维数组:

二维数组:以数组作为数组项的数组。通过指定数组中的元素为数组的方式可以创建二维甚至多维数组。

var matrix = [
	['星期日', 'Sunday'],
	['星期一', 'Monday'],
]
consolr.log(matrix[1][0]) // 星期一

数组与类数组:

  1. 数组的特征:可以通过角标调用,如 array[0];具有长度属性 length;可以通过 for 循环和 forEach 方法进行遍历。
  2. 类数组的特征:可以通过角标进行调用;具有 length 属性;也可以通过 for 循环进行遍历,但不能通过 forEach 进行遍历,因为 forEach 是数组原型链上的方法。
    var arrayLike = {
          0: 'item1',
          1: 'item2',
          2: 'item3',
          length: 3
    } // 这个对象就是类数组
    

Function 类型:

函数是定义一次但却可以调用任意多次的一段 JS 代码。ECMAScript 中的函数使用 function 关键字来声明,后跟函数名,小括号的参数,大括号的函数体。

函数是一种特殊的对象,它和普通对象的区别是内部包含的是可运行的代码,因此可以执行。

每个函数都是 Function 类型的实例,所以函数实际上是对象。
函数名是指向函数对象的指针,不会与某个函数绑定。
使用不带圆括号的函数名是访问函数指针,而非调用函数。
函数本身没有运行功能,必须调用才可以执行。
方法就是函数,用在面向对象的时候叫做方法,单独用的时候叫做函数。
函数是 JS 中的一等公民,可以赋值给变量,也可以作为函数的参数或者返回值。

function method(){// 没有参数的函数
   console.log('没有参数的函数')
}
method() // 直接调用函数

function method(name, age){ // 带有参数的函数。多个参数用逗号隔开
   console.log(‘带有参数的函数:名字:’ + name + ’,年龄:’ + age)
}
method('Lee',18) // 调用函数,并传参

声明方式:

  1. 使用函数声明语法:
    function method(num1, num2){	
     	return num1+num2
    }
     console.log(method(1,2))
    
  2. 使用函数表达式:

    函数表达式和函数声明的区别:函数表达式会变量提升。

    var add = function(num1, num2){
           return num1+num2
    }
     console.log(add(1,2)
    
  3. 使用 Function 构造函数:
    var add=new Function(‘num1’, ’num2’, ’return num1+num2’)
     console.log(add(1,2))
    

    不推荐这种方式,因为会导致解析两次代码,第一次解析常规 ECMAScript 代码(全部代码),第二次解析传入构造函数中的字符串,从而影响性能。

默认属性:

函数本身也是对象,因此不仅有默认的属性和方法,也可以任意添加属性和方法。

默认属性:

  1. name:函数名称。
  2. length:函数本来要接收的参数的个数,其实也就是形参的个数。
    function fn(name, age, ...args) {}
    fn('Lee', 18, 1.88, 160)
    console.log(fn.length) // 2
    
    length 属性不会将剩余参数算进去。
    function fn(name, age, ...args) {}
    fn('Lee', 18, 1.88, 160)
    console.log(fn.length) // 2
    
    如果给参数加上默认值,length 属性也不会将其算进去。
    function fn(name, age = 18) {}
    fn('Lee', 18, 1.88, 160)
    console.log(fn.length) // 1
    

参数:

  1. 把函数的返回值作为参数传递。
    function add(num1,num2){
       return num1 + num2
    }
    function sum(num3){
       return num3 + 10
    }
    var result=add(sum(10), 10)
    console.log(result) // 30。sum(10)传递的是第二个函数的返回值20
    
  2. 把函数本身作为参数传递。因为ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。
    function add(sum,num){
        return sum(num)
    }
    function sum(num){
       return num + 10
    }
    var result=add(sum, 10) // sum是一个函数
    alert(result) // 返回20
    

return 返回值 :

return语句如果不带有返回值,将返回undefined。

break用在循环和switch分支语句里。
return false的作用:阻止默认行为,取消事件冒泡,停止执行事件处理函数。

function method(){
   return 'Lee';//return表示函数把字符串返回给调用部分
}                     
method() // 相当于 method()='Lee',所以无效果
console.log(method()) // Lee
function method(name, age){
    return ‘名字:’ + name + ’,年龄:’ + age;
}
var strInfo = method(‘Lee’, 18) // 把返回值赋值给一个变量
console.log(strInfo)
function method(){
     return 10 // 函数会在执行完第一个 return 语句之后立即停止并退出,位于 return 语句之后的任何代码都永远不会被执行
     return 100
}
console.log(method()) // 10

arguments 对象:

arguments 是函数运行时自动生成的一个内部对象,是类数组对象,包含着传入函数中的所有参数。

在函数内部,有两个特殊的对象,arguments 对象和 this 对象。

function fn() {
	console.log(arguments)
}
fn('Lee', 18)

请添加图片描述

属性:
  1. length:传入函数的参数的个数,其实也就是实参的个数。
  2. callee:该属性是一个指针,指向拥有这个 arguments 对象的函数。

this 对象:

this 是函数运行时自动生成的一个内部对象。谁调用这个函数,就把谁绑定到函数内部的 this 对象上,this 对象就指向谁。

有四种绑定函数中 this 对象的方式:

function Person() {
	console.log(this)
}
  1. 通过独立调用的方式默认绑定 this。
    Person() // 全局变量和全局函数都是 window 对象的属性和方法,实际上相当于 window.Person(),因此此处的 this 指向 window。但是在严格模式下(在 script 中的顶部加上 "use strict"),指向的是 undefined。
    
  2. 通过对象调用的方式隐式绑定 this。
    var obj  = {}
    obj.getPerson =  Person
    obj.getPerson () // 将 Person 赋值给 obj.getPerson,也就是将 function() {console.log(this)} 赋值给  obj.getPerson,obj.getPerson = function() {console.log(this)},通过 obj 调用 getPerson(),因此此处的 this 指向 obj
    
  3. 通过 call、apply、bind 调用的方式显式绑定 this。
    var obj  = {}
    Person.call(obj) // call、apply、bind 都是用来改变函数内部 this 的指向的,第一个参数就是 this 的指向对象,因此此处的 this 指向 obj
    
  4. 通过 new 关键字调用的方式绑定 this。
    var p = new Person() // 通过 new 构造函数实例化一个对象,构造函数内部的 this 指向新创建的对象,因此此处的 this 指向 p
    

这四种绑定函数中 this 对象方式的优先级:通过 new 绑定 this > 通过 bind 调用的方式显式绑定 this > 通过 call、apply 调用的方式显式绑定 this > 通过对象调用的方式隐式绑定 this > 通过直接调用的方式默认绑定 this 。谁的优先级高,同时出现时,this 会取谁的绑定方式。

在一个代码片段里, this 有可能代表不同的对象。因为 JS 可以多层嵌套代码,如果函数里面再嵌套一个函数,父函数中的 this 代表的是调用父函数的对象,子函数中的 this 代表的是调用子函数的对象。如果在子函数中想要获取父函数中的 this,可以在父函数中提前创建一个临时变量来保存父函数中的 this,子函数中就可以通过这个临时变量来获取到父函数中的 this 了。

$("#btn").click(function(){
    var _this = this // 这里 this 和 _this 都代表了 "#btn" 这个对象
    $(".tr").each(function(){
         this // 在这里 this 代表的是 ".tr" 对象
         _this // 仍代表最初的对象 "#btn"
    })
})

现在通过使用 ES6 中的箭头函数已经不再需要这么操作了。

call()apply()bind()

call()apply()bind() 都可以用来改变函数体内部 this 的指向。它们的第一个参数要求是一个对象,是 this 的指向对象,如果不是对象,则 this 指向其基本包装类型,如果也没有基本包装类型,则 this 指向 window。其他参数是传入函数中的实参。

fn.call(obj, arg, arg, ...)
fn.apply(obj, [arg, arg, ...])
fn.bind(obj, arg, arg, ...)()

它们之间的区别是:

  1. bind() 返回的是一个新的函数,称为绑定函数(bound function, BF),绑定函数和原函数相比,只是明确改变了内部 this 的指向,绑定函数必须调用才会执行。
    const obj = {
    	name: 'Lee',
    	show: function(){
    	  console.log(this.name)
    	}
    }	
    const obj1 = {
    	name: 'Mary',
    }
    	
    obj.show.call(obj1) // Mary。相当于 obj1.show(),临时让 show() 成为 obj1 的方法进行调用。
    obj.show.apply(obj1) // Mary。相当于 obj1.show(),临时让 show() 成为 obj1 的方法进行调用。
    const show1 = obj.show.bind(obj1) // 相当于 obj1.show(),返回一个新的绑定函数,绑定函数内部的 this 已经明确指向了 obj1,必须调用才会执行
    show1() // Mary
    
  2. call()bind() 的其他参数是用逗号分隔,apply() 的其他参数需要放到一个数组中。
    const obj = {
      name: 'Lee',
      show: function(age){
        console.log(this.name, age)
      }
    }
    const obj1 = {
      name: 'Mary',
    }
    
    obj.show.call(obj1, 17) // Mary, 17
    obj.show.apply(obj1, [17]) // Mary, 17
    obj.show.bind(obj1, 17)() // Mary, 17
    

手动实现 call()apply()bind()

var obj = {}
function foo(name, age) {
	console.log(this, name)
}
foo.call(obj, 'Lee', 18)
foo.apply(obj, ['Lee', 18])
foo.bind(obj, 'Lee')(18)

// 模拟实现 call 方法
// 把 foo 作为一个对象,获取它的 call 方法。所有的函数都有 call 方法,因此可以得出 call 不是单独加在某个函数自身上的,而是在函数的隐式原型对象,也就是 Function 的显式原型对象上的
Function.prototype.customCall = function(thisArg, ...restArgs) {
	// 1. 边界判断
	thisArg = (thisArg === null || thisArg === undefined) ? window : Object(thisArg)
	// 2. 给 thisArg 对象添加一个 fn 属性,并将 customCall 内部的 this,也就是 foo 赋值给 fn
	thisArg.fn = this
	// 3. 使用 thisArg 调用 fn,因此 fn,也就是 foo 内部的 this 指向 thisArg,成功修改 this 指向
	thisArg.fn(...restArgs)
}
foo.customCall(obj, 'Lee', 18)

// 模拟实现 apply 方法
Function.prototype.customApply = function(thisArg, restArgs) {
	thisArg = (thisArg === null || thisArg === undefined) ? window : Object(thisArg)
	thisArg.fn = this
	thisArg.fn(...restArgs)
}
foo.customApply(obj, ['Lee', 18])

// 模拟实现 bind 方法
Function.prototype.customBind = function(thisArg, ...restArgs) {
	thisArg = (thisArg === null || thisArg === undefined) ? window : Object(thisArg)
	thisArg.fn = this

	// 需要返回一个对象,以便调用
	return (...otherArgs) => {
		thisArg.fn(...restArgs, ...otherArgs)
	}
}
foo.customBind(obj, 'Lee')(18)
一些内置函数中,被作为参数传入的函数中 this 的指向:

JS 中的一些内置函数,会要求传入另外一个函数作为其参数。被传入的这些函数并不是由开发者显式调用的,而是 JS 内部帮助执行的,这些函数内部的 this 指向只能依靠经验得知。

setTimeout(function(){
	console.log(this) // window
}, 1000)
var btn = document.getElementById('btn')
btn.addEventListener('click', function(){
	console.log(this) // btn
})
var names = ['Lee', 'Mary']
names.forEach(function(){
	console.log(this) // window
})

函数提升:

函数声明提升直接把整个函数提升到所在执行上下文的最顶端。通过 function 声明的函数,在声明之前就可以访问到,值为函数本身。

变量提升只提升变量名,而函数提升会提升整个函数。

console.log(foo)
function foo(){}

//相当于
function foo(){}
console.log(foo) // 输出函数本身

函数表达式遵循的是变量提升,而不是函数提升。

console.log(foo)
var foo = function(){}

//相当于
var foo
console.log(foo) // 输出 undefined
foo = function(){}

函数提升在变量提升上面。

console.log(foo) // 输出函数
var foo=10
console.log(foo)
function foo(){
    console.log(10)
} 

// 相当于:

function foo(){
    console.log(10)
}
var foo
console.log(foo) //函数提升在变量提升上面,var foo只是声明,并未赋值,因此调用上面的
foo=10

重载:

重载就是根据参数,选择相同函数名而参数不同的函数。

ECMAscript 中的函数不具备重载功能,是因为其参数是由包含 0 个或多个值的数组来表示的。

function method(num,a){
     return num+100
}
function method(num){
     return num+200 // 第二个函数会把第一个函数覆盖掉
}
console.log(method(50,1)) // 250

匿名函数:

匿名函数也就是没有名称的函数。如果单独只写一个匿名函数,是不符合语法要求会报错的,需要给匿名函数包裹一个括号,被小括号包裹的内容会被 JS 识别为一个函数表达式。

(function () {

})

执行匿名函数,只需要后面追加括号即可,也就是立即执行函数。

(function () {

})()

匿名函数传参与普通函数传参的方式一样,调用的时候将参数传入即可。

(function (str) {
	console.log(str) // '我是匿名函数的参数'
})('我是匿名函数的参数')

在匿名函数内部调用一个函数,那么这个函数中的 this 对象指向全局对象 window。

(function () {
	fn1()
})()

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

立即执行函数(IIFE、Immediately-Invoked Function Expression):

声明一个匿名函数,并立即调用它,就叫做立即执行函数。

写法:
  1. (function() {...})()
  2. (function() {...}())
  3. !function() {...}(),!、+、-等运算符都能起到立即执行的作用。
// 匿名函数自调用就是立即执行函数
(function () {
	console.log('我是立即执行函数')
})()
参数:

如果立即执行函数中需要使用全局变量,可以将全局变量作为一个参数传递给立即执行函数。i 就是一个全局变量,代表的是实参,j 是 i 在立即执行函数中的形参。

(function(j){
	//在代码中可以使用j
})(i)
作用:
  1. 不必为函数命名。
  2. 立即执行函数会形成一个单独的作用域,定义的所有变量都会成为立即执行函数的局部变量,避免污染全局变量,而且也使得内存在执行完后能够立即释放。
    (function(){}(
      // 如果写在外部,就会多生成一个全局变量;写在立即执行函数里面,就是一个局部变量,立即执行函数执行完毕就会释放所占用的内存空间
      var num = 3
      console.log(num + 1)
    ))
    

回调函数:

回调函数是一个作为参数被传递给另外一个函数的函数,它在主体函数执行完之后执行。

因为函数实际上也是一种对象,它可以存储在变量中,作为参数被传递给另一个函数。

常见的回调函数有:DOM 事件回调函数、定时器回调函数、AJAX 请求回调函数。

function A(callback) {   
    console.log('我是主函数')
    callback()
}
function B(){
    console.log('我是回调函数')
}
A(B) // 我是主函数 我是回调函数

document.getElementById('btn').click = function(){
	console.log('我是点击按钮')
}

setTimeout(function(){
	console.log('我是定时器')
}, 2000)

闭包(Closure):

如果一个函数引用了外部的变量,那么,就可以说,这个函数和它周围的环境形成了一个闭包。

// 闭包通常是通过嵌套函数实现的。在一个函数里面嵌套了另外一个函数,并且在内层函数中引用外层函数中的变量
function fn1() {
	var n = 1
	function fn2() {
		console.log(n) // fn2 读取 fn1 内部的变量,因此形成了一个闭包
	}
	return fn2
}
var f = fn1() 
f()

function fn1() {
	var n = 1
	function fn2() {
		... // 如果 fn2 没有读取 fn1 内部的变量,不会形成闭包
	}
	return fn2
}
var f = fn1() 
f()
// 函数内部引用了全局变量
var str  = 'aaa';
function f3() {
	console.log(str) // // f3 读取了外部变量,因此也形成了一个闭包
}
f3()
闭包的生命周期:

产生:在形成闭包的函数的定义被执行完就产生了,而不是在调用了这个函数后才产生。

死亡:在形成闭包的函数成为垃圾对象被回收后才死亡。

function fn1() {
	var n = 1
	var str = 'Hello'
	var boolean = true
	function fn2() {
		console.log(n)
	}
	return fn2
}
// 执行完这行代码,闭包就已经产生了
var f = fn1()
// f 的执行上下文出栈,0xb00 在某个时刻被垃圾回收器回收。本来执行完 var f = fn1(),fn1 的执行上下文就出栈了,0xa00 就应该在某个时刻被垃圾回收器回收,但直到此刻,0xa00 还被 GO 中的 f 属性引用,因此永远都不会被销毁了,造成内存泄漏
// 针对这种情况,V8 引擎进行了一些优化,只会保留 fn1 中的 n,str、boolean 由于没有被引用到,会被删除掉,减少 0xa00 对象的大小
f() // 2

// 需要手动解除引用,等待垃圾回收器回收 0xa00 后,闭包才会死亡
f = null

请添加图片描述

闭包的优点和缺点:

优点:

  1. 让函数外部可以读取和操作函数内部的数据。
  2. 让函数内部的局部变量的值仍然保持在内存中,不会在函数调用结束后,被垃圾回收机制回收,延长了局部变量的生命周期。

缺点:

  1. 由于闭包会使得函数内部被引用的变量,在函数执行完仍然保存在内存中,造成内存泄漏,影响网页的性能,严重时甚至可能会导致内存溢出。
  2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,一定不要随便改变父函数内部变量的值。

递归:

递归就是在函数的内部调用其自身。

递归的原理就是传递 + 回归,函数不断调用自身,直到找到终点,把结果沿着原来的路线传递,直到回到起点。

递归和循环的效果是一样的,如果没有返回值,就会一直死循环下去导致发生栈溢出错误,所以必须要加退出条件。

function fn() {
	fn()
}

fn()

在这里插入图片描述

利用递归来实现求阶乘。

// 4 的阶乘:
0! = 1
1! = 1
2! = 1 *2 = 2
3! = 1 *2 * 3 = 6
4! = 1 *2 * 3 * 4 = 24

可以发现:4 的阶乘等于 3 的阶乘乘以 4,3 的阶乘等于 2 的阶乘乘以 3,以此类推,规律就是上一个阶乘的结果乘以当前的阶乘数。因此可以得到这样的公式:factorial(n) = factorial(n -1) * n,起点就是 0 的阶乘。

// 代码实现:
function factorial(n) {
	if (n === 0) return 1

	return n * factorial(n - 1)
}

factorial(4) // 24

调用 factorial(4) 生成的执行上下文栈:

  1. 调用 factorial(4) ,生成 factorial(4) 的执行上下文并进栈 ,返回 4 * factorial(3) ;。
  2. 接着调用 factorial(3) ,生成 factorial(3) 的执行上下文并进栈 ,返回 3 * factorial(2)
  3. 接着调用 factorial(2) ,生成factorial(2) 的执行上下文并进栈 ,返回 2 * factorial(1)
  4. 接着调用 factorial(1) ,生成 factorial(1) 的执行上下文并进栈 ,返回 1 * factorial(0)
  5. 接着调用 factorial(0) ,生成factorial(0) 的执行上下文并进栈 ,返回计算结果 1 并出栈。
  6. factorial(1) 的执行上下文返回计算结果 1 * 1 = 1 并出栈。
  7. factorial(2) 的执行上下文返回计算结果 1 * 2 = 2 并出栈。
  8. factorial(3) 的执行上下文返回计算结果 2 * 3 = 6 并出栈。
  9. factorial(4) 的执行上下文返回计算结果 6 * 4 = 24 并出栈。

在这里插入图片描述

高阶函数:

高阶函数是一个函数,它接收函数作为参数或者返回函数作为输出。

数组的 forEach()map() 等方法就都是高阶函数。

function fn() {
	console.log('这是作为参数被传入的函数 fn')
}

// fn1 接收一个函数作为参数,因此是一个高阶函数
function fn1(f) {
	f() // 这是作为参数被传入的函数 fn
}
fn1(fn)
// fn1 返回一个函数,因此是一个高阶函数
function fn1(f) {
	return function() {
		console.log('fn1 返回一个函数作为输出')
	}
}
fn1()() // fn1 返回一个函数作为输出

eval() 是一个特殊的内建函数,它可以将传入的字符串当做 JS 代码来执行,并且会将最后一条执行语句的结果,作为返回值。

eval(`var message = 'Hello'; console.log(message)`)

var message = 'Hello'
eval(`console.log(message)`) // `eval()` 函数里也可以引用到外部变量

eval(`var message = 'Hello'`)
console.log(message) // `eval()` 函数里创建的变量,外部也可以引用到

eval() 代码的可读性非常差;eval() 要执行的是一个字符串,不能被 JS 引擎优化,并且有可能在执行的过程中被可以修改,造成被攻击的风险。因此不建议在开发中使用。

RegExp 正则表达式:

一个正则表达式是由普通字符以及特殊字符组成的字符串模式,用来与所搜素的字符串进行匹配。正则表达式并不依赖于某种编程语言,许多编程语言都支持正则表达式。

也就是说,正则表达式是用来操作字符串的。

创建正则表达式:

正则表达式主要由两部分组成:匹配的规则 patterns 和匹配的修饰符 flags,修饰符是可选的。

  1. 使用字面量的方式创建:匹配的规则包含在 / / 之间,匹配的修饰符在 / / 后面。
    var pattern = /abc/i
    
  2. 使用 RegExp 构造函数来创建:接收两个参数,第一个参数是匹配的规则,第二个参数是匹配的修饰符。
     var pattern = new RegExp('abc', 'i')
    

正则表达式的组成:

规则:
  1. 字符类:是一个特殊的符号,用于匹配某些特定集中的符号。

    • .:匹配除了换行符之外的一个字符。
    • \d:digit,匹配一个数字字符,相当于 [0-9]
      /\d/:可以匹配 a1b 中的 1
      
    • \s:space,匹配一个空格字符,如空格,制符表,换行符等。
    • \w:word,匹配一个英文字母,数字字符或者下划线。
  2. 反向类:

    • \D:匹配一个非数字字符。
    • \S:匹配一个非空格符。
    • \W:匹配除 \w 外的任意一个字符。
  3. 转义字符:如果要把特殊字符作为普通字符来使用,需要对其进行转义,只需要在它前面加个反斜杠。常见的需要转义的字符 ()[]\.^$|*+? 等。

    斜杠 / 并不是一个特殊符号,但是在字面量正则表达式中也需要转义。

  4. 锚点::

    • ^:匹配字符串的开头。
      /^g/:可以匹配 good 中的 g,但不能匹配 bag 中的 g
      
    • $:匹配字符串的结尾。
      /g$/:可以匹配 bag 中的 g,但不能匹配 good 中的 g
      
  5. 词边界:

    • \b:匹配单词的边界。只要边界不是英文字母,数字字符或者下划线,就可忽略,将其作为词边界。
      /e\b/:匹配 i love seek 中 love 的 e,但不匹配 seek 中的 e   
      
      var str = '1abc'
      var reg = /\babc/
      console.log(reg.test(str)) // false
      
      var str = '\(abc'
      var reg = /\babc/
      console.log(reg.test(str)) // true
      
    • \B:匹配单词的非边界。
      /e\b/:匹配 i love seek 中 seek 的 e,但不匹配 love 中的 e 
      
  6. 集合和范围:

    • [ ]:匹配方括号中字符序列的任意一个字符。其中也可以使用连字符“-”匹配指定范围内的任意字符。
      [102]:可以与 0A1B2C 中的字符 0 或 1 或 2 匹配
      [0-5]:可以与 a3g02gsf91dfs4 中的 0 到 5 之间的任意数字字符匹配
      
    • [^ ]:匹配方括号中字符序列未包含的任意一个字符。其中也可以使用连字符“-”匹配不在指定范围内的任意字符。
      [^102]:可以与 0A1B2C 中的字符 A 或 B 或 C 匹配
      [^a-z]:可以与 a3g02gsf91dfs4 中的不在小写字母 a 到 z 之间的任意字符匹配
      
  7. 量词:

    • ?:匹配前一项 0 次或 1 次。
      /js?/:可以匹配 jscript 中的 js 或者 javascript 中的 j
      
    • +:匹配前一项 1 次或多次,但至少出现一次。
      /js+/:可以匹配 js,也可以匹配 jsssssss
      
    • *:匹配前一项任意次。
    • {n}:匹配前一项 n 次,n 为非负整数。
      /o{2}/:可以匹配 book 中的 oo,也可以匹配 booook 中的任意两个连续的 oo
      
    • {n,}:匹配前一次至少 n 次。
    • {n,m}:匹配前一项 n 到 m 次,n<=m。

    默认的匹配方式是贪婪模式,查找到匹配的内容后,会继续向后查找,一直找到最后一个匹配的内容。
    但是,只要在量词后面加一个 ? 就可以启用惰性模式,只要获取到对应的内容后,就不再继续向后匹配。
    // 贪婪模式:从第一个 《 开始,到最后一个 》,都被匹配上了
    var message = ‘我做喜欢的三本书是:《黄金时代》、《沉默的大多数》和《人性的弱点》’
    var fileNameReg = /《.》/ig
    console.log(message.match(fileNameReg))
    请添加图片描述
    // 惰性模式:从《 开始,只要匹配到 》,一次匹配就结束
    var message = ‘我做喜欢的三本书是:《黄金时代》、《沉默的大多数》和《人性的弱点》’
    var fileNameReg = /《.
    ?》/ig
    console.log(message.match(fileNameReg))
    请添加图片描述

  8. 分组:

    • 捕获组:用小括号括起来。它有两个作用:一是会将括号中的内容当做一个整体;二是会将匹配到的内容作为结果数组中单独的一项。
      // 将 ava 当做整体
      /j(ava)?/:可以匹配 jscript 中的 j,也可以匹配 javascript 中的 java   
      
      var message = '我正在学 javascript'
      var reg = /java/ig
      console.log(reg.exec(message))
      
      请添加图片描述
      var message = '我正在学 javascript'
      var reg = /j(ava)/ig
      console.log(reg.exec(message))
      
      请添加图片描述
    • 非捕获组:用小括号括起来,在左侧小括号后面紧跟 ?:,匹配到的内容将不会作为结果数组中单独的一项。
    • 命名组:用小括号括起来,在左侧小括号后面紧跟 ?<名称>,匹配到的内容不仅会作为结果数组中单独的一项,而且会以 名称: 匹配到的内容 这种 key: value 的形式放在返回的结果数组的 groups 中。
      var message = '我正在学 javascript'
      var reg = /j(?<name>ava)/ig
      console.log(reg.exec(message))
      
      请添加图片描述
  9. 选择匹配符:只有一个 |,可以匹配指定的多个选项中的任意一项。

    /(world|dream)/:可以匹配 one world one dream 中的 world 或 dream
    
修饰符:
  1. g:global,表示全局匹配。
    var str = '123abc123abc'
    var reg = /abc/
    console.log(str.replace(reg, 'Hello')) // 123Hello123abc
    
    var str = '123abc123abc'
    var reg = /abc/g
    console.log(str.replace(reg, 'Hello')) // 123Hello123Hello
    
  2. i:ignore,表示不区分大小写。
  3. m:multiple,表示多行匹配 。

静态属性:

分别有一个长属性名和一个短属性名,可以通过两种方式访问。由于短属性名大多不是有效的 ECMAScript 标识符,所以要通过方括号来访问。

  1. input、$_:最近一次要匹配的字符串。
  2. lastMatch、$&:最近一次的匹配项。
  3. leftContext、$`:input 字符串中 lastMatch 之前的文本。
  4. rightContext、$’:input 字符串中 lastMatch 之后的文本。
  5. lastParen、$+:最近一次匹配的捕获组。
    var text=”this has been a short summer”;
    var pattern=/(.)hort/g;
    if(pattern.test(text)){
         alert(RegExp.$_);//this has been a short summer
         alert(RegExp.[“$`”]);//this has been a            
         alert(RegExp.[“$’”]);//summer 
         alert(RegExp.[“$&”]);//short
         alert(RegExp.[“$+”]);//s
    }
    
  6. RegExp.$1RegExp.$2…用于存储捕获组。
    var text=”this has been a short summer”;
    var pattern=/(..)or(.)/g; 
    alert(RegExp.$1);//sh
    alert(ReExp.$2);//t
    

实例属性:

  1. global:返回布尔值,表示是否已设置了 g 标志。
  2. ignoreCase:返回布尔值,表示是否已设置了 i 标志。
  3. multiLine:返回布尔值,表示是否已设置了 m 标志。
  4. lastIndex:返回整数,表示开始搜索下一个匹配项的字符位置,从 0 开始。
  5. source:返回正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。

实例方法:

  1. toString()toLocaleString():不管创建正则表达式的方式是哪种,都会返回正则表达式的字面量形式的字符串。
  2. valueOf():返回正则表达式本身。
  3. test():接收一个字符串作为参数,用来判断传入的字符串是否与正则表达式的规则相匹配,返回值是布尔值。
    var reg1 = /abc/
    console.log(reg1.test('abc123')) // true
    
    var reg2 = /^abc$/
    console.log(reg2.test('abc123')) // false
    
  4. exec():接收一个字符串作为参数。有匹配项的情况下,返回值是匹配项信息的数组,返回的数组是 Array 的实例,但还包含三个额外的属性:index(表示匹配项在字符串中的位置)、groups(表示分组)和 input(表示应用正则表达式的字符串);没有匹配项的情况下,返回值是 null。

    exec() 方法每次只会返回一个匹配项的信息。如果不设置 g 全局修饰符,在同一个字符串上多次调用 exec() 将始终返回第一个匹配项的信息;如果设置了 g 全局修饰符,每次调用 exec() 会在字符串中继续查找新匹配项。

    var reg = /abc/
    console.log(reg.exec('123abc123abc'))
    
    请添加图片描述

正则表达式可以用于以下方法中:

  1. 字符串的实例方法 match()matchAll()replace()replaceAll()split()search() 中,传入一个正则表达式。
  2. 正则表达式的实例方法 test()exex() 中, 传入一个字符串。

Date 类型:

用于处理日期和时间。

Date 类型使用 UTC1970 年 1 月 1 日 0 时开始经过的毫秒数来保存日期,Date 类型保存的日期能够精确到 1970 年 1 月 1 日之前或之后的 285616 年。

创建Date类型:

使用 new 运算符调用 Date 构造函数。

var now=new Date();//创建一个日期对象。如果不传递参数,会自动把当前日期和时间保存为其初始值(获取到的是年月日时分秒)。
alert(now);//不同浏览器显示不同,所以很少直接使用
方法:
  1. 静态方法:

    1. Date.parse():接收一个表示日期的字符串参数,然后根据这个字符串返回本地时间的毫秒数。如果Date.parse()没有传入参数或者不是标准的日期格式,那么就会返回NaN。

      ECMA-262没有定义Date.parse()应该支持哪种日期格式,默认通常接收的日期格式如下:

      1. ’月/日/年’,如’2/14/2017’
      2. ‘英文月名 日,年’,如’May 25,2017’
      3. ‘英文星期几 英文月名 日 年 时:分:秒 时区’,如’Tue May 25 2017 00:00:00 GMT-070’
    2. Date.UTC():返回UTC时间的毫秒数。参数有年,月(0 ~ 11),日(1 ~ 31),时(0 ~ 23),分(0 ~ 59),秒(0 ~ 59),毫秒(0~999)。只有年和月是必须的。

      UTC是世界标准时间。

    3. Date.now():返回自 1970-1-1 00:00:00 至今所经过的毫秒数,类型为Number。

      比较两个日期大小,可以转换成毫秒数进行比较。

  2. 通用方法:

    • toString()
      var date = new Date();
      var time =date.toString();//Tue Dec 04 2018 16:43:25 GMT+0800 (中国标准时间)
      
    • toLocaleString():返回本地格式的时间。
      var date = new Date();
      var time =date.toLocaleString();//2018/12/4 下午4:44:37
      
    • valueOf():返回日期的毫秒数。
      var date = new Date();
      var time =date.valueOf();//1543913115591
      
  3. 日期格式化方法:将日期格式化为字符串。

    • toDateString():返回特定格式的星期几,月,日,年的字符串。
      var date = new Date();
      var time =date.toDateString();//Tue Dec 04 2018
      
    • toTimeString():返回特定格式的时,分,秒,时区字符串。
      var date = new Date();
      var time =date.toTimeString();//16:34:16 GMT+0800 (中国标准时间)
      
    • toLocaleDateString():返回当地格式的星期几,月,日,年的字符串。
    • toLocaleTimeString():返回当地格式的时,分,秒字符串。
    • toUTCString():返回特定格式的完整的utc日期时间。
  4. 组件方法:

    • getTime():获取日期的毫秒数,和valueOf()的返回值一样。
    • setTime():以毫秒数设置日期。
    • getFullYear():获取四位年份。
    • setFullYear():设置四位年份,返回的是毫秒数。
    • getMonth():获取月份(0~11),+1才是真正的月份。
    • setMonth():设置月份(0~11),超过11则增加年份,返回的是毫秒数。
    • getDate():获取天数(1~31)。
    • setDate():设置天数(1~31),返回的是毫秒数。
    • getDay():获取星期几,0表示星期日,6表示星期六。
    • getHours():获取小时(0~23)。
    • setHours():设置小时(0~23),传入的值超过23则增加月份中的天数,返回的是毫秒数.
    • getMinutes():返回分钟(0~59)。
    • setMinutes():设置分钟(0~59),返回的是毫秒数。
    • getSeconds():获取秒数。
    • setSeconds():设置秒数,返回的是毫秒数。
    • getMilliseconds():获取毫秒。
    • setMilliseconds():设置毫秒数,返回的是毫秒数。
    //获取7天前:
    var now = new Date();//获取当前时间
    var nowMs = now.getTime();//获取当前时间的毫秒数
    var beforeMs =  nowMs -  1000 * 60 * 60 * 24 * parseInt(n);//前几天,n就取几,整数
    var beforeDate = new Date().setTime(beforeMs );
    

Math 对象:

为保存数学公式和信息提供的一个对象,用于执行数学任务。

没有构造函数Math(),直接把Math作为对象调用其属性和方法。

静态属性:
  1. Math.LN10:10的自然对数。
  2. Math.LOG2E:以2为底e的对数。
  3. Math.LOG10E:以10 为底e的对数。
  4. Math.SQRT1_2:1/2的平方根。
  5. Math.SQRT2:2的平方根。
静态方法:
  1. Math.max() :确定一组数值中的最大值。
    Math.min() :确定一组数值中的最小值。
    console.log(Math.max(2,4,6,1,0)); // 6
    
  2. 舍入方法:
    Math.ceil():对数进行向上取整。
    Math.floor():对数进行向下取整。
    Math.round():四舍五入。
    console.log(Math.ceil(25.1)); // 26
    
  3. Matn.random():返回 0~1 之间的随机数,不包括 0 和 1。
    //得到位于 [start,end] 之间的随机整数
    function select(start,end){
         var total=end-start+1;
         return Math.floor(Math.random()*total+start);
    }
    
  4. 其他方法:
    Math.sin(x)
    Math.cos(x)
    Math.tan(x)
    Math.asin(num) :返回 num 的反正弦值。
    Math.acos(num)
    Math.atan(num)
    Math.sqrt(num):返回 num 的平方根。
    Math.log(num):返回 num 的自然对数。
    Math.exp(num):返回 e 的 num 次幂。
    Math.pow(x,y):返回 x 的 y 次幂。
    Math.abs(num):返回 num 的绝对值。