函数
- 函数是一段被封装的代码,可以反复被调用。
- 在JS中,函数也可以是一个值、一个对像、还可以是一个表达式,因此函数可以赋值、可以运算、可以拥有属性和方法
- JavaScript拥有函数式编程的很多特性和风格,灵活使用函数,可以编写出功能大、代码简洁、设计优雅的程序。
//自我介绍
/* var yourGirlFri = {};
yourGirlFri.name = "张三";
yourGirlFri.sex = "女";
yourGirlFri.age = "40";
yourGirlFri.price = "388";
var yourGirlFri = {};
yourGirlFri.name = "李四";
yourGirlFri.sex = "女";
yourGirlFri.age = "18";
yourGirlFri.price = "188";
var yourGirlFri = {};
yourGirlFri.name = "王大麻子";
yourGirlFri.sex = "女";
yourGirlFri.age = "12";
yourGirlFri.price = "1200";
*/
//一段相同的代码重复执行 我们可以书写一个函数把代码包含起来 使用的时候 直接调用函数即可
function yourGirlFri(name,sex,age,height) {
var obj = {};
obj.name = name;
obj.sex = sex;
obj.age = age;
obj.height = height;
return obj;
}
var girl1 = yourGirlFri("lily","男","19","180")
var girl2 = yourGirlFri("王八八","女","100","190")
var girl3 = yourGirlFri("王久久","女","90","110")
console.log(girl1)
console.log(girl2)
console.log(girl3)
定义函数
声明函数
-
在 Javascript中可以使用function语句声明函数。
function funName ([args]){
statements
}
- funName是函数名,与变量名一样都必须是JavaScript合法的标识符。
- 在函数名之后是一个由小括号包含的参数列表,参数之间以逗号分隔。参数是可选的,没有数量限制。
- 作为标识符,参数仅在函数体内被访问,参数是函数作用域的私有成员。调用函数时,通过为函数传递值,然后使用参数获取外部传入的值,并在函数体内干预函数的运行
- 在小括号之后是一个大括号,大括号内包含的语句就是函数体结构的主要内容。在函数体中,大括号号是必不可少的,缺少大括号, JavaScript将会抛出语法错误,
-
var语句和function语句都是声明语句,它们声明的变量和函数都在JavaScript预编译时被解析,也被称为变量提升和函数提升。
//直接声明函数,可以直接提升
fn1(1,2,3);
function fn1(a,b,c) {
console.log(a,b,c)
}
//函数只有定义在了其他函数中,才是局部的函数,否则都是全局的,在哪都能调用
//ES5中,不允许 函数书写在非函数的代码块中
fn2();//oh
console.log(fn2);
var a = 100;
if (a > 10){
function fn2() {
alert("oh");
}
}
fn2();//oh
//函数也可以不书写名字,这种函数被称作为匿名函数
function () {
alert("heng")
}
//匿名函数的执行方法 1 自调用(分为直接扩住函数体调用、在函数前添加 !~ + - 一元运算符 然后 添加小括号调用)
(function (){
alert("heng");
})();
!function (){
alert("heng");
}()
//匿名函数的执行方法 2 赋值调用 (也被称作为函数表达式) 这种方法只能提升fn3 不能提升函数体
var fn3 = function (){
alert("heng");
}
fn3()
构造函数
- 使用
Function(p1,p2,p3,p4,..pn,body)构造函数可以快速生成函数 - Functiono的参数类型都是字符串,p1-pn表示所创建函数的参数名称列表,body表示所创建函数的函数结构体语句,在body语句之间以分号分隔。
- 使用Function()构造函数不是很常用,因为一个函数体通常会包含很多代码,如果将这些代研以一行字符串的形式进行传递,代码的可读性会很差。
var fn1 = new Function("console.log(111)");
fn1();
//以下两种传参方式都可以使用
var fn2 = new Function("a","b","c","console.log(a+b+c)");
var fn2 = new Function("a,b,c","console.log(a+b+c)");
fn2(1,2,3);
函数直接量
- 函数直接量也称为匿名函数,即函数没有函数名,仅包含函数关键字,参数和函数体,具体用法如下
function (){} - 匿名函数就是一个表达式,即函数表达式,而不是函数结构的语句,可以把匿名函数作为值赋值给变量或者对象等等
- 当把函数结构作为一个值赋值给变量之后,变量就可以作为函数被调用,此时变量就指向那个匿名函数
- 匿名函数可以自己调用,比如加上小括号然后整体调用,或者在最前边添加!-+~等等一元操作符
// 把函数当做一个值
function fn1() {
console.log("fn1");
}
var fn2 = fn1;
var fn3 = function (){
console.log("匿名")
}
fn2();
fn3();
//函数也可以是一个对象
function fn1() {
alert("fn1");
}
fn1.name1 = "lily";
fn1.sex = "nv";
// fn1();
alert(fn1.name1);
alert(fn1.sex);
//函数可以作为对象的某个方法
var obj = {
name :"lily",
sex:"nan",
say:function () {
alert("aaaaaaaaa");
},
do:fn1
}
console.log(obj.name);
console.log(obj.say());//undefined
obj.do();
调用函数
JavasScipt供4种函数调用模式,函数调用,方法调用,使用call或apply动态调用(后边讲解),使用new间接调用(后边讲解)
函数调用
在默认状态下,函数是不会被执行的,使用小括号可以激活并执行函数。在小括号中可以包含零个或多个参数,参数之间通过逗号进行分隔(可以返回函数并调用)
函数的返回值
- 函数提供两个接口与外界的交互,其中参数作为入口,接受外界的信息。返回值作为出口,把运算结果反馈给外界。
- 在函数体内,使用return语句可以设置函数的返回值。一旦执行返回语句,将停止函数的运行,并运算和返回返回后面的表达式的值
- 如果函数不包含return语句,则执行完函数体内每条语句后,返回undefiend值。
- JavaScript是一种弱类型语言,所以函数对接收和输出的值都没有类型限制,JavaSeript也不会自动检测输入和输出值的类型
- 可以返回一个计算值 也可以返回多个值(使用对象或者数组)
function fn1(a,b){
var sum = a + b;
return sum;
alert("计算完毕");
}
fn1(1,2);//如果不用变量接受,或者是不使用,那么这个返回值是看不到的
console.log(fn1(1,2)+4);//7
function fn1() {
var a = 1;
return function () {
alert(a);
}
}
fn1()();
var b = fn1();
console.log(b);
a();
function teacherMessage(name,age,sex) {
alert("我的名字是"+name);
alert("我的年龄是"+age);
alert("我的性别是"+sex);
}
var teacher1 = {
name:"lily",
age:"18",
sex:"nv"
}
var teacher2 = {
name:"lily1",
age:"128",
sex:"nan"
}
function teacherMessage(obj) {
alert("我的名字是"+obj.name);
alert("我的年龄是"+obj.age);
alert("我的性别是"+obj.sex);
}
teacherMessage(teacher1);
function fn1() {
var a = 1;
return;//返回的是undefined
alert(2);
}
console.log(fn1());
方法调用
- 当一个函数被设置为对象的属性值时,称之为方法,使用点语法可以调用一个方法
var obj = {
name :"lily",
sex:"nan",
say:function () {
alert("aaaaaaaaa");
},
do:fn1
}
console.log(obj.name);
console.log(obj.say());//undefined
obj.do();
函数参数
参数是函数对外练习的唯一入口,用户只能通过参数来控制函数的运行。
01.形参和实参
- 形参:在定义函数时,声明的参数变量仅在函数内部可见
- 实参:在调用函数时,实际传入的值
- 一般情况下,函数的实参和形参的数量应该相同,但是JS并没有这样的要求。可以不相同
- 如果函数的实参数量少于形参数量,那么多出来的形参会默认会undefined
- 如果函数实参数量多余形参数量,那么多出来的实参就不能通过形参访问。函数忽略掉多余的实参。
- 可以设定形参默认值或者判断给值
function f(a,b) {
}
f(1,2);
function f2(a,b,c) {
console.log(a,b,c);//1 2 undefined
console.log(typeof c);//undefined
}
f2(1,2);
function f3(a,b,c) {
console.log(a,b,c)
}
f3(1,2,3,4,5,6,7)
//书写一个函数 计算 三个值的和
function add(a,b,c) {
return a+b+c;
}
console.log(add(1, 2));//NaN
//直接给函数形参默认值
function add(a,b,c=0) {
return a+b+c;
}
console.log(add(1, 2));
//判断参数是否存在,否则给出默认值
function add(a,b,c) {
c = typeof c === "undefined" ? 0 : c;
typeof c === "undefined" ? c = 0 : c;
return a+b+c;
}
console.log(add(1, 2));//NaN
获取参数个数
- 使用arguments对象的length属性可以获取函数的实参个数。
- argument对象只能在函数内可见,因此arguments.legth也只能在函数体内使用。
- 使用函数对象的length属性可以获取函数的形参个数,该属性为只读属性,在函数体内,体外都可以使用
function add(a,b,c) {
alert(add.length)//函数形参的个数
alert(argument.length)//获取实参的个数
return a+b+c;
}
alert(add.length)//函数形参的个数
console.log(add(1, 2));
使用arguments对象
- arguments对象表示函数的实参集合,仅能够在函数体内可见,并可以直接访间。
- 参数对象是一个伪类数组,不能够继承Array的原型方法。可以使用数组下标的形式访问每个实参,如参数[0]表示第一个实参
- 通过修改length属性值,可以改变函数的实参个数。
function fn1(a,b,c) {
console.log(arguments.length);//获取实参的个数
}
fn1(1,2,3,4)
//输入一组数字,求平均值的函数
function f() {
//先获取到所有的实参 使用arguments
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum / arguments.length;
}
console.log(f(12, 34, 54, 32, 100, 98, 45, 34, 12));
// 直接使用函数对象的length属性,就可以获取到函数的形参个数
function f3(a,b,c) {
console.log(f3.length);//3
}
f3(1,2,3,4,5,6);
// 检测函数的形参和实参是否一致,如果不一致,则抛出错误
function f4(a,b,c,d) {
if (arguments.length != f4.length) {
throw new Error("参数不一致");
}else{
alert("go")
}
}
f4(1,2,3,4,5)
使用callee
callee是arguments对象的属性,它引用当前argument对象所在的函数,使用该属性可以在函数体内调用自身。
// 检测函数的形参和实参是否一致,如果不一致,则抛出错误
function f4(a,b,c,d) {
if (arguments.length != arguments.callee.length) {
throw new Error("参数不一致");
}else{
alert("go")
}
}
f4(1,2,3,4,5)
var a = 0;
(function (){
a++;
alert(1);
if (a > 1){
return;
}
arguments.callee()
})();
函数的练习
当点击不同的按钮的时候 让页面加载相应的颜色

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>函数的练习</title>
</head>
<body>
<button id="red">红色</button>
<button id="green">绿色</button>
<button id="blue">蓝色</button>
<button id="pink">粉色</button>
<script>
/*
* 当点击不同的按钮的时候 让页面加载相应的颜色
* 改变屏幕的颜色 就是控制body的背景颜色
*/
var oBody=document.getElementsByTagName("body")[0];
var oRed = document.getElementById("red");
var oGreen = document.getElementById("green");
var oBlue = document.getElementById("blue");
var oPink = document.getElementById("pink");
function changeColor(col){
oBody.style.backgroundColor = col;
}
oRed.onclick=function () {
changeColor("red");
}
oGreen.onclick=function () {
changeColor("green");
}
oBlue.onclick=function () {
changeColor("blue");
}
oPink.onclick=function () {
changeColor("pink");
}
</script>
</body>
</html>
函数作用域
变量作用域
变量作用域(scope)是指变量在程序中可以访问的有效范围。也称为变量的可见性。分为全局变量和局部变量
- 全局变量:变量在整个页面中都是可见的,可以被自由的访问
- 局部变量,变量仅能在声明的函数内部可见,函数外是不允许访问的。
- var声明的作用与是按照函数划分的
var a = 1;
function f() {
console.log(a);
}
f();
function f2 (){
var a = 2;
function f3() {
console.log(a);
}
f3();
}
// f3();//f3是局部函数 外部使用不了的
f2();
console.log(a);
执行上下文
- JS引擎并不是一行行的解析和执行代码,而是一段段的去分析和执行,当执行一段代码时,先开始预处理,比如声明提升和函数提升
- 在执行某段js代码的时候,会进行一个准备工作,这个准备工作用专业的说法 叫“执行上下文”,其实执行上下文也是在内存中开辟的一个空间
- js可执行的代码分为3种类型, 全局代码 、 函数代码 、eval代码(忽略)
- 每执行一段代码,都会创建相对应的执行上下文,在脚本中可能存在多个执行上下文
- 因为有太多的执行上下文, JS创建了一个执行上下文栈(stack) 用来管理执行上下文
- 当js开始解析程序的时候,最先遇到的全局代码,此时向执行上下文栈中 压入一个全局执行上下文,全局的一定是在整体运行结束以后才被清空
- 当执行一个函数的时候 会创建一个函数的执行上下文,并压入到执行上下文栈中,只要函数执行完成,会将函数从栈里弹出
function fun3() {
console.log("fun3");
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
/*
//用伪代码来实现上边代码执行上下文的流程
//1.js引擎创建了一个执行上下文栈
var Stack = [];
//2.向栈中押入 全局执行上下文
Stack = [globalContext]
//3.在fun1中创建执行上下文 并压入栈中
Stack.push(<fun1>Context)
//4.在fun2中创建执行上下文 并压入栈中
Stack.push(<fun2>Context)
//5.在fun3中创建执行上下文 并压入栈中
Stack.push(<fun3>Context)
//6.fun3执行完毕
Stack.pop();
//7.fun2执行完毕
Stack.pop();
//8.fun1执行完毕
Stack.pop();
//9整体代码执行结束 全局执行上下文出去
*/
var scope = "hello";
function checkscope() {
var scope = "world";
function f() {
return scope;
}
return f();
}
checkscope();
/*
var Stack = [globalContext];
Stack.push(<checkscope>Context);
Stack.push(<f>Context);
Stack.pop()
Stack.pop()
*/
变量对象
-
每个执行上下文 都有三个重要属性:1.变量对象(VO) 2.作用域链 3.this
-
变量对象是 ECMAScript规范术语。在一个执行上下文中,变量对象才被激活,只有激活的变量对象,其各种属性才能被访问
-
变量对象是与执行上下文相关的数据作用域,储存了在上下文中定义的变量和函数声明
全局上下文的变量对象
window是预定义对象,作为JS全局函数和全局属性的占位符(全局的变量和函数就是window对象属性和方法)- 全局执行上下文的变量对象其实就是全局对象window
函数上下文的变量对象
- 进入执行上下文 不会立马执行代码,只进行分析。此时首先第一步,变量对象包括了函数所有的形参和实参
- 检查所有声明的函数,由名称和对应值 组成一个变量对象的属性 被创建。如果变量对象已经有相同名字的属性,则完全替换
- 检查所有的声明的变量,创建键值对儿
- 变成变量对象的属性,如果变量名和已经声明的形参或函数相同,则变量声明不会干扰已经存在的这类属性
function foo(a) {
var b = 2;
function c() {}
var d = function () {};
b = 3;
}
foo(1, 2);
//模拟以上代码的变量对象:
var AO = {
//arguments是保存所有的的实参
arguments:{
0:1,
1:2,
length:2
},
a:1,
c:function c(){},
b:undefined,
d:undefined
}
//练习
function fn1(a) {
console.log(a); //1
var a = 2;
console.log(a); //2
}
fn1(1);
function fn2() {
console.log(foo); //函数
function foo() {}
var foo = 1;
}
fn2();
作用域链
- 当代码在一个环境中执行时,会创建变量对象的一个作用域链( scope chain),作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
- 作用域链的前端,始终都是当前执行的代码所在环境的变量对象。
- 全局执行环境的变量对象始终都是作用域链中的最后一个对象。
- 标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)
this和调用对象
什么是this
- javascript函数的作用域是静态的,但是函数的调用却是动态的,由于函数可以在不同的运行环境内执行,因此 JavaScript在函数体内定义了this关键字,用来获取当前的运行环境。
- this是一个指针型变量,它动态引用当前的运行环境,具体来说,就是调用函数的对象。
- this永远指向其所在函数的调用者,如果没有所有者则指向全局对象window
this的指向方式
-
默认绑定 :常用的函数调用类型:独立函数调用
可以把这个规则看作是无法应用其他规则的时候 默认的规则,基本指向的是window
function foo() { console.log(this); } foo(); //window var obj = { do: function () { foo(); //foo是直接使用不带任何修饰的函数引用进行调用,因此只能使用默认绑定 规则 }, }; obj.do(); -
隐式绑定
当函数引用有上下文对象的时候(obj),隐式绑定规则会把函数中的this绑定到这个上下文对象上
function foo() { console.log(this.a); } var obj = { a: 2, foo: foo, // }; obj.foo(); // //当foo调用的时候,它的落脚点确实是指向的obj对象,当函数引用有上下文对象的时候(obj),隐式绑定规则会把函数中的this绑定到这个上下文对象上 -
隐式绑定可能会出现隐式丢失的问题 :被隐式绑定的函数,会丢失了绑定对象
function foo() { console.log(this.a); } var obj = { a: 2, foo: foo, }; var fn1 = obj.foo; var a = "hello"; fn1(); //hello 虽然fn1是obj.foo的一个引用,但是实际上它的引用是foo函数本身,因此fn1其实是一个不带任何修饰的函数调用,属于默认绑定 // function foo() { console.log(this.a); } function doFoo(fn) { fn(); //传参也是隐式赋值,所以传递函数也是隐式赋值,赋值的是foo函数本身 } var obj = { a: 2, foo: foo, }; doFoo(obj.foo); -
显式绑定
function foo() { console.log(this.a); } var obj = { a: 2, }; foo.call(obj); //2 //通过call 调用foo时,把foo的this强制的绑定给了obj上 -
new绑定
构造函数只是一些使用new操作符被调用的函数,使用new调用函数的时候,会构造一新的对象,这个时候 就把新的对象绑定给了函数的this上
function Foo(a) { this.a = a; } var bar = new Foo(2); console.log(bar.a); //2
怎么判断this指向
- 函数是否在new中调用,如果是的话,this绑定的是新创建的对象
- 函数是否通过call、apply(显示绑定)调用,如果是,则this绑定的是执行的对象
- 函数是否在某个上下文对象中调用(隐式绑定),如果有,则this绑定在这个上下文对象上
- 如果以上都不是 则默认绑定 执行window
//obj.f在上面代码中,obj.f表示在obj对象上调用f函数,则调用对象为obj,此时this就指向obj,this.x就等于obj.x,即返回结果为2
//若把obj.f赋值给变量f1,然后在全局上下文中调用们函数,则f函数体的运行环境在全局上下文中执行,此时this就指向 window, this.x就等于 window.x,即返回结果为1
var x = 1;
var obj = {
f:function(){
console.log(this.x)
},
x:2
}
obj.f();
var f1 = obj.f;
f1();
其他练习
var x = 1;
var obj = {
f:function(){
console.log(this.x);
}
x:2,
}
(obj.f = obj.f)();//正确理解“运行环境”obj.f=obj.f是赋值表达式,把obj.f赋值给obj.f,obj.f是一个地址,把地址赋值给obj.f属性,表达式的运行环境发生在全局上下文中,所以此时函数f内的this就指向了全局上下文的调用对象 window
(false||obj.f)();//逻辑表达式,左侧操作数为false,则运算右侧操作数,返回obj.f的值,即引用地址。由于这个逻辑表达式运算发生在全局作用域内,此时的f函数内this就指向了全局对象
(obj.f,obj.f);//逗号运算表达式,逗号左侧和右侧的obj.f都是一个地址,都被运算一次,最后返回第2个操作数的值,即返回引用地址。由于这个操作发生在全局作用域内,所以f函数内this也指向了全局对象
(obj.f)//his指向obj对象,因为小括号不是一个运算符,它仅是一个逻辑分隔符,不执行运算,不会产生运行环境,当使用小括号调用函数时,此时生成的运行环境就是obj
作用域练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
//demo1
var v = "hello";
(function(){
alert(v);
var v = "world";
})();
alert(v);
//demo2
(function(){
alert(a+b+c);
var a = "1";
var f = function(){};
var b = "2";
var c = "3";
})();
//demo3
(function(){
f2();
f1();
var f1 = function(){
alert(2)
};
function f2(){
alert(1);
}
})();
//demo4
var a = 1;
function outer(){
var b = 2;
function inner(){
var c = 4;
alert(a);
}
inner();
}
outer();
//demo5
var a = 1;
function check(){
a = 100;
alert(a);
}
check();
alert(a);
//demo6
var v = "hello";
if(true){
alert(v);
var v = "world";
}
alert(v);
//demo7
function
