JS 函数

函数

js的函数时参数化的:

  • 函数的定义会包括一个称为形参的标识符列表,这些参数会像局部变量一样工作
  • 函数调用会为形参提供实参的值
  • 函数使用它们实参的值来计算返回值,成为该函数调用表达式的值
  • 除了实参之外,每次调用还会有另一个值——本次调用的上下文(就是this的值)
  • 如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法,当通过这个对象来调用函数时,该对象就是此次调用的上下文,也就是该函数this的值

函数的定义

函数使用 function关键字来定义:
1、函数定义表达式
2、函数声明语句

// 函数声明
function init () {
  //do something
}
// 函数定义表达式
var init = function () {
   // do something
}
  • 函数声明语句“被提前”
    可以在定义之前调用
init();
function init () { // }
  • 表达式定义
    要使用一个表达式方式定义的函数之前,必须把它赋值给一个变量,变量的声明提前了,但是给变量赋值是不会提前的,所以定义之前是无法调用的
init(); // 报错   init is not a function 
var init = function () {};
init(); // 成功调用

函数命名

  • 通常以动词或以动词为前缀的词组
  • 通常第一个字符为小写(编程约定)
  • 当函数名包含多个参数时,或以下划线分割,或者驼峰
  • 内部函数或私有函数,通常以下划线为前缀
  • 经常调用的函数指定短名称

函数调用

定义函数并不会执行,只有调用该函数时,它们才会执行,有4种方式调用:
1、作为函数
2、作为方法:保存在一个对象的属性里的js函数
3、作为构造函数
4、通过它们的call()apply()方法间接调用
任何函数只要作为方法调用实际上都会传入一个隐式的的实参——这个实参是一个对象,方法调用的母体就是这个对象。

  • this是一个关键字,不是变量,也不是属性名,不允许给它赋值
  • this没有作用域限制,嵌套的函数不会从调用它的函数中继承this
  • 如果嵌套函数作为方法调用,其this的值指向调用它的对象。
  • 如果嵌套函数作为函数调用,this值不是全局对象(非严格模式)就是undefined(严格模式下)
var o = {
  m:function () {                  // 挂载在对象上的方法 m()
    var self = this;              //  将this的值保存至一个变量中
    console.log(this === o);     // => true this 就是当前这个对象 o
    f();
    function f () {             // 定义一个嵌套函数f()
      console.log(this);       // => Window
      console.log(this === o); // => false  this 的值是全局对象或者undefined
      console.log(self === o); // => true self 变量保存的是外部函数的this
    }
  }
}
var w = function () {
  console.log('w',this); // => 输出对象o
  console.log(o === this); // => true 
}
o.n = w;
o.n(); 
o.m();

构造函数调用
如果函数或者方法调用之前带有关键字new,它就构成构造函数调用

  • 凡是没有形参的构造函数调用都可以省略括号
var o = new Object();
var o = new Object;  // 凡是没有形参的构造函数调用都可以省略括号
  • 构造函数调用创建一个新的空对象,这个对象继承自构造函数的prototype属性,构造函数试图初始化这个新创建的对象,并将这个对象用作其上下文,因此构造函数可以使用this来引用这个新创建的对象。
var o = {
  m:function (x) {                 // 挂载在对象上的方法 m()
    var self = this;              //  将this的值保存至一个变量中
    console.log(this);           // 输出的是对象d
    console.log(this.w);        // => undefined 对象d在调用构造函数的时候还未添加属性 w
    console.log(this === o);   // => false 此时this是调用构造函数创建的d
    this.x = x;                 // => 调用构造函数创建对象时添加的参数,给新创建对象指定 属性 x 并为其复制
    f();
    function f () {             // 定义一个嵌套函数f()
      console.log(this === o); // => false this的值是全局对象或者undefined
      console.log(self === o); // => false 对象 d
      console.log(this);       // => Window 对象
    }
  }
}
var d  = new o.m(1);        // 通过构造函数创建一个新对象
d.w = "scp"
console.log(d.x);           // => 1 调用构造函数时添加的属性值

实参列表(arguments)

  • 标识符arguments是指向实参对象的引用,实参对象是一个类数组对象,可以通过数字下标就能访问传入函数的实参值,而不用非要通过名字来得到实参。
init(1,2,3)
function init(){
  console.log(arguments[0]); // => 1
  console.log(arguments[1]); // => 2
  console.log(arguments[2]); // => 3
}
  • arguments包含一个length属性,用以标识其所包含元素的个数。
init(1,2,3);
function init () {
  console.log(arguments.length); // => 3 传入三个参数
}
  • 实参对象让函数可以操作任意数量的实参。
    如下函数可以接收任意个数的实参,称为“不定实参函数”
max(1,2,3,100);
function max () {
  var max = Number.NEGATIVE_INFINITY; // 负无穷
  for (var i = 0; i < arguments.length; i ++) {
    if (arguments[i] > max) {
      max = arguments[i];
    }
  }
  return max;
}
// 
Math.max(1,2,3,100);
  • calleecaller属性
    1、ES5严格模式中,这俩属性的读写操作都会产生类似错误
    2、非严格模式下, callee属性指代当前正在执行的函数
    3、callee属性在某些时候回非常有用,比如在匿名函数中通过callee来调用自身
var init = function () {
  if (arguments.length < 2){
    arguments.callee(1,2)
  }
  console.log(arguments.length); // => 1   2
}
init(1);

闭包

首先回顾一下变量 作用域 和 作用域链

  • 全局变量拥有全局作用域
  • 在函数内声明的变量只在函数体内有定义,它们是局部变量,作用域是局部性的
  • 函数参数也是局部比那里,它们只在函数体内有定义
  • 在函数体内,局部变量的优先级高于同名的全局变量,如果在函数体内声明的一个局部变量或者函数参数中带有的变量和全局变量重名,那么全局变量就被局部变量所遮盖。
  • 声明局部变量必须使用var,否则会认为是什么一个全局变量或者修改全局变量
var a = 'global';   // 全局变量
function init () {
  var a = 'local';  // 同名的局部变量
  console.log(a); // => "local"
}
init();
  • 函数定义是可以嵌套的,由于每个函数都有自己的作用域,因此会出现几个局部作用域嵌套的情况。
var a = 'global';   // 全局变量
function init () {
  var a = 'local';  // 同名的局部变量

  console.log(a); // => local
  
  function n (){
    var a = 'n local';
    console.log(a); // => n local
  }
}
init();

函数作用域和声明提前

  • js使用函数作用域:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。
  • js的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的,这意味着变量在声明之前甚至已经可用,js的这个特性被非正式的称为“声明提前”
var a = 'global';    // 声明一个全局变量
function f () {
  console.log(a);  // => "undefined" 并没有输出“global”
  var a = 'local';    // 变量在这里声明并赋值,但是变量在函数体内任何地方都是有定义的
  console.log(a); // => "local"
}
// 如上 等价于
function f () {
  var a;                  //   在函数顶部声明了局部变量
  console.log(a);  //   变量存在,但是未赋值,所以值是undefined
  a = "local";        //   对局部变量进行赋值
  console.log(a); //   => "local"
}

作用域链
变量取值是到创建这个变量的函数作用域中取值,如果当前作用域没有取到值,就会到上一级作用域去查,直到查到全局作用域,这么一个查找的过程形成的链条就叫作用域链

  • 最终没有查到会抛出异常
var a = 1;
function i_a () {
  var b = 2;
  function i_b () {
    var c = 3;
    console.log(a + b + c);     // => 6
  }
  i_b(); 
}
i_a(); 
  • ai_b作用域中并不存在,此时就会到上一级作用域i_a中查到,i_a中也不存在变量a,那么就再往上一级到全局中查到了a取值成功。

闭包

  • 函数执行依赖于变量作用域,这个作用域是在函数定义时决定的
  • 函数嵌套
  • 一个函数访问另一个函数的作用域
  • 闭包无处不在,js编写几乎时刻都在发生一个函数访问另一个函数的作用域

利用闭包实现私有属性的读写

function counter(){
  var n = 0;
  return {
    count:function(){
        return n++;
    },
    reset:function(){
        n = 0;
    }
  }
}
var c = counter();
console.log(c.count()); // => 0
console.log(c.count()); // => 1
c.reset(); // 重置变量
console.log(c.count());  // => 0

函数属性、方法和构造函数

length属性

  • arguments.length表示实参个数
  • 函数本身的length属性是只读属性,代表函数的形参数量
function counter(x){
    var arg_length = arguments.length;
    var ex_length = arguments.callee.length;

    console.log("arg_length:",arg_length); // => 2 实际传入两个参数
    console.log("ex_length:",ex_length); // => 1 定义函数时定义了一个参数
}
counter(1,1);

prototype属性

每个函数都包含一个prototype属性,这个属性是指向一个对象的引用,这个对象称做“原型对象”,每个函数都包含不同的原型对象,当将函数做构造函数的时候,新创建的对象会从原型对象上继承属性。

方法的prototype属性

function counter () {
}
var c = new counter();

call()方法和apply()方法

  • call()apply()的第一个实参是要调用函数的母对象,它是调用上下文,在函数体内通过this来获取对它的引用。
    通俗来讲第一个参数是谁要调用函数
var o = {
  name:"my name is o"
}
function f () {
  console.log(this);   // => {name: "my name is o"} 此时this指向调用者o
  console.log(this.name); // => "my name is o"
}
f.call(o);
f.apply(o); // 俩个调用结果是一样的
  • call()的参数直接方进去的,第二第三第 n个参数全都用逗号分隔
var o = {
    name:'my name is o'
}

function counter(){
    console.log(arguments.length); // => 2 传入两个参数
    console.log(this.name);  // => 'my name is o'
    console.log("my height is " + arguments[0] + 'cm'); // => 'my height is 180cm'
    console.log("my weight is " + arguments[1] + 'kg'); // => 'my weight is 70kg'
}
counter.call(o,180,70);
  • apply()的所有参数必须放在一个数组里传入
counter.apply(o,[180,70]);

bind()方法

bind()用来将函数绑定至某个对象。

function f() {
  return this.x + 1;
}
var o = {x:1};
f.bind(o)(); // => 输入2 会把f当做o的方法来调用

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

推荐阅读更多精彩内容

  • 在js中,函数本身属于对象的一种,因此可以定义、赋值,作为对象的属性或者成为其他函数的参数。函数名只是函数这个对象...
    bjhu电net阅读 524评论 0 5
  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,516评论 0 5
  • 1. 函数声明和函数表达式有什么区别 (*) 函数在JS中有三种方式来定义:函数声明(function decla...
    进击的阿群阅读 437评论 0 1
  • 函数只定义一次,但可能被执行或调用任意次。JS函数是参数化的,函数的定义会包括一个称为形参的标识符列表,这些参数在...
    PySong阅读 512评论 0 0
  • 函数只定义一次,但可能被执行或调用任意次。JS函数是参数化的,函数的定义会包括一个称为形参的标识符列表,这些参数在...
    PySong阅读 307评论 0 0