JS中 我认为设计最出色的便是函数,它几乎接近完美,但是也有些许瑕疵
函数对象
JS中的函数就是对象, 对象是 key value对的集合并拥有一个连到原型对象的隐藏连接,对象字面量产生的对象连接到Object.prototype 函数对象连接到Function.prototype。每个函数在创建时都会附加两个隐藏属性: 函数的上下文和实现函数行为的代码。
因为函数式对象,所以它们可以像其他值一样被使用,函数可以保存变量 对象和数组, 函数可以被当做参数传递给其他函数,函数也可以再返回函数,而且,因为函数是对象,所以函数可以拥有方法。
函数的与众不同在于他们可以被调用。
函数字面量
var add = function(a,b){
return a+ b;
}
- 第一个为保留字 function
- 第二部分为函数名,可以被省略 如上例,这个被称为匿名函数
- 函数的参数,它们不像普通的变量那样将初始化为undefined 而是该函数被调用时初始化实际提供的参数
- 大括号内部的一组语句,这些是函数的主体。
调用
调用一个函数会暂停当前函数的执行,传递控制权和参数给新函数,每个函数还接受两个附加的参数: this和arguments,在刚写JS的代码的时候经常被this搞崩溃,this在面向对象编程的时候非常的重要,它的值取决于调用的模式。JS中一共有六种调用模式: 每一种调用方式在如何初始化this存在差异性。
- 方法调用 保存为一个对象的属性的时候
- 函数调用 不是为一个对象的属性的时候
- 构造器调用
- apply call调用
- bind()调用 es6 用来返回一个重新绑定this的函数
- 箭头符号调用 es6 this的绑定从它的封闭域来绑定
函数的参数问题: 当实际的参数的个数和形式参数的个数不匹配时,不会导致运行时错误,如果实际参数过多,过多的参数会被忽略掉,如果实际参数过少,则缺失的值会被替换为undefined 对参数值不会进行类型检查,任何类型的值都可以呗传递给任何参数
this
调用点 为了理解this绑定,我们不得不理解调用点: 函数在代码中被调用的位置。在实际中,我们要考虑到调用栈,首先来段代码来熟悉一下调用栈和调用点
function baz() {
// 调用栈是: `baz`
// 我们的调用点是global scope(全局作用域)
console.log( "baz" );
bar(); // <-- `bar`的调用点
}
function bar() {
// 调用栈是: `baz` -> `bar`
// 我们的调用点位于`baz`
console.log( "bar" );
foo(); // <-- `foo`的call-site
}
function foo() {
// 调用栈是: `baz` -> `bar` -> `foo`
// 我们的调用点位于`bar`
console.log( "foo" );
}
baz(); // <-- `baz`的调用点
函数是如何被调用的决定了函数执行期间this指向哪里,this在一般人看来有点头晕在于亮点1. this不是在编译的时候确定的,相反作为一个动态语言来说 this是在函数运行的时候来确定的,2. this如何绑定取决于它是如何被调用的。
-
函数调用
function foo(){ console.log(this.a); } var a = 2; foo();
函数调用来说 this 默认是绑定在全局变量中, 如果js运行在浏览器中则为window 如果是node.js则为undefined。
-
方法调用
如果函数式作为一个对象的属性来调用的话,也就是调用点是有一个环境对象(context object),当函数是在一个对象中 通过 . 符号来触发的时候 ,此时函数中的this 默认是指向它所在的对象种。
function foo(){ console.log(this.a); } var obj = { a : 2, foo: foo } obj.foo();// 2
调用点使用Obj环境来引用函数,因为obj是foo调用的this 所以 this.a就是obj,a的同义词
只有对象属性引用链中的最后一层是影响调用点的
function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.foo(); // 42
this丢失 在方法调用的过程中,最常见的问题便是this丢失导致的错误。先看下端代码
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var bar = obj.foo; // 函数引用! var a = "oops, global"; // `a`也是一个全局对象的属性 bar(); // "oops, global"
上面代码 ,首先bar 看上去是obj.foo的引用,实际上其实它不过是foo的自己的引用而已,另外,起作用的调用点是bar() 为普通的函数调用,导致此处的this 其实是绑定到全局变量上面。
这种错误在回调函数比较常见
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var a = "oops, global"; // `a`也是一个全局对象的属性 setTimeout( obj.foo, 100 ); // "oops, global"
不管是哪种改变this的方式,你都不能真正地控制你的回校函数引用如何被执行,所以一般我们需要固定this来解决问题
-
apply call调用
我们可以明确的指定this来明确绑定this, 允许我们强制函数的this指定我们需要的参数
function foo() { console.log( this.a ); } var obj = { a: 2 }; foo.call( obj ); // 2
在函数固定绑定中 有一个小技巧,我们可以利用闭包的概念来实现硬绑定来防止我们需要绑定的this没改变。
function foo() { console.log( this.a ); } var obj = { a: 2 }; var bar = function() { foo.call( obj ); }; bar(); // 2 setTimeout( bar, 100 ); // 2 // `bar`将`foo`的`this`硬绑定到`obj` // 所以它不可以被覆盖 bar.call( window ); // 2
-
构造器绑定
something = new MyClass(..);
它看起来和我们之前使用的面向对象的语言是一样,其实Js的机制和new在JS的用法面向对象的功能,没有任何联系。
首先,让我们重新定义JavaScript的“构造器”是什么。在JS中,构造器 仅仅是一个函数,它们偶然地被前置的
new
操作符调用。它们不依附于类,它们也不初始化一个类。它们甚至不是一种特殊的函数类型。它们本质上只是一般的函数,在被使用new
来调用时改变了行为。当在函数前面被加入
new
调用时,也就是构造器调用时,下面这些事情会自动完成:- 一个全新的对象会凭空创建(就是被构建)
- 这个新构建的对象会被接入原形链([[Prototype]]-linked)
- 这个新构建的对象被设置为函数调用的
this
绑定 - 除非函数返回一个它自己的其他 对象,这个被
new
调用的函数将 自动 返回这个新构建的对象。
-
判断this
现在,我们可以按照优先顺序来总结一下从函数调用的调用点来判定this
的规则了。按照这个顺序来问问题,然后在第一个规则适用的地方停下。
-
函数是和
new
一起被调用的吗(new绑定)?如果是,this
就是新构建的对象。var bar = new foo()
-
函数是用
call
或apply
被调用(明确绑定),甚至是隐藏在bind
硬绑定 之中吗?如果是,this
就是明确指定的对象。var bar = foo.call( obj2 )
-
函数是用环境对象(也称为拥有者或容器对象)被调用的吗(隐含绑定)?如果是,
this
就是那个环境对象。var bar = obj1.foo()
-
否则,使用默认的
this
(默认绑定)。如果在strict mode
下,就是undefined
,否则是global
对象。var bar = foo()
箭头函数
一般我们遵循上面的四种规则,ES6引入了一种不适合上面这些规则的函数: 箭头函数。箭头函数不是靠关键字function来声明的,而是所谓的箭头函数: =》 ,箭头函数从封闭它的作用域(function global)采取this绑定。
function foo() {
// 返回一个arrow function
return (a) => {
// 这里的`this`是词法上从`foo()`采用
console.log( this.a );
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是3!
foo中会创建的箭头函数捕捉foo调用的this,因为foo()被this绑定到obj1,所以bar(被返回的箭头函数的一个引用),一个箭头函数的this绑定是不能被覆盖的。
网友评论