JavaScript复习——03 函数

  • JavaScript复习——03 函数已关闭评论
  • 81 次浏览
  • A+
所属分类:Web前端
摘要

函数在JS中也是一个对象,它具有其它对象的所有功能,函数中可以存储代码,且可以在需要的时候调用这些代码

函数在JS中也是一个对象,它具有其它对象的所有功能,函数中可以存储代码,且可以在需要的时候调用这些代码

函数的操作

函数的定义

  1. 函数声明
function 函数名([参数列表]) { 	// 函数体 	return 返回值; } 
  1. 函数表达式
const 函数名 = function([参数列表]) { 	return 返回值; } 
  1. 箭头函数
const 函数名称 = ([参数列表]) => { 	return 返回值; }  const 函数名称 = ([参数列表]) => console.log("箭头函数"); 

函数的调用

函数名称(参数1,参数2,.....); 

函数的类型

function fn(){ 	console.log("我是牛逼") }  // 返回function console.log(typeof fn) 

函数的参数

参数:

  1. 如果实参和形参相同,那么对应的实参会赋值给形参
  2. 如果实参多于形参,则多余的实参不会使用
  3. 如果形参多于实参,则多余的形参为undefined

注意:JS不检查的参数的类型,任何类型都可以作为参数传递

箭头函数的参数

  1. 我们用箭头函数作为参数的时候,只有一个参数的时候,可以省略()
  2. 定义参数时,我们可以指定默认值
  3. 箭头函数没有arguments
  4. 箭头函数的 this 不能修改
const fn = (a,b) => {     console.log('a=',a);     console.log('b=',b); }  // 当我们箭头函数,只有一个参数时,可以省略() const fn2 = a => {     console.log('a =',a); }  // 定义参数的时候,我们可以指定默认值 const fn3 = (a = 10,b = 20,c = 0) => { 	console.log('a = ',a);    	console.log('b = ',b);     console.log('c = ',c); } 

对象作为参数

注意

  1. 我们传递参数的时候,我们传递的是变量中的值,而不是变量本身
  2. 函数每次调用,都会重新创建一个新的对象
function fn(a) {     a.name = '?' 	// 打印:a={name:'?'}     console.log('a =',a); }  let obj = {name:'孙悟空'}; fn(obj); // 打印:{name:'?'} console.log(obj) 

两次都是打印唐僧、孙悟空

如果第二次打印 孙悟空、孙悟空,就说明我们函数的调用只会创建一个对象,但是实际不是

function fn2(a = {name:'唐僧'}) {     console.log(a);     a.name = '孙悟空'     console.log(a) }  // print 唐僧 孙悟空 fn2() // print 唐僧 孙悟空 fn2() 

函数作为参数

在JS中,函数也是一个对象,函数作为对象传递,类似于Spring中的AOP思想,主要是增强代码的扩展性

function fn(a) {     console.log('a =',a) }  function fn2() {     console.log('我是一个函数') }  fn(fn2) fn(()=>{     console.log('我是箭头函数') }) 

函数的返回值

  1. 任何值都可以是函数的返回值,包括对象、函数之类的
  2. 如果return之后不跟任何值,返回值就是undefined

箭头函数的返回值

注意

  1. 如果我们直接在箭头之后设置对象字面量设置返回值,对象字面量必须使用()包起来
const obj = () => ({name:'孙悟空'})  console.log(obj()) 
const sum = (a,b) => a + b   let result = sum(123,456) console.log(result) 

作用域

作用域指定是我们变量的可见区域,也就是我们变量在哪里可以被访问

作用域有两种

  1. 全局作用域
    1. 生命周期:在网页开启时创建,在网页关闭时销毁
    2. 所有直接编写在 script 标签中代码都位于全局作用域中
    3. 全局作用域的变量,是全局变量,可以在任意位置访问
  2. 局部作用域
    1. 块作用域
      1. 块作用域是一种局部作用域
      2. 块作用域在代码执行的时候创建,在代码执行结束的时候销毁
      3. 在块作用域中声明的变量是局部变量,只能在块内部访问
    2. 函数作用域
      1. 函数作用域也是一种局部作用域
      2. 函数作用域在我们函数调用时产生,调用结束后销毁
      3. 函数每次调用都会产生一个新的作用域
      4. 在函数内部定义的变量是局部变量,只能在函数内部访问
      5. var 虽然没有块作用域,但是有函数作用域

windows对象

在我们的浏览器中,浏览器为我们提供了一个window对象,可以直接访问

  • window对象代表我们的浏览器的窗口,通过对象可以对浏览器的窗口进行各种操作
    • 除此之外window对象还负责存储JS中内置对象和浏览器的宿主对象(浏览器作为编译器提供的对象)
  • window对象的属性可以通过window对象访问,也可以直接访问
  • 向window对象中添加的变量,会自动变成全局变量
alert('哈哈') window.console.log('哈哈') 
  • 在全局中使用var声明的变量,会自动变成window对象的属性保存
  • 使用function声明的函数,都会作为window对象的方法保存
  • 使用 let 声明的变量不会存在window对象中,而是其它地方
    • 在我们访问变量的时候,会先访问 let 声明的变量,没有再去找window的属性
    • let会存在我们的Script这个对象上,它与Globe(Window的“代理”对象)同级

注意

  1. 我们在局部作用域中,既没有使用 var 来声明变量,也没有使用 let 声明变量,它会自动变成window对象的属性

提升

变量的提升:使用 var 声明的变量,它会在我们所有代码执行前被声明(没有啥子意义)

函数的提升:使用函数声明来创建函数的时候,会在其它的代码执行前被创建,所以我们可以在函数声明前调用函数(也就是函数的创建会被提升到最前面,但是你使用变量形式来声明函数,就不能在函数声明之前使用函数)

let 声明的变量也会被提升,但是在赋值之前,浏览器禁止我们访问

// 这里会输出 undefined 因为只是被声明,而赋值则是在被执行的时候才会赋值 console.log(a)  var a; a = 10; 
// 这里函数会提升 fn() function fn() {     alert("我是function") }  // 这里就不行 // 因为这里使用 var 定义只会提升fn2变量的声明,但是他没有赋值 fn2() var fn2 = function() {     console.log("我是function2") }  // let 声明的变量也会被提升,但是在赋值之前,浏览器禁止我们访问  fn3() let fn3 = function() {     console.log("我是function3") } 

立即执行函数

我们应该减少在全局作用域中编写代码,我们代码要尽量编写到局部作用域中,这样代码就不会相互干扰

匿名立即执行函数创建(IIFE)

  1. 立即执行函数只会调用一次,它时一个匿名函数

  2. 我们可以利用这个IIFE来解决变量冲突问题

  3. 由于JS解析器解析的时候,我们如果有多个立即执行函数,会解析成 (XXX)(XXX)

    1. 这样会报错,因为解析器解析后面加 () 会默认为函数,但是,实际不是函数
    2. 解决这个问题我们需要在两个立即执行函数末尾加上分号;
(function() {      var a = 10;     console.log(a) })(); (function() {     var a = 11;     console.log(a) })(); 

this

函数在执行时,JS解析器每次都会传递一个隐含的参数,这个参数就是this,this会指向一个对象

this指向的对象会根据函数调用的方式不同而不同

  1. 函数形式调用时,this指向的是window
function fn() { 	// 这里this ==> window 	console.log(this) } 
  1. 当我们以方法的形式调用的时候,this指向的是调用方法的对象本身
function fn() { 	// 这里this ==> window 	console.log(this) }  const obj = { name:'jack' } obj.test = fn; // 这里this ==> obj console.log(obj.fn()) 
  1. 箭头函数,this是由外层来决定,外层的 this 是什么就是什么
    1. 它的this与调用方式无关
function fn() {     // window     console.log('fn --->',this) }  const fn2 = () => {     // window     consloe.log('fn2 --->',this) }  const obj = {     name:'jack',     // 这里属性值和属性名一样可省略属性名     fn:fn,     fn2:fn2,     sayHello() { 		console.log('syHello -->',this)                         function t() {             console.log("t-->",this)         }         /*         	这里是以函数的形式调用,所以它的 this 是 window         */         t()                  const t2 = () => {             console.log("t2-->",this)         }          /*         	这里是以箭头函数来调用,它的this由外层来决定,外层的this是obj         	所以this就是obj         */         t2()     } }  // obj obj.fn() // window obj.fn2() 

高阶函数

根据OCP原则,我们对修改关闭,对扩展开放,有些地方我们需要扩展,我们可以往我们一直使用的函数的参数中传递一个函数,通常回调函数都是匿名函数,而我们将一个函数的参数或返回值时函数,则称为这个函数为高阶函数

将函数作为参数,意味着,我们可以往函数里面动态的传递代码

function filter(arr,fn) {     for(let i = 0; i < arr.length; i++) {         if(fn()) {             return arr[i];         }     } }  // 使用高阶函数 const arr = [1,2,5,3,4,3,5]; const arr1 = [{name:'孙悟空',name:'白菜'}]  // 这样就可以动态的改变条件 filter(arr,(a)=>{ return a>5 }) filter(arr,a => a.name === '孙悟空') 
/* 	希望在使用someFn()函数的时候,可以记录一条日志 	在不修改原函数的基础上,为其增加记录日志的功能 	 	可以通过告诫函数,来动态的生成一个函数 */ function someFn() {     return 'hello'; }  function outer(cb) {     return () => {         // 这里不仅仅可以写这个东西         console.log('someFn执行')         return cb()     } }  let result = outer(someFn) console.log(result()) 

闭包

我们现在希望创建一个函数,第一次调用时,打印1,第二次调用,打印2

我们要完成上面这种操作,就需要定义全局变量,但是我们定义全局变量,当我们协同工作时,变量很可能被别人修改,风险是比较大的

而为了解决上面这种变量不安全的问题,我们就需要将变量放在一个局部作用域中,而将变量放在函数作用域这种局部作用域就称之为闭包

闭包:

​ 闭包就是能访问外部函数作用域中变量的函数

什么时候使用:

​ 当我们需要隐藏一些不希望被别人访问的内容,就可以使用闭包

// 我们可以将不希望别人访问的变量放在这样的函数中 function outer() {     let num = 0          return () => {         num++         console.log(num)     } }  const newFn = outer() newFn() 

闭包的要求:

  1. 函数的嵌套
  2. 内部函数要引用外部函数中的变量
  3. 内部函数作为返回值返回

闭包的原理

我们函数的外层作用域,在函数创建的时候就已经确定了(语法作用域),与调用的位置无关

闭包利用的就是 词法作用域

闭包的注意事项

注意

  1. 闭包主要用来隐藏一些希望不被外部访问的变量,这就意味着闭包要占用一些内存空间

  2. 相较于类来说,闭包比较浪费空间(类可以使用原型,而闭包不可以使用原型)

  3. 需要执行比较少的时候用闭包,需要执行次数比较多的时候使用类

  4. 闭包的生命周期:闭包在外部函数调用时产生,外部函数每次调用就会产生一个新的闭包

  5. 闭包在内部函数丢失时销毁(内部函数被CG回收了,闭包就会消失)

function outer() {     let someValue = 'someValue'      return function() {         console.log(someValue)     } } 
 	function outer() {         let someValue = 1         return function () {           console.log(someValue++)         }       }       let result = outer()       // 这里每次调用都要用一块新空间来给内部的匿名函数使用       result()       result() 

可变参数

arguments

除了this以外,我们函数中还隐藏了一个参数arguments,arguments时一个类数组对象

  • arguments用来存储我们的实参,无论我们是否定义了形参
  • arguments可以通过索引来获取元素,也可以通过for循环遍历,但是它不是数组对象,不能用数组的方法

通过arguments 我们可以不受参数的数量限制,但是它也缺点

  1. 它不知道参数的数量
  2. 它不能调用数组的方法
function fn() {     let result = 0     for(const arg of arguments) {         result += arg     }     return result } 

可变参数

特点;

  1. 可变参数可以接收任意数量的参数,并且把它存到一个数组中
  2. 可变参数的名字我们可以自己指定
  3. 可变参数可以使用数组的方法
  4. 可变参数可以配合其它的参数一起使用
  5. 可变参数一定要定义到形参列表的最后
function fn(...args) {     return args.reduce((a,b) => a+b,0) } 

call 和 apply

根据我们函数调用的不同,我们的this也不同

  • call()和apply()的第一个参数可以指定函数的this
  • bind()可以为我们的新函数绑定this,绑定之后无法修改

函数的调用,除了使用函数名()调用,还可以使用call和apply方法
call和apply除了可以调用函数,还可以指定函数中的this

注意

  1. call()和apply()的第一个参数可以指定函数的this
  2. 通过call()第一参数之后参数会作为函数的参数传递过去
  3. 通过apply()要向函数传递参数,需要传递数组
let obj = {name:'孙悟空'} 函数.call(obj,函数的参数1,函数的参数2……) // this ---> obj 函数.apply(函数的this,[函数的参数1,函数的参数2……]) // this ---> window 

bind()

bind()是函数的方法,用来创建一个新的函数

作用

  1. bind()可以为我们的新函数绑定this
  2. bind()可以我们的新函数绑定参数(也就是将我们的新函数的参数固定不变)
function fn() {     console.log('fn执行了') }  let obj = {name:'孙悟空'} // 函数的前三个参数固定为 10 20 5 const newFn = fn.bind(obj,10,20,5) // this ---> obj newFn()