目录
- 常用事件和事件处理函数
- 递归函数
- 函数的属性和方法
- 函数的作用域
常用事件和事件处理函数
事件参考--MDN
在DOM
当中,存在有很多的事件,通过这些事件,我们能够很好地监听用户在浏览器网页当中的一些动作,并且根据这些动作来设置相关的事件处理函数。
我们可以将事件分成三个部分,事件源,事件,以及事件发生后执行的动作。
对应着元素、事件以及事件处理函数。
例如:
按钮.单击= function () {事件发生后执行的动作}
例如我们在网页中有一个按钮,当用户点击按钮的时候我们让网页弹出弹窗,弹出信息为hello,world。
具体代码可以如下:
// 找到按钮
var oBtn = document.getElementById('btn');
// 绑定事件
oBtn.onclick = function () { // 事件处理函数
// 当用户发生点击操作之后弹出弹窗
alert('hello,world');
};
在上面的代码中,我们通过给一个元素绑定单击事件,并且添加了事件处理函数。
一旦用户点击了按钮,那么就立刻会弹出弹窗。
当然,我们除了上面的写法以外,对于给一个元素绑定事件和事件处理函数的写法还可以换做另外一种形式:
<button id="btn" onclick="sayHello()">点击</button>
<script >
function sayHello(){
alert('hello,world');
}
</script>
在上面的代码中,我们把事件和事件处理函数都绑定到了元素的身上,这样做的好处是能够省略了获取元素的过程。
在我们执行事件处理函数的时候,在某些时刻可能需要进行参数的传递,可以采用下面的几种方式。下面同样是点击问好的案例,但是问好的对象是对指定的人进行问好。
第一种方式:
demo:
<button onclick="sayHello('张三')">点击问好</button>
<script >
function sayHello(name){
alert('hello,' + name );
}
</script>
第二种方式:
demo:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<input type="text" id="name">
<button id="btn">点击问好</button>
</body>
<script>
// 获取input
var oInput = document.getElementById('name');
// 获取按钮
var oBtn = document.getElementById('btn');
// 点击按钮然后执行问好
oBtn.onclick = function () {
var userName = oInput.value;
sayHello(userName);
};
function sayHello(name) {
alert('hello,' + name);
}
</script>
</html>
上面的代码中,我们可以在input中输入用户名,当用户点击按钮的时候,我们会将通过单击事件的事件
处理函数来调用sayHello方法,在这个方法中,获取用户输入的用户名,然后问好。
上面的代码基本上已经完成了我们的需求,但是我们仍然需要让我们的代码变得更加的完善。
例如,当我们点击按钮的时候,发现用户并没有输入用户名,我们该怎么办?
demo:
// 点击按钮然后执行问好
oBtn.onclick = function () {
var userName = oInput.value;
// 判断一下用户输入的内容是否为空
if (userName === "") {
alert("请输入用户名");
}else {
sayHello(userName);
}
};
在上面的代码中,当用户点击按钮调用了事件处理函数之后,我们进行了一个判断,判断用户输入的内容是否为空,如果为空,我们就可以继续提示用户,请输入用户名。
当然。上面的代码其实演示的仅仅是一种思路,代码仍然不是太严谨。
在我们的实际项目开发中,必须要让我们的代码尽可能的变得更加严谨,使我们的代码变得更加的强壮。
鼠标事件:
。click
当用户按下并释放鼠标按键或其他方式“激活”元素时触发
。contextmenu
可以取消的事件,当上下文菜单即将出现时触发。当前浏览器在鼠标右击时显示上下文菜单
。dblclick
当用户双击鼠标时触发
。mousedown
当用户按下鼠标按键时触发
。mouseup
当用户释放鼠标按键时触发
。mousemove
当用户移动鼠标时触发
。mouseover
当鼠标进入元素时触发。relatedTarget(在IE中是
fromElement)指的是鼠标来自的元素
。mouseout
当鼠标离开元素时触发。relatedTarget(在IE中是toElement)指的是鼠标要去往的元素
。mouseenter
类似mouseover
,但不冒泡。IE将其引入,HTML5将其标准化,但尚未广泛实现
。mouseleave
类似mouseout
,但不冒泡。IE将其引入,HTML5将其标准化,但尚未广泛实现键盘事件
。keydown
按下
。keypress
点击
。keyup
抬起加载事件
。load
页面完全加载后会触发该事件
。error
当加载失败后触发,所有可以触发load事件的元素,都可以触发该事件
。abort
元素加载中止时,(如加载过程中按下ESC键,停止加载),触发该事件,常用于图片加载 (只有IE支持)
。unload
与load
事件对应的是unload事件,该事件在文档被完全卸载后触发。刷新页面时,也会触发该事件。chrome/firefox/safari浏览器会阻止alert等对话框,IE浏览器会阻止console.log()等控制台显示。
。DOMContentLoaded
则在形成完整的DOM树之后就会触发,而不理会图像、javascript文件、CSS文件或其他资源是否下载完毕。(IE8不支持)表单事件
。submit
表单提交时触发,绑定给form元素
。reset
表单发生重置时触发,绑定为form元素
。blur
失去焦点时触发
。change
表单控制的内容发生改变时触发其他事件
。scroll
元素内部的内容滚动时触发
。resize
窗口尺寸发生变化时触发
并不是所有元素都具有所有事件
1.
box.onclick = function(){
console.log(1)
};
2.
box.onclick = function fn2(){
console.log(2)
};
3.在事件后,直接放函数名,别加()
box.onclick = fn3;
function fn3(){
console.log(3)
}
4.在这个函数内部,执行其他函数
box.onclick = function(){
fn4()
};
function fn4(){
console.log(4)
}
根据执行方式不同的分类:
1.被事件直接执行的函数叫事件处理函数
;
2.通过参数执行函数,这个作为参数的函数,叫回调函数
。
递归函数
当函数发生在函数体内自己调用自己这样的情况时,这个函数我们可以称为递归函数
。
例如下面的 demo
,就是一个递归函数
:
// 递归函数
function fib(num) {
if (num === 0) return 0;
if (num === 1) return 1;
return fib(num - 2) + fib(num - 1);
}
console.log(fib(6)); // 8
在上面的demo
中,通过递归函数计算了一下斐波那契数列
。
上面代码中,fib函数内部又调用了fib,计算得到斐波那契数列的第6个元素是8。
第一等公民的函数
在js
中,函数被称为一等公民
。
什么意思呢,就是说函数
被看作是一种值,与其他的数据值(数值、字符串、布尔值等)地位相等。凡是可以使用值的地方都可以使用函数。
例如,我们可以将一个值赋值给一个变量,对应的,同样也可以将函数赋值给一个变量。
var fn = function () {};
同时,因为函数是一等公民
,所以也可以将函数当做参数传递给其他函数,或者作为函数的返回结果。
function fn1() {}
function fn2(parm) {
parm();
}
fn2(fn1);
函数的属性和方法
name 属性
函数的name属性返回函数的名字。
function fn1() {}
fn1.name; // "fn1"
如果是通过变量赋值定义的函数,那么name属性返回函数的名字。
var fn2 = function () {};
fn2.name; // fn2
上面的案例中,函数是一个匿名函数,我们使用name属性就可以获得存储函数的变量的名称,但是一旦我们给匿名函数也定义了一个函数名,那么我们通过name属性查看到的将会是函数的名字而不是变量名。
var fn2 = function test () {};
fn2.name; // test
需要注意的是,虽然打印出来的名称是test,但是真正的函数名还是fn2,test只是表达式的名字,并且test这个名字只能够在表达式内部使用。
我们可以通过name属性获取参数函数的的名字。
function fn1() {}
function fn2(parm){
console.log(parm.name);
}
fn2(fn1); // fn1
在上面的代码中,在函数的内部通过name属性就获取了要传入的函数的名字。
length属性
函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数也就是形参
的个数。
function fn1(a,b) {
}
console.log(fn1.length);//2
上面代码定义了空函数f,它的length属性就是定义时的参数个数。不管调用时输入了多少个参数,length属性始终等于2。
length属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象
编程的“方法重载”(overload)。
toString()
函数的toString
方法返回一个字符串,内容是函数的源码
。
function fn1(a,b) {
console.log("hello,world!");
}
console.log(fn1.toString());
通过toString
方法可以将函数全部的源码返回。
而toString
方法可以返回function(){native code}
。
console.log(Math.abs.toString()); // function abs() { [native code] }
上面代码中,Math.abs
是js
提供的原生
的函数,toString
方法就会返回原生代码的提示。
需要注意的是,当我们使用toString方法
时,函数内部的注释也会被返回
函数的作用域
定义
作用域(scope)指的是变量可以生存的范围或者说存在的范围。
在老版本的ES5的规范里,JS只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,在任何的地方都可以读取;另外一种
是函数作用域,变量只在函数内部存在,在函数的外部没有办法访问到函数内部的变量。
ES6当中新增加了块级作用域。
对于顶层函数来说,函数外部声明的变量就是全局变量(global variable),可以在函数内部读取。
var a = 10;
function fn1() {
console.log(a);
}
fn1(); // 10
上面的代码中,我们在函数的外部声明了一个变量,我们在函数的内部也读取到了全局变量的值。
在函数内部定义的变量,在函数的外部则无法读取,我们将其称为局部变量(local variable)。
function t_fn1() {
var a = 10;
}
console.log(a); // ReferenceError: a is not defined 引用错误:未定义
上面的案例中,我们尝试从函数的外部访问函数内部的局部变量(local variable),发现js提示我们变量没有定义,访问失败。
函数内部定义的局部变量,会覆盖掉函数外部定义的同名的全局变量
例如:
var x = 10;
function fn1(){
var x = 20;
console.log(x);
}
fn1(); // 20
在上面的代码中,变量x同时在函数外部和函数内部定义。结果显而易见,函数内部的变量x覆盖了函数外部的变量x。
但是需要注意的是,这种覆盖的变量,生效范围只能是在函数内部,如果出了函数,则变量x恢复到本身的10。
例如:
var x = 10;
function fn1() {
var x = 20;
console.log(x);
}
fn1(); // 20
console.log(x); // 10
在上面的代码中,我们在函数内部访问变量,x的值输出为函数内部变量的值20,而当出了函数,我们再来访问,你会发现变量x的值仍然为10。
所以说函数内部变量的覆盖仅停留于函数内部,出了函数就会恢复过来。
注意,对于var
命令来说,局部变量
只能在函数内部声明,在其他区块中声明,一律都是全局变量
。
if (true) {
var x = 5;
}
console.log(x); // 5
函数内部的变量提升
与全局作用域一样,函数作用域内部也会产生变量提升现象。var
命令声明的变量,不管在什么位置,变量声明都会提升到函数体的头部。
例如:
function fn1() {
var x = 2;
if (x > 3){
var tmp = x - 100;
x = x + 1;
}
}
上面的代码并没有什么实际的意义,但是这样一段代码在发生变量提升之后等同于下面的代码:
function fn1() {
var x,tmp;
x = 2;
if(x > 3) {
tmp = x - 100;
x = x + 1;
}
}
函数本身的作用域
上面我们说过,函数
在js
当中是一等公民
。本质上来讲也就是一个能够运行的值
。So,函数既然是一个值,那么函数也就有着自己的作用域。
函数的作用域与变量相同,就是其声明时所在的作用域,一定要注意的是,函数的作用域与其运行时所在的作用域没有关系。
例如:
var a = 1;
var x = function () {
console.log(a);
};
// 函数x在全局作用域环境下创建,所以说x函数的作用域就是全局,虽然x函数是在函数f中调用,但是作用域仍然是在全局。
function f() {
var a = 2;
x();
}
f() // 1
在上面的代码中,函数x是在全局作用域声明的函数,所以此时函数的作用域被绑定到全局。
函数的参数
函数的运行时,有时需要提供外部数据,我们提供不同的数据会导致不同的结果。这种外部的数据就叫做参数。
// 定义一个函数,此时函数需要外部的数据a和b
function fn1(a,b) {
return a + b;
}
// 调用函数并且传入数据
var x = 10;
var y = 20;
console.log(fn1(x,y));
在上面的案例中,a和b就是函数fn1的参数。在调用的时候,我们需要从外部传入数据来使用。
函数参数使用时需要注意的点:
- 函数参数不是必须的,JavaScript允许省略参数。
function f1(a,b) {
// 如果函数在运行时没有使用参数,那么参数也可以省略
console.log('hello,world!');
}
f1(); // hello,world
上面的代码中,我们的函数在运行过程中并不需要参数a和b,所以我们在调用函数时候可以选择省略。但是需要注意的是,我们在函数运行的过程中如果定义了参数并且使用了参数而你却没有传入实参。那么函数体内使用该参数会返回undefined
。
function f1(a,b) {
// console.log('hello,world!');
console.log(a,b);// undefined
}
// 调用函数的时候并没有传入实参
f1();
如果通过length属性查看参数,那么length属性返回的值其实是形参的长度而不是实参的长度,也就意味着,只要你定义了函数
的形参,即使你在调用函数的时候没有传入实参,length属性也可以返回值。
- 函数的参数不能只省略个别
当我们调用函数的时候,我们不能省略只省略靠前的参数,而保留靠后的参数。如果一定要这么做,那么只有显示的传入undefined才可以。
function f1(a,b,c,d) {
console.log(a,b,c,d);
}
// 调用函数的时候,如果想省略其中的个别参数,只能显示的传入undefined
// 而如果省略其中的一个参数就会报错。
f1(undefined,undefined,10,20);
- 函数参数传递方式
函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
例如:
var a = 10;
function fn1(param) {
param += 3;
console.log('在函数内部的变量值为:' + param);
}
fn1(a); // 在函数内部的变量值为:13
console.log(a);// 10
上面代码中,变量p是一个原始类型的值,传入函数f的方式是传值传递。因此,在函数内部,p的值是原始值的拷贝,无论怎么修改,都不会影响到原始值。
但是,如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
var obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
上面代码中,传入函数f的是参数对象obj
的地址。因此,在函数内部修改obj
的属性p
,会影响到原始值。
注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。
var obj = [1, 2, 3];
function f(o) {
o = [2, 3, 4];
}
f(obj);
obj // [1, 2, 3]
上面代码中,在函数f
内部,参数对象obj
被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(o)
的值实际是参数obj
的地址,重新对o
赋值导致o
指向另一个地址,保存在原地址上的值当然不受影响。
- 同名参数
如果有同名的参数,则取最后出现的那个值。
例如:
function f1(a,a){
console.log(a);
}
f1(1,2); // 2
上面代码中,函数f有两个参数,且参数名都是a。取值的时候,以后面的a为准,即使后面的a没有值或被省略,也是以其为准。
function f(a, a) {
console.log(a);
}
f(1) // undefined
调用函数f的时候,没有提供第二个参数,a的取值就变成了undefined
。这时,如果要获得第一个a的值,可以使用arguments
对象。
function f(a, a) {
console.log(arguments[0]);
}
f(1) // 1