您好,欢迎访问代理记账网站
  • 价格透明
  • 信息保密
  • 进度掌控
  • 售后无忧

JavaScript学习:函数

问题导向

函数的基本使用?

如果你都有了答案,可以忽略本文章,或去JS学习地图寻找更多答案


function函数

在JS中,函数是一种特殊的数据类型,它让JS动了起来,有了能力,它可以是一个方法,或者是一个功能,提供某种能力
它常用来 封装 一些方法,供应用重复调用

函数特点

  • 具有特定功能/作用的
  • 能够重复调用的
  • 闭合状态的代码块
  • 能够兼容代码变化的

参数

形参:函数声明时填的参数
实参:函数调用时填的参数

例子:使用function关键字声明一个函数

function sayHi(name, age){   //声明()里的是形参
    console.log(`你好,我是${name},我今年${age}岁了`)
}
sayHi('小李', 18)  //调用()里的是实参

封装

两个概念:公共与私有
抽离公共逻辑,传递私有参数
怎么封装:取决于想怎么调用

如上面的例子:两个人都有打招呼的方法,打招呼的语句是共有的,名字和年龄是私有的

function sayHi(name,age){
    console.log(`你好,我是${name},我今年${age}岁了`)
}
sayHi('小李', 18)  你好,我是小李,我今年18岁了
sayHi('小陈', 19)  你好,我是小陈,我今年19岁了

函数深入

函数的几种写法

函数声明
function fn(x, y){
   console.log(x + y)
}
fn(5,3) //8函数表达式(表达式就是 什么 = 什么)
let fn = function(x, y){
   console.log(x + y)
}
fn(4,3) //7


箭头函数:ES6写法
const fn = () => { console.log('hello js') }
fn()
​

匿名函数
window.onload = function(){
   console.log('匿名函数')
}立即执行函数,自调用
(function(x, y){
   console.log(x + y)
})(6,7)

函数的书写结构

有参数,有返回值
注意:没传实参是undefined,少写实参是NaN
因为undefined + 数字 = NaN(not a number)

function fn(a, b){
   return a + b
}
const result = fn(2,5)  //将结果返回,赋值给result
console.log(result) //7


有参数,无返回值
function fn(a, b){
   console.log(a + b)
}
fn(3, 4) //7

​
无参数,有返回值
注意:实参无效,能得到结果
function fn(){
   console.log(1 + 2)
}
fn(3,4) //3

return返回值

将值或函数返回给函数,单独成行,后面的代码不再执行

直接返回,fn存了a + b的结果,调用fn就可以得到它
function fn(a,b){
   return a + b
   console.log('这句不再执行')
}
console.log(fn(2,5)) //7
​

返回变量
function fn(a, b) {
    let num = a + b
    return num
}
console.log(fn(2, 5))
​

返回函数
function fn(a, b){
   return function(){
       return a + b
  }
}
let result = fn(2,3)  //fn现在存的是一个return的匿名函数,需要再次调用result才能得到结果
console.log(result()) //5

一等公民

函数不仅可以声明与调用,还可以像简单值一样,赋值,传参,返回

1.函数可以像普通值一样,作为属性的值
var obj = {
    'eat': function () {
        console.log('吃东西')
    }
};
obj.eat() //吃东西



2.函数可以像普通值一样,赋值给变量
var fn = function () {
    console.log('我是一个函数')
};
fn() //我是一个函数



3.函数可以像普通值一样,作为参数进行传递,其实这就是回调函数
(函数调用时,把其他函数作为参数传进去调用)
例子1:传入匿名函数
function f1(fn){
    fn()
}
f1(function(){
    console.log('传入匿名函数')
})

例子2:传入命名函数
function test() {
    console.log('函数作为参数')
}
function method(test) {
    test() //调用test函数
}
method(test) //传入命名函数,不需加()


4.函数可以像普通值一样,被返回
function test() {
    var a = 666;
    function test1() {
        return a++
    }
    return test1
}
var result = test();
console.log(result); //test1函数
console.log(result()); //666
console.log(result()); //667

回调函数

函数作为参数使用

如果需要获取一个函数中的异步操作结果,就必须使用回调函数来获取

function fn(callback){

	模拟异步
    setTimeout(()=>{ 
        let data = 'hello'
        callback(data)
    },1000)
}


传入回调函数
fn((value)=>{
    console.log(value) //'hello'
})

同名参数

1,当函数出现同名的参数时,是保留最后一个行参的值。
function fn(a,a){
    console.log(a); //20
    return a + a
}
console.log(fn(10,20)) //40

2,当实参的个数小于行参的个数,则最后一个形参的值是undefined
function fn(a,b){
    console.log(a);//10
    console.log(b);//undefined
    return a + b
}
console.log(fn(10)) //10 + undefined = NaN 

函数作用域

作用域:代码有效范围

在函数中,没有块级作用域,只有函数作用域,变量声明在函数的内部,只在{}内生效

全局作用域
if(true){
    var num2 = 20
}
console.log(num2) //20


函数作用域
function fn(){
    var num = 30
}
console.log(num) //num is not defined

函数作用域链

优先在本层中查找,本层没有则向上一层查找,直到全局作用域

如果本层有var变量,则变量提升

var x = 100;
function test() {
    console.log(x);   //预解析,结果undefined
    
    var x = 10;
    console.log(x);   //10
    
    function show() {
        var x = 66;   //如果这个66不存在,下面打印的就是10
        console.log(x)//66
    }
    show()
}
test();

自由变量

在函数中,变量的查找,是在函数声明的地方,向上级作用域查找,不是执行的地方

函数作为返回值
function fn(){
    let a = 100
    return function(){
        console.log(a)
    }
}
let foo = fn()
let a = 200
foo() //100,a重新赋值无效,因为调用的是函数内部中的a


函数作为参数
function print(func) {
    let a = 200
    func()
}
let a = 100
function fn() {
    console.log(a);
}
print(fn) //100,调用的是变量声明地方的a

闭包

将函数作用域内的变量暴露到函数外部,当函数作用域被延长时,闭包就产生了
闭包的模式:函数模式的闭包,对象模式的闭包
闭包优缺点:优点是缓存数据,缺点是没有及时释放内存

闭包作用

缓存数据,延长作用域链,在函数外部读取函数中的变量,让局部变量生存在内存当中,避免被垃圾回收机制杀死。

闭包原理

函数的每次调用,都会开辟一个空间,在函数执行完毕后,就销毁空间,函数里面的变量没有被再次使用,也因此被销毁了,如果环境(空间)里的变量被使用,就不会被删除,只有延长作用域,环境里的变量才能被外部使用。

例子

函数模式的闭包
function fn() {
    var num = 10
    function f2() {
        console.log(num)
    }
    f2()
}
fn()//10


对象模式的闭包:函数中有一个对象,对象使用了函数中的值
function f1(){
    var num = 10
    var obj = {
        age:num
    }
    console.log(obj.age)
}
f1()//10

function f2(){
    var num = 20
    return {
        age:num
    }
}
let obj = f2()
console.log(obj.age) //20



数据被缓存的列子
function f3(){
    var num = 10
    return function(){
        num++
        return num
    }
}
let fn = f3() //返回的函数
console.log(fn()) //11
console.log(fn()) //12
console.log(fn()) //13
总结:如果想要缓存数据,就把这个数据放在外层函数和里层函数的中间,然后把里层函数返回,在外部执行


小例子:产生相同的随机数
function fn() {
    var num = parseInt(Math.random() * 10 + 1)
    return function () {
        console.log(num)
    }
}
let ff = fn()
ff()
ff()
ff()

this指向

this:属性和方法所属,归谁管

1.全局执行上下文中的this:指向window
2.普通函数的this(谁调用就指向谁,一般情况指向window/document)
3.严格模式的thisundefined
4.定时器方法中的this指向window
5.闭包中的this指向window

6.构造函数中的this指向实例对象
7.原型方法中的this指向实例对象

8.普通对象.方法中的this指向当前对象
9.嵌套函数中的this指向window
10.箭头函数的this指向当前函数所在的作用域
普通函数:谁调用就指向谁
function fn(){
    console.log(this)
}
fn() //window
window.fn()  //实际是这样


严格模式的this
"use strict" 
function fn(){
    console.log(this)
}
fn() //undefined
window.fn() //window


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

document.onclick = function(){
   console.log(this)//指向document
}


箭头函数:指向其定义/声明时所在的作用域
document.onclick = () =>{
  console.log(this) //指向window
}
document.onclick = function(){
    let fn = () => {
        console.log(this)
    }
    fn() //指向document,因为外面的function的作用域是document,所以指向document
}

window.onclick = function(){
    let fn1 = () => {
        console.log(this)
    }
    fn1() //指向window 因为外面的function的作用域是window,所以指向window
}



定时器方法中的this指向window
setInterval(function(){
   console.log(this) //window
},1000)


构造函数中的this指向实例对象
原型方法中的this指向实例对象
function Person(){
    console.log(this) //和per是一样的
    this.sayHi = function(){
        console.log(this)
    }
}
Person.prototype.eat = function(){
    console.log(this)
}

let per = new Person()
console.log(per) //两者一样

per.sayHi() //实例对象
per.eat() //实例对象



对象.方法中的this指向实例对象
let obj = {
    name: 'litao',
    say: function () {
        console.log(this.name)
        function xxx() {
            console.log(this)  //window
        }
        xxx()
    },
}
obj.say()


嵌套函数中的this指向window
//解决方法:
1.把forEach的回调变成箭头函数
2.在forEach的回调后,加上第二个参数this
3.let self = this,把console中的this变成self
4.使用call,apply, bind改变指向

const video = {
    title:'a',
    tags:['a','b','c'],
    
    问题:this指向window
    showTags() {
        this.tags.forEach(function (tag) {
            console.log(this)   //window
            console.log(this.title, tag)  //undefined,因为这时this是window的
        })
    },
    
    解决方法1:直接使用箭头函数
    showTags() {
        this.tags.forEach((tag) => {
                console.log(this)
                console.log(this.title, tag)
            })
    }
    
    解决方法2:在forEach的回调后,加上第二个参数this
    showTags() {
        this.tags.forEach(function (tag) {
            console.log(this) 
            console.log(this.title, tag)
        }, this)
    },
    
    解决方法3let self = this,把console中的this变成self
    showTags() {
        let self = this
        this.tags.forEach((tag) => {
                console.log(self)
                console.log(self.title, tag)
            })
    }
    
    解决方法4:使用bind改变forEach的this指向
    showTags() {
        this.tags.forEach(
            function (tag) {
                console.log(this)
                console.log(this.title, tag) 
            }.bind(this)
        )
    },  
}
video.showTags()

改变this指向

apply和call方法的作用:改变this的指向,借用别人的能力

apply和call方法属于谁的:自身能力不够,把爷爷/别人的秘法拿出来解决问题

骚后补上

同名参数

1,当函数出现同名的参数时,是保留最后一个行参的值。
function fn(a,a){
    console.log(a); //20
    return a + a
}
console.log(fn(10,20)) //40

2,当实参的个数小于行参的个数,则最后一个形参的值是undefined
function fn(a,b){
    console.log(a);//10
    console.log(b);//undefined
    return a + b
}
console.log(fn(10)) //10 + undefined = NaN 

不定参arguments

arguments对象包含了函数【运行时】的所有参数

不传形参也能拿到实参,前提是调用的时候传了实参

注意:

  • 此对象非常类似于数组,是集合而非数组
  • 箭头函数没有arguments
箭头函数
let fn = () => {
    console.log(arguments)
}
fn(1,2,3)//报错,arguments is not defined

普通函数
let fn = function(){ //没有形参
    console.log(arguments)
}
fn(1,2,3)// arguments数组[1,2,3]

函数成员

function fn(x, y){
    console.log(fn.name)      //函数的名字,在这里是fn,只可读不可改
    console.log(fn.arguments) //函数中实参的个数
    console.log(fn.length)    //函数中形参的个数
    console.log(fn.caller)    //调用者,哪个函数调用了fn函数,没有是null
}
fn() //自动调用自己

递归

在函数中,函数调用自己,这就是递归

递归一定要有结束的条件,考虑2层和考虑返回的时机

执行上下文堆栈,后进先出,递归深度等于堆栈中上下文的最大数量

例子:求N个数字的和

平常做法:1+2+3+4+5
var sum = 0
for (let i = 1; i <= 5; i++) {
    sum += i

console.log(sum) //15


递归实现:5+4+3+2+1
function fn(x){
    if(x === 1){
        return 1
    }
    return x + fn(x - 1)
}
console.log(fn(5)) //15

5进来,x !== 15 + (5 - 1 = 4) + 然后调用自身,把4传进去(4 - 1 = 3)...直到x = 1,返回1,然后逐层相加:5 + 4 + 3 + 2 + 1


简写:
function add(num){
    return num === 1 ? 1 : (num + add(--num))
}
let ret = add(5)  //15

例子:计算公司员工薪资

let company = {
  sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 1600 }],
  development: {
    sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }],
    internals: [{name: 'Jack', salary: 1300}]
  }
};

function sumSalaries(department) {
  if (Array.isArray(department)) { //如果是数组,就计算结果
    return department.reduce((prev, current) => prev + current.salary, 0); // 求数组的和
  } else { //如果是对象,再继续递归
    let sum = 0;
    //subdep代表该对象的每一项
    for (let subdep of Object.values(department)) {
      sum += sumSalaries(subdep); // 递归调用所有子部门,对结果求和
    }
    return sum;
  }
}
alert(sumSalaries(company)); // 7700

沙箱

小环境,黑盒,独立环境,与外部定义的变量不冲突,在一个虚拟的环境中模拟真实世界,做试验,试验结果不会影响真实世界

写法一:
(function(){
    var num = 10
    console.log(num + 10)
})()

写法二:
var = 100
(function(){
    var num = 20
    console.log(num) //20
}())
console.log(num) //100

写法三:
{
    
}

学习更多

JS学习地图


分享:

低价透明

统一报价,无隐形消费

金牌服务

一对一专属顾问7*24小时金牌服务

信息保密

个人信息安全有保障

售后无忧

服务出问题客服经理全程跟进