函数
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);
-
callee
和caller
属性
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();
-
a
在i_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
属性,这个属性是指向一个对象的引用,这个对象称做“原型对象”,每个函数都包含不同的原型对象,当将函数做构造函数的时候,新创建的对象会从原型对象上继承属性。
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的方法来调用