JS函数

函数

函数包含一组语句,是JS的基本模块单元,用于代码复用、信息隐藏、组合调用。函数用于指定对象的行为。一般来说,所谓编程就是讲一组需求分解成一组函数与数据结构的技能。

函数对象

在JS中函数就是对象,是一个特殊的对象,是Function类的实例。

function fn(){

}
// 函数就是对象,只是一个特殊的对象,函数Function类的实例。
console.log(typeof fn);// function

函数对象是“名值对”的集合并拥有一个连接原型对象的隐藏连接。

函数对象在内存中使用“键值对”存储,函数名作为键名(栈),函数体作为值并指向内存中一个名为Function的对象。

每个函数在创建时拥有两个附加的隐藏属性:

  • 函数的上下文
  • 实现函数行为的代码

每个函数在创建时拥有一个prototype属性,其值是一个拥有constructor属性且值为该函数的对象。

这和隐藏连接到Function.prototype完全不同。

ES中的函数对象主要有3种作用:

  • 作为object对象使用,函数对象本身是一种增强的object对象,这是主要使用其中的属性。
  • 作为方法来处理具体业务,这是函数最常见的用法。
  • 用户创建object类型对象

对应函数的3种作用,函数对象有3种子类型:

  • 自身属性
  • 内部变量
  • 所创建对象的属性

函数定义/创建函数

ES中创建函数有3种方式:

  • 函数声明
  • 函数表达式
  • 使用Function类来创建

常见定义函数有两种方式:

  • 函数声明
// 函数声明
function 函数名(参数){
  函数体
}
function log(msg){
  console.log(msg);
}

关于函数声明,有一个重要特征是函数声明提升,在执行代码前会先读取函数声明,意味着可以把函数声明放在调用它的语句之后。

log('hello');// 代码执行前会先读取函数声明
function log(msg){
  console.log(msg);
}
  • 函数字面量/函数表达式

函数表达式的结构是将函数声明中的函数名去掉,将创建后的结果赋值给一个变量。

// 由于函数是一个对象,所以可使用变量形式进行定义,看起来好像是常规的变量赋值语句。
// 这种情况下创建的函数叫做匿名函数/拉姆达函数(anonymous function),因为function关键字后没有标识符。
// 函数表达式与其他表达式一样,在使用前必须先赋值。
log('hello');// Uncaught TypeError: log is not a function
var log = function(msg){
  console.log(msg);
}

使用对象字面量产生的对象连接到Object.prototype,函数对象连接到Function.prototype,该原型对象本身连接到Object.prototype

  • 使用Function类创建

Function类是ES中一个内置的特殊的function

console.log(typeof Function); // function

Function是一个特殊的function,使用它创建的对象并不是object类型,而是function类型。

var log = new Function(‘msg’, 'console.log(msg);');
console.log(typeof log);// function

使用Function类创建函数时,编译器不会检查其正确性,如果有错误只有在调用时才能发现,在创建时不会报错。另外,使用Function类创建的函数效率相对而言比较低。

不同创建函数方法之间的关系是什么样的呢?

在ES中所有的数据只有两种存在的形式:

  • 对象属性
  • 变量

函数也不例外,无论是对象的属性还是变量都是“名值对”的结构,所以函数在内存中也采用这种“名值对”的结构。

使用函数表达式方式创建函数中,可以很容易看明白这一点。函数表达式将所创建的对象赋值给一个变量。其实,在函数声明方式创建函数的时候,ES在背后自动帮我们做了很多事情,首先它创建了函数对象,然后又创建了跟函数名同名的变量,最后将创建出来的函数赋值为变量。

function fn(){ 

}
// // 首先创建了函数对象,然后又创建了跟函数名同名的变量,最后将创建出来的函数赋值为变量。
var fn = function fn(){

}
函数的创建

 function fn(){ 
}
//  函数是通过对象的拷贝来实现赋值
var fn2 = fn;
函数的创建

为进一步理解

function log(msg){
  console.log(msg);
}
var fn = log;
log = null;
fn('hello');// hello

函数表达式
通过函数表达式来创建同样,只是它会创建一个匿名函数,然后在赋值给定的变量。

var anonymous = function(){

}
var anony = anonymous;

函数虽然是一个对象,但与对象却有区别:对象是通过引用的指向来完成对象的赋值,而函数却是通过对象的拷贝来实现的。

  • 函数通过对象的拷贝来实现赋值
function fn1(){
    alert('fn1');
}
var fn2 = fn1;

fn1 = function(){
    alert('fn1 change');
}
fn1();//fn1 change

//函数是通过对象的拷贝来实现赋值
fn2();//fn1

  • 对象通过引用指向实现赋值
var obj1 = new Object()
var obj2 = obj1;
console.log(obj1.name);// undefined

obj1.name = 'alice';
console.log(obj1.name);//alice
console.log(obj2.name);//alice

小结:函数的创建主要有三部分:函数名、参数、函数体,其中参数和函数体属于函数对象自身,而函数名是获取函数对象的一个引用,可视为一个函数对象的快捷方式。函数表达式会直接将所创建的函数返回给一个快捷方式,而函数声明会在内部自动创建一个和函数同名的快捷方式。

函数重载

函数的参数和调用没有关系

function sum(num1){
    return num1;
}
function sum(num1,num2){
    return num1+num2;
}
alert(sum(10));//NAN
alert(sum(10,20));//30

函数是对象,不存在重载仅存在覆盖。

function sum(num1,num2){
    return num1+num2;
}
function sum(num1){
    return num1;
}
alert(sum(10));//10
alert(sum(10,20));//10

为便于理解,转换为变量写法:

var sum = function(num1,num2){
    return num1+num2;
}
//此时sum所指向的内存空间已经从两个参数的函数转变为仅有一个参数的函数中
var sum = function(num1){
    return num1;
}
alert(sum(10));//10
alert(sum(10,20));//20

总结:在Javascript中函数不存在重载

函数即对象

ECMAScript最令人感兴趣的可能莫过于函数实际上是功能完整的对象

var funcname = new Function(arg1,arg2,...,argN,funcbody);
// 函数定义的对象方式
var fn = new Function('num1','num2','alert(num1+num2)');
fn(10,20);//30

//转变为函数定义写法为
function fn(num1,num2){
    alert(num1+num2);
}
fn(10,20);//30

函数的传递

由于函数就是对象,所以可以将函数作为参数进行传递

function callFn(fn,arg){
    return fn(arg);
}

function say(str){
    alert('hello,'+str);
}

callFn(say,'javascript');//hello,javascript

为便于理解函数即对象,可将say函数转换为参数形式:

var say = function(str){
    alert('hello,'+str);
}

为理解函数即对象,可将函数作为返回值

function fn(arg){
    var rel = function(num){
        return arg+num;
    }
    return rel;
}
var func = fn(10);//此时func是一个函数对象,可完成调用。
alert(func(20));//30
//简化写法
fn(10)(20);

:Javascript排序

Javascript sort()

  • 用法:sort()用于对数组元素进行排序
  • 语法:arrayObject.sort(sortby)
  • 参数:sortby可选规定排序顺序,必须是函数。无参数时将按字母顺序对数组元素排序,实质上是按字符编码顺序进行排序。
  • 返回值:数组在原数组上进行排序,不生成副本。
var as = [-2,31,50,3,50,-29];
function sortByNum(a,b){
    return a-b;
}
as.sort();
console.log(as);//[-2, -29, 3, 31, 50, 50]

若要按照其他标准排序,需提供比较函数,该函数比较两个值,返回一个用于说明两个值相对顺序的数字。

比较函数应具有两个参数a和b,其返回值为:

  • a<b 排序后数组中a在b前,则返回小于0的值。
  • a=b 返回0
  • a>b 返回大于0的值

扩展:按数值排序

function sortByNum(a,b){
    return a-b;
}

var numarr = [-2,31,50,3,50,-29];
numarr.sort(sortByNum);
console.log(numarr);//[-29, -2, 3, 31, 50, 50]

当数组中存在字符串类型的数字时,并不影响数值排序,因为在Javascript中 <kbd>+</kbd> 被用作字符串连接运算符,而 <kbd>-</kbd> 仍旧是算术运算符。

var arr = [0,2,'-2',20,'11',1,'190'];
arr.sort(function(a,b){
    return a-b;//字符串数字与数值进行减法运算时会将字符串转换为数值后计算
});
console.log(arr);//["-2", 0, 1, 2, "11", 20, "190"]

若出现数字和字符串混合形式的数组元素,该怎么办呢?

var arr = [0,'2',-1,'3px',20,13,9];
arr.sort(function(a,b){
    return parseInt(a) - parseInt(b);//手工转换为整数
});
console.log(arr);//["-2", 0, 1, 2, "11", 20, "190"]

扩展: 根据对象排序

根据对象属性进行排序

function Person(name,age){
    this.name = name;
    this.age = age;
}
var p1 = new Person('carl',29);
var p1 = new Person('vivi',18);
var p1 = new Person('mark',30);
var arr = [p1,p2,p3];

//按姓名排序
function sortByName(obj1,obj2){
    if(obj1.name > obj2.name){
        return 1;
    }else if(obj1.name == obj2.name){
        return 0;
    }else{
        return -1;
    }
}

//按年龄排序
function sortByAge(obj1,obj2){
    return parseInt(obj1.age) - parseInt(obj2.age);
}

//数组排序
arr.sort(sortByName);

//页面指定位置输出
function show(id,arr){
    var el = document.getElementById(id);
    for(var i=0; i<arr.length; i++){
        el.innerHTML += arr[i].name + ' '+arr[i].age+'<br/>';
    }
}
show('person',arr);

由于对象属性不固定,按对象属性排序非常不灵活,解决此问题可采用使用函数返回值调用

function Person(name,age){
    this.name = name;
    this.age = age;
}
var p1 = new Person('zen', 60);
var p2 = new Person('alice',20);
var p3 = new Person('ben', 28);
var arr = [p1,p2,p3];

//使用函数返回调用按对象属性排序
function sortByAttr(attr){
    var fn = function(obj1,obj2){
        if(obj1[attr] > obj2[attr]){
            return 1;
        }else if(obj1[attr] == obj2[attr]){
            return 0;
        }else{
            return -1;
        }
    };
    return fn;
}

//按对象名称排序
arr.sort(sortByAttr('name'));
//按对象年龄排序
arr.sort(sortByAttr('age'));

函数内部属性

arguments

在函数对象中有一个属性叫作arguments,通过此属性可获取相应的参数值,此属性是一个数组,其实就是传递进来的参数。

function fn(){
    console.log(arguments);//[1, 2, 3, 4, 5, 6]
    for(var i=0; i<arguments.length; i++){
        console.log(arguments[i]);
    }
}
fn(1,2,3,4,5,6);

递归求阶乘

//使用递归求阶乘的方法
function factorial(num){
    return num<=1 ? 1 : num*factorial(num-1);
}
factorial(3);//6

var fn = factorial;
console.log(fn(3));//6

使用递归求阶乘的方式中,递归调用的函数名和函数自身的名称耦合在一起,若将来函数名称更改后,递归就会失效。

function factorial(num){
    return num<=1 ? 1 : num*factorial(num-1);
}
var fn = factorial;
factorial = null;
fn(3);// Uncaught TypeError: factorial is not a function

使用argument.callee()来反向调用函数。

function factorial(num){
    return num<=1 ? 1 : num*arguments.callee(num-1);
}
console.log(factorial(3));//6

小结:使用arguments对象中的callee(),传入函数参数可反向调用函数以实现函数名的解耦合。在Javascript中通常都是此种方式来完成递归。

this

当需要创建一个类的时候,设置类的属性和方法需通过this关键字来引用。但是需要特别注意的是this关键在在调用时会根据不同的调用对象变的不同。

var color = 'red';
function show(){
    console.log(this.color);
}
function Circle(color){
    this.color = color;
    this.show = show;
}
var circle = new Circle('yellow');
circle.show();//yellow:此时调用的对象为circle
show();//red:此时调用的对象等于window,因此此处的this就是window

函数属性

函数的属性是能够直接使用函数名来调用的属性,函数有2个非常重要的属性length和prototype,length指的是该函数所期望传递过来的参数个数。

length

函数的length表示该函数所期望的参数个数。

function fn1(){}
function fn2(arg1){}
function fn3(arg2){}
console.log(fn1.length);//0
console.log(fn2.length);//1
console.log(fn3.length);//2

函数还具有2个非常有趣的方法call()和apply(),它们可通过函数名称来调用函数。

call & apply

  • call()是与经典对象冒充方法最相似的方法,其语法结构是:call(obect,arg1,arg2,...,argN)。call()的第一个参数用作this的对象(上下文对象),其他参数都直接传递给函数自身。
  • apply(object, arglist)有两个参数,第一个是调用的上下文,第二个是参数数组,可直接使用arguments。
  • call()和apply()常用于Javascript面向对象的继承中。
function sum(num1,num2){
    return num1+num2;
}
function sumCall(num1,num2){
    return sum.call(this,num1,num2);//call使用的是参数列表,用于确定的参数个数。
}
function sumApply(){
    return sum.apply(this,arguments);//apply使用的参数数组,用于不确定参数个数中。
}
console.log(sumCall(1,2));//3
console.log(sumApply(1,2));//3

使用call()和apply()可发现,对象中可不依赖于任何行为。

var color = 'red';
function show(){
    console.log(this.color);
}
function Color(color){
    this.color = color;
}

var obj = new Color('yellow');
show.call(this);//red
show.call(obj);//yellow

小结:使用call()和apply()之后,对象中可无需定义任何方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容

  • 在js中,函数本身属于对象的一种,因此可以定义、赋值,作为对象的属性或者成为其他函数的参数。函数名只是函数这个对象...
    bjhu电net阅读 541评论 0 5
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,234评论 0 4
  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,564评论 0 5
  • JavaScript 函数定义 JavaScript 使用关键字 function 定义函数。函数可以通过声明定义...
    hx永恒之恋阅读 439评论 0 1
  • “作者近藤麻理惠是日本知名的整理咨询顾问,她独创的心动整理法能将看不见地板的垃圾屋,整理成酒店套房般清爽的心动空间...
    瑜头阅读 128评论 0 0