1、函数
1、函数不仅仅可以调用,还能作为参数和返回值,这一点要十分注意。
2、浅谈对象
1、由于在函数当中我们要去使用到面向对象的知识,所以我们这里简单的讲讲对象。
2、什么是对象,对象就是值的集合,而且是任意值的集合。
3、对象的创建方式有3种:
(1)var 对象名 = { 多个键值对} ;
(2)构造方法:var 对象名 = new Object();
(3)老版本(有兼容性问题):Object.create();
4、对象属性的调用(两种方法)
(1)对象.属性名:cat.type
(2)对象[‘属性名’]:cat[‘type’](这种方式比较强大,里面还能做字符串拼接的一些运算)
5、对象属性的增加和删除
(1)增加就直接:对象.新属性 = 值
(2)删除:delete 对象.属性
6、判断属性是否存在于对象当中
(1)使用in关键字,‘属性’ in 对象,返回值为布尔类型
7、对象属性名的循环(for in关键字)
/**
* 1、对象的定义
**/
var cat= {
'name':'jerry',
'age':4,
'family': ['father','mamam'],
'speak': function() {
console.log('miaomiao');
},
'friend': {
'name': 'tom',
'age': 4
}
};
//var cat = new Object();
/**
* 2、对象属性的增加和删除(delete)
**/
cat.type = '加菲猫';
console.log(cat['type']); // 加菲猫
delete cat.type;
console.log(cat['type']); // undefined
/**
* 3、判断属性是否存在于对象当中(in)
**/
console.log('name' in cat); //true
console.log('pick' in cat); //false
/**
* 4、对象属性名的循环(for in关键字)
**/
for(var p in cat){
console.log(p); //所有的属性名
console.log(cat[p]) //所有属性的值,注意只能使用方括号这种形式,因为p是个字符串
}
3、函数的介绍
1、函数的作用:代码的重用,一次封装,四处调用
2、函数名的作用:使用函数名来直至函数在内存中的位置,通过函数名可以将函数从内存中拎出来,所以函数中的函数名很重要,但是函数名又可以被省略,所以我们会在函数的调用哪一章去详细的解释具名函数和匿名函数的调用。
3、函数的参数:书写的习惯,参数之间的逗号后面使用一个空格,JS的参数在定义和使用可以不一致。
4、函数体:函数体是一个局部作用域。
5、当一个函数的定义和调用时都发生了什么?
(1)函数的定义就是封装了一段代码,没有编译,也没有执行。
(2)函数的执行就比较复杂了:如下代码
function add(num1, num2) {
return num1 + num2;
}
首先add函数在被定义的时候,在add所在作用域中创建了一个add的结点,函数执行的时候在add所在的作用域中去创建了一个全新的局部作用域,但是这个节点和作用域是分开的,所以当函数执行完毕后这个局部作用域和其中的局部变量都被销毁了,但是add这个结点还存在,所以add可以被重复调用。
6、为什么要使用函数?
(1)代码的复用
(2)统一的修改和维护
(3)函数可以增加程序的可读性(代码不应该包含太多的逻辑细节,否则会增加阅读的思考和分析)
7、函数的本质
(1)函数的本质是可以调用的对象,其中两个特性,一个是调用,一个是对象。
(2)由于函数是对象,所以函数也有两种定义方式,就是字面量的方式和构造函数的方式
//字面量的方式(函数声明和函数表达式)
function add(num1, num2) {}
var add = function() {}
var add = function fn() {}//这种方法我们后面讲fn到底有什么用,此时的fn是个局部变量,只能在函数内部使用
//构造函数的方式
var add=new Function('num1','num2','return num1+num2')//注意这里面都要用引号
//三种方式的区别
(1)构造函数的方式写法难看,而且效率比较低(字符创解析和实例化对象)
(2)字面量的两种方式的区别就是变量提升的区别:如果是传统的函数定义,函数的调用可以发生在定义之前,如果是函数表达式的定义,则函数的调用不能在函数表达式之前,因为有变量提升,传统的函数定义是将整体提升,而且比变量优先,但是函数表达式提升了变量但是没有赋值,所以不能在函数表达式之前调用函数
(3)由于函数是对象,函数也能添加属性和方法,这种特性导致能向函数上面添加一下属性还缓存一些东西,比如计算1+1,那么第一次计算需要计算,但同时能将计算的值缓存下来,下次直接使用,这个特性我们后面说。
(4)由于函数是对象,函数也能作为数据值使用:函数作为对象存储在堆内存中,我们需要在栈中找一个变量来存放函数的在堆中的地址:所以这里的函数表达式就是这样子:var add = function() {},而且此时不具有函数名,是匿名函数
var add = function () {
return 1;
}
function addd() {
return 2
}
console.log(add); //函数本身 : ƒ () {return 1; }
console.log(add()); //函数返回值1
console.log(addd); 函数本身 :ƒ addd() { return 2}
console.log(addd());//函数返回值1
(5)由于函数是对象,函数还能作为参数来使用,比如说setTimeout函数,第一个参数就是一个匿名函数,
(6)由于函数是对象,函数还能作为返回值来使用。
4、函数的调用
1、普通函数的调用
(1)命名函数:直接就是 函数名(),例如:
function add() {}
add();
(2)匿名函数:
a、将匿名函数赋值给变量,调用的方式和命名函数一样
var add = function() {}
add();
b、匿名函数直接调用:因为函数名和函数表达式中的变量,本身就是函数体本身,所以对于匿名函数直接在函数本体后面加()
function () {}();
但是这实际上是不对的,因为js解析器当看到function的时候就认为这是个声明,声明和执行是不能同时执行的,所以我们的解决方法就是不让function打头就行了,所以我们这样:将整个函数体的部分用()包裹,这样是没有问题的,因为函数也是对象,对象用()包裹怎么会出问题?只不过没有实际的意义,但是能避免使用function打头。
(function() {
....
})()
//或者下面这样
(function() {
}())
//或者是这样(不常用,但要认识,有人会这样写)
!+-~function() {
...
}()
2、递归调用(这种方法不太好,后面使用arguments.callee)
function jiecheng() {
if(arguments[0] == 1) return 1;
return arguments[0]*jiecheng(arguments[0]-1);
}
console.log(jiecheng(5))//120
var add1 = function () {
if(arguments[0] == 1) return 1;
return arguments[0]*add1(arguments[0]-1);
}
console.log(add1(6))//720
var add = function jiecheng() {
if(arguments[0] == 1) return 1;
return arguments[0]*jiecheng(arguments[0]-1);
}
console.log(add(4))//24
3、构造函数的调用
(1)没有构造函数,只有函数的构造调用。什么意思,就是不同的调用方式会决定你这个函数是普通函数还是构造函数,实际上函数调用都有返回值,return后面没有东西,或者没有return,返回值就是undefined,而构造函数的返回值始终都是个对象。
function add() {
console.log(1);
}
//普通调用,则add就是普通函数
var a=add();
console.log(a);//undefined
//构造调用,则add就是构造函数
var b = new add() //1
console.log(b); //add {}
4、函数的间接调用
(1)前面我们都是对函数的直接调用,就是add(),但是还有函数的间接调用,主要是两个方法,一个是call,一个是apply,因为函数也是对象,对象当然有属性和方法,这两个函数可以帮助我们间接调用函数。
var name = "全局";
var person = {};
person.name= "局部";
person.getName = function() {
return this.name;
}
console.log(person.getName()); //此时this指向的是person这个对象
console.log(person.getName.call(window))//此时getName函数中的this是window
console.log(person.getName.apply(window))//此时getName函数中的this是window
(2)那么call和apply两个方法中都有几个参数,第一个参数就是this的指向,区别是后面的参数不一样,call传递参数是一个一个传,而apply是以数组的方式传递
function add(num1, num2){
return num1+num2;
}
console.log(add(1,2));
console.log(add.call(window,1,2));
console.log(add.apply(window,[1,2]));
但是call和apply有个更重要的作用就是去判断数据类型,虽然有typeof和instance,但是缺点很明显。
5、参数的使用
(1)函数的参数其实是声明的一种局部变量,如下:所以不能认为arguments和命名参数(我们常叫做形参)是同一个东西,或者认为两者是同一段内存,两者内存独立,虽然arguments[n]和参数的值相等。
function add(num1,num2){
//相当于在局部声明了num1,num2
var num1=undefined,num2=undefined
//当1和2两个实参传递进来是这样执行的,实参赋值给形参
num1 = 1;
num2 = 2
}
add(1,2)
(2)参数的个数
当我们的实参个数小<形参,那么没有赋值的形参依旧保持了undefined的值,所以我们通常使用短路操作,
function add(num1,num2){
//相当于在局部声明了num1,num2
var num1=undefined,num2=undefined
//当1实参传递进来是这样执行的,实参赋值给形参
num1 = 1;
}
//短路操作
function add(num1,num2) {
num1= num1 || 0;
num2= num2 || 0;
}
add(1)
(3)arguments:是一个类数组,类似数组,其实是个对象,每个函数中都独有一个arguments,对象中的属性是‘0’,‘1’,‘2’等等,最后还有个length属性。很重要的是;arguments有个属性callee,arguments.callee指函数本身。所以这里的arguments独有的两个属性length和callee就说完了。
arguments :{
'0':第一个实参的值
'1':第二个实参的值
....
length:除了length本身其他所有的属性的个数
}
function add(num1, num2){
console.log(arguments.callee);
return num1+num2;
}
add();
//结果就是函数本体
//function add(num1, num2){
// console.log(arguments.callee);
// return num1+num2;
//}
那么arguments.callee怎么用?常用在递归当中,因为我们之前在递归中使用的都是函数名,或者是函数表达式当中的变量名,但是函数名可能会被改,我们就用到了arguments.callee,但是假如使用了严格模式,那么arguments.callee就不能用了
function jiecheng() {
if(arguments[0] <= 1) return 1;
return arguments[0]*arguments.callee(arguments[0]-1);
}
console.log(jiecheng(5))//120
所以这个时候我们又想起了之前很鸡肋的一个东西,是函数表达式当中有函数名
var add = function jiecheng() {
if(arguments[0] == 1) return 1;
return arguments[0]*jiecheng(arguments[0]-1);
}
console.log(add(4))//24
(4)arguments.length和函数名.length
首先arguments.length表示的是实参的个数,函数名.length表示的是形参的个数
function jiecheng(n) {
//if(arguments.length!=jiecheng.length)
if(arguments.length!=arguments.callee.length){
throw new Error('不合法的参数')
return;
}
if(n <= 1) return 1;
return n*arguments.callee(n-1);
}
console.log(jiecheng(5,9))//抛出错误
6、什么东西能做为参数?
因为JS当中参数传递都是值值传递,所以数据都能作为参数来传递。
7、函数的返回值(函数在没有return和return后面什么都么有写的情况都有返回值,只不过是undefined)
(1)什么可以作为返回值?
数字,字符串,布尔值,null,数组,对象,函数等等
(2)return对象的时候,千万不要把 { }放在return的下一行,这个已经不是风格问题,而是解析器会自动在return后面结束。