JavaScript是一种面向对象的动态语言,它包含类型、运算符、标准内置(built-in)对象和方法。它的语法源自于 Java 和 C ,所以这两种语言的许多语法特性同样适用于JavaScript。需要注意的一个主要区别是JavaScript不支持类,类这一概念在JavaScript跳过对象原型(object prototype)得到延续。另一个主要区别是JavaScript中的函数也是对象,JavaScript允许函数在包含可执行代码的调试,能像其他对象一样被传递。
- Nuber(数字)
- String(字符串)
- Boolean(布尔)
- Symbol(符号)
- Object(对象)
- Function(函数)
- Array(数组)
- Date(日期)
- Math(数学对象)
- RegExp(正则表达式)
- Null(空)
- Undefined(未定义)
- Error(错误类型)
一、number详解
JavaScript中采用全浮点计算(双精度64位)
注意 ! 在某些运算中会出错 eg : 0.1 + 0.2 = 0.30000000000000004
我们也可以使用JavaScript提供的Math内置对象,和内置函数parseIne();
此外JavaScript还提供parseFloat(),但是它只支持解析十进制数字
在parseInto("123",10) //123 ->10的含义是十进制
parseInt("010",10);//10
parseInt("010");//8 如果没有第二个参数,那么以0开头的就是八进制 0x开头就是16进制
psrseInt("0x10");//16
//如何把二进制转换为整数值
parseInt("10011",2);
//当第一个参数不是数字型字符串
parseInt("hello",10);//NaN
//NaN是一个特殊值 用它与如何数字计算 结果都是Nan
此外,还有两个特殊值 Indinity 和 -Infinity (正无穷和负无穷)
用内置函数 isFinite()可以判断一个变量是否为Infinity -Infinity NaN
二、字符串
JavaScript中的字符串只一个Unicode字符序列
每一个编码单元有一个16位二进制数表示。每一个Unicode字符由一个或两个编码单元来表示。
"hello".chaAt(0);//"h"
"hello,world".replace("hello","goodbye");//"goodbye,world"
"hello".toUpperCase();//"hello"
"hello".Iength;//5
三、其他类型(null 和 undefined)
//null是一个空值,必须使用null关键字才能够使用
undefined是一个未定义类型的对象,它表示一个未初始化的值,也就是还没有被分配值。
布尔类型:(true / false)
四、变量
在JavaScript中声明一个变量用var
var a;
var name = "simon";
五、运算符
JavaScript的算术操作符包括+、-、*、/ 和% --求余(与模运算不同)。赋值使用 = 运算符,此外还是一些复合运算符,如 += 和 -= ,他们等价于 x=x op y。
+ 可以用来连接字符串
"3" + 4 + 5; //345
3 + 4 + "5"; //75
1 == true; //false
123 == "123"; //false
123 == "123" //true
1 == true; //true
六、控制结构
if()...else if()...else()
while()
do...while()
for( ; ; )/for( : )
- 短路与:var name = o && o.getName();
- 短路或:var name = otherName || "default";
- 三元运算符:var allowed = (age > 18) ? "yes" : "no";
七、对象
对象的创建方式
- var obj = new object();
- var obj = { } --->对象字面量
var obj = {
name:"carrot",
"for":"Max",
details:{
color:"orange",
size:12
}
创建一个Person的对象:
var You = new Person("wangyuehua","123");
可以用 You.nema=“ZYH” 来赋值
obj["name"] = "Simon";
可以用 var name = You.name; 来取值
var name = obj["name"] ;
中括号的方式的优点:中括号内都是字符串,所以可以用于运行时计算,且可以使用关键字,但在后期被解释时
obj.for = "Simon"//语法错误,以为 for 是一个关键字
obj["for"] = "Simon";//语法正确
八、数组
JavaScript中的数组是一个特殊的对象。
与普通对象的区别在于,对于数组中的“属性”(元素)只能通过中括号来访问。同事数组比普通对象多了一个数组的创建:
var a = ['dog','cat','hen'];//数组字面量
var b = new Array('dog','cat','hen');//传统方式
如果访问;额一个不存在的数组索引会得到undefined
数组的遍历:
for(var i=0;i<a.length;i++) //传统方法--->不推荐,每次循环都会计算length,效率不高
for(var i=0,len = a.length;i<len;i++ ) //改进方法--->先循环开始前,先缓存a.length
for(var i=0,item;item=a[i++]) //有局限,每次循环都会判断item是否空值或是否定义(是否为真)但是在数组内容中出现假值 会影响循环
for(var i in a) //这种循环 不单单会遍历出元素 ,也会遍历出自定义添加的array属性(向 Array.prototype添加了新的属性)
九、函数
对JavaScript的学习最重要的就是对象和函数两个部分。(函数也是对象)
函数:
function add(x,y){
var total = x + y;
return total;
}
一个JavaScript函数可以有0个或者多个变量,如果有return,个返回return的值;否则返回undefined。
在使用函数时,函数中的变量也不一定强制与定义函数时的变量一致,如:add();此时x,y为undefined;若add(1,2,3)此时3将会被忽略。
JavaScript 允许你创建匿名函数:
var avg = function() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum / arguments.length;
};
闭包:
var a = 1;
var b = 2;
(function() {
var b = 3;
a += b;
})();
a; // 4
b; // 2
递归:
function countChars(elm) {
if (elm.nodeType == 3) { // 文本节点
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += countChars(child);
}
return count;
}
闭包递归:
var charsInBody = (function counter(elm) {
if (elm.nodeType == 3) { // 文本节点
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += counter(child);
}
return count;
})(document.body);
如上所提供的函数表达式的名称的作用域仅仅是该函数自身。这允许引擎去做更多的优化,并且这种实现更可读、友好。该名称也显示在调试器和一些堆栈跟踪中,节省了调试时的时间。
ps:需要注意的是 JavaScript 函数是它们本身的对象——就和 JavaScript 其他一切一样——你可以给它们添加属性或者更改它们的属性,这与前面的对象部分一样。
十、自定义对象
命名空间:注意:需要认识到重要的一点是:与其他面向对象编程语言不同的是,Javascript中的普通对象和命
var MYAPP = MYAPP || {}; //全局命名空间
MYAPP.event = {}; //子命名空间
下面是用于创建命名空间和添加变量,函数和方法的代码写法:
//给普通方法和属性创建一个命名空间
MYAPP.commonMethod = {
regExForName = "",
regExForPhone = "",
validateName = function(name){
//对参数name做一些操作
},
validatePhoneNo = function(phoneNo){
//对参数phoneNo做一些操作
}
}
MYAPP.event = {
addListener = function(el , type , fn){
//code
}
removeListener = function(el , type , fn){
//code
}
}
//使用addListner方法的写法
MYAPP.event.addListener("yourel" , "type" , callback)
自定义对象
类
JavaScript是一种基于原型的语言,它并没有类似于其他C++/JAVA中的类声明语言。在JavaScript中,我们通常使用方法(function)来当做类。定义一个类跟定义一个函数一样easy
function Person(){}
//或
var Person = function(){}
对象(类的实例)
我们使用 new obj()来创建一个新的实例。
eg:
function Person(){}
var person1 = new Person();
var person2 = new Person();
构造器
在实例化时构造器被调用,在JavaScript中,初始的函数就可以作为构造器使用,而不需要额外的定义构造器方法。类中的每个声明的函数都可以在被实例化后调用执行。(构造器常用于给对象的属性赋初始值)
function Person() {
alert('Person instantiated');
}
var person1 = new Person();
var person2 = new Person();
属性(对象属性)
属性就是当前类中的变量。通常为了正确继承,属性应当为定义在原型属性中。
可以使用关键字this来调用类中的属性,this是对当前对象的引用。
function Person(name){
this.name = name;
alert("this is my name" + this.name);
}
var person1 = new Person("Alice");
var person2 = new Person("Bob");
方法(对象属性)
方法和属性相似,不同的是:一个是函数,另一个是可以被定义为函数。为定义一个方法,通常需要将一个函数赋值给类的原型属性,这个赋值给函数的名称就是用来给对象在外部调用它使用的。
function Person(name){
this.name = name;
}
Person.protopype.sayHello = function(){
alert("hello,i'm" + this.name);
}
var person1 = new Person("Alice");
var person2 = new Person("Bob");
person1.sayHello();
在JavaScript中方法通常是绑定到一个对象中的普通函数,所以我们也可以在访问域之外对其调用访问。
function Person(name){
this.name = name;
}
Person.protopype.sayHello = function(){
alert("hello,i'm" + this.name);
}
var person1 = new Person("Alice");
var person2 = new Person("Bob");
person1.sayHello(); //Alice
var helloFunction = person1.sayHello;
helloFunction(); //undefined
console.log(helloFunction === person1.sayHello); // logs true
console.log(helloFunction === Person.prototype.sayHello); // logs true
helloFunction.call(person1);
继承
创建一个或者多个类的某专门版本类方式称为继承(JavaScript只支持单继承)。创建的专门版本的类通常称作子类。在JavaScript中通过赋予子类一个父类的实例并专门化子类来实现。
function Person(name){
this.name = name;
}
Person.prototype.walk = function(){
alert("i'm walking!");
}
Person.prototype.sayHello = function(){
alert("Hello , I'm" + this.name );
}
//定义Student构造器
function Student(name , subject){
Person.call(this , name);//这里也可以用apply(),详细见总结末端
this.subject = subject;
}
//建立一个由Person.prototype继承而来的Student.prototype对象
// 注意: 常见的错误是使用 "new Person()"来建立Student.prototype.
// 这样做的错误之处有很多, 最重要的一点是我们在实例化时
// 不能赋予Person类任何的FirstName参数
// 调用Person的正确位置如下,我们从Student中来调用它
Student.prototype = Object.create(Person.prototype);
//设置constructor 属性指向Student
Student.prototype.constructor = Student;
Student.prototype.sayHello = function(){
console.log("hello , I'm" + this.name + "I'm studying" + this.subject);
}
Student.prototype.sayGoodBye = function(){
alert("GOODBYE");
}
// 测试实例:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello(); // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk(); // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"
// Check that instanceof works correctly
console.log(student1 instanceof Person); // true
console.log(student1 instanceof Student); // true
封装
在上一个例子中,Student类虽然不需要知道Person类的walk()方法是如何实现的,但是仍然可以使用这个方法;Student类不需要明确地定义这个方法,除非我们想改变它。 这就叫做封装,对于所有继承自父类的方法,只需要在子类中定义那些你想改变的即可。
抽象
抽象是允许模拟工作问题中通用部分的一种机制。这可以通过继承或者组合来实现。
JavaScript通过继承来实现具体化,通过让类的实例是其他对象的属性值来实现组合。
var foo = function(){};
console.log( 'foo is a Function: ' + (foo instanceof Function) ); // logs "foo is a Function: true"
cpnsole.log( 'foo.prototype is an Object: ' + (foo.prototype instanceof Object) ); // logs "foo.prototype is an Object: true"
多态
就像所有定义在原型属性内部的方法和属性一样,不同的类可以定义具有相同名称的方法;方法是作用于所在的类中。并且这仅在两个类不是父子关系时成立(继承链中,一个类不是继承自其他类)。
总结:
在经典面向对象中,对象指的是数据以及操作数据的集合。与C++ 和 Java 不同,JavaScript是一种基于原型的编程语言,没有Class语句,而是把函数作为类。
eg:(a)
function makePerson(first , last){
return {
first : first,
last : last
}
}
function personFullName(person){
return person.first + " " + person.last;
}
function personFullNameReversed(person){
return person.last + " " + person.first;
}
person1 = makePerson("Kobe","Branyt");
personFullName(person1);
personFullNameReversed(person1);
显然上述方法十分麻烦,需要在全局命名空间中写很多的函数。既然函数本身就是对象,如果需要让一个函数
属于一个对象,那么不难得到:(b)
function makePerson(first , last){
return{
first : fitst,
last : last,
fullName : function(){
return this.first + ' ' this.last;
}
}
}
s = makerPerson('Kobe','Branty');
s.fullName();// Kobe Branty
注意:this关键字, 当使用在函数中时,this指的是当前的对象,也就是调用该函数的对象。如果一个对象使用了.或者{}来访问其下的属性时,那么该对象就成了this。如果并不存在某个对象,那么此时this指向全局对象。
s = makePerson('Allen','Iverson');
var fullName = s.fullName;
fullName(); //undefined undefined
所以我们可以用this来改进上述makePerson函数:
function Person(first, last) {
this.first = first;
this.last = last;
this.fullName = function() {
return this.first + ' ' + this.last;
}
this.fullNameReversed = function() {
return this.last + ', ' + this.first;
}
}
var s = new Person("Simon", "Willison");
但此时创建一个Person对象的时候,我们都在其中创建了两个新的函数对象。所以我们可以将里面的函数拿出来:
function personFullName(person) {
return this.first + ' ' + this.last;
}
function personFullNameReversed(person) {
return this.last + ', ' + this.first;
}
function Person(first, last) {
this.first = first;
this.last = last;
this.fullName = personFullName;
this.fullNameReversed = personFullNameReversed;
}
最好的方法:
function Person(first, last) {
this.first = first;
this.last = last;
}
Person.prototype.fullName = function() {
return this.first + ' ' + this.last;
}
Person.prototype.fullNameReversed = function() {
return this.last + ', ' + this.first;
}
Person.porotype 是一个可以被Person的所有实例共享的对象。他的名字叫做原型链(prototype chain)的查询链的一部分:当你试图访问一个Person中没有定义的属性时,解释器会先检查这个Person.porotype来判断是否存在这样一个属性。所以,在任何时候任何分配给Person.porotype的东西通过this对象构造的实例都是有用的。
这也使得我们可以在任何时候改造原型中的一些东西,甚至是内置对象的原型。给
String
添加一个方法用来返回逆序的字符串:
var s = "Simon";
s.reversed(); // TypeError on line 1: s.reversed is not a function
String.prototype.reversed = function() {
var r = "";
for (var i = this.length - 1; i >= 0; i--) {
r += this[i];
}
return r;
}
s.reversed(); // nomiS
原型组成链的一部分,那条链的根节点是Object.pototype,它包括toString()方法 --> 将对象转换成字符串调用的方法。这对于调用我们的Person对象很有用。
var s = new Person("Simon", "Willison");
s; // [object Object]
Person.prototype.toString = function() {
return '<Person: ' + this.fullName() + '>';
}
s.toString(); // <Person: Simon Willison>
ps:apply()和 call()
function trivialNew(constructor , ...args){ (...args是剩余参数2015新特性)
var o = {};//创建一个新对象
constructor.apply(o,args);
return o;
}
apply()
有一个姐妹函数,名叫
call
,它也可以允许你设置
this
,但它带有一个扩展的参数列表而不是一个数组。
function lastName(){
return this.last.toUpperCase();
}
var s = new Person("Simon","Willison");
lastName.call(s);
//与下面效果一致
s.lastName = lastName;
s.lastName();
十一、内部函数
JavaScript 允许在一个函数内部定义函数,如果某个函数依赖于其他的一两个函数,而这一两个函数对其余的代码没有用处,可以将它们嵌套在会被调用的那个函数内部,这样做可以减少全局作用域下的函数的数量,这有利于编写易于维护的代码。
function betterExampleNeeded() {
var a = 1;
function oneMoreThanA() {
return a + 1;
}
return oneMoreThanA();
}
闭包
闭包是JavaScript中必须提到的功能。首先我们来看一个内部函数应用的例子:
function makeAdder(a){
return funtion(b){
return a+b;
}
}
var x = makeAdder(5);
var y = makeAdder(20);
x(6);//11
y(7);//27
我们来观察一下,在上述代码运行过程中发生了什么:每当JavaScript执行一个函数时,都会创建一个作用域对象(Scopeobject),用来保存 在这个函数中创建的局部变量。它和被传入函数的变量一起被初始化。这与那些保存的所有全局变量和函数的全局对象类似,但有一些区别:
1.每次函数被执行时都会创建一个新的,特定的作用域对象;
2.与全局对象(在浏览器中当做window对象来访问的)不同的是,不能从JavaScript代码中直接访问作用域对象,也没有可以遍历当前的作用域对象里面属性的方法。
过程:当调用makeAdder时,解释器创建了一个作用域对象,它带有一个属性a(它被当做参数传入makeAdder函数中,然后makeAdder返回一个新创建的函数)。通常JavaScript的垃圾回收器会在这时候回收makeAdder创建的作用域对象,但是返回的函数却保留了一个指向那个作用域对象的引用。结果是这个作用域对象并不会被垃圾回收器回收。直到指向makeAdder 的那个函数对象的引用计数为零。
作用域对象组成了一个名为作用域链(scope chain)的链.它类似于原形(prototype)链一般,被JavaScript的对象系统使用。
那么 : 一个闭包就是一个函数 和 在这个函数中被创建函数的作用域对象的组合。
闭包允许你保存状态--所以他们通常可以替代对象来使用。
内存泄漏
使用闭包的一个缺点就是容易造成浏览器内存泄漏。JavaScript 是一种具有垃圾回收机制的语言——对象在被创建的时候分配内存,然后当指向这个对象的引用计数为零时,浏览器会回收内存。宿主环境提供的对象都是按照这种方法被处理的。
IE 浏览器有自己的一套垃圾回收机制,这套机制与 JavaScript 提供的垃圾回收机制进行交互时,可能会发生内存泄露。
function leakMemory() {
var el = document.getElementById('el');
var o = { 'el': el };
el.o = o;
}
在 IE 中,每当在一个 JavaScript 对象和一个本地对象之间形成循环引用时,就会发生内存泄露。这段代码的循环引用会导致内存泄露:IE 不会释放被 el 和 o 使用的内存,直到浏览器被彻底关闭并重启动后。