函数
- 为什么要学东西?
- 假设我们要输出一个故事 函数
- 为什么要学东西?
- 假设我们要输出一个故事
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
- 行参的值是在函数调用的时候由实参决定的
参数个数的关系
- 行参比实参少
- 因为是按照顺序一一对应的
- 行参少就会拿不到实参给的值,所以在函数内部就没有办法用到这个值
function fn(num1, num2) {
// 函数内部可以使用 num1 和 num2
}
// 本次调用的时候,传递了两个实参,100 200 和 300
// 100 对应了 num1,200 对应了 num2,300 没有对应的变量
// 所以在函数内部就没有办法依靠变量来使用 300 这个值
fn(100, 200, 300)
- 行参比实参多
- 因为是按照顺序一一对应的
- 所以多出来的行参就是没有值的,就是 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