JavaScript学习笔记(五)-- 函数

155 阅读15分钟

函数

  • 为什么要学东西?
  • 假设我们要输出一个故事 函数
  • 为什么要学东西?
  • 假设我们要输出一个故事
console.log("从前有座山,山里有座庙");
console.log("庙里有个在给小和尚讲故事");
console.log("讲的是什么呢?");
console.log("老和尚对小和尚说:");

  • 如果我想随时随地的给别人讲,代码就会很多;不友好;
// 1次
console.log("从前有座山,山里有座庙");
console.log("庙里有个在给小和尚讲故事");
console.log("讲的是什么呢?");
console.log("老和尚对小和尚说:");
​
​
// 2次
console.log("从前有座山,山里有座庙");
​
// ......

介绍

  • 函数:我们把一段相对独立的具有特定功能的代码块封装起来,形成一个独立实体,起个名字(函数名),在后续开发中可以随时反复调用。
  • 作用:封装(包起来)一段代码,将来可以随时拿来使用。 ​ 语法
console.log("从前有座山,山里有座庙");
console.log("庙里有个在给小和尚讲故事");
console.log("讲的是什么呢?");
console.log("老和尚对小和尚说:");
  • 如果我想随时随地的给别人讲,代码就会很多;不友好;
// 1次
console.log("从前有座山,山里有座庙");
console.log("庙里有个在给小和尚讲故事");
console.log("讲的是什么呢?");
console.log("老和尚对小和尚说:");


// 2次
console.log("从前有座山,山里有座庙");

// ......

介绍

  • 函数:我们把一段相对独立的具有特定功能的代码块封装起来,形成一个独立实体,起个名字(函数名),在后续开发中可以随时反复调用。
  • 作用:封装(包起来)一段代码,将来可以随时拿来使用。

语法

var a;
// function 关键字 用于声明函数
// tellStroy 函数名
function tellStroy() {
  // 里面叫函数体:我们封装,我们想随时随地拿来使用的东西;
  console.log("从前有座山,山里有座庙");
  console.log("庙里有个在给小和尚讲故事");
  console.log("讲的是什么呢?");
  console.log("老和尚对小和尚说:");
}
  • 调用:声明的函数,一段代码被包起来;需要被调用,才能执行当前的函数;
tellStroy(); // 此时在控制台中就会输出一个故事

// 如果想输出多次,就可以调用多次这个函数
tellStroy(); 
tellStroy(); 
tellStroy(); 
  • 起名字重名,会覆盖;和变量一样;

注意: 定义完一个函数以后,如果没有函数调用,那么写在 {} 里面的代码没有意义,只有调用以后才会执行

声明式

  • 使用 function 这个关键字来声明一个函数
  • 语法:
function fn() {
	// 一段代码 
}
/*
function: 声明函数的关键字,表示接下来是一个函数了
fn: 函数的名字,我们自己定义的(遵循变量名的命名规则和命名规范)
 (): 必须写,是用来放参数的位置(一会我们再聊)
 {}: 就是我们用来放一段代码的位置(也就是我们刚才说的 “盒子”)
 */

赋值式

  • 其实就是和我们使用 var 关键字是一个道理了
  • 首先使用 var 定义一个变量,把一个函数当作值直接赋值给这个变量就可以了
  • 语法:
var fn = function() {
	// 一段代码 
}
// 不需要在 function 后面书写函数的名字了,因为在前面已经有了

调用上的区别

  • 虽然两种定义方式的调用都是一样的,但是还是有一些区别的
  • 声明式函数: 调用可以在 定义之前或者定义之后
// 可以调用
fn()
// 声明式函数
function fn() {
	console.log('我是 fn 函数')
}
// 可以调用 f
n()

赋值式函数: 调用只能在 定义之后


// 会报错 
fn()
// 赋值式函数
var fn = function() {
	console.log('我是 fn 函数')
}
// 可以调用
fn()

参数

配置参数

  • 参数:对于函数来说,函数内部的变量;
  • 作用:为什么?给别人讲故事,不同的情况套用不同的人名;把函数封装的代码,变活了;
  • 定义参数:位置,小括号()上自定的参数;
  • 特点:只能在内部使用,和外部没有关系;
  • 使用:传入值,给了形参;
  • 语法:

// 在小括号内的变量,对于函数来说,就是参数;
// 参数就是函数 内部的变量;
function 函数名(参数){
  // 函数体
}
// 参数:对于函数,这些参数都是形式上的参数,它就是代替位置,相当于是变量在这占了个坑;至于这个参数真实背后代表什么值,我们现在还不知道;
function tellStroy(name){
  console.log("从前有座山,山里有座庙");
  console.log("庙里有个老和尚在给小和尚讲故事");
  console.log("讲的是什么呢?");
  console.log("老和尚对"+ name +"说:");
}
  • 既然是变量,可以被改变存储值;如何改变?调用时传递一个值;
// 调用函数的时候,传入参数;
tellStroy('清风');
tellStroy('明月');

  • 需求:如果我们想在调用函数的时候,把老和尚的名字也明确一下,就把老和尚的名字作为变量改变下;
// 声明函数,配置参数;
function tellStroy(name1,name2){
  console.log("从前有座山,山里有座庙");
  console.log("庙里有个老和尚在给小和尚讲故事");
  console.log("讲的是什么呢?");
  console.log(name1 "对"+ name2 +"说:");
}

// 调用函数
tellStroy('圆通','清风');

参数不赋值

  • 变量:函数内部的变量,没有赋值,默认为undefined;和我们的变量一模一样;
function tellStroy(name){
  console.log("从前有座山,山里有座庙");
  console.log("庙里有个老和尚在给小和尚讲故事");
  console.log("讲的是什么呢?");
  console.log("老和尚对"+ name +"说:");   // 老和尚对undefined说;
}

  • 解决:对参数进行判断,如果是undefined,给一个默认值;
function tellStroy(name){
    
  // if 条件语句
  if(name==undefined) {
      name = '小和尚'
  }
  else {
      name = name;
  }
  
  // 三元表达式;
  name = name?name:"小和尚"console.log("从前有座山,山里有座庙");
  console.log("庙里有个老和尚在给小和尚讲故事");
  console.log("讲的是什么呢?");
  console.log("老和尚对"+ name +"说:");
}

形参与实参

  • 形参:外面不能使用;和外面没有任何关系;只能函数内部使用;
  • 实参:调用函数时,真实参与运算的数据,外部调用函数的时候,传入真实数据,实参;把真实数据赋值了一份给形参;
  • 相互不影响:传入简单值类型,互不影响;
  • 核心点:函数里面和函数外面,没有关系;
 // 特点(规则):形参与实参相互不影响;
  // 形参:外面不能使用;和外面没有任何关系;只能函数内部使用;
  // 实参:把实参的数据,赋值(复制)了一份给形参;

  function fn(a) {
    a = a + 10;
  }

  var a = 10;
  fn(a);
  // a = a;
  // 前面a:形参,只能在函数内部使用,和外面没有关系;
  // 后面a:实参,外面的实参把自己的值,赋值(复制)了给里面的形参a;
  • 什么时候配置形参:形参,内部的变量,只能在内部使用;既然只能在内部使用,和外面没有关系,那么什么时候需要配置形参?如果外面的数据需要给内部加工下,那就配置形参;实参传入真实数据;
  • 里面加工,和外面没有关系;如何把里面的加工给到外面呢?返回值值;

如果只有行参的话,那么在函数内部使用的值个变量是没有值的,也就是 undefined

  • 行参的值是在函数调用的时候由实参决定的

参数个数的关系

  1. 行参比实参少
  • 因为是按照顺序一一对应的
  • 行参少就会拿不到实参给的值,所以在函数内部就没有办法用到这个值
function fn(num1, num2) {
	// 函数内部可以使用 num1 和 num2 
}
// 本次调用的时候,传递了两个实参,100 200 和 300 
// 100 对应了 num1,200 对应了 num2,300 没有对应的变量
// 所以在函数内部就没有办法依靠变量来使用 300 这个值 
fn(100, 200, 300)
  1. 行参比实参多
  • 因为是按照顺序一一对应的
  • 所以多出来的行参就是没有值的,就是 undefined
function fn(num1, num2, num3) {
	// 函数内部可以使用 num1 num2 和 num3
}
// 本次调用的时候,传递了两个实参,100 和 200 
// 就分别对应了 num1 和 num2 
// 而 num3 没有实参和其对应,那么 num3 的值就是 undefined 
fn(100, 200)

返回值

  • 作用:函数内部运算出来的一切和外面没有任何;如果外面想用内部运算完的结果,设置返回值;
  • 语法:
 function fn(a) {
    a = a + 10;
    // 内部:只能在函数内部使用,把变量后面真实数据返回出去;
    // 关键字:return;
    return a;
  }

  // 外面:需要找个变量接受
  var b = fn(10);
  console.log(b);
  

**特点规则: **

// 特点(规则):
//  1. 作用:函数返回值,把内部的值进行返回,返回到外面;
//  2. 只要函数内部出现了return,函数内return下面的代码(函数内)不再执行;
//  3. 如果return 后面没有任何数据(变量),默认返回undefined;
//  4. 如果连return都没有,函数就没有返回值,执行函数,默认返回undefined;


// 规则2:
function fn(a) {
  a = a + 10;
  // 内部:只能在函数内部使用,把变量后面真实数据返回出去;
  // 关键字:return;

  return a;
  // 函数内部 return下面的代码不再执行;
  console.log("--------------------------------------------");
}


// 规则3:
function fn() {
  return;
}
var a = fn();
console.log(a);

// 规则4:
function fn() {
  // return;
}
var a = fn();
console.log(a);


属性之间用逗号

函数内部用分号


	var person2 = {
    "name":"lz", 
    "say" : function(){
           alert('hello word!');
   	 }
  }
  person2.say();


封装函数


   <script>
  	window.Node2 = function(nodeOrSelector) {
  		let node
  		if (typeof nodeOrSelector === 'string') {
  			node = document.querySelector(nodeOrSelector)
  		}else {
  			node = nodeOrSelector
  		}

  		return {
  			getSiblings: function(){
  				var allChildren = node.parentNode.children
  				var array = {
  					length: 0
  				};
  				for(let i = 0; i < allChildren.length;i++) {
  					if (allChildren[i] === node) {
  						array[array.length] = allChildren[i]
  						array.length += 1
  					}
  				}
  				return array
  			},

  			addClass:function(classes){
  				console.log(classes)
  				classes.forEach((value) => node.classList.add(value))
  			}
  			
  		}


  	}

  var node2 = Node2('ul > li:nth-child(2)')
  node2.getSiblings()

  node2.addClass(['red','b','c'])
  </script>


小结

  • 函数:把一些复用代码 封装 起来,在未来 调用;
  • 需求:函数里面的功能不能写成固定代码;变活;
  • 参数:
    • 形参:函数内部的变量,只能在函数内部玩耍;函数的外面不能使用;即使外面的变量和内部的变量同名,也没有任何关系;
    • 实参:实际参与运算的数据,把实参的数据,赋值(复制)了一份给形参;
  • 返回:如何函数里面加工完结果,外面需要,设置返回值 return 数据;
  • 核心:函数把代码分成 里面 和 外面;里面和外面没有任何关系;
    • 两座桥:
      • 外面的数据 怎么 进入函数内部?参数;
      • 函数内部的数据怎么 给到函数的外面?返回值

案例:求1-n之间所有数的和

  • 步骤:

  • 1.把实际过程写出来。1-10;

  • 2.试着封装函数:

  • 3.是否配置参数?是否设置返回值?

function getSum(m) {
    var sum = 0;
    for (var i = 1; i <= m; i++) {
      sum += i;
    }
    return sum;
  }
  • 函数的说明:(了解,抒写规范)
// 经验:以后大家会经常看别人写的函数,用法;
/**
 * 函数的作用 - 求n-m之间的整数和
 * @param {type:number} n 较小值
 * @param {type:number} m 较大值
 * @returns {type:number} 整数和
 */
 

arguments

  • 解决:多个参数的问题;
  • 目标:无论输入多少个参数,都可以参加运算;
  • 语法:arguments,获取所有实参的伪数组,函数内部的变量(不是我们声明的,也不需要我们声明)
function fn(){
  console.log(arguments);
}
fn(1); // 输出 [1]
fn(1,2) // 输出 [1,2]
fn(1,2,3,4,5) // 输出 [1,2,3,4,5]
  • arguments 这个东西看起来样子像数组,但是其实不是一个数组,我们管它叫 伪数组。它具有数组的长度和顺序等特征。本质为对象,
  • arguments 伪数组可以循环遍历;
function getSum(){
  var sum = 0;
  for(var i = 0; i < arguments.length ; i++){
    sum += arguments[i];
  }
  return sum;
}

getSum(1,2,3);// 输出 6
getSum(1,2,3,4,5); // 输出15
  • 应用场景:当我们不知道我们的参数个数的时候;

匿名函数

  • 匿名函数:没有名字的函数,但是在js的语法中,是不允许匿名函数单独存在的,要配合其它语法使用: 如:
      function (参数){
    函数体
      }
    
      var fn = function(a,b){
       return a + b;
      }
     
    
  • 自调用函数(自执行函数):匿名函数的另外一种使用方法;很多时候,我们需要加载页面后,自动执行一个函数;
      // 定义之后,立刻调用,输出10
      (function(){  
       console.log(10);  
      })();
    

函数类型

  function fn(){}
  console.log(typeof fn);  // 输出字符串的function
  • 在js中,只要是一种数据类型的,都可以作为函数的参数,
function f1(a,b){
  return a + b;
}
f1(10,20); // 数字作为参数
f1('abc','def') // 字符串作为参数

回调函数

  • 函数有数据类型,也可以作为别的函数的参数传入;
// fn 只不过在函数内部是一个形参,内部变量;
function f1(a,fn){
  console.log(a);
  // 函数的调用,在函数名的后面加括号;
  // 内部的函数对外面的函数叫回调函数;
  fn(); 
}

function f2(){
  console.log('f2函数执行了');
}
f1(10,f2);// 输出 10 和 'f2函数执行了'
  • 像这种作为函数的参数,并在之内调用的函数,我们称为 回调函数;

作用域

  • 作用域:作用范围,能生效的范围;

  • 为什么要学作用域?

    • 函数:里面和外面;
    • 目前,我们要分清楚自己的声明的变量在哪个作用域下,也就是生效的范围是多大;配合下面预解析的知识,经常是面试比较常问的基础题;
  • 全局:

    • 全局作用域规则:全局的变量,能在JS部分的任何位置都可以访问;
    • 全局变量:在全局作用哉下声明的变量;
  • 局部:

    • 局部作用域:只能在局部的作用域范围进行访问;
    • 局部变量:在局部作用域下声明的变量;
      var a = 10;
      function f1(){
    	console.log(a);
      }
      f1();// 变量a在函数外定义,可以在函数内使用
      function f2(){
       var b = 20;
      }
      // 变量b在函数内定义,在函数外无法访问,报错: b is not defined
      console.log(b); 
    

预解析

  • 原因:JS代码 不是一下在浏览器内执行,需要浏览器对JS代码进行预解析:
    • 理解:浏览器里面有一个人,先把JS代码,一行一行读;
    • 做一件事:预解析;
    • 在内存上执行;
  • 预解析(规则):发现一个新的作用域,声明的: var 变量、function 函数(){},全部提升到当前作用域的最顶端;
    • 当前作用域:全局和局部;
    • 提升:var 变量 function 函数(){};
  • 预解析过程(了解):从概念的字面意义上说,“变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,但这么说并不准确。实际上变量和函数声明在代码里的位置是不会动的,而是在编译阶段被放入内存中。
    • 变量:已经声明;函数:已经声明;
    • 而变量的赋值和函数的调用还在原来的位置;
  • 如何使用:找到当前作用域的顶端,提升上去
fn();// 正常执行
function f1(){
  console.log(1);
}
fn(); // 正常执行

f2();// 报错 : f2 is not a function
var f2 = function(){
  console.log(2);
}
// function 关键字定义的函数,可以在定义之前使用,函数表达式的不行

  • 上面的代码预解析后:
function f1(){
  console.log(1);
}
var f2;
fn();
fn();
f2();
f2 = function(){
  console.log(2);
}

  • 所以在调用f1的时候,其实函数已经声明好了,但是在调用f2的时候,f2还是undefined,就会报错
  • 面试基础题:
// 观察下面的代码,说出执行结果
var num = 10;
fun();
console.log(num);


function fun() {
  console.log(num);
  var num = 20;
}

// ------------------------------------------------------------变量提升的演示
// 预解析:先把你声明变量、函数先全部提升到你当前的作用域的最顶端;
var num;

function fun() {
    var num;
    console.log(num);
    num = 20;
}

// 赋值;
num = 10;
// 函数调用;
fun(); // 输出 undefined;


格式化日期的封装

function getFormateDate() {
  var date = new Date();
  var year = date.getFullYear();
    
  var month = date.getMonth() + 1;
  month = patchFrontZero(month);
    
  var day = date.getDate();
  day = patchFrontZero(day);
    
  var hour = date.getHours();
  hour = patchFrontZero(hour);
    
  var minutes = date.getMinutes();
  minutes = patchFrontZero(minutes);
    
  var seconds = date.getSeconds();
  seconds = patchFrontZero(seconds);
    
  var format = year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds;
  return format;
}

简单类型和复杂类型

  • 规则

    • JS 数据都是存在内存上的,内存上分为两个地方: 栈 堆;只要var 变量,就要在 栈上开个格子;
    • 简单类型 存储在内存的栈空间中
    • 复杂类型 存储在内存的堆空间中

基本数据类型在内存中的存储情况

  • var num = 100,在内存中的存储情况 栈

  • 直接在 栈空间 内有存储一个数据

复杂数据类型在内存中的存储情况

  • 下面这个 对象 的存储
     			var obj = {
     			name: 'Jack',
     			age: 18,
     			gender: '男'
       		 }	
    
    

堆

复杂数据类型的存储

- 在堆里面开辟一个存储空间
- 把数据存储到存储空间内
- 把存储空间的地址赋值给栈里面的变量

-这就是数据类型之间存储的区别

数据类型之间的比较

  • 基本数据类型是 值 之间的比较

    	var num = 1
    	var str = '1'
    	console.log(num == str) // true
    
  • 复杂数据类型是 地址 之间的比较

    	var obj = { name: 'Jack' }
    	var obj2 = { name: 'Jack' }
    	console.log(obj == obj2) // false   
    
  • 因为我们创建了两个对象,那么就会在 堆空间 里面开辟两个存储空间存储数据(两个地址)

  • 虽然存储的内容是一样的,那么也是两个存储空间,两个地址

  • 复杂数据类型之间就是地址的比较,所以 obj 和 obj2 两个变量的地址不一样

  • 所以我们得到的就是 false