1.函数声明提升
函数声明与变量声明会被JavaScript引擎隐式地提升到当前作用域的顶部,但是只提升名称不会提升赋值部分。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>函数声明提升</title>
</head>
<body>
<script type="text/javascript">
fn();//我是函数声明
var fn=function(){
console.log("我是函数表达式");
};
function fn(){
console.log("我是函数声明");
}
fn();//我是函数表达式
/*
实际执行顺序:
var fn;
function fn(){
console.log("我是函数声明");
}
fn();//我是函数声明
fn=function(){
console.log("我是函数表达式");
};
fn();//我是函数表达式
*/
/*
函数表达式最大的问题,在于js会将此代码拆分为两行代码分别执行。
所以函数声明覆盖了变量声明
*/
</script>
</body>
</html>
<pre name="code" class="html">//封装函数
//1、传统方式封装
//该方式允许封装的函数"先调用,后声明",该过程称为函数的"预加载"
//预加载:程序代码没有执行之前,函数的声明先进入内存
//表面上是函数调用在前、函数声明在后,本质上是函数声明在前、调用在后
//预加载条件:"声明和调用"必须在同一个script标记里面
//原因是每个script标记是独立加载、解释和运行的
//getInfo();//也可以成功调用
function getInfo(){
console.log('beijing');
}
getInfo();
//js中一切都是对象
//函数也是对象(数据类型的一种)
//2、变量赋值方式声明函数,就是函数表达式(不支持函数预加载)
//getWeather();//TypeError: getWeather is not a function
var getWeather=function(){
console.log('晴朗');
}
getWeather();//晴朗
2.函数-callee
arguments.callee是一个指向正在执行的函数的指针
function jisuan(n){
if(n<=1){
return 1;
}else{
//return n*jisuan(n-1);//TypeError: jisuan is not a function
return n*arguments.callee(n-1);
}
}
var js=jisuan;
jisuan=null;
console.log(js(5));//120
3.caller属性
caller属性保存着调用当前函数的函数引用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>caller属性</title>
</head>
<body>
<script type="text/javascript">
function outer(){
inner();
}
function inner(){
//inner.caller指向outer
console.log(inner.caller);// outer()
//alert(inner.caller);//显示outer函数的源代码
//这样写实现了更松散的耦合
alert(arguments.callee.caller);//显示outer函数的源代码
}
outer();
</script>
</body>
</html>
4.匿名函数自执行
//变量赋值声明一个函数
var getInfo=function(){
console.log("Today is sunshine");
}
//var getInfo="okok";//使用一个同名的getInfo变量把上面的函数给污染覆盖
getInfo();//Today is sunshine
//使得以下匿名函数发生执行
//特点:程序代码没有停顿,立即执行
//好处:可以避免变量污染
(function(){
console.log("I am no name function");
})();//I am no name function
5.arguments
arguments属性是一个类数组对象,保存着传入函数的所有参数,主要用途就是保存函数参数
arguments有一个callee属性,该属性是一个指针,指向拥有arguments属性的函数
//arguments关键字 在函数内部可以接受传递进来的实参
//可以通过数字下标形式来访问各个实参信息
//其length属性可以获得实参个数
function getInfo(){
var num=arguments.length;
//根据传递参数的个数做灵活处理
if(num==0){
console.log('个人信息');
}else if(num==1){
console.log('名字:'+arguments[0]);
}else if(num==2){
console.log('名字:'+arguments[0]+' age:'+arguments[1]);
}else if(num==3){
console.log('名字:'+arguments[0]+' age:'+arguments[1]+' job:'+arguments[2]);
}
//console.log('名字:'+arguments[0]+' age:'+arguments[1]+' job:'+arguments[2]);
//console.log(arguments);
}
//同一个函数调用的时候,由于传递不同个数的实参,最终导致的结果也不一样
//其实是模仿方法重载(定义了许多名称一致的方法,这些方法的参数个数或者参数类型不一样
//这些方法就构成了重载)
//在php和js中没有重载
//
//重写:子类的同名方法去覆盖父类的同名方法
//重载:一个类里面有许多同名的方法
getInfo();
getInfo('liujie');
getInfo('liujie',23);
getInfo('liujie',23,'student');
6.apply和call
apply()和call()方法。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。
apply()方法接收两个参数:一是在其中运行函数的作用域;二是参数数组。第二个参数可以是Array的实例,也可以是arguments对象。
apply()和call()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于call()方法而言,第一个参数this值没有变化,变化的是其余参数都是直接传递给函数,参数必须逐个列举出来。
第一个参数传null或undefined时,将是JS执行环境的全局变量。浏览器中是window,其它环境(如node)则是global。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>apply和call</title>
</head>
<body>
<script type="text/javascript">
/*apply()和call()方法真正强大的地方是能够扩充函数赖以运行的作用域
*/
var color="red";
var o={color:"blue"};
function sayColor(){//全局函数
console.log(this.color);
}
sayColor();//red //这里在全局环境调用,this.color会转化为window.color
sayColor.call(this);//red 显示的在全局作用域中调用函数
sayColor.call(window);//red 显示的在全局作用域中调用函数
sayColor.call(o);//blue 使用call()方法将函数内部的this指向了对象o
</script>
</body>
</html>
7.bind方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>bind方法</title>
</head>
<body>
<script type="text/javascript">
/*
bind方法的主要作用是将函数绑定至某个对象
*/
function f(y){//待绑定的函数
return this.x+y;
}
var o={x:1};//将要绑定的对象
var g=f.bind(o);//通过调用g(x)来调用o.f(x)
console.log(g(2));//3
var color="red";
var o={color:"blue"};
function sayColor(){
console.log(this.color);//blue
}
var objectSayColor=sayColor.bind(o);
//sayColor()调用bind()并传入对象o,创建了objectSayColor()函数
//objectSayColor()函数的this值等于o
objectSayColor();
</script>
</body>
</html>
8.私有变量
严格来讲,JavaScript中没有私有成员概念, 所有对象属性都是公有的。不过, 倒是有一个私有变量的概念。任何在函数中定义的变量, 都可以认为是私有变量, 因为外部不能访问它们。
私有变量包括:函数参数、局部变量以及函数内部定义的其他函数。
function fn(a,b){
var sum = a+b;
return sum;
}
在上面代码中包含了3个私有变量a,b和sum.在函数内部都可以访问它们,但是在函数外部无法访问。
但是, 我们可以通过在函数内部创建一个闭包, 那么闭包通过自己的作用域链也可以访问这些变量, 而利用这一点, 就可以创建用于访问私有变量的公有方法.我们把有权访问私有变量和私有方法的公有方法称为特权方法.
第一种,在构造函数中定义特权方法:
function MyObject(){
var privateVariable = 10 ;//私有变量
function privateFunction() {
return false ;
}
this.publicMethod = function() { // 特权方法
privateVariable++;
return PrivateFunction();
} ;
}
这个模式在构造函数内部定义了所有私有变量和函数。然后,又创建了能够访问这些私有成员的特权方法。能够在构造函数中定义特权方法,是因为
特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。
在上面这个例子中,私有变量privateVariable和私有函数privateFunction只能通过特权方法publicMethod()来访问。在创建了MyObject的实例后,除了使用publicMethod()方法外,没有其他途径可以访问私有变量privateVariable和私有函数privateFunction.
利用私有变量和特权方法可以隐藏那些不应该被直接修改的数据。
另一种方法:静态私有变量
(function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//构造函数
MyObject = function(){};
//公有特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
}
)();//匿名函数自执行
通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法。
上面的例子中就创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。在私有作用域中,首先定义了私有变量和私有函数,然后又定义了构造函数及其公有方法。公有方法是在原型上定义的,这一点体现了典型的原型模式。
注意:这个模式在定义构造函数时并没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数,但那并不是我们想要的。出于同样的原因(不想创建局部函数),我们在声明MyObject时并没有使用var关键字。
记住:初始化未经声明的变量,总是会创建一个全局变量。这里MyObject就成了一个全局变量,能够在私有作用域之外被访问到。但也要注意,在严格模式下给未经声明的变量赋值会导致错误。
Demo1:
function Person(name){
//在构造函数内部定义了两个特权方法:getName()和setName()。这两个方法都可以在构造函数外部使用,而且都有权访问私有变量name
this.getName=function(){
return name;
};
this.setName=function(value){
name=value;
};
}
var person1=new Person("liujie");
console.log(person1.getName());//liujie
person1.setName("liujiejie");
console.log(person1.getName());//liujiejie
Demo2:
<script type="text/javascript">
(function(){
var name = "";
Person = function(value){
name = value;
}
Person.prototype.getName = function(){
return name;
}
Person.prototype.setName = function(value){
name = value;
}
var person1 = new Person("lisi");
console.log(person1.getName());//lisi
person1.setName("wangwu");
console.log(person1.getName());//wangwu
var person2 = new Person("lisisi");
console.log(person2.getName());//lisisi
console.log(person1.getName());//lisisi
})();
</script>
这个例子中的Person构造函数与getName()和setName()方法一样,都有权访问私有变量name.在这种模式下,变量name就变成了一个静态的、由所有实例共享的属性。也就是说,在一个实例上调用setName()会影响所有实例。而调用setName()或新建一个Person实例都会赋予name属性一个新值。结果就是所有实例都会返回相同的值。
构造函数模式的缺点:针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方法可以避免这个问题。
通过创建静态私有变量会因为使用原型而增加代码复用性,但是每个实例都没有自己的私有变量。
9.块级作用域
function outputNumbers(count){
//由于JS中没有块级作用域,所以这里的i实际是在包含函数outputNumbers创建的,而不是在块语句中创建的
//在java、C++等语言中,变量i只会在for循环的块语句中有定义,循环一旦结束,变量i就会被销毁
//而在js中,变量i是定义在outputNumbers()函数的活动对象中的,因此,从它开始有定义起,就可以在函数内部随处访问它
for(var i=0;i<count;i++){
console.log(i);//0、1、2、3、4
}
console.log("块语句外:"+i);//5
}
outputNumbers(5);
10.模仿块级作用域
/*
匿名函数可以用来模仿块级作用域
(function(){
//这里是块级作用域,通常称为私有作用域
//将函数声明放在圆括号中,表示它是一个函数表达式,而紧随其后的另一对圆括号会立即调用这个函数
})();
*/
function outputNumbers(count){
(function(){
//这里在for循环的外部插入了一个私有作用域,在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中使用,使用后即被销毁。
for(var i=0;i<count;i++){
console.log(i);//0、1、2、3、4
}
})();
console.log(i);//ReferenceError: i is not defined
}
outputNumbers(5);
11.静态方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>静态方法</title>
</head>
<body>
<script type="text/javascript">
function Person(name){
this.name=name;
this.sayName=function(){
console.log(this.name);
}
}
Person.sayAge=function(){//静态方法
console.log(1);
};
var person1=new Person('lisi');
person1.sayName();//lisi
Person.sayAge();//1
</script>
</body>
</html>
静态方法可以通过构造函数名来调用
12.用apply改变函数作用域
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>用apply改变函数作用域</title>
</head>
<body>
<script type="text/javascript">
/*
apply、call、bind都有个作用就是改变作用域,这里用apply将foo函数的作用域指向obj对象,同时传入参数。
再简单分析一下bind函数内部的嵌套,执行bind函数的时候返回的是一个匿名函数,所以执行bar(3)的时候实际上是执行的bind内部的匿名函数,返回的是之前传入的foo函数的执行结果。
函数没有返回值的情况下默认返回undefined。
*/
function foo(somthing){
console.log("一:"+this.a, somthing);//一:2 3
}
function bind(fn, obj){
return function(){
return fn.apply(obj, arguments);
}
}
var obj = {
a:2
}
var bar = bind(foo, obj);//foo中的this指向了obj
/*
bar=function(){
return fn.apply(obj, arguments);
}
*/
var b = bar(3);
console.log("二:"+b);//二:undefined
</script>
</body>
</html>
this对象
<script type="text/javascript">
var name = "lisi";
var obj = {
name:"wangwu",
getName:function(){
return function(){
return this.name;
}
}
};
console.log(obj.getName()());//lisi
</script>
在这个例子中,先创建了全局变量 name, 又创建了一个包含 name 属性的对象, 这个对象还包含一个方法getName(), 它返回一个匿名函数, 而匿名函数又返回 this.name
为什么上面的结果是lisi,而不是wangwu?
前面曾经提过, 每个函数在被调用时, 其活动对象都会自动取得两个特殊变量 this 和 arguments, 内部函数在搜索这两个变量时, 只会搜索到活动对象为止, 因此永远不可能访问到外部函数中的这两个变量. 不过, 把外部作用域中的 this对象保存在一个闭包能够访问的到的变量里, 就可以让闭包访问到该变量了.
<script type="text/javascript">
var name = "lisi";
var obj = {
name:"wangwu",
getName:function(){
var that = this;
return function(){
return that.name;
}
}
};
console.log(obj.getName()());//wangwu
</script>
之所以这个会显示我们想要的结果, 是因为在这个作用域链上没有 that 同名的属性, 所以当闭包搜索 that 属性时, 这个 Object 里的 that 就是最顶端了, 换句话说是最后了, 所以自然就要显示这个 that 的结果了.