自律每一天,进步每一天,幸福每一天
知识导航
- 函数
- 作用域
- 预解析
- js对象
博文脑图大纲:
1. 函数
函数:就是封装了一段可被重复调用执行的代码块。通过此代码块可以实现大量代码的重复使用。
1.1 函数声明
1.利用函数关键字 function 自定义函数方式(命名函数)
// 声明定义方式
function fn() {...}
// 调用
fn();
2.函数表达式方式(匿名函数)
//声明方式
var fn = function(){...};
// 调用的方式,函数调用必须写到函数体下面
fn();
值得注意的:
- 此函数是没有名字的,fn是变量的名字,只不过是变量里面放的是个函数而已。因为没有名字所以被叫做匿名函数。
- 函数调用的代码必须写到函数体后面(js预解析的时候你便知道为什么了)
1.2 函数基本使用
函数的使用包括函数声明和函数调用俩个基本步骤。
基本使用:
// 声明函数
function 函数名() {
//函数体代码
}
// 调用函数
函数名(); // 通过调用函数名加小括号来执行函数体代码
function sayHi() {
console.log("你好啊");
}
sayHi();
注意:
- 调用的时候千万不要忘记添加小括号
- 声明函数本身并不会执行代码,只有调用函数时才会执行函数体代码。
栗子:
使用函数封装一个可以计算1-100的和方法。
function getSum() {
var sum = 0;
for (var i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
console.log(getSum());
函数的封装:是把一个或者多个功能通过函数的方式封装起来,对外只提供一个简单的函数接口。上面的可以计算1-100的和的函数体就可以当做一个功能块
1.3 参数
函数的参数包括形参和实参
-
形参:形式上的参数,函数定义的时候使用的参数。当时并不知道参数的具体值
-
实参:实际上的参数,函数调用时传递的参数,实参是传递个形参的
如下:
// 带参数的函数声明
function 函数名(形参1, 形参2 , 形参3...) { // 可以定义任意多的参数,用逗号分隔
// 函数体
}
// 带参数的函数调用
函数名(实参1, 实参2, 实参3...);
实例:
函数的实参和形参个数不匹配时
实参个数多于形参:
只取到形参的个数
实参个数少于形参:
多出的形参是undefined
arguments的使用
当不确定有多少个参数传递的时候,可以用 arguments 来获取。JavaScript 中,arguments实际上它是当前函数的一个内置对象。所有函数都内置了一个 arguments 对象,arguments 对象中存储了传递的所有实参。arguments 展示形式是一个伪数组,因此可以进行遍历。伪数组具有以下特点:
-
具有 length 属性
-
按索引方式储存数据
-
但是不具有数组的 push , pop 等方法
注意:在函数内部使用该对象,用此对象获取函数调用时传的实参。
可见实参都被存到arguments里面中,按照数组方式进行遍历
function fn() {
for (var i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
fn(1, 2, 3, 4);
1.4 返回值
return 语句
函数的功能是执行我们指定的功能,并返回我们想要的数据。(理论上输出语句是不应该在函数体中见到的)
// 声明函数
function 函数名(){
...
return 需要返回的值;
}
// 调用函数
函数名();
如1.3中我写的栗子便不是很规范
function demo(a, b) {
console.log("我是参数" + a + "和" + b);
}
demo(1);
应该这样写(这种形式上面那样写真的是方便不信你看下面的我这里也只是举个栗子。实际上我也不会使用下面这种方法):
var date = [];
function demo(a, b) {
date = arguments;
return date;
}
var date02 = demo(1, 2);
var str = "";
for (var i = 0; i < date02.length; i++) {
str = str + date02[i] + " ";
}
console.log(str);
值得注意的是:
- 在执行到 return 语句时,函数会停止执行,并返回指定的值。return下面的语句便不再执行了
- 如果函数没有 return ,返回的值是 undefined
break ,continue ,return 的区别:
- break :结束当前的循环体(如 for、while)
- continue :跳出本次循环,继续执行下次循环(如 for、while)
- return :不仅可以退出循环,还能够返回 return 语句中的值
1.5 小栗子
(1) 求任意个数字的最大值
var max;
function getMax() {
max = arguments[0]
for (var i = 1; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i];
}
}
return max;
}
console.log(getMax(11, 23, 2, 4, 66, 8));
(2)翻转数组
// 翻转数组
function reverse(arr) {
var newArr = [];
//方法一:
for (var i = 0; i < arr.length; i++) {
newArr[i] = arr[arr.length - 1 - i];
}
// 方法二
// for (var i = arr.length - 1; i >= 0; i--) {
// newArr[newArr.length] = arr[i];
// }
return newArr;
}
console.log(reverse([1, 2, 3, 4, 5]));
(3)判断闰年
// 判断闰年
// 何为闰年,1可以同时被4和100整除的 2.可以被400整除的
function isYear(year) {
if (year % 4 == 0 && year % 100 == 0 || year % 4000 == 0) {
console.log("是")
} else {
console.log("不是");
}
}
isYear(2000);
isYear(2020);
2. 作用域
通常来说,一段程序代码中所用到的名字(变量名字)并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。
2.1 局部与全局作用域
JavaScript(es6前)中的作用域有两种:
- 全局作用域
作用于所有代码执行的环境(整个 script 标签内部)或者一个独立的 js 文件。 - 局部作用域(函数作用域)
作用于函数内的代码环境
js中是没有块级作用域的:
块作用域由 { } 包括。其他编程语言中(如 java、c#等),在 if 语句、循环语句中创建的变量,仅仅只能在本 if 语句、本循环语句中使用,如下面的Java代码:
if(true){
int num = 123;
system.out.print(num); // 123
}
system.out.print(num); // 这里会报错
而在js(es6之前)中则能正常运行。
if(true){
var num = 123;
console.log(123); //123
}
console.log(123); //123
2.2 变量的作用域
局部变量:
在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)
- 局部变量只能在该函数内部使用
- 在函数内部 var 声明的变量是局部变量
- 函数的形参实际上就是局部变量
全局变量:
在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)。
全局变量和局部变量的区别:
- 全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存
- 局部变量:只能在函数内部使用,当其所在的代码块被执行时,才会被初始化;当代码块运行结束后,就会被销毁,因此更节省内存空间
2.3 作用域链
只要是代码都在一个作用域中,写在函数内部的在局部作用域,未写在任何函数内部即在全局作用域中;如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域;根据在**[内部函数可以访问外部函数变量]**的这种机制,用链式查找决定哪些数据能被内部函数访问,就称作作用域链
作用域链:采取就近原则的方式来查找变量最终取值。
看下面的栗子来查看它的作用
思考以下代码:
var a = 1;
function fn1() {
var a = 2;
var b = '22';
fn2();
function fn2() {
var a = 3;
fn3();
function fn3() {
var a = 4;
console.log(a); //a的值 ?
console.log(b); //b的值 ?
}
}
}
fn1();
最后输出的a,b的值?
分析:
查验结果:
3. 预解析
JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器在运行 JavaScript 代码的时候分为两步:预解析和代码执行。
-
预解析:在当前作用域下, JS 代码执行之前,浏览器会默认把带有 var 和 function 声明的变量在内存中进行提前声明或者定义。 即:预解析会把变量和函数的声明在代码执行之前完成。
-
代码执行: 从上到下执行JS语句
3.1 变量预解析
变量预解析也被叫做变量提升: 变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。
如思考它的结果是什么:
console.log(num); // 结果是多少?
var num = 10; // ?
是10吗,先看一下执行结果吧。
疑惑: 如果按照普通代码js会从上到下逐行执行。num还没有声明,也还没有赋值应该会报错才对。为什么是undefined呢,我们知道一个只声明不赋值的变量的返回结果才是undefined。那这里在背后是怎么个顺序呢。
回答: 上面我们说到变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。则它背后的执行顺序是这样的
var num;
console.log(num); // 结果是多少?
// var num = 10; 变量声明提升,但赋值不提
num = 10;
3.2 函数预解析
函数提升: 函数的声明会被提升到当前作用域的最上面,但是不会调用函数。
栗子:
我们书写的顺序:
fn();
function fn() {
console.log('打印');
}
它的执行顺序:
function fn() { //把声明提到当前作用域最前
console.log('打印');
}
fn();
这里要注意的是:匿名函数和命名函数是不一样的。
栗子:
fn();
var fn = function() {
console.log('想不到吧');
}
看结果:
为什么呢?
分析一下,匿名函数的声明方式是把函数放到了一个变量里,此时它是按照变量提升的顺序来执行的。即如下:
var fn;
fn();
fn = function() {
console.log('想不到吧');
}
接下来按照从上到下的顺序执行,相当我们只定义了一个fn变量。执行fn()
语句时当然会报错了,变量怎么还能调用呢。
4. 对象
何为对象:
在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象。
有过java、c++学习经历的对象是司空见惯了。没有也不要紧张
所谓万物皆对象意思是说,我们能看到的所有的具体事物都可以看做一个对象。比如你看这篇博客的工具,可能是手机可能是电脑。它们都是一个对象
手机有它的外观属性:它的颜色,它的尺寸,它的品牌
手机还有它的行为:它能打电话,它能上网,它能聊天
把这些数据储存到一起即对应上面对象是一组无序的相关属性和方法的集合
4.1 为什么需要对象
保存一个值时,可以使用变量,保存多个值(一组值)时,可以使用数组。
如果要保存一个人的完整信息呢?
难不成要这样:
var arr = [‘张三’, ‘男', 180,180];
前面的数据我们能看的明白,一个人叫张三,是个男的。后面两是个什么玩意呢
为了让更好地存储一组数据,对象应运而生
如下:
var obj = {
"name":"张三",
"sex":"男",
"age":180,
"height":180
}
这样岂不一目了然。
4.2 创建对象的三种方式
4.2.1 使用对象字面量创建对象
基本语法:
var obj={};
实例:
var obj= {
name : '宫小白',
age : 18,
sex : '男',
sayHi : function(){
alert('大家好啊~');
}
};
注释:
-
键:相当于属性名
-
值:相当于属性值,可以是任意类型的值(数字类型、字符串类型、布尔类型,函数类型等)
对象的使用
访问对象的属性:
对象里面的属性调用 : (1)对象.属性名(2)对象[‘属性名’],注意方括号里面的属性必须加引号
调用对象的方法:
对象里面的方法调用:对象.方法名() 。不要忘记小括号哦
变量、属性、函数、方法总结
- 变量:单独声明赋值,单独存在
- 属性:对象里面的变量称为属性,不需要声明,用来描述该对象的特征
- 函数:单独存在的,通过“函数名()”的方式就可以调用
- 方法:对象里面的函数称为方法,方法不需要声明,使用“对象.方法名()”的方式就可以调用,方法用来描述该对象的行为
4.2.2 使用 new Object 创建对象
通过内置构造函数Object创建对象
基本语法:
var arr= new Obect();
通过对象操作属性和方法的方式,来为对象增加属性和方法
arr.name = "宫小白";
arr.name = 18;
arr.sex = "男";
arr.sayHi = function() {
console.log("大家好");
}
4.2.3 利用构造函数创建对象
类比于java的类的有参构造和无参构造(java基础我也会在前端重要知识总结完成之后规整到博客的)
总之你现在可以这样理解。
构造函数:是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值。
比如,我要储存tom毛和杰瑞鼠的信息。它们有很多公共点。比如都是哺乳动物,都有毛,都会叫。
var tom = {
type: "哺乳动物",
shape: "蓝毛",
voice: function() {
console.log("喵喵");
}
}
var jim = {
type: "哺乳动物",
shape: "白毛",
voice: function() {
console.log("吉吉");
}
}
这样写代码的笼余度太高了,它们只是属性值不同而已。
这时构造函数便需要出厂了。
语法格式
function 构造函数名(形参1,形参2,形参3) {
this.属性名1 = 参数1;
this.属性名2 = 参数2;
this.属性名3 = 参数3;
this.方法名 = 函数体;
}
修正上面猫鼠实例。
function ani(type, shape, voice) {
this.type = type;
this.shape = shape;
this.voice = function() {
console.log(voice);
}
}
//实例化
var tom = new ani("哺乳动物", "蓝毛", "喵喵");
var jim = new ani("哺乳动物", "白毛", "吉吉");
就是它们的共同点抽象出来,具体对象用到时再实例化出来。(抽象:即它们都是哺乳动物,拥有哺乳动物的一些特性把这些特性先放到构造函数里;实例化就是一个具体的对象,如tom猫它也有那些特性。就可以利用构造函数的方式创建这个对象。自己独有的单加(4.2.2的方法)即可。)
值得注意:
- 构造函数约定首字母大写。
- 函数内的属性和方法前面需要添加 this ,表示当前对象的属性和方法。
- 构造函数中不需要 return 返回结果。
- 构造函数,如 Ani(),抽象了对象的公共部分,封装到了函数里面
- 创建对象,如 new Ani(),特指某一个具体
new关键字的作用
- 在构造函数代码开始执行之前,创建一个空对象;
- 修改this的指向,把this指向创建出来的空对象;
- 执行函数的代码
- 在函数完成之后,返回创建出来的对象
(this指向问题在后面js高级的原型链再详细说,这里先简单理解)
4.3 对象遍历
利用for in循环
基本语法:
for (变量 in 对象名字) {
// 在此执行代码
}
//语法中的变量是自定义的,它需要符合命名规范,通常我们会将这个变量写为 k 或者 key。
首先我们先看一下for中这个变量是什么?
可见此变量k里面放的是属性或者方法名。
function ani(type, shape, voice) {
this.type = type;
this.shape = shape;
this.voice = function() {
}
}
//实例化
var tom = new ani("哺乳动物", "蓝毛", "喵喵");
var jim = new ani("哺乳动物", "白毛", "吉吉");
for (var k in tom) {
console.log(tom[k]);
}
结果:
值得注意:
这里console.log(tom[k]);
不可修改为tom.k
。