- A+
Javascript基础Day4
函数(下)
作用域(重点)
-
什么是作用域,就是一个变量可以生效的范围
-
变量不是在所有地方都可以使用的,而这个变量的使用范围就是作用域
全局作用域
-
整个页面起作用,在<script>内都能访问到;
-
在全局作用域中有全局对象window,代表一个浏览器窗口,由浏览器创建,可以直接调用;
-
全局作用域中声明的变量和函数,会作为window对象的属性和方法保存;
-
变量在函数外声明,即为全局变量,拥有全局作用域。
var a = 123;//全局变量 function fn() { console.log(a);//123 } fn(); console.log(a);//123
局部作用域
-
局部作用域内的变量只能在函数内部使用,所以也叫函数作用域;
-
变量在函数内声明,即为局部变量,拥有局部作用域。
function fn() { var b = 456;//局部变量 console.log(b);//456 } fn(); console.log(b);//b is not defined
注:可以直接给一个未声明的变量赋值(全局变量),但不能直接使用未声明的变量!因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。当全局与局部有同名变量的时候,访问该变量将遵循 "就近原则"。
变量的生命周期
-
全局变量在页面打开时创建,在页面关闭后销毁。
-
局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。
变量使用规则(重点)
-
有了作用域以后,变量就有了使用范围,也就有了使用规则
-
变量使用规则分为两种,访问规则 和 赋值规则
访问规则
-
当我想获取一个变量的值的时候,我们管这个行为叫做 访问
-
获取变量的规则:
-
首先,在自己的作用域内部查找,如果有,就直接拿来使用
-
如果没有,就去上一级作用域查找,如果有,就拿来使用
-
如果没有,就继续去上一级作用域查找,依次类推
-
如果一直到全局作用域都没有这个变量,那么就会直接报错(该变量 is not defined)
var num = 100 function fn() { var num2 = 200 function fun() { var num3 = 300 console.log(num3) // 自己作用域内有,拿过来用 console.log(num2) // 自己作用域内没有,就去上一级,就是 fn 的作用域里面找,发现有,拿过来用 console.log(num) // 自己这没有,去上一级 fn 那里也没有,再上一级到全局作用域,发现有,直接用 console.log(a) // 自己没有,一级一级找上去到全局都没有,就会报错 } fun() }fn()
-
-
变量的访问规则 也叫做 作用域的查找机制
-
作用域的查找机制只能是向上找,不能向下找
function fn() { var num = 100 } fn() console.log(num) // 发现自己作用域没有,自己就是全局作用域,没有再上一级了,直接报错
赋值规则
-
当你想给一个变量赋值的时候,那么就先要找到这个变量,在给他赋值
-
变量赋值规则:
-
先在自己作用域内部查找,有就直接赋值
-
没有就去上一级作用域内部查找,有就直接赋值
-
在没有再去上一级作用域查找,有就直接赋值
-
如果一直找到全局作用域都没有,那么就把这个变量定义为全局变量,在给他赋值
function fn() { num = 100 }fn() // fn 调用以后,要给 num 赋值 // 查看自己的作用域内部没有 num 变量 // 就会向上一级查找 // 上一级就是全局作用域,发现依旧没有 // 那么就会把 num 定义为全局的变量,并为其赋值 // 所以 fn() 以后,全局就有了一个变量叫做 num 并且值是 100console.log(num) // 100
-
常见事件
-
浏览器事件
-
onload 加载完毕
-
onscroll 浏览器滚动事件
-
onresize 浏览器窗口改变事件
-
-
鼠标事件
-
onclick :点击事件
-
ondblclick :双击事件
-
oncontextmenu`: 右键单击事件
-
onmousedown :鼠标左键按下事件
-
onmouseup :鼠标左键抬起事件
-
onmousemove :鼠标移动
-
onmouseover :鼠标移入事件
-
onmouseout :鼠标移出事件
-
onmouseenter :鼠标移入事件(不冒泡)
-
onmouseleave :鼠标移出事件(不冒泡)
-
onselectstart:选中事件(不被 input 和 textarea 标签支持 )
-
onselect:选中事件(支持 input 和 textarea 标签)
-
-
键盘事件
-
onkeydown 键盘按下事件
-
onkeyup 键盘释放事件
-
onkeypress 产生可打印字符事件
注:键盘事件绑定的位置,要么是document,要么是输入框
-
-
触摸事件(移动端)
-
ontouchstart 触摸开始
-
ontouchmove 触摸移动
-
ontouchend 触摸结束
-
-
表单事件
-
onchange 表单改变事件(失去焦点时触发)
-
oninput 表单输入事件(输入时触发)
-
onsubmit 表单提交事件(点击submit时触发)
-
onfocus :获得焦点事件
-
onblur :失去焦点事件
-
-
其他事件
-
ontransitionend 过渡结束的时候触发
-
onanimationend 动画结束的时候触发
-
自执行函数
要执行一个函数,我们必须要有方法定义函数、引用函数。 匿名函数如何调用? 匿名自执行函数,也叫立即执行函数(IIFE)。 (function () { console.log(123); })(); 小括号能把我们的表达式组合分块,并且每一块都有一个返回值,这个返回值实际上就是小括号中表达式的返回值。 自执行函数的好处:独立的作用域,不会污染全局环境! 传参:(function (a,b) { console.log(a + b); })(2,3); 常见形式:(function () { console.log(11); })(); (function(){ console.log(22); }()); !function() { console.log(33); }(); +function() { console.log(55); }(); -function() { console.log(66); }(); ~function() { console.log(77); }();
递归函数
-
什么是递归函数
-
在编程世界里面,递归就是一个自己调用自己的手段
-
递归函数: 一个函数内部,调用了自己,循环往复
-
一般来说,递归需要有边界条件、递归前进段和递归返回段。
-
当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
// 下面这个代码就是一个最简单的递归函数 // 在函数内部调用了自己,函数一执行,就调用自己一次,在调用再执行,循环往复,没有止尽function fn() { fn()}fn()
-
其实递归函数和循环很类似
-
需要有初始化,自增,执行代码,条件判断的,不然就是一个没有尽头的递归函数,我们叫做 死递归
简单实现一个递归
-
我们先在用递归函数简单实现一个效果
-
需求: 求 1 至 5 的和
-
先算 1 + 2 得 3
-
再算 3 + 3 得 6
-
再算 6 + 4 得 10
-
再算 10 + 5 得 15
-
结束
-
-
开始书写,写递归函数先要写结束条件(为了避免出现 “死递归”)
function add(n) { // 传递进来的是 1 // 当 n === 5 的时候要结束 if (n === 5) { return 5 }} add(1)
-
再写不满足条件的时候我们的递归处理
function add(n) { // 传递进来的是 1 // 当 n === 5 的时候要结束 if (n === 5) { return 5 } else { // 不满足条件的时候,就是当前数字 + 比自己大 1 的数字 return n + add(n + 1) }} add(1)
-
老王有四个子女,老四比老三小2岁,老三比老二小2岁,老二比老大小2岁,老大现在16岁,问老四几岁?
function countAge(who) { if (who == 1) { return 16; } else { return countAge(who - 1) - 2; } } alert(countAge(4)); // 10
注:递归函数在运行的时候,每调用一次函数就会在内存中开辟一块空间,内存消耗较大,注意防止栈溢出。
递归算法一般用于解决三类问题:
1.数据的定义是按递归定义的;
2.问题解法按递归算法实现;
3.数据的结构形式是按递归定义的。
构造函数(了解)
构造函数:用于创建特定类型的对象。
JS内部构造函数:Object、Number、String、Array、Function、Boolean等等...
当任意一个普通函数用于创建一类对象,并通过new操作符来调用时它就可以作为构造函数。
构造函数一般首字母大写。
简单对象(掌握)
-
对象是一个复杂数据类型
-
对象是一组无序的键值对,是带有属性和方法的集合。
-
通俗讲,对象就是无序的数据集合。
-
属性是与对象相关的值,方法是能够在对象上执行的动作。
-
对象的作用:用于在单个变量中存储多个值。
创建一个对象
-
字面量的方式创建一个对象
var obj = { 键:值, 键:值 ...... }; 键:一般用双引号引起来(不用引号也可以) 值:可以是任意类型的数据 var obj = { name: '小错', age: 18, sayHi: function (){ alert('hi,大家好'); }}
-
内置构造函数的方式创建对象
var obj2 = new Object(); obj2.name = '小错'; obj2.age = 18; obj2.sayHi = function (){ alert('hi,大家好'); } console.log( obj2.name ); obj2.sayHi( );
-
操作对象
访问对象成员: 1. 对象.属性 对象.方法() 2. 对象[变量或字符串]删除属性: delete obj.attr;遍历对象:{} for / in 循环 for (var key in obj){ console.log( obj[key] ); }
{}和new创建对象的对比(了解)
字面量的优势:
它的代码量更少,更易读;它可以强调对象就是一个简单的可变的散列表,而不必一定派生自某个类;对象字面量运行速度更快,因为它们可以在解析的时候被优化——它们不需要"作用域解析"!因为存在我们创建了一个同名构造函数Object()的可能,所以当我们调用Object()的时候,解析器需要顺着作用域链从当前作用域开始查找,如果在当前作用域找到了名为Object()的函数就执行,如果没找到,就继续顺着作用域链往上找,直到找到全局Object()构造函数为止
构造函数的优势:
Object()构造函数可以接收参数,通过这个参数可以把对象实例的创建过程委托给另一个内置构造函数(Number()、String()等),并返回另一个对象实例。使用自定义构造函数创建对象,可以通过传参添加属性和方法,当需要定义的同类对象较多时,节省了定义对象的代码量,并且使对象属性和方法的结构更加清晰
-
数据类型之间存储的区别(重点)
-
既然我们区分了基本数据类型和复杂数据类型
-
那么他们之间就一定会存在一些区别
-
他们最大的区别就是在存储上的区别
-
我们的存储空间分成两种 栈 和 堆
-
栈: 主要存储基本数据类型的内容
-
堆: 主要存储复杂数据类型的内容
基本数据类型在内存中的存储情况
-
var num = 100
,在内存中的存储情况 -
直接在 栈空间 内有存储一个数据
复杂数据类型在内存中的存储情况
-
下面这个 对象 的存储
var obj = { name: 'Jack', age: 18, gender: '男' }
-
复杂数据类型的存储
-
在堆里面开辟一个存储空间
-
把数据存储到存储空间内
-
把存储空间的地址赋值给栈里面的变量
-
-
这就是数据类型之间存储的区别
数据类型之间的赋值
-
基本数据类型之间的赋值
var num = 10var num2 = num num2 = 200 console.log(num) // 10 console.log(num2) // 200
-
相当于是把 num 的值复制了一份一摸一样的给了 num2 变量
-
赋值以后两个在没有关系
-
-
复杂数据类型之间的赋值
var obj = { name: 'Jack' } var obj2 = obj obj2.name = 'Rose' console.log(obj.name) // Roseconsole.log(obj2.name) // Rose
-
因为复杂数据类型,变量存储的是地址,真实内容在 堆空间 内存储
-
所以赋值的时候相当于把 obj 存储的那个地址复制了一份给到了 obj2 变量
-
现在 obj 和 obj2 两个变量存储的地址一样,指向一个内存空间
-
所以使用 obj2 这个变量修改空间内的内容,obj 指向的空间也会跟着改变了
-
数据类型之间的比较
-
基本数据类型是 值 之间的比较
var num = 1var str = '1' console.log(num == str) // true
-
复杂数据类型是 地址 之间的比较
var obj = { name: 'Jack' } var obj2 = { name: 'Jack' } console.log(obj == obj2) // false
-
因为我们创建了两个对象,那么就会在 堆空间 里面开辟两个存储空间存储数据(两个地址)
-
虽然存储的内容是一样的,那么也是两个存储空间,两个地址
-
复杂数据类型之间就是地址的比较,所以
obj
和obj2
两个变量的地址不一样 -
所以我们得到的就是
false
-
-
函数的参数
-
函数的参数也是赋值的,在函数调用的时候,实参给行参赋值
-
和之前变量赋值的规则是一样的
-
函数传递基本数据类型
function fn(n) { n = 200 console.log(n) // 200 } var num = 100fn(num) console.log(num) // 100
-
和之前变量赋值的时候一样,在把 num 的值复制了一份一摸一样的给到了函数内部的行参 n
-
两个之间在没有任何关系了
-
-
函数传递复杂数据类型
function fn(o) { o.name = 'Rose' console.log(o.name) // Rose } var obj = { name: 'Jack' } fn(obj) //所传递的就是obj的引用地址console.log(obj.name) // Rose
-
和之前变量赋值的时候一样,把 obj 内存储的地址复制了一份一摸一样的给到函数内部的行参 o
-
函数外部的 obj 和函数内部的行参 o,存储的是一个地址,指向的是一个存储空间
-
所以两个变量操作的是一个存储空间
-
在函数内部改变了空间内的数据
-
obj 看到的也是改变以后的内容
-
js常见错误类型
SyntaxError :语法错误
// 1) 变量名不符合规范var 1 // Uncaught SyntaxError: Unexpected numbervar 1a // Uncaught SyntaxError: Invalid or unexpected token// 2) 给关键字赋值function = 5 // Uncaught SyntaxError: Unexpected token =
ReferenceError :引用错误(要用的变量没找到)
// 1) 引用了不存在的变量a() // Uncaught ReferenceError: a is not definedconsole.log(b) // Uncaught ReferenceError: b is not defined// 2) 给一个无法被赋值的对象赋值console.log("abc") = 1 // Uncaught ReferenceError: Invalid left-hand side in assignment
TypeError: 类型错误(调用不存在的方法)
// 1) 调用不存在的方法123() // Uncaught TypeError: 123 is not a functionvar o = {}o.run() // Uncaught TypeError: o.run is not a function// 2) new关键字后接基本类型var p = new 456 // Uncaught TypeError: 456 is not a constructor
RangeError: 范围错误(参数超范围)
// 1) 数组长度为负数[].length = -5 // Uncaught RangeError: Invalid array length// 2) Number对象的方法参数超出范围var num = new Number(12.34)console.log(num.toFixed(-1)) // Uncaught RangeError: toFixed() digits argument must be between 0 and 20 at Number.toFixed // 说明: toFixed方法的作用是将数字四舍五入为指定小数位数的数字,参数是小数点后的位数,范围为0-20.