JavaScript ( JS ) 是一种具有函数优先的轻量级,解释型(直接以文本形式被浏览器解析)或即时编译型的编程语言。虽然它是作为开发Web 页面的脚本语言而出名的,但是它也被用到了很多非浏览器环境中,例如 Node.js、 Apache CouchDB 和 Adobe Acrobat。JavaScript 是一种基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式和声明式(如函数式编程)风格。
一、数据类型
JavaScript 的数据类型,共有六种。
- 数值(number):整数和小数(比如1和3.14)。
- 字符串(string):文本(比如Hello World)。
- 布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)。
- undefined:表示“未定义”或不存在。
- null:表示空值,即此处的值为空。
- 对象(object):各种值组成的集合。
通常,数值、字符串、布尔值这三种类型,合称为原始类型(primitive type)的值,即它们是最基本的数据类型,不能再细分了。对象则称为合成类型(complex type)的值,因为一个对象往往是多个原始类型的值的合成,可以看作是一个存放各种值的容器。至于undefined和null,一般将它们看成两个特殊值。
对象是最复杂的数据类型,又可以分成三个子类型。
- 狭义的对象(object)
- 数组(array)
- 函数(function)
数据类型判断方法
- typeof运算符
- instanceof运算符
- Object.prototype.toString方法
// instanceof运算符可以区分数组和对象。
var o = {};
var a = [];
o instanceof Array // false
a instanceof Array // true
typeof null // "object"
typeof NaN // 'number'
null的类型是object,这是由于历史原因造成的。1995年的 JavaScript 语言第一版,只设计了五种数据类型(对象、整数、浮点数、字符串和布尔值),没考虑null,只把它当作object的一种特殊值。后来null独立出来,作为一种单独的数据类型,为了兼容以前的代码,typeof null返回object就没法改变了。
NaN不是独立的数据类型,而是一个特殊数值
二、原型链
书面解释
JavaScript 常被描述为一种基于原型的语言 (prototype-based language) ——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain)
通俗解释
每个对象都有一个
__proto__属性等于上级构造函数的prototype,当我们访问一个obj的某个属性,如果该对象没有这个属性,则会继续查找obj.__proto__,即obj上级构造函数有没有这个属性,如果obj.__proto__也没有这个属性,则继续查找obj.__proto__.__proto__,逐级往上查,知道查到该属性返回值或者查到根对象Object,如果根对象Object.prototype没有这个属性,则返回undefined。这种对象之间通过__proto__联系起来,形成的链式结构被称为原型链。
一些有用的知识点
create()
当我们使用Object.create()方法创建实例的时候,可以设置新的实例对象以谁为原型对象。
function Person(first, last, age, sex, interests) {
this.first = first;
this.last = last;
this.age = age;
this.sex = sex;
this.interests = interests;
}
var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
var person2 = Object.create(person1);
console.log(person2.__proto__); // person1
constructor 属性
每个实例对象都从原型中继承了一个constructor属性,该属性指向了用于构造此实例对象的构造函数。(constructor 汉语意思“施工单位”,保存当前实例对象的创建者信息)
var person2 = Object.create(person1);
person2.constructor
person2.constructor.name // person1
person1.constructor.name // Person
call() 和 apply()
每个函数都包含两个非继承而来的方法,这两个函数的作用是一样的,都是在特定的作用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖以运行的作用域。
一般来说,this总是指向调用某个方法的对象,但是使用call()和apply()方法时,就会改变this的指向。
不同点:接收参数的方式不同。
- apply()方法 接收两个参数,一个是函数运行的作用域(this),另一个是参数数组。
apply([thisObj [,argArray] ]); - call()方法 第一个参数和apply()方法的一样,但是传递给函数的参数必须列举出来。
call([thisObject[,arg1 [,arg2 [,...,argn]]]]);
function add(c,d){
return this.a + this.b + c + d;
}
var s = {a:1, b:2 };
console.log(add.call(s,3,4)); // 1+2+3+4 = 10
console.log(add.apply(s,[5,6])); // 1+2+5+6 = 14
三、作用域
js里作用域可以分为全局作用域和函数内局部作用域
声明变量
变量声明,无论发生在何处,都在执行任何代码之前进行处理。用
var声明的变量的作用域是它当前的执行上下文,它可以是嵌套的函数,或者对于声明在任何函数外的变量来说是全局。当赋值给未声明的变量, 则执行赋值后, 该变量会被隐式地创建为全局变量(它将成为全局对象的属性)。
变量提升
由于变量声明(以及其他声明)总是在任意代码执行之前处理的,所以在代码中的任意位置声明变量总是等效于在代码开头声明。这意味着变量可以在声明之前使用。 var与let声明变量的区别 var声明变量存在变量提升,let不存在。var声明的变量可以多次声明,let不可以
四、闭包
概念
一个闭包,就是一个函数与其被创建时所带有的作用域对象的组合。
function makeAdder(a) {
return function(b) {
return a + b;
}
}
var add5 = makeAdder(5);
var add20 = makeAdder(20);
add5(6); // 11
add20(7); // 27
每当 JavaScript 执行一个函数时,都会创建一个作用域对象(scope object),用来保存在这个函数中创建的局部变量。它使用一切被传入函数的变量进行初始化(初始化后,它包含一切被传入函数的变量)。这与那些保存的所有全局变量和函数的全局对象(global object)相类似,但仍有一些很重要的区别:第一,每次函数被执行的时候,就会创建一个新的,特定的作用域对象;第二,与全局对象(如浏览器的 window 对象)不同的是,你不能从 JavaScript代码中直接访问作用域对象,也没有可以遍历当前作用域对象中的属性的方法。
通常,JavaScript的垃圾回收器会在这时回收外部函数创建的作用域对象(暂记为 a),但是,外部函数的返回值(内部函数),拥有一个指向作用域对象a的引用。最终,作用域对象a不会被垃圾回收器回收,直到没有任何引用指向内部函数。
闭包的优缺点
优点:1、闭包的作用域对象,不会污染全局对象。
缺点:1、闭包会使得函数中的变量都被保存在内存中,内存消耗很大,可能会造成网页的性能问题,甚至内存泄露。2、 可能的this指向问题
闭包的用途
1、闭包可以实现外部读取函数作用域内的局部变量
function f1(){
var n=666;
function f2(){
console.log(n);
}
return f2;
}
var result=f1();
result(); // 666
2、让局部变量终保持在内存中,不会被自动清除。可以用来做数据的缓存。
function f1(n) {
var cache = {} // 缓存对象
return {
f2: function () {
if(cache[n]) {
// 缓存存在,直接返回
console.log('=======>走缓存')
return cache[n]
}
console.log('=======>赋值')
var p = 'test'
cache[n] = p
return p
}
}
}
var a = f1('1')
a.f2() // "test" =======>赋值
a.f2() // "test" =======>走缓存
// 上面代码中,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
// 设想f2是一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。
3、封装对象的私有属性和私有方法。
function f1(n) {
return function () {
return n++;
};
}
var a1 = f1(1);
a1() // 1
a1() // 2
a1() // 3
var a2 = f1(5);
a2() // 5
a2() // 6
a2() // 7
//这段代码中,a1 和 a2 是相互独立的,各自返回自己的私有变量。
五、事件
事件并不是JavaScript的核心部分——它们是在浏览器Web APIs中定义的
网页事件的方式
- 事件处理器属性
const btn = document.querySelector('button');
btn.onclick = function() {
const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
document.body.style.backgroundColor = rndCol;
}
- 行内时间处理器
也称为内联事件处理程序,他们被认为是不好的做法。混用 HTML 和 JavaScript,会使文档很难解析,对搜索引擎不太友好。
<button onclick="alert('Hello');">Press me</button>
- addEventListener和removeEventListener(事件监听)
事件监听有很多优点,如果需要的话,可以使用
removeEventListener()删除事件处理程序代码,而且如果有需要,您可以向同一类型的元素添加多个监听器。
事件对象
在事件处理函数内部,可能会看到一个固定指定名称的参数,例如
event或e。 这被称为事件对象,它被自动传递给事件处理函数,以提供额外的功能和信息,事件对象e的target属性始终是事件刚刚发生的元素。
阻止默认行为
preventDefault()
事件冒泡和捕获
js中事件的执行都要经历捕获和冒泡两个阶段 捕获--浏览器检查元素的最外层祖先
<html>,是否在捕获阶段中注册了一个onclick事件处理程序,如果是,则运行它。然后,它移动到<html>中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素。
冒泡--浏览器检查实际点击的元素是否在冒泡阶段中注册了一个onclick事件处理程序,如果是,则运行它。- 然后它移动到下一个直接的祖先元素,并做同样的事情,然后是下一个,等等,直到它到达<html>元素。
阻止冒泡--e.stopPropagation(); 利用事件冒泡机制,我们还可以实现事件委托。例如:如果想要在大量子元素中单击任何一个都可以运行一段代码,我们可以将事件监听器设置在其父节点上,并让子节点上发生的事件冒泡到父节点上,而不是每个子节点单独设置事件监听器。
六、异步
Javascript是一种“单线程”的语言,一次只能完成一件任务,如果有多个任务,就必须排队。Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。 异步执行的运行机制如下 (1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。(4)主线程不断重复上面的第三步。
异步编程的四种方法
- 回调函数 这是异步编程最基本的方法。所谓"回调函数"(callback),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
- 事件监听 采用事件驱动模式
- 发布/订阅模式(观察者模式) 某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。
- Promise对象 每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。
延伸 摘自阮一峰es6入门--JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
参考:JavaScript运行机制详解:再谈Event Loop www.ruanyifeng.com/blog/2014/1…
七、正则
正则表达式是描述字符模式的对象。用于对字符串模式匹配及检索替换,是对字符串执行模式匹配的强大工具。 正则表达式的学习涉及几个重要知识点:语法、修饰符(i, g, m)、方括号([])、元字符、量词、对象方法(test, exec)。
语法
var re = new RegExp("\w+");
var re = /\w+/;
对象方法
test() 方法用于检测一个字符串是否匹配某个模式,如果字符串中含有匹配的文本,则返回 true,否则返回 false。
var patt = /e/;\
patt.test("The best things in life are free!"); // true
exec() 返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。
var patt = /e/
patt.exec('the first apple is red!');
// ["e", index: 2, input: "the first apple is red!", groups: undefined]
参考:www.runoob.com/jsref/jsref…
八、面向对象(Object-oriented programming ,OOP)
OOP 的基本思想是:在程序里,我们通过使用对象去构建现实世界的模型,把原本很难(或不可)能被使用的功能,简单化并提供出来,以供访问。类和实例是大多数面向对象编程语言的基本概念,但是,JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。
特性
- 封装
一层含义是把对象的属性和行为看成一个密不可分的整体,二层含义是隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。
- 继承
提高代码复用性;继承是多态的前提。
- 抽象
抽象是允许模拟工作问题中通用部分的一种机制。这可以通过继承(具体化)或组合来实现。JavaScript通过继承实现具体化,通过让类的实例是其他对象的属性值来实现组合。
- 多态
父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。