创建方式
- 函数表达式
var fun1 = function fn() {
console.log(111);
};
console.dir(fun1);
fun1();
- 函数声明式
function fun2() {
console.log(222);
}
console.dir(fun2);
- JS内置的Function构造函数创建
var fun3 = new Function('console.log(666); return 11;');
console.log(fun3());
调用函数
当调用函数时,如果没有return语句则返回undefined
- 全局中的函数
function fun1() {
console.log('这是全局中的函数');
}
fun1();
window.fun1();//this指向window
- 对象中的函数
var obj = {
fn: function () {
console.log('这是对象中的函数');
}
};
obj.fn();//this指向对象
- 构造函数
function Person() {
this.name = '大海';
}
var person1 = new Person();
- 数组中的函数
var arr = [
function () {
console.log('这是数组中的函数');
console.log(this);
},
666
];
arr[0]();
- 函数被间接调用 call apply是函数的方法
function fn() {
console.log('这个函数要被调用了 ---call ---apply', this);
}
fn.call();
fn.apply(obj);//改变this的指向
函数参数
参数的分类 显示参数:定义函数的时候的形参 隐式参数:arguments;
- 显示参数:在声明/定义函数时,小括号内的元素叫做形参,在函数调用时,小括号内的元素叫做实参
function fun(params1, params2) {//params1, params2形参
console.log(params1, params2);
console.log(111);
console.log(arguments); // 实参组成的类数组对象
}
fun(1,2);//1,2是实参
- 隐式参数:当函数调用时,实参会放在arguments这个数组里
隐式参数arguments和显示参数存在对应关系(有实参的形参才会和arguments形成对应的关系 没有则不会)
function fun(a, b) {
console.log(a, b); // 10 20
a = 100;
console.log(arguments); // [10, 20]
arguments[0] = 9999;
console.log(a);
}
fun(10, 20);
function fun1(a, b) {
console.log(a, b); // 100 undefined
console.log(arguments); // [100]
arguments[1] = 300;
console.log(b); // undefined
}
fun1(100);
- 参数的个数
在函数内部使用arguments.length可以得到实参的个数,在函数为外部可以使用函数名.length得到实参个数
function fun(a, b, c, d) {
console.log(arguments.length); // 实参的个数
// for (k of arguments) {
// console.log(k);
// }
console.log(arguments.callee.length); // 形参的个数
if (arguments.length == arguments.callee.length) {
alert('形参的个数和实参的个数相等')
} else {
alert('形参的个数和实参的个数不相等')
}
};
fun(11, 22, 33);
自调用函数
立即执行函数执行完毕后空间销毁,即使是函数的声明式也会被当作表达式进行执行,最后变成匿名函数,调用完没有人能再引用这个函数的地址
- 函数表达式的自调用
var fun = function fn1() {
var x = 'Hello,JavaScript1';
console.log(x);
}();
console.log(fun); // undefined
var fun1 = (function fn2() {
var x = 'Hello,JavaScript2';
console.log(x);
}());
var fun2 = (function fn3() {
var x = 'Hello,JavaScript3';
console.log(x);
})();
fn1(); // fn1 is not defined
fn2(); // fn2 is not defined
fn3(); // fn3 is not defined
- 函数声明式的自调用
// 语法检查不通过
/* function fun1() {
var y = 'hello, Function!';
console.log(y);
}(); */
var res = (function fun2() {
var y = 'hello, Function!';
console.log(y);
return 11;
}());
(function fun3() {
var y = 'hello, Function!';
console.log(y);
})();
fun2(); // fun2 is not defined
fun3(); // fun3 is not defined
// 函数自调用与逗号运算符练习1
function test(a, b, c, d) {
console.log(a + b + c + d);
}
(1, 2, 3, 4);
function test(a, b, c, d) {
console.log(a + b + c + d);
}
var res = (1, 2, 3, 4);
console.log(res);
// 函数自调用与逗号运算符练习2
var f = (
function f() {
return "1";
},
function g() {
return 2;
}
)(); // (function g() {return 2})()
console.log(typeof f); // a:function b:number c:string
作用域链
- 函数产生(定义)的时候继承当前的作用域链
- 函数在被调用的时候 产生AO对象 放在自己作用域链的头部
- 函数内部在引用变量时,沿着自己的作用域链从头往后找
- 函数调用完毕,AO销毁 从自己作用域链头部去掉 里面的变量和函数也随之被销毁
function a() {
function b() {
var b = 234;
aa = 0;
}
var aa = 123;
b();
console.log(aa);
}
var glob = 100;
a();
- 下面代码来验证一下:
function a() {
function b() {
var b = 234;
aa = 0;
}
var aa = 123;
b();
console.log(aa);
}
var glob = 100;
a();
答案:是同一个AO的引用。
再问,在上面的例子中,第7行b函数执行完之后,概念上是执行期上下文被销毁,而实际函数a和函数b的作用域变化应该是什么样的呢?
答案:
- b函数执行完之后,自己的执行期上下文AO被干掉(销毁),即b.[[scope]]回到b被定义的状态。
- 往下进行到第9行,a函数执行完后,b函数作用域b.[[scope]]直接被销毁;同时,a函数的执行期上下文AO被干掉(销毁),a.[[scope]]回到被定义的状态,a函数等待下一次被调用执行。
思考一下,下面函数执行的作用域链:
function a() {
function b() {
function c() {
}
c();
}
b();
}
a();
具体过程如下
a 定义: a.[[scope]] --> 0:GO
a 执行: a.[[scope]] --> 0:AO(a)
1:GO
b 定义: b.[[scope]] --> 0:AO(a)
1:GO
注意b执行了才会产生c的定义哈!!
b 执行: b.[[scope]] --> 0:AO(b)
1:AO(a)
2:GO
c 定义: c.[[scope]] --> 0:AO(b)
1:AO(a)
2:GO
c 执行: c.[[scope]] --> 0:AO(c)
1:AO(b)
2:AO(a)
3:GO
现在再来看这句话,函数里边能访问函数外边的变量,但函数外边不能访问函数里边的变量;从上边的过程来看,在b中访问c中的局部变量,是不可能的,因为b.[[scope]]中不存在函数c的执行期上下文AO(c)。
关于作用域链,练习一下:
a=100;
function demo(e){
function e() { }
arguments[0]=2;
document.write(e+"<br>");
if(a){
var b=123; //存在变量提升
function c() { //没有函数提升
}
}
var c;
a=10;
var a;
document.write(b+"<br>");
f=123;
document.write(c+"<br>");
document.write(a+"<br>");
}
var a;
demo(1);
document.write(a+"<br>");
document.write(f+"<br>");
预解析
- 将函数提升为window属性
- 将函数中的var进行提升,值为undefined
- 对函数的形参进行提升,值为undefined
- 使用实参为形参赋值
- 将函数中的function进行提升
- 执行非var非function语句
function test(a, b) {
console.log(a);
c = 0;
var c;
a = 3;
b = 2;
console.log(b);
function b() {}
console.log(b);
}
test(1); // 1 2 2
function test(a, b) {
console.log(a);
console.log(b);
var b = 234;
console.log(b);
a = 123;
console.log(a);
function a() {}
var a;
b = 234;
var b = function () {};
console.log(a);
console.log(b);
}
test(1); // f a() {} u 234 123 123 f () {}
function fn(a) {
console.log(a);
var a=123;
console.log(a);
function a() {}
console.log(a);
var b = function() {}
console.log(b);
function d() {}
}
fn(1);
1. 打印:function a() {},
2. 变量赋值(变量a声明已在预编译阶段完成)
AO{
a:123,
b:undefined,
d:function d() {}
}
3. 打印:123,
4. 打印:123(打印语句的上一行,函数a声明的提升已在预编译阶段完成),
5. 变量赋值(变量b提升已完成)
AO{
a:123,
b:function() {},
d:function d() {}
}
6. 打印:function() {},
7. 函数执行完成(函数d声明的提升已在预编译阶段完成)
闭包
官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
面试题:闭包是什么?
答:闭包是可访问上一层函数作用域里变量的函数,即便上一层函数已经关闭。
function a() {
var num=100;
function b() {
num++;
console.log(num);
}
return b;
}
var demo=a();
demo();
demo();
function fun(n, o) {
console.log(o);
return {
fun: function(m) {
return fun(m, n);
}
};
}
var a = fun(0);
a.fun(1);
a.fun(2);
a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);
c.fun(2);
c.fun(3);
function Person(name,age,sex){
//var this={}
var a=0;
this.name=name;
this.sex=sex;
function sss(){
a++;
document.write(a);
}
this.say=sss;
//return this;
}
var oPerson = new Person();
oPerson.say();
oPerson.say();
var oPerson1= new Person();
oPerson1.say();
闭包的特点
- 作为一个函数变量的一个引用,当函数返回时,其处于激活状态。
- 一个闭包就是当一个函数返回时,一个没有释放资源的栈区。
简单的说,Javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。
优点:可以访问局部变量。
缺点:局部变量一直占用内存,内存占用严重,还容易造成内存泄漏(内存被占用,剩余的内存变少,程序加载、处理速度变慢)。
闭包的几种写法
第一种:写在原型对象的方法上
// 第1种写法
function Person() {
}
Person.prototype.type = "人类";
Person.prototype.getType = function() {
return this.type;
}
var person = new Person();
console.log(person.getType()); // "人类"
// 继承性 (函数自己的原型对象上的属性被函数外的访问了)
第二种:内部函数语句访问外部函数的变量,将内部函数写在外部函数的return中
var Circle = function() {
//var this={}
var obj = new Object(); //obj={}
var a=100;
obj.PI = 3.14159;
obj.area = function( r ) {
console.log(a);
return this.PI * r * r;
//this访问了外部函数的变量obj
}
return obj;
//return this;
}
var c = new Circle(); //{PI:3.14159,area:function(){……}}
alert( c.area( 1.0 ) );
// 在函数的内部返回了一个对象 这个对象中有一个属性是函数 对象中的函数引用了外部函数的变量
第三种:给函数添加一些属性
function Circle(r) {
this.r = r;
}
Circle.PI = 3.14159;
Circle.prototype.area = function() {
return Circle.PI * this.r * this.r;
}
var c = new Circle(1.0);
alert(c.area()); //3.14159
// 函数身上的属性PI被外面访问了
第四种:通过属性创建写在对象的方法上
var Circle = {
PI: 3.14159,
area: function(r) {
// var a = 'lll';
return this.PI * r * r;
}
};
alert(Circle.area(1.0));
// 外层能够通过调用函数访问函数里面的变量
第五种:通过全局变量赋值(类似于第二种写法的原理)
var demo;
function test(){
var aaa=100;
function b(){
console.log(aaa)
}
return b;
}
demo=test()
test();
demo();
闭包的用途
1. 实现公有变量
eg:函数累加器
function add() {
var counter = 0;
return counter += 1;
}
add();
add();
add();
你可以使用全局变量,函数设置计数器递增:
var counter = 0;
function add() {
return counter += 1;
}
add();
add();
add();
// 计数器现在为 3
但问题来了,页面上的任何脚本都能改变计数器,即便没有调用 add() 函数。
这时我们需要闭包。
function fn(){
var count=0;
return function(){
count+=1;
return count;
}
}
var add=fn()
add();
add();
add();
2. 可以做缓存
eg:eater
function eater() {
var food = "";
var obj = {
eat: function() {
console.log("i am eating" + food);
food = "";
},
push: function(myfood) {
food = myfood;
}
}
return obj;
}
var eater1 = eater();
eater1.push("banana")
eater1.eat(); // i am eating banana
eater2 = eater();
eater2.push("apple");
eater2.eat();
eater2.eat();
暂时不必要理解”缓存“代表的是什么,先把这种闭包调用过程理解了就行(多个方法都可以在外部对同一个局部变量进行操作),以后讲到闭包高级应用的时候会再提出来的。
3. 可以实现封装,属性私有化
var person = function() {
//变量作用域为函数内部,外部无法访问
var name = "default";
return {
getName: function() {
return name;
},
setName: function(newName) {
name = newName;
}
}
}();
console.log(person.name);
console.log(person.getName());
person.setName("abruzzi");
console.log(person.getName());
4. 模块化开发,防止污染全局变量
var a = (function(j) {
return function() { console.log(j) }
//模块内的函数变量不会被外部函数所污染,且执行完立即被销毁,不会浪费空间
}(i))
5. 实现类和继承
function Person() {
var name = "default";
return {
getName: function() {
return name;
},
setName: function(newName) {
name = newName;
}
}
};
var p = new Person();
p.setName("Tom");
alert(p.getName());
var Jack = function() {};
Jack.prototype = new Person();
Jack.prototype.Say = function() {
alert("Hello,my name is" + name);
};
var j = new Jack();
j.setName("Jack");
j.Say(); //Hello,my name is
alert(j.getName());
闭包的避免
看一下下main函数的执行结果:
function test() {
var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = function() {
document.write(i + " ");
}
}
return arr;
}
var myArr = test();
for (var j = 0; j < 10; j++) {
myArr[j]();
}
上面函数将在界面上打印出10个10,数组myArr中保存的时test()执行后返回的数组arr,该数组存的是10个函数。
从第12行开始一个for循环,将数组myArr中的每一个函数都取出来调用一遍。
为了可以打印出0~9来,首先考虑内部函数自调用:
for (var i = 0; i < 10; i++) {
arr[i] = function() {
document.write(i + " ");
//上面的语句中并不存在赋值,只有在该函数被调用时,上面语句才会去寻找变量i的值
}()
}
但这是立即执行,执行一遍就没有了。要想达到随时可以调用函数打印出0~9来,要怎么做?看看下面的代码:
for (var i = 0; i < 10; i++) {
(function(j) {
arr[j] = function() {
document.write(j + " ");
}
}(i))
}
- 看得懂么?看不懂的话,往下看。
- 循环执行10次,就有10个立即执行函数被执行;每个立即执行函数执行后其引用都会被销毁,所以10次循环对应的是10个立即函数。
- 每个立即执行函数执行时,该立即执行函数中的形参j 都被实参i 所赋值。
- 在10个立即执行函数执行后,数组arr中保存了10个函数 function(){ document.write(j+" ");}。
- 这10个函数对应的j 是10个立即执行函数的形参j,所以,数组arr中保存的10个函数中j 的值分别对应的是0~9。
这样才能随心所欲的打印出循环里的值0~9。
看一道阿里巴巴笔试题,练习一下:
<ul>
<li>a</li>
<li>a</li>
<li>a</li>
<li>a</li>
</ul>
<!-- 使用原生js给每个li绑定一个点击事件,点击对应的li,输出其顺序 -->
标签:function,return,函数,var,console,log
From: https://www.cnblogs.com/Kongqingzhi/p/16617727.html