基础
ECMAScript是JS的标准,各个浏览器厂商对ES有自己的实现,比如Chrome的v8,这个v8引擎很快,而且node.js用的也是v8引擎。
JavaScript包含:
- ECMAScripte
- DOM(Document Object Model)
- BOM(Browser Object Model)
JS的特点:
- 解释型语言
- 动态语言(变量可以保存任意类型数据,类似于python)
- 基于原型的面向对象
1 | /* |
1 |
|
1 | var a; //声明一个变量 |
1 | /* |
1 | /* |
1 | /* |
字符串
1 | var str = "abcd" |
Array
Array.prototype.shift函数表示删除第一个元素,并返回第一个元素
Array.prototype.indexOf表示寻找某个元素的下标
Array.prototype.map方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
1 | var arr = [1,2,3,4] |
对象
内建对象:Math String Number等
宿主对象
有js的运行环境提供的对象,主要由浏览器提供的对象:
比如 BOM DOM
自定义对象
1 | /* |
1 | /* |
1 | /* |
1 | /* |
1 | /* |
Object
Object.create()
具体见:https://www.jianshu.com/p/28d85bebe599
- 语法:
Object.create(proto, [propertiesObject])
//方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。 - 参数:
- proto : 必须。表示新建对象的原型对象,即该参数会被赋值到目标对象(即新对象,或说是最后返回的对象)的原型上。该参数可以是
null,对象, 函数的prototype属性(创建空的对象时需传null , 否则会抛出TypeError异常)。 - propertiesObject : 可选。 添加到新创建对象的可枚举属性(即其自身的属性,而不是原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应
Object.defineProperties()的第二个参数。 - 返回值:
在指定原型对象上添加新属性后的对象。
常见的六种继承方式
原型链继承
这种方式实现的本质是通过将子类的原型指向了父类的实例。子类的实例就可以通过__proto__访问到父类的实例,这样就可以访问到父类的私有方法,然后再通过__proto__指向父类的prototype就可以获得父类原型上的方法。
1 |
|
Person是一个对象,Person.prototype.constructor才是我们定义的那个f Person()函数;Person由Function.prototype.constructor构造而来,所以有prototype属性,同时Person也继承自Function,所以Person._proto_等于Function.prototype。

我们在子类中为父类添加新的方法或者重写父类的方法时,切记一定要先为prototype指定父类的实例。
1 | /* |
特点:
- 父类可以新增原型方法、原型属性,子类都能访问到
缺点:
- 无法实现多继承
- 来自原型对象的所有属性被所有实例共享
- 创建子类实例时,无法向父类构造函数传参
prototype的由来
参考:http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
若没有prototype,则每一个实例对象,都有自己的属性和方法的副本。这不仅无法做到数据共享,也是极大的资源浪费。比如构造函数中设置了name和age, species属性,则每个实例对象改变了自己的属性,并不会影响到别的实例对象。
考虑到这一点,Brendan Eich决定为构造函数设置一个prototype属性。这个属性包含一个对象(以下简称”prototype对象”),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。比如,species属性就可以放在prototype中。
1 | DOG.prototype = { species : '犬科' }; |
实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。而实例对象引用的属性和方法都放在__proto__中。

如上图所示,当你使用foo构造器构造了一个实例对象f1后,f1显然是通过function foo() 这个构造器(构造函数)创建的,所以f1实例对象中有一个指向function foo()的属性:constructor。但是如果直接把constructor放在实例对象里面,就像name属性一样,那么对于所有foo的实例对象,都会冗余地存储constructor,constructor是一样的,为什么要占用存储空间存储那么多次,所以就把constructor放在了foo.prototype中。
借用构造函数继承
在子类构造函数中调用父类的call()函数实现继承
1 |
|
特点:
- 解决了原型链继承中子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call多个父类对象)
缺点:
- 只能继承父类的属性和方法,不能继承父类原型的属性和方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能。
原型链+借用构造函数的组合继承
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
1 |
|

这是结果。我们可以看到,每个对象的构造函数是存在于它的__proto__中的。
如果每个Person也通过组合继承的方式继承自Object,那么Student也可以继承Object的属性且不是共享实例的。
特点:
- 经过这种继承方式,Student对象可以继承Person对象的属性和方法,且不是共享实例的,但是继承了Person对象的原型属性或方法的实例。
- 可以传参
- 函数可复用
缺点:
- 调用了两次父类构造函数
组合继承优化
1 |
|
Student继承了Person原型的所有属性和方法,这是最完美的继承方法。
总结
果然,组合继承优化才是最合适的继承方案。
把父类的非prototype属性作为子类的非prototype属性,这样就使得每个子类实例不会共享父类的非prototype属性,这就解决了原型链继承的一个缺点:子类实例共享父类属性。
通过在子类构造函数中调用
FatherClass.call(this)来实现1功能,这样就可以向父类构造函数中传参,解决了原型链继承的不能向父类构造函数传参的缺点。通过
ChildClass.prototype = Object.create(FatherClass.prototype)代码,得到这样的原型链:ChildClass.prototype(初始为空,通过ChildClass.prototype.func1 = function()可以添加属性)ChildClass.prototype.__proto__(这个就是FatherClass.prototype)。这样的话,所有子类实例既可以共享父类prototye中的属性,又不用在每次实例化子类的时候两次实例化父类;可以方便地通过
ChildClass.prototype.func1 = function()代码扩充子类原型,而不影响父类原型,非常简洁。
还有一点,每次定义对象的时候,要把数据属性放在构造函数中,把函数属性放在prototype中,这样配合使用组合继承优化,非常方便。在我看vue源码的时候,就发现,很多对象的定义都是遵循上述原则的,原因是,函数可以共享使用,而数据在所有实例中不能共享。
看一个官网上组合继承优化的例子。
1 | // Shape - 父类(superclass) |
DOM
文档对象模型
查找HTML元素
通过id查找HTML元素
1 | document.getElementById('id名称'); |
通过标签名查找HTML元素
1 | document.getElementByTagName('tag') |
通过类名查找HTML元素
1 | document.getElementByClassName('class') |
DOM HTML
创建动态的HTML内容:
1 | document.write('Hello') |
改变HTML元素内容
1 | var a = document.getElementById('id') |
改变元素属性
1 | var a = document.getElementById('id') |
DOM CSS
改变CSS样式
1 | var a = document.getElementById('id') |
DOM事件
下面是一个使用DOM事件的例子
1 | <div id="coordiv" style="float:left;width:199px;height:99px;border:1px solid #c3c3c3" onmousemove="cnvs_getCoordinates(event)" onmouseout="cnvs_clearCoordinates()"></div><br /> |

DOM事件体现了浏览器端的交互。
BOM
浏览器对象模型
window: 浏览器窗口对象,其成员包括所有的全局变量,函数和对象
screen:屏幕对象
location:位置对象,用于获取当前页面的URL地址,还可以把浏览器重定向到新的指定页面

history
1
2
3
4
5
6
7
8
9
10<!--
获得历史记录信息
-->
<button onclick="history.back()">
back
</button>
<button onclick="history.forward()"
forward
</button>
navigator:
setTimeout()异步方法
1 | function func(){ |
setInterval()
与setTimeout()方法类似,但这是每隔一段时间就执行程序。
异步方法
异步方法一般要求回调函数
有个很重要的点:比如
1 | function getDogZhuliangProp() { |
我想通过http请求获取数据,然后赋值给res,返回,但是上面的代码是错的,
原因是,用到了axios用到了ajax请求,这是异步的,这意味着当执行axios.get()时
会并行地执行return res,这就导致res并没有数据,却直接返回了。
js中的class
js的class仅仅为一个语法糖,是在原先构造函数的基础上出现的class,仅仅如此。所以使用构造函数构造类,或者使用class语法糖构造类都是相同的。具体还是使用prototype和this来进行模拟类。
类声明
1 | class Rectangle { |
匿名类
1 | let Rectangle = class { |
static
为一个静态方法,该静态new出的来的对象不能进行使用。
1 | class Point { |
extends
类不可继承没有构造函数的对象
1 | class Animal { |
super
使用super调用超类
js中的跨域问题
什么是跨域
跨域是指从一个域名的网页去请求另一个域名的资源。比如从www.baidu.com 页面去请求 www.google.com 的资源。但是一般情况下不能这么做,它是由浏览器的同源策略造成的。
所谓同源是指,域名,协议,端口均相同。
为什么要跨域
既然有安全问题,那为什么又要跨域呢? 有时公司内部有多个不同的子域,比如一个是location.company.com ,而应用是放在app.company.com , 这时想从 app.company.com去访问 location.company.com 的资源就属于跨域。
解决跨域问题
跨域资源共享(CORS)
CORS背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。
1 | //指定允许其他域名访问 |
jsonp
JSONP的原理:通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。(即用javascript动态加载一个script文件,同时定义一个callback函数给script执行而已。)
在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。

ajax是要求服务器发送回来一个json文件,但是jsonp是引用服务器的一个php代码,这个代码到了浏览器端后,然后浏览器端以自己的php代码中的数据为参数,来这执行这个回调函数。因此,要实现jsonp,浏览器端要指明回调函数,服务器端要在代码中体现数据。
jquery实现jsonp
$.ajax()方法
1
2
3
4
5
6
7
8
9$.ajax({
url:'http://外域.com/xxx.php',
dataType:"jsonp",
jsonp: "callback",
jsonpCallback:"ooo",
success:function(data){
console.log(data);
}
});其中,
jsonp: "callback"是设置路径里的参数名。你这里写callback,那么jQuery发送请求的时候路径里就用callback=还有,
jsonpCallback:"ooo"是设置函数名。你这里写ooo,那么php必须返回ooo为函数名的内容。$.get()方法
$.get()方法的基本用法是同域名抓数据,是真正的ajax原理。而高级用法就是抓jsonp数据。写法是:
1
2
3$.get('http://外域.com/xxx.php', {各种数据}, function(data) {
console.log(data);
}, 'jsonp');$.getJSON()方法
$.getJSON()方法跟$.get()方法类似,区别在于:
1、$.getJSON()的最后一个参数不用写,而$.get()方法的最后一个参数必须是’jsonp’。
2、$.getJSON()虽然省掉了最后一个参数,但这时候,你需要在路径中带上
callback=?或者abc=?这样的字眼,这样jQuery会自动判断,既然你带上了参数,而且参数值是问号,那么jQuery就认为你想搞jsonp勾当,如果你不加任何参数,或者参数值不是问号,那么jQuery就认为你不想搞jsonp勾当,那么这时候$.getJSON()方法就全等于$.get(…,…, ‘json’)方法。
jsonp的优缺点
优点:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制
缺点:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。
js中的运行时上下文context,以及call,apply,bind
以前遇到过,当函数通过对象的方法执行的时候,this是这个对象,当函数作为全局函数的时候,this是window。这就是js中的运行时上下文。
apply, call
apply, call完全一样,但是语法稍有不同。
在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部 this 的指向。
JavaScript 的一大特点是,函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念。
1 | function fruits() {} |
但是如果我们有一个对象banana= {color : “yellow”} ,我们不想对它重新定义 say 方法,那么我们可以通过 call 或 apply 用 apple 的 say 方法:
1 | banana = { |
类(伪)数组使用数组方法
Javascript中存在一种名为伪数组的对象结构。比较特别的是 arguments 对象,还有像调用 getElementsByTagName , document.childNodes 之类的,它们返回NodeList对象都属于伪数组。不能应用 Array下的 push , pop 等方法。但是我们能通过 Array.prototype.slice.call 转换为真正的数组的带有 length 属性的对象,这样 domNodes 就可以应用 Array 下的所有方法了。
1 | var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*")); |
bind
bind()最简单的用法是创建一个函数,使这个函数不论怎么调用都有同样的this值。
MDN的解释是:bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
1 | var foo = { |
用js实现bind
1 | //bind源码实现 |
节流函数与防抖函数
1 | function throttle(fn,delay){ |
1 | debounce: function (fn, delay) { |
为什么经常用fn.apply(this)的写法
因为给事件加回调函数fn的时候,不知道调用这个函数的是window还是定义fn的对象,但是不管是哪个,fn函数内的this肯定都是定义fn的那个对象,所以直接使用fn.apply(this)肯定没有错。
注意:要注意词法作用域的问题,比如上面debounce函数第3行和第7行,要用箭头函数来使this按照词法作用域来绑定到外层的对象,防止在调用回调的时候,this指向window对象。
js实现排序
快排
1 | function quickSort(arr,begin,end){ |