JavaScript是按照ECMAScript标准设计和实现的,后文说的JavaScript语法实际上是ES5的标准的实现。
先说说有哪些基础语法?javascript
基础语法几乎全部的语言差别不大,无非数据类型、操做符、控制语句、函数等,简单列举下。php
JavaScript包含5种基本数据类型,分别是undefined
/ null
/ boolean
/ number
/ string
,基本数据类型就这五种,没有其余的!
JavaScript包含1种复杂的数据类型,就是Object
类型,Object
类型是全部其余对象的基类。
注意:JavaScript并不区分浮点数和整数,都是用Number
来表示。java
前面提到的5种基本数据类型,以及这儿的1种复杂数据类型,这就是数据类型的所有了!面试
这个是常识,知道怎么回事就好。
经常使用的操做符包括:算术操做符、关系操做符、布尔操做符、赋值操做符等。正则表达式
这就是咱们常说的if-else之类的控制语句。
经常使用的并很少:if语句、switch语句、for语句、while语句、for-in语句。算法
函数就是一小段逻辑的封装,理论上逻辑越独立越好。
JavaScript函数相对其余语言来讲有很大不一样。JavaScript函数既能够做为参数,也能够做为返回值。
此外JavaScript函数能够接受任意数量的参数,而且能够经过arguments对象来访问这些参数。编程
任何一门语言的基础语法都是相通的,除开一些细节差别,大体就是上面这些了:数据类型、操做符、控制语句、函数、模块等等。数组
接下来介绍稍微复杂的一些概念。浏览器
JavaScript变量分为两种:基本类型和引用类型。其中基本类型就是前面提到的5种基本数据类型,引用类型就是前面提到的Object
以及基于它的其余复杂数据类型。
✦ 基本类型:在内存中占据实际大小的空间,赋值的时候,会在内存中建立一份新的副本。保存在栈内存
中。
✦ 引用类型:指向对象的指针而不是对象自己,赋值的时候,只是建立了一个新的指针指向对象。保存在堆内存
中。闭包
一句话就是,基本类型在内存中是实际的值;而引用类型在内存中就是一个指针,指向一个对象,多个引用类型可能同时指向同一个对象。
那么,如何肯定某个变量是哪一种数据类型呢?
肯定一个变量是哪一种基本类型用typeof操做符。
肯定一个变量是哪一种引用类型用instanceof操做符。
这个别忘了!
变量是在某个特定的做用域中声明的,做用域决定了这些变量的生命周期,以及哪些代码能够访问其中的变量。
JavaScript做用域只包括全局做用域和函数做用域,并不包含块级做用域!
做用域是能够嵌套的,从而造成做用域链。因为做用域链的存在,可让变量的查找向上追溯,即子函数能够访问父函数的做用域=>祖先函数的做用域=>直到全局做用域,这种函数咱们也称为闭包,后文会介绍。
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();
以下图所示,每一个做用域可以访问到的变量以及嵌套的做用域可向上追溯。
做用域的概念看着简单,实际使用会有很多问题,遇到问题要细心分析。
JavaScript引擎具备自动垃圾回收机制,不须要太关注内存分配和垃圾回收问题。这儿就不展开了!
前面提过,Object是惟一的复杂数据类型,引用类型都是从Object类型上继承而来。
✦ Array:数组类型
✦ Date:日期类型
✦ RegExp:正则表达式类型,这个多学学有好处!
✦ 等等...
那问题来了,咱们用的最多的函数是什么数据类型呢?答案是Function类型!
诶,好像发现了点什么东西?因为Function是引用类型,而JavaScript又能够往引用类型上加属性和方法。那么,函数也能够!这也是JavaScript函数强大和复杂的地方。也就是说:函数也能够拥有自定义方法和属性!
此外,JavaScript对前面提到的5种基本类型的其中3种也作了引用类型封装,分别是Boolean、Number、String,但其实使用很少,了解就行。
对了,在全部代码执行以前,做用域就内置了两个对象,分别是Global和Math,其中浏览器的Global就是window啦!
到此为止,JavaScript中基础的概念都差很少介绍了,其中函数和做用域相对来讲复杂一些,其余的都比较浅显。
接下来,我会介绍介绍JavaScript中一些稍微复杂一些的概念:面向对象。
JavaScript自己并无类和接口的概念了,面向对象都是基于原型实现的。
为了简单,咱们只分析面向对象的两个问题:
✦ 如何定义一个类?
✦ 如何实现类的继承
不扯其余的,直接告诉你。咱们使用构造函数+原型的方式来定义一个类。
使用构造函数建立自定义类型,而后使用new操做符来建立类的实例,可是构造函数上的方法和属性在每一个示例上都存在,不能共享,因而咱们引入原型来实现方法和属性的共享。
最后,咱们将须要共享的方法和属性定义在原型上,把专属于实例的方法和属性放到构造函数中。到这儿,咱们就经过构造函数+原型的方式定义了一个类。
// 构造函数 function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; } // 原型 Person.prototype = { constructor: Person, sayName: function() { return this.name; } } // 实例化 var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //输出"Shelby,Count,Van" alert(person2.friends); //输出"Shelby,Count" alert(person1.friends === person2.friends); //输出false alert(person1.sayName === person2.sayName); //输出true
前文讲了如何定义一个类,那么咱们定义一个父类,一个子类。
如何让子类继承父类呢?不扯别的,直接告诉你。JavaScript经过原型链来实现继承!
如何构建原型链呢?将子类实例赋值给父类构造函数的原型便可。好绕,可是千万得记住了!
构建原型链以后,子类就能够访问父类的全部属性和方法!
// 父类 function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; }; // 子类 function SubType() { this.subproperty = false; } //子类继承父类 SubType.prototype = new SuperType(); //给子类添加新方法 SubType.prototype.getSubValue = function() { return this.subproperty; }; //重写父类的方法 SubType.prototype.getSuperValue = function() { return false; }; // 实例化 var instance = new SubType(); console.log(instance.getSuperValue()); //输出false
面向对象的知识能够用一本书来写,这儿只是简单的介绍下最基础最经常使用的概念。
JavaScript中有两种定义函数的方式:函数声明和函数表达式。
使用函数表达式无须对函数命名,从而实现动态编程,也即匿名函数。有了匿名函数,JavaScript函数有了更强大的用处。
递归是一种很常见的算法,经典例子就是斐波拉契数列。也不扯其余的,直接说递归的最佳实践,上代码:
// 最佳实践,函数表达式 var factorial = (function f(num) { if (num <= 1) { return 1; } else { return num * f(num - 1); } }); // 缺点: // factorial存在被修改的可能 // 致使 return num * factorial(num - 1) 报错 function factorial(num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); } } // 缺点: // arguments.callee,规范已经不推荐使用 function factorial(num) { if (num <= 1) { return 1; } else { return num * arguments.callee(num - 1); } }
递归就是这样,好多人还在使用arguments.callee的方式,改回函数表达式的方式吧,这才是最佳实践。
啰嗦一句,好多人以为递归难写,其实你将其分为两个步骤就会清晰不少了。
✦ 边界条件,一般是if-else。
✦ 递归调用。
按这个模式,找几个经典的递归练练手,就熟悉了。
不少人常常以为闭包很复杂,很容易掉到坑里,其实否则。
那么闭包是什么呢?若是一个函数能够访问另外一个函数做用域中的变量,那么前者就是闭包。因为JavaScript函数能够返回函数,天然,建立闭包的经常使用方式就是在一个函数内部建立另外一个函数!
这并无什么神奇的,在父函数中定义子函数就能够建立闭包,而子函数能够访问父函数的做用域。
咱们一般是由于被闭包坑了,才会被闭包吓到,尤为是面试题里一堆闭包。
闭包的定义前面提了,如何建立闭包也说了,那么咱们说说闭包的缺陷以及如何解决?
/* 咱们经过subFuncs返回函数数组,而后分别调用执行 */ // 返回函数的数组subFuncs,而这些函数对superFunc的变量有引用 // 这就是一个典型的闭包 // 那么有什么问题呢? // 当咱们回头执行subFuncs中的函数的时候,咱们获得的i其实一直都是10,为何? // 由于当咱们返回subFuncs以后,superFunc中的i=10 // 因此当执行subFuncs中的函数的时候,输出i都为10。 // // 以上,就是闭包最大的坑,一句话理解就是: // 子函数对父函数变量的引用,是父函数运行结束以后的变量的状态 function superFunc() { var subFuncs = new Array(); for (var i = 0; i < 10; i++) { subFuncs[i] = function() { return i; }; } return subFuncs; } // 那么,如何解决上诉的闭包坑呢? // 其实原理很简单,既然闭包坑的本质是:子函数对父函数变量的引用,是父函数运行结束以后的变量的状态 // 那么咱们解决这个问题的方式就是:子函数对父函数变量的引用,使用运行时的状态 // 如何作呢? // 在函数表达式的基础上,加上自执行便可。 function superFunc() { var subFuncs = new Array(); for (var i = 0; i < 10; i++) { subFuncs[i] = function(num) { return function() { return num; }; }(i); } return subFuncs; }
综上,闭包自己不是什么复杂的机制,就是子函数能够访问父函数的做用域。
而因为JavaScript函数的特殊性,咱们能够返回函数,若是咱们将做为闭包的函数返回,那么该函数引用的父函数变量是父函数运行结束以后的状态,而不是运行时的状态,这即是闭包最大的坑。而为了解决这个坑,咱们经常使用的方式就是让函数表达式自执行。
此外,因为闭包引用了祖先函数的做用域,因此滥用闭包会有内存问题。
好像把闭包说得一无可取,那么闭包有什么用处呢?
主要是封装吧...
闭包能够封装私有变量或者封装块级做用域。
➙ 封装块级做用域
JavaScript并无块级做用域的概念,只有全局做用域和函数做用域,那么若是想要建立块级做用域的话,咱们能够经过闭包来模拟。
建立并当即调用一个函数,就能够封装一个块级做用域。该函数能够当即执行其中的代码,内部变量执行结束就会被当即销毁。
function outputNumbers(count) { // 在函数做用域下,利用闭包封装块级做用域 // 这样的话,i在外部不可用,便有了相似块级做用域 (function() { for (var i = 0; i < count; i++) { alert(i); } })(); alert(i); //致使一个错误! } // 在全局做用域下,利用闭包封装块级做用域 // 这样的话,代码块不会对全局做用域形成污染 (function() { var now = new Date(); if (now.getMonth() == 0 && now.getDate() == 1) { alert("Happy new year!"); } })(); // 是的,封装块级做用域的核心就是这个:函数表达式 + 自执行! (function() { //这里是块级做用域 })();
➙ 封装私有变量
JavaScript也没有私有变量的概念,咱们也可使用闭包来实现公有方法,经过隐藏变量暴露方法的方式来实现封装私有变量。
(function() { //私有变量和私有函数 var privateVariable = 10; function privateFunction() { return false; } //构造函数 MyObject = function() {}; //公有/特权方法 MyObject.prototype.publicMethod = function() { privateVariable++; return privateFunction(); }; })();
这差很少就是JavaScript的一些基础语法和稍微高级一些的用法,其实所谓的高级,都是JavaScript“不太成熟”的表现,尤为是面向对象,出于工程化的须要可是JavaScript自己并不完美支持。好在ES6最新标准解决了不少问题,结合Babel用起来也不用太考虑兼容性问题,若是你是新手的话,建议你直接去撸ES6+Babel吧。
✦ JavaScript的基础主要包括:5中基本数据类型、1种复杂的数据类型、操做符、控制语句、函数等。
✦ 了解基本的语法后,你还须要学习学习JavaScript的变量、做用域、做用域链。
✦ 常见的引用类型能够边查边用。做为过来人,建议多学学正则,对你的代码功底会有较大的提高。
✦ 面向对象编程的部分外面有不少种方式,你只须要记住使用构造函数+原型去定义一个类,使用原型链去实现继承便可。更多的扩展,去翻翻书吧。
✦ 函数表达式引出了几个比较好玩的东西:递归、闭包、封装。记住递归的最佳实践、闭包的定义及缺陷、闭包的适用场景。
JavaScript做为一门动态语言,和其余语言有较大的差别,这也形成不少人学习JavaScript时会以为难学。但你如今看看前文,虽然是一个简略的总结,但JavaScript主要的内容就这些了,因此不要被本身吓到了。
再补一句,若是你是新手的话,建议你直接去撸ES6+Babel吧。