基本概念
- 区分大小写
- 注释与java相同
- 变量如果不使用var声明,默认是全局变量
数据类型
5种基本类型 Boolean string null undefined number
3种引用类型 Object function Array
特殊的操作——with语句(不推荐使用)
with语句的作用是将代码的作用域设置到一个特定的对象中,
表达式:with (expression) statement;
描述:在statement中可以直接访问expression中的属性和方法
定义 with 语句的目的主要是为了简化多次编写同一个对象的工作,如下面的例子所示:
var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href;
上面几行代码都包含 location 对象。如果使用 with 语句,可以把上面的代码改写成如下所示:
with(location){
var qs = search.substring(1);
var hostName = hostname;
var url = href;
}
执行环境及作用域链
执行环境定了一变量或函数有权访问其他数据,也就是说一个变量或者函数都有其各自的执行环境。每一个执行环境都会把该环境下定义的所有的变量和函数都封装成一个变量对象(解析器去使用)
例如:全局执行环境对应的变量对象是window对象,关闭网页该对象才会被销毁。
环境栈
每个函数都有自己的执行环境,当执行流程进入到该函数时,函数的环境就会被推入一个环境栈中,在函数执行后,环境栈将该函数环境弹出,把环境控制权返回给之前的执行环境。
作用域链
当代码在某个环境中执行时,会创建变量对象的一个作用域链。它的作用是:保证执行环境有权访问的变量和函数的有序访问。
作用域前端
作用域的前端都是当前代码执行环境的变量对象。如果执行环境是函数,则这个变量对象就是该函数的活动对象。
活动对象
(函数的变量对象)活动对象最开始只包含一个对象(对象中包含对象?)即arguments对象(全局中是不存在的)
作用域的下一个对象
作用域链中下一个变量对象来自外部环境(?作用域每个函数都有一个作用域链把,怎么会来自外部环境呢?也就是说这个作用域链不只是包含当前的环境对象),而下一个变量对象来自于下一个外部环境;
执行环境与作用域链的关系
一个执行环境会对应一个作用域链,在该环境下,变量和函数的访问(查找)顺序会顺着作用域链去找变量或函数的值或者声明。这些变量或函数都被封装在作用域链上的变量对象中。而这些变量对象的在作用域中的顺序是:当前变量对象->外层环境的变量对象->外外层环境的变量对象、、、、、
示例:
var color = "blue";
function changeColor() {
if (color === "blue") {
color = "red";
} else {
color = "blue";
}
}
changeColor();
console.log("Color is now " + color);
运行结果: Color is now red
[Finished in 0.4s]
函数changeColor的作用域链中包含两个对象,一个是该函数自己的活动对象(即arguments对象),一个是外部环境(全局)的变量对象。
作用域链的顺序是:arguments->window
标识符的查找是沿着作用域链进行的,如果整个作用域链中都找不到这个标识符,那么程序会报错
可以在函数中访问color,就是因为在作用域链中可以找到包含color变量的变量对象(window)
示例:
var color = "blue";
function changeColor() {
var anotherColor = "red";
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问 color、anotherColor 和 tempColor
}
// 这里可以访问 color 和 anotherColor,但不能访问 tempColor
swapColors();
}
// 这里只能访问 color
changeColor();
图解:
这里swapColors函数的作用域链包含四个变量对象:
arguments->anotherColor->changeColor->windows
总结:
其实对变量的访问顺序和java是相同的,不过为了更好的理解作用域链的概念还是有必要用书中的概念来解释一下变量的访问顺序,同时也更好的理解这些概念(其实是名词)。书中后面的内容会常常出现这些概念,为了方便理解后面的知识点,概念其实是很重要的。
没有块级作用域
if (true) {
var color='red';
};
console.log(color);
执行结果:red
[Finished in 0.4s]
这里的color是全局变量,在java中该变量就是局部变量,块级外部无法访问。
模仿块级作用域
(function(){
//这里是块级作用域
})();
Function类型(函数)
1. 函数声明与函数表达式
函数声明: 解析器会率先读取函数声明的内容,并使其在执行任何代码前可以使用。函数声明不属于语句,所以结尾不用加封号。
console.log(sum(10,18));//结果 28
function sum(num1,num2){
return num1+num2;
}
函数表达式:需要等待解析器执行到它所在的代码行,才会真正的被解析执行。因为是表达式,所有结尾需要加封号。
console.log(sum(10,18));//TypeError: sum is not a function
var sum = function(num1,num2) {
return num1+num2;
};
2.函数没有重载
当声明两个同名函数,后面的函数会覆盖前面的函数;
function sum(num1,num2){
return num2+num1;
}
function sum(num1,num2,num3){
return num1-num2+num3;
}
console.log(sum(11,1));
结果:NaN
[Finished in 0.4s]
后面的函数会覆盖前面的函数,所有应该传入三个参数。
同理:如果定义一个同名的变量,后面的值也会覆盖前面的值。
var color='blue';
if (true) {
//重复定义
var color='red';
};
console.log(color);
执行结果:red
[Finished in 0.4s]
如果后面的再次声明没有初始化,解析器会选择对后面的声明视而不见:
var i=7;
var i;
console.log(i)//运行结果是7
3.函数内部属性
函数就是对象 所以也包含了属性和方法。
内部特殊对象:this ,arguments
内部特殊属性:length和prototype
两个非继承的方法:call()和apply()
argumentes 是一个类数组对象,包含着传入函数的所有参数;还有callee属性
arguments.callee属性是一个指针,指向拥有arguments对象的函数。
function fact(num){
if (num==1) {
return 1;
}else{
// return num*fact(num-1);
return num*arguments.callee(num-1);
}
}
console.log(fact(6));
720
[Finished in 0.4s]
this对象
有了上面介绍的执行环境和**变量对象的概念**this就比较好理解了,该对象(指针)引用的是函数执行环境对象。(函数被调用时候所在的环境对象)
color = 'red';
var o={
color:'blue'//字面量没有封号结尾,有点类似数组
};
function logColor(){
var cc=this.color;
console.log(cc);
};
logColor();//函数在window全局中调用,this指向window对象
// o.logc=logColor();
o.logc=logColor;//这里只传指针
o.logc();//函数在O的作用域中被调用,this指向O
logColor函数是在全局环境中定义,它引用了this对象,在该函数被调用之前,this的值并不是确定的
函数的名字仅仅是包含指针的一个变量而已,即使在不同的环境中指向的也是同一个函数对象
this对象是在运行时基于函数的执行环境绑定的;当在函数在全局环境中执行,this指向该函数,当函数作为某个对象的方法被调用,this执行该对象。
匿名函数的执行环境具有全局性,其this通常指向window
name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
return function() {
return this.name;
};
}
};
console.log(object.getNameFunc()()); //"The Window"(在非严格模式下)
this的活动对象变成了window
看几个比较绕的this所指活动对象:
name = "The Window";
var object = {
name: "My Object",
getName: function() {
return this.name;
}
};
var na = object.getName(); //"My Object"
console.log(na);
na = (object.getName)(); //"My Object"
console.log(na);
var na=Object.getName;
console.log(na());//windows
// 此时函数的执行环境变成了全局的
方式一和方式二其实是一样的。
方式三 被重新赋值以后 this的值得不到维持了()
length属性
这个是指函数接收参数的个数:
function sayName(name) {
alert(name);
}
function sum(num1, num2) {
return num1 + num2;
}
function sayHi() {
alert("hi");
}
console.log(sayName.length); //1
console.log(sum.length); //2
console.log(sayHi.length); //0
prototype属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含让所有实例共享的的属性和方法(也可以在够造函数中添加,但是通过原型这个属性,就可以不再构造函数中添加了)。
例如:
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
这些字person实例中共享的数据都被封装到了一个原型对象中:
而这些实例只保留了指向该原型对象的原型属性prototype,从而可以使用这些原型属性。
引用类型
对象都保存在堆内存中,对象名不过是保存的对象的地址。
// 引用类型 保存地址
//
var obj={
name:'geguo',
age:24
};
var obj1=obj;
var obj2=obj;
obj.name='weiwei';
console.log(obj.name);
console.log(obj1.name);
console.log(obj2.name);
obj1.name='ceshi';
console.log(obj.name);
console.log(obj1.name);
console.log(obj2.name);
obj2.name='haha';
console.log(obj.name);
console.log(obj1.name);
console.log(obj2.name);
weiwei
weiwei
weiwei
ceshi
ceshi
ceshi
haha
haha
haha
[Finished in 0.5s]
因为obj12三个对象名都指向同一个对象,其他任意一个对对象的修改都会引起对象的变化,同时三个对象名指向的对象都是同一个对象,所以值是相同的。
这点和java中的对象的引用是相同的。
People p1=new People();
People p2=p1;
People p3=p1;
System.out.println(p1.name);
System.out.println(p2.name);
System.out.println(p3.name);
p2.name="www";
System.out.println(p1.name);
System.out.println(p2.name);
System.out.println(p3.name);
p2.name="laosan";
System.out.println(p1.name);
System.out.println(p2.name);
System.out.println(p3.name);
java运行结果:
geguo
geguo
geguo
www
www
www
laosan
laosan
laosan
函数作为参数传递
所有函数的参数都是值传递这点与java是不同的。
// 定义一个函数接收一个函数作为参数
function setName(obj){
obj.name='mic';
obj={
name:'duanyinbiao'
};
}
var person={};
setName(person);
// 如果是引用传递,那么这个值应该是duanyinbiao
console.log(person.name);
执行结果 mic
[Finished in 0.4s]
这个例子说明,函数作为参数不是引用传递。
那为什么是值传递呢?
当obj被重新赋值给一个对象的时候,这个对象是一个局部对象,函数执行完后就会被销毁。(函数的参数可以想象成局部变量)
单体内置对象
在所有代码执行之前,作用域中就存在了两个内置对象:Global和Math
Global
常用方法:
- isNan()
- isFinite()
- parseInt()
- parseFloat()
特殊方法:
- URL编码方法用法:encodeURI(‘www.baidu.com’)
- eval();显示一个完整的ECMAScript解析器
- window对象,他是Global对象的部分实现
闭包
首先闭包是一个函数,这个函数有权访问另一个函数作用域中的变量。创建闭包的常见方式是在一个函数中创建另一个函数。
//创建一个闭包
function createComparisonFunction(propertyName) {
return function(object1, object2) {
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
};
}
var compare = createComparisonFunction('name');
var result = compare({name:'AAAA'},{name:'BBB'});
console.log(result);
运行结果:-1
[Finished in 1.0s]
有了前面关于作用域链的概念,就好理解下边这个作用域链图了:
这里有两个函数:createComparisonFunction和匿名函数。
他们的作用域链分别是:
匿名函数活动对象(三个):
匿名函数(arguments)->createComparisonFunction()->window
createComparisonFunction活动对象(2个):
活动对象(arguments)->window
在运行完后,匿名函数对象还没有被销毁,因为compare还持有它的引用,手动释放其引用即可:compare=null;
防止内存泄露。
比较java内部类
java中的内部类可以访问外部类的属性,因为内部类对象会持有外部类的引用,导致外部类无法被GC,从而产生内存泄露的问题。