前端基础(三)-- JavaScript
JavaScript简介
JavaScript
可以说是世界上最流行的脚本语言,这也正达到了作者给这种语言取名字的目的,由于当时Java
很火,所以这哥们儿为了JavaScript
也能火起来就取了个相似的名字,并希望JavaScript
也能火起来,所以现在就有了很多程序员的段子,一脸黑线。
JavaScript
是一种解释型的动态语言,我们知道用JavaScript
开发web,然而自从有了Node.js
,前端程序员瞬间变成了全栈,自从facebook
开源了跨平台开发框架react-native,前端程序员瞬间能写源生客户端了,最近阿里也开源了更轻量级Weex
,而且最近RoyLi的创业公司搞出来一个硬件产品Ruff
,也可以用JavaScript
开发,前端程序员瞬间成为了真正的全栈,JavaScript
这是要大一统的节奏啊,想想也是醉了。所以,是时候学一波JavaScript
了。
虽然之前基本没怎么用过JavaScript
,但是也学过像python
这种动态语言,说实话,学完JavaScript
就有一种感觉这是tm什么玩意儿的感觉,可以说非常怪异,也可以说非常magic
,用一个词形容感觉特别合适,喜欢NBA的童鞋应该深有感触,妖刀–GinoBili。真的是太妖了。幸好有最新的标准ECMAScript 6
(以下简称ES6)写起来还能轻松点,不然真的坑太多了。下文会对比着说。
数据类型
JavaScript
中一个有5种基本数据类型:
- 数字,包括整数和浮点和NaN(not a number)
- 字符串
- 布尔值
- undefined:未声明或者声明了却未赋值
- null:空值,与undefined的区别在于这是一个已经声明的变量
JavaScript
,并且任何不属于基本数据类型的东西都是对象。
数组,Map
什么的就不写了。
变量
说到变量我真是喷出一口老血,太特么容易坑了。
由于是动态语言,所以不需要指定变量的类型,可以在运行时绑定。声明变量用var(variable)。
作用域
为什么说容易坑呢,先看个列子
竟然打印了10。法克。
用var
声明的变量的作用域是函数级别的,也就是说在函数内部有效,不同于java
等静态语言变量声明是块级别的。由于for语句属于函数,所以变量i
的作用域就是整块代码。
那为啥会打印出f**k
?如果不用var
声明变量,那么默认就是全局变量,也就是说b
其实是个全局变量..醉了。JavaScript
中有一种严格模式,在JavaScript代码的第一行写上:'use strict';
,就会强制通过var
声明变量,避免发生错误。
另外一种办法就是,ES6
新增了let
命令用来声明变量,该变量的作用域是块级别的,把上面的例子var
改为let
最终输出就是变成6。
变量提升
又为啥这么说坑呢,看例子
结果竟然是undefined
,再法克。
上面函数的意思是匿名函数立即执行的写法。JavaScript
的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部。所以上面代码在JavaScript
引擎看来是这样的:
那有什么办法解决呢?
用let
,用let
,用let
。
坑爹的this
一般正常的面向对象的语言this
就是指向对象本身,而JavaScript
很坑爹,this
指向视情况而定,用好了是指向对象本身,用不好就指向全局对象(非strict模式)或者指向undefined
(strict模式),全局对象是指web
中是window
,Node.js
中是global
。
- 指向对象本身
obj.fuc()
;- 由于
JavaScript
中函数也是对象,调用函数对象的call()
、apply()
,第一个参数传入要绑定的this
对象。
- 指向全局对象或者
undefined
- 没有绑定对象
- 间接调用方法
var a = obj.func(), a();
- 方法中返回闭包,闭包中使用了
this
总之,this
坑很多,能不用最好不用。
闭包
闭包(closure
)是一种包含了外部函数的参数和局部变量的返回函数。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外 隐藏起来。
|
|
Android开发中常用的回调就是一种闭包,只不过是用对象方法的方式表达,而JavaScript
中函数也是一种对象,所以无需多余的对象引用。
类似Java
的lambda
表达式,ES6中可以用箭头函数定义匿名函数:
|
|
面向对象
JavaScript
是一种面向对象的语言,刚才已经说了除基本数据类型外,所有的东西都是对象,但是又跟正常的面向对象语言不一样。像Java
、C++
这种大多数面向对象语言,类和实例是面向对象的基础,而JavaScript
不区分类和实例的概念,而是通过原型(prototype
)来实现面向对象编程。
JavaScript
对每个创建的对象都会设置一个原型,指向它的原型对象。并且有一个属性查找原则,当我们用obj.xxx
访问一个对象的属性时,JavaScript
引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype
对象,最后,如果还没有找到,就只能返回undefined
。
创建对象
JavaScript
面向对象基于原型实现,这就导致其用法比较灵活,也足够简单,缺点就是比较难理解,容易出错。下面是几种创建对象的方法。
直接用{ ... }
创建一个对象
Student
就是一个对象
原型链是这样的:
JavaScript
的原型链和Java
的Class
区别就在,它没有“Class”的概念,所有对象都是实例,所谓继承关系不过是把一个对象的原型指向另一个对象而已。
Object.create()
传入一个原型对象作为参数,并创建一个基于该原型的新对象,但是新对象什么属性都没有
|
|
构造函数
JavaScript
的构造函数就是普通函数
|
|
原型链是这样的
也就是说,xiaoming
的原型指向函数Student
的原型。验证一下
封装构造函数,以对象作为初始化参数
|
|
这个createStudent()
函数有几个巨大的优点:一是不需要new来调用,二是参数非常灵活,可以不传,也可以传一个对象。
原型继承
一张图看懂上面的关系xiaoming
的原型指向Student
的prototype
对象,这个原型对象有个constuctor
属性,指向Student()
函数本身。
上面可以看到xiaoming.hello() !== xiaohong.hello()
,各自的hello()
函数实际上是两个不同的函数,如果我们要创建共享的hello
函数,根据属性查找原则,只需要把函数定义在他们共同所指的原型对象上来就可以了。
那么假如我们想从Student
扩展出PrimaryStudent
,使得新的基于PrimaryStudent
创建的对象不但能调用PrimaryStudent.prototype
定义的方法,也可以调用Student.prototype
定义的方法。也就是说原型链是这样的
那要怎么做呢?
我们可以定义一个空函数F
,用于桥接原型链,并将其封装起来,隐藏F
的定义,代码如下
使用
原型链图如下
类继承
说实话,你让我写原型继承,我的内心其实是拒绝的,这特么都是些什么啊乱七八糟的,继承要写这么多,而且很容易出错有没有!那么有没有类似Java
这种类继承的方式呢,答案是当然有,ES6早就为我们准备好了。
ES6中增加了新的关键字class
用于定义类。extends
用于实现类的继承。
创建对象的方式跟原型继承一样,new
就可以了。
继承:
是不是炒鸡简单,跟Java
的类继承基本一模一样。这样写跟原型继承的写法在JavaScript
引擎看来完全一样。
总结
这篇基本描述了JavaScript
的一些注意容易踩坑的点和与Java
面向对象实现不同的点。像一些基础的集合、字符串等都没写。当然还有一些前端用的比较多的比如DOM
、AJAX
、jQuery
就不写了,大概浏览下就好了。
另外一点要说的就是,对于我们这些非前端工程师来说,ES6中定义了的一律用ES6的,而且像Node.js
这种脱离了浏览器引擎的框架已经完全支持ES6的写法。
学完JavaScript
就得学Node.js
啊。下一篇带来Node.js
基础和实战。
参考
《JavaScript面向对象编程指南(第二版)》
阮一峰的网络日志
廖雪峰 JavaScript教程
JavaScript秘密花园
本文链接: http://w4lle.com/2016/05/31/JavaScript/
版权声明:本文为 w4lle 原创文章,可以随意转载,但必须在明确位置注明出处!
本文链接: http://w4lle.com/2016/05/31/JavaScript/