五种基本数据类型和一种复杂数据类型
基本数据类型5种
undefined
null
boolean
number 不区分浮点数和整数, 一律使用number来表示
string
复杂数据类型1种
Object类型, 也是所有其他对象的基类
最基础的语法:
编程语言基础语法差异不大, 无非是数据类型, 操作符, 控制语句, 函数等
基本操作符:
常用的操作股包括:算术操作符 , 关系操作符, 布尔操作符, 赋值操作符
控制语句
if语句
switch语句
for语句
while语句
for-in语句
函数
函数是一小段逻辑的封装, 理论上逻辑越独立越好.
JavaScript函数相对其他语言来说有很大不同. JavaScript函数既可以作为参数, 也可以作为返回值.
此外JavaScript函数可以接受任意数量的参数, 并且可以通过arguments对象访问这些参数.
任何一门语言的基础语法都是相通的, 除开一些细节差异, 大致就是上面这些了: 数据类型,操作符, 控制语句, 函数, 模块等等
复杂概念,进阶
变量,作用域,内存问题
1.变量
JavaScript变量分为两种: 基本类型和引用类型.其中基本类型就是前面的五种基本数据类型 引用类型就是前面的Object以及基于它的复杂数据类型.
基本数据类型
在内存中占据实际的大小控件, 赋值的时候, 会在内存中穿件一份新的副本.保存在"栈内存"中
引用类型:
只想对象的指针而不是对象本身, 赋值的时候 只是其创建了一个新的指针指向对象.保存在"堆内存"中.
简单来说就是:基本类型在内存中是实际的值;而引用类型在内存汇总就是一个指针,指向一个对象, 多个引用类型可能同时指向同一个对象.
确定一个变量是哪种基本类型用typeof操作符.
确定一个变量是哪种引用类型用instanceof操作符.
作用域
1.变量是在某个特定的作用域中生命的, 作用域决定了这些变量的生命周期,以及哪些代码可以访问其中的变量.
2.JavaScript作用域只包括全局作用域和函数作用域, 并不包含块级作用域!
3.作用域是可以嵌套的, 从而形成作用链.由于作用域链的存在, 可以让变量的查找向上追溯, 既子函数可以访问父函数的作用域=>祖先函数的作用域=>直到全局作用域, 这种函数我们成为闭包.
例:
var color = "blue";
function changeColor() {
var anotherColor = ""red;
function swapColors() {
var temCorlor = anothrtColor;
color = temCorlor;
//这里可以访问color, anotherColor, tempColor
}
}
// 这里只能访问color, changeColor();
作用域的概念看着简单, 实际应用会有不少问题, 遇到问题细心分析.
内存问题:
JavaScript引擎具有自动垃圾回收机制, 不需要太关注内存分配和垃圾回收问题.
引用类型:
Object是唯一的复杂数据类型, 应用类型都是从Object类型上继承而来.
* Array : 数组类型
* Date :日期类型
* RegExp : 正则表达式类型
* 等等...
但我们用的最多的函数是Function类型:
由于Function是引用类型, 而JavaScript又可以往引用类型上加属性和方法.那么. 函数也可以!这也是javaScript函数强大和复杂的地方.也就是说: 函数也可以拥有自定义方法和属性!
JavaScript对前面提到的5种基本类型的其中三种也做了引用类型封装, 分别是Boolean, Number, String, 但使用不多, 只做了解.
注: 在所有代码执行之前, 作用域就内置了两个对象, 分别是Global和Math, 其中浏览器的Global就是window.
到此为止,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.orototype = 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);
}
}
// 缺点1:
// factorial 存在被修改的可能
// 导致 return num *factorial (num - 1) 报错
function facorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
}
}
//缺点2:
// argumentscallee, 规范已经不推荐使用
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * argument.callee(num - 1);
}
}
注: 递归就是这样,arguments.callee已经是过去式, 改回函数表达式才是最常用的,实践出真知.要注意两点1.边界条件, 通常是if-else. 2.递归调用
闭包:
如果一个函数可以访问另一个函数作用域中的变量, 那么牵着就是闭包.犹豫JavaScript函数可以返回函数, 自然, 创建闭包的常用方式就是在一个函数里创建另一个函数.
这很简单, 例如在父函数中定义子函数就可以创建闭包, 而子函数可以访问父函数的作用域.
如何解决闭包的缺陷:
0.我们通过subFuncs返回函数数组, 然后分别调用执行
1. 返回函数的数组subFuncs, 而这些函数对superFunc的变量有引用
2.当我们回头执行subFuncs中的函数的时候, 我们得到的i其实一直都是10,
3.原因是当我们返回subFuncs之后, superFunc中的i=10
4, 所以当执行subFuncs中的函数的时候, 输出i都为10.
以上就是闭包最大的坑, 一句话的理解就是: 子函数对父函数变量的引用, 是父函数运行结束之后的变量的状态
function superFunc() {
var subFuncs = new Array();
for (var i = 0; i<10; i++) {
subFuncs[i] = function() {
return i;
}
}
return i;
}
那么, 如何解决上诉闭包的坑呢?
其实原理很简单, 既然闭包坑的本质是:子函数对父函数变量的引用, 是父函数运行结束之后的变量的状态,那么我们解决这个问题的方式就是: 子函数对父函数变量的引用, 使用运行时的状态, 就是在函数表达式的基础上, 加上自执行就好.
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 outputNumber(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;
}
// 构造函数
MyObjc = function(){};
// 公有/特权方法
Myobject.prototype.pulicmethod = function() {
privateVariable++;
return privateFunction;
};
})();
总结:
* JavaScript的基础主要包括 : 5中基本数据类型, 1种复杂数据类型, 操作符, 控制 语句,函数等.
* 了解基本的语法后, 要学习JavaScript的变量, 作用域, 作用域链
* 常见的引用类型可以边查边用, 注重正则的学习
* 面向对象编程的部分外面有很多种方式, 你只需记住使用构造函数+原型去定义一个类, 使用原型链去实现继承即可.
* 函数表达时间引出了几个比较好玩的东西: 递归, 闭包, 封装. 记住递归的最佳实践,闭包的定义及缺陷,闭包的适用场景.
JavaScript作为一门动态语言, 和其他语言有较大的差异.