函数也是对象,所有函数都是Function的实例.
1 | a.constructor === Function // true |
创建函数
有两种方法创建函数,函数声明和函数表达式
- 函数声明
1 | function say(word) { |
- 函数表达式
1 | var say = function(word) { |
调用函数
用函数名后接一对括号的形式调用函数,括号内填函数所需参数
1 | say('hi') //hi |
也可以把函数赋值给一个变量,通过变量调用函数.但这样就不能在外部使用原函数名调用了.
1 | var b = function say() { |
匿名函数
函数名可以省略,这种结构被称为匿名函数
1 | function(word) { |
可以将匿名函数赋值给一个变量,通过这个变量调用匿名函数.
1 | var say = function(word){ |
匿名函数常用于事件绑定,闭包储存,立刻执行函数等等
//打印触发事件的元素名
window.onclick = function(event){
alert(event.target.tagName);
}
//声明后立即执行
(function(){console.log('hi')})() //hi
自执行匿名函数(IIFE)
声明匿名函数后,立即执行.需用一对括号包裹,否则会报错
1 | (function(){ |
也有在函数前加叹号的写法(不建议使用)
因为这两个符号会对返回结果产生影响.如果不需要处理返回结果,可以使用这种方法
1 | !function(){console.log('hi')}() |
箭头函数
箭头函数是es6新增函数语法,解决了this的指向问题
1 | var say = (word) => { |
如果函数只有一行,可省略大括号
1 | var say = (word) => console.log(word); |
如果函数只有一个参数,还可省略参数外的括号
1 | var say = word => console.log(word); |
箭头函数没有自己的this,而是使用创建环境上下文的this(在哪创建的就用哪的)
1 | function cat(){ |
return
函数执行时遇到return语句,则停止运行,并返回指定数据
1 | function t() { |
如果函数中没有return语句/只有return,则返回undefined.
1 | function t() { |
arguments
arguments是函数的特殊属性,包含调用函数时传入的所有变量
1 | function print(a, b) { |
未定义的参数,也能用arguments获取到
1 | print(1, 2, 3) // 1 2 3 |
修改arguments项,参数也会随之发生改变,反之亦然.
1 | function test(a){ |
虽然arguments是类数组对象,无法对其使用push pop等方法.
1 | function print() { |
可用Array.prototype.slice或Array.from把arguments转换成的数组
1 | function print() { |
构造函数
构造函数就是普通函数, 使用new关键字调用,返回一个对象
调用构造函数时,会以如下方式运行:
- 新建对象
- 将构造函数的作用域赋给新建对象
- 运行构造函数内的代码
- 返回新建对象
1 | function Cat(name){ |
未绑定在this上的属性不会被反回
1 | function Cat(name){ |
作用域
js中的变量分为两种,局部变量和全局变量.
全局变量
在函数外声明的变量叫全局变量,所有全局变量都被绑定在window下
var v = 1;
console.log(window.v) // 1
在函数中,不使用var关键字声明的变量也是全局变量
(function(){
v = 1;
})();
//v是一个全局变量,因此可以在函数外被获取
console.log(v); //1
console.log(window.v); //1
局部变量
在函数中,用var关键字声明的变量叫"局部变量"
不能在函数外读取函数内的局部变量,但能在函数内读取函数外的变量
如果内部局部变量名和外部变量名相同,内部的局部变量会覆盖外部的变量
var a = 1;
(function(){
var b = 2;
console.log(a); // 1
})()
//b是局部变量,在函数外无法获得.
console.log(b); //ReferenceError: b is not defined
注:函数的参数以及arguments等内部关键字也是局部变量
变量/函数提升
js在编译时,会把所有变量和函数提升到作用域顶部
(function test() {
a = 1;
console.log(window.a); //undefined
var a = 2;
console.log(a); // 2
})();
//实际执行顺序
(function test() {
var a;
a = 1; //var a 被提升到顶部,这里的a = 1实际上是局部变量
console.log(window.a); //undefined
var a = 2;
console.log(a); // 2
})();
函数也会被提升,即便先调用后声明函数.也能正常执行
say(); // hi
function say() {
console.log('hi');
}
//实际的执行顺序
function say() {
console.log('hi');
}
say(); // hi
需注意的是,变量只会被提升,不会立刻赋值.
say(); // TypeError: say is not a function
var say = function(){console.log('hi')};
//实际执行顺序
var say; //虽然变量say被提升到作用域顶端,但右边的匿名函数还没被赋值给say.
say();
say = function(){console.log('hi')}; //运行到这里才被赋值
闭包
什么是闭包?如果一个"函数"能访问作用域中的"变量",则称函数和变量为闭包.
所有Js函数都是闭包,因为他们都能访问全局作用域中的变量
1 | function a(){ |
1 | var arr = []; |
this
this是函数内部的特殊关键字,指向函数的调用位置.
1 在全局范围调用函数,this会指向全局对象(window).
var word = 'hi';
function say(){
console.log(this.word);
}
say(); //hi
严格模式下,函数内的this将不再被封装成对象.
"use strict";
var word = 'hi';
function say(){
console.log(word); // hi
console.log(this.word);
// TypeError: Cannot read property 't' of undefined
}
say(); //hi
2 在对象的方法中调用,指向该对象
var cat = {
name:'anna',
//this指向cat对象
getName: function(){
return this.name;
}
}
cat.getName(); // anna
3 在构造函数中调用,指向新生成的对象
var Cat = function() {
this.name = "anna";
}
Cat.prototype.getName = function(){
return this.name;
}
var cat = new Cat();
cat.name = 'dog';
//虽然新创建对象的prototype上指向原构造函数的prototype,但其中的this仍指向新生成对象.
cat.getName(); // dog
注意,把方法从对象中拿出来调用,也会造成this的指向改变
function Cat() {
this.name = 'anna';
this.getname = function(){
return this.name;
}
}
var c = new Cat();
console.log(c.getname()); // anna
var c = this.getname;
//c中的this指向window
console.log(c()); // c is not a function
4 使用call apply bind方法调用
call apply bind都可以改变函数的上下文,使函数中的this指向传入的第一个参数
function.call(thisArg,arg1,arg2,...)
function.apply(thisArg,[arg1,arg2,...])
function.bind(thisArg,arg1,arg2,...)
call apply bind的区别
1 call和apply会立刻执行函数,而bind会返回一个函数
var cat = {
name: 'anna',
}
function printName() {
console.log(this.name);
}
//把printName的上下文指向cat
printName.call(cat); // anna
printName.apply(cat); // anna
//bind返回一个函数
printName.bind(cat); // ƒ printName()
printName.bind(cat)(); // anna
2 call和apply的区别是接收参数格式不同
call需要把函数参数依序传入,而apply接受一个数组,内含所有函数参数
var num = {
c: 3
}
function sum(a, b){
console.log(a + b + this.c);
}
sum.call(num, 1, 2); //6
sum.apply(num, [1, 2]); //6
//bind的参数格式和call一样
sum.bind(num, 1, 2)(); //6
需注意,对同一个函数多次调用bind方法,只有第一次有效
这是因为bind函数会创建一个新函数(绑定函数),内部包含原函数和call方法,用call把原函数其他对象
var num = {n: 1}
var num1 = {n: 10}
function printN(){
console.log(this.n);
}
printN.bind(num).bind(num1)(); //1
使用new调用构造函数时,bind方法无效
修复this指向
setTimeout和setInterval这类函数中的this指向window,因为在运行的时候上下文环境已经变了
function Person() {
this.age = 0;
console.log(this.age++); // 0
setInterval(function growUp() {
console.log(this.age++); //NaN
}, 1000);
}
new Person();
可以用以下几种方法修复setTimeout和setInterval的指向问题
0 使用箭头函数
function Person() {
this.age = 0;
console.log(this.age++); // 0
setInterval(() => {
console.log(this.age++); //NaN
}, 1000);
}
new Person();
1 储存this到其他变量
function Person() {
this.age = 0;
//把this保存到that中
var that = this;
setInterval(function() {
console.log(that.age++);
}, 1000);
}
new Person();
2 使用闭包保存this
function Person() {
this.age = 0;
setInterval(
// 将this保存在自执行匿名函数中,返回函数upage.
// 函数upage在匿名函数中,可以读取匿名函数中的局部变量.
// 因为upage依赖匿名函数.匿名函数在被执行后,不会被立刻销毁.
// 这样做可以避免不必要的变量污染局部作用域.
(function(self) {
return function upage() {
console.log(self.age++);
}
})(this),
1000);
}
new Person();
3 使用bind绑定this
function Person() {
this.age = 0;
var callback = function() {
console.log(this.age++);
}
//setInterval接受一个函数作为参数,稍后执行.
// 因此这里要用bind,而不是call|apply
setInterval(callback.bind(this), 1000);
}
new Person();
参考
JavaScript 秘密花园
Javascript的this用法 阮一峰
严格模式 MDN
bind MDN
学习Javascript闭包(Closure)阮一峰
箭头函数 MDN