js学习笔记知识点

AJAX  

用法

AJAX不是JavaScript的规范,它只是一个哥们“发明”的缩写:Asynchronous JavaScript and XML,意思就是用JavaScript执行异步网络请求
在现代浏览器上写AJAX主要依靠XMLHttpRequest对象:

  1. 'use strict';
  2. function success(text){
  3. var textarea = document.getElementById('test-response-text');
  4. textarea.value = text;
  5. }
  6. function fail(code){
  7. var textarea = document.getElementById('test-response-text');
  8. textarea.value ='Error code: '+ code;
  9. }
  10. var request =newXMLHttpRequest();// 新建XMLHttpRequest对象
  11. request.onreadystatechange =function(){// 状态发生变化时,函数被回调
  12. if(request.readyState ===4){// 成功完成
  13. // 判断响应结果:
  14. if(request.status ===200){
  15. // 成功,通过responseText拿到响应的文本:
  16. return success(request.responseText);
  17. }else{
  18. // 失败,根据响应码判断失败原因:
  19. return fail(request.status);
  20. }
  21. }else{
  22. // HTTP请求还在继续...
  23. }
  24. }
  25. // 发送请求:
  26. request.open('GET','/api/categories');
  27. request.send();
  28. alert('请求已发送,请等待响应...');

对于低版本的IE,需要换一个ActiveXObject对象:

  1. 'use strict';
  2. function success(text){
  3. var textarea = document.getElementById('test-ie-response-text');
  4. textarea.value = text;
  5. }
  6. function fail(code){
  7. var textarea = document.getElementById('test-ie-response-text');
  8. textarea.value ='Error code: '+ code;
  9. }
  10. var request =newActiveXObject('Microsoft.XMLHTTP');// 新建Microsoft.XMLHTTP对象
  11. request.onreadystatechange =function(){// 状态发生变化时,函数被回调
  12. if(request.readyState ===4){// 成功完成
  13. // 判断响应结果:
  14. if(request.status ===200){
  15. // 成功,通过responseText拿到响应的文本:
  16. return success(request.responseText);
  17. }else{
  18. // 失败,根据响应码判断失败原因:
  19. return fail(request.status);
  20. }
  21. }else{
  22. // HTTP请求还在继续...
  23. }
  24. }
  25. // 发送请求:
  26. request.open('GET','/api/categories');
  27. request.send();
  28. alert('请求已发送,请等待响应...');

如果你想把标准写法和IE写法混在一起,可以这么写:

  1. var request;
  2. if(window.XMLHttpRequest){
  3. request =newXMLHttpRequest();
  4. }else{
  5. request =newActiveXObject('Microsoft.XMLHTTP');
  6. }

通过检测window对象是否有XMLHttpRequest属性来确定浏览器是否支持标准的XMLHttpRequest。注意,不要根据浏览器的navigator.userAgent来检测浏览器是否支持某个JavaScript特性,一是因为这个字符串本身可以伪造,二是通过IE版本判断JavaScript特性将非常复杂。

当创建了XMLHttpRequest对象后,要先设置onreadystatechange的回调函数。在回调函数中,通常我们只需通过readyState === 4判断请求是否完成,如果已完成,再根据status === 200判断是否是一个成功的响应。

XMLHttpRequest对象的open()方法有3个参数,第一个参数指定是GET还是POST,第二个参数指定URL地址,第三个参数指定是否使用异步,默认是true,所以不用写。

注意,千万不要把第三个参数指定为false,否则浏览器将停止响应,直到AJAX请求完成。如果这个请求耗时10秒,那么10秒内你会发现浏览器处于“假死”状态。

最后调用send()方法才真正发送请求。GET请求不需要参数,POST请求需要把body部分以字符串或者FormData对象传进去。


安全限制

上面代码的URL使用的是相对路径。如果你把它改为'http://www.sina.com.cn/',再运行,肯定报错。在Chrome的控制台里,还可以看到错误信息。

这是因为浏览器的同源策略导致的。默认情况下,JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致。

完全一致的意思是,域名要相同(www.example.com和example.com不同),协议要相同(http和https不同),端口号要相同(默认是:80端口,它和:8080就不同)。有的浏览器口子松一点,允许端口不同,大多数浏览器都会严格遵守这个限制。

那是不是用JavaScript无法请求外域(就是其他网站)的URL了呢?方法还是有的,大概有这么几种:

一是通过Flash插件发送HTTP请求,这种方式可以绕过浏览器的安全限制,但必须安装Flash,并且跟Flash交互。不过Flash用起来麻烦,而且现在用得也越来越少了。

二是通过在同源域名下架设一个代理服务器来转发,JavaScript负责把请求发送到代理服务器:

'/proxy?url=http://www.sina.com.cn'

代理服务器再把结果返回,这样就遵守了浏览器的同源策略。这种方式麻烦之处在于需要服务器端额外做开发。


JSONP

第三种方式称为JSONP,它有个限制,只能用GET请求,并且要求返回JavaScript。这种方式跨域实际上是利用了浏览器允许跨域引用JavaScript资源:

  1. <html>
  2. <head>
  3. <scriptsrc="http://example.com/abc.js"></script>
  4. ...
  5. </head>
  6. <body>
  7. ...
  8. </body>
  9. </html>

JSONP通常以函数调用的形式返回,例如,返回JavaScript内容如下:

  1. foo('data');

这样一来,我们如果在页面中先准备好foo()函数,然后给页面动态加一个<script>节点,相当于动态读取外域的JavaScript资源,最后就等着接收回调了。

以163的股票查询URL为例,对于URL:http://api.money.126.net/data/feed/0000001,1399001?callback=refreshPrice,你将得到如下返回:

refreshPrice({"0000001":{"code": "0000001", … });

因此我们需要首先在页面中准备好回调函数:

  1. function refreshPrice(data){
  2. var p = document.getElementById('test-jsonp');
  3. p.innerHTML ='当前价格:'+
  4. data['0000001'].name +': '+
  5. data['0000001'].price +';'+
  6. data['1399001'].name +': '+
  7. data['1399001'].price;
  8. }

最后用getPrice()函数触发:

  1. function getPrice(){
  2. var
  3. js = document.createElement('script'),
  4. head = document.getElementsByTagName('head')[0];
  5. js.src ='http://api.money.126.net/data/feed/0000001,1399001?callback=refreshPrice';
  6. head.appendChild(js);
  7. }

就完成了跨域加载数据。


CORS

如果浏览器支持HTML5,那么就可以一劳永逸地使用新的跨域策略:CORS了。

CORS全称Cross-Origin Resource Sharing,是HTML5规范定义的如何跨域访问资源。

了解CORS前,我们先搞明白概念:

Origin表示本域,也就是浏览器当前页面的域。当JavaScript向外域(如sina.com)发起请求后,浏览器收到响应后,首先检查Access-Control-Allow-Origin是否包含本域,如果是,则此次跨域请求成功,如果不是,则请求失败,JavaScript将无法获取到响应的任何数据。

用一个图来表示就是:
廖雪峰

假设本域是my.com,外域是sina.com,只要响应头Access-Control-Allow-Origin为http://my.com,或者是*,本次请求就可以成功。

可见,跨域能否成功,取决于对方服务器是否愿意给你设置一个正确的Access-Control-Allow-Origin,决定权始终在对方手中。

上面这种跨域请求,称之为“简单请求”。简单请求包括GET、HEAD和POST(POST的Content-Type类型
仅限application/x-www-form-urlencoded、multipart/form-data和text/plain),并且不能出现任何自定义头(例如,X-Custom: 12345),通常能满足90%的需求。

无论你是否需要用JavaScript通过CORS跨域请求资源,你都要了解CORS的原理。最新的浏览器全面支持HTML5。在引用外域资源时,除了JavaScript和CSS外,都要验证CORS。例如,当你引用了某个第三方CDN上的字体文件时:

  1. /* CSS */
  2. @font-face {
  3. font-family:'FontAwesome';
  4. src: url('http://cdn.com/fonts/fontawesome.ttf') format('truetype');
  5. }

如果该CDN服务商未正确设置Access-Control-Allow-Origin,那么浏览器无法加载字体资源。

对于PUT、DELETE以及其他类型如application/json的POST请求,在发送AJAX请求之前,浏览器会先发送一个OPTIONS请求(称为preflighted请求)到这个URL上,询问目标服务器是否接受:

  1. OPTIONS /path/to/resource HTTP/1.1
  2. Host: bar.com
  3. Origin: http://my.com
  4. Access-Control-Request-Method: POST

服务器必须响应并明确指出允许的Method:

  1. HTTP/1.1 200 OK
  2. Access-Control-Allow-Origin: http://my.com
  3. Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS
  4. Access-Control-Max-Age: 86400

浏览器确认服务器响应的Access-Control-Allow-Methods头确实包含将要发送的AJAX请求的Method,才会继续发送AJAX,否则,抛出一个错误。

由于以POST、PUT方式传送JSON格式的数据在REST中很常见,所以要跨域正确处理POST和PUT请求,服务器端必须正确响应OPTIONS请求。


面向对象编程

创建对象

JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象。

当我们用obj.xxx访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到Object.prototype对象,最后,如果还没有找到,就只能返回undefined。

例如,创建一个Array对象:

  1. var arr =[1,2,3];

其原型链是:

arr —-> Array.prototype —-> Object.prototype —-> null

Array.prototype定义了indexOf()、shift()等方法,因此你可以在所有的Array对象上直接调用这些方法。
当我们创建一个函数时:

  1. function foo(){
  2. return0;
  3. }

函数也是一个对象,它的原型链是:

foo —-> Function.prototype —-> Object.prototype —-> null

由于Function.prototype定义了apply()等方法,因此,所有函数都可以调用apply()方法。

很容易想到,如果原型链很长,那么访问一个对象的属性就会因为花更多的时间查找而变得更慢,因此要注意不要把原型链搞得太长。


构造函数

除了直接用{ … }创建一个对象外,JavaScript还可以用一种构造函数的方法来创建对象。它的用法是,先定义一个构造函数:

  1. functionStudent(name){
  2. this.name = name;
  3. this.hello =function(){
  4. alert('Hello, '+this.name +'!');
  5. }
  6. }

你会问,咦,这不是一个普通函数吗?

这确实是一个普通函数,但是在JavaScript中,可以用关键字new来调用这个函数,并返回一个对象:

  1. var xiaoming =newStudent('小明');
  2. xiaoming.name;// '小明'
  3. xiaoming.hello();// Hello, 小明!

注意,如果不写new,这就是一个普通函数,它返回undefined。但是,如果写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this,也就是说,不需要在最后写return this;。

新创建的xiaoming的原型链是:

xiaoming —-> Student.prototype —-> Object.prototype —-> null

也就是说,xiaoming的原型指向函数Student的原型。如果你又创建了xiaohong、xiaojun,那么这些对象的原型与xiaoming是一样的:

  1. xiaoming ↘
  2. xiaohong -→ Student.prototype ----> Object.prototype ----> null
  3. xiaojun ↗

new Student()创建的对象还从原型上获得了一个constructor属性,它指向函数Student本身:

  1. xiaoming.constructor ===Student.prototype.constructor;// true
  2. Student.prototype.constructor ===Student;// true
  3. Object.getPrototypeOf(xiaoming)===Student.prototype;// true
  4. xiaoming instanceof Student;// true

看晕了吧?用一张图来表示这些乱七八糟的关系就是:
廖雪峰

红色箭头是原型链。注意,Student.prototype指向的对象就是xiaoming、xiaohong的原型对象,这个原型对象自己还有个属性constructor,指向Student函数本身。

另外,函数Student恰好有个属性prototype指向xiaoming、xiaohong的原型对象,但是xiaoming、xiaohong这些对象可没有prototype这个属性,不过可以用__proto__这个非标准用法来查看。

现在我们就认为xiaoming、xiaohong这些对象“继承”自Student。

不过还有一个小问题,注意观察:

  1. xiaoming.name;// '小明'
  2. xiaohong.name;// '小红'
  3. xiaoming.hello;// function: Student.hello()
  4. xiaohong.hello;// function: Student.hello()
  5. xiaoming.hello === xiaohong.hello;// false

xiaoming和xiaohong各自的name不同,这是对的,否则我们无法区分谁是谁了。

xiaoming和xiaohong各自的hello是一个函数,但它们是两个不同的函数,虽然函数名称和代码都是相同的!

如果我们通过new Student()创建了很多对象,这些对象的hello函数实际上只需要共享同一个函数就可以了,这样可以节省很多内存。

要让创建的对象共享一个hello函数,根据对象的属性查找原则,我们只要把hello函数移动到xiaoming、xiaohong这些对象共同的原型上就可以了,也就是Student.prototype:
廖雪峰

修改代码如下:

  1. functionStudent(name){
  2. this.name = name;
  3. }
  4. Student.prototype.hello =function(){
  5. alert('Hello, '+this.name +'!');
  6. };

用new创建基于原型的JavaScript的对象就是这么简单!

  • 忘记写new怎么办
    如果一个函数被定义为用于创建对象的构造函数,但是调用时忘记了写new怎么办?

在strict模式下,this.name = name将报错,因为this绑定为undefined,在非strict模式下,this.name = name不报错,因为this绑定为window,于是无意间创建了全局变量name,并且返回undefined,这个结果更糟糕。

所以,调用构造函数千万不要忘记写new。为了区分普通函数和构造函数,按照约定,构造函数首字母应当大写,而普通函数首字母应当小写,这样,一些语法检查工具如jslint将可以帮你检测到漏写的new。
最后,我们还可以编写一个createStudent()函数,在内部封装所有的new操作。一个常用的编程模式像这样:

  1. functionStudent(props){
  2. this.name = props.name ||'匿名';// 默认值为'匿名'
  3. this.grade = props.grade ||1;// 默认值为1
  4. }
  5. Student.prototype.hello =function(){
  6. alert('Hello, '+this.name +'!');
  7. };
  8. function createStudent(props){
  9. returnnewStudent(props ||{})
  10. }

这个createStudent()函数有几个巨大的优点:一是不需要new来调用,二是参数非常灵活,可以不传,也可以这么传:

  1. var xiaoming = createStudent({
  2. name:'小明'
  3. });
  4. xiaoming.grade;// 1

如果创建的对象有很多属性,我们只需要传递需要的某些属性,剩下的属性可以用默认值。由于参数是一个Object,我们无需记忆参数的顺序。如果恰好从JSON拿到了一个对象,就可以直接创建出xiaoming。


原型继承

在传统的基于Class的语言如Java、C++中,继承的本质是扩展一个已有的Class,并生成新的Subclass。

由于这类语言严格区分类和实例,继承实际上是类型的扩展。但是,JavaScript由于采用原型继承,我们无法直接扩展一个Class,因为根本不存在Class这种类型。

但是办法还是有的。我们先回顾Student构造函数:
···javascript
function Student(props) {
this.name = props.name || 'Unnamed';
}

Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}

  1. 以及Student的原型链:
  2. ![](http://www.liaoxuefeng.com/files/attachments/001439872136313496e60e07ed143bda40a0200b12d8cc3000/l)
  3. 现在,我们要基于Student扩展出PrimaryStudent,可以先定义出PrimaryStudent
  4. ```javascript
  5. function PrimaryStudent(props) {
  6. // 调用Student构造函数,绑定this变量:
  7. Student.call(this, props);
  8. this.grade = props.grade || 1;
  9. }

但是,调用了Student构造函数不等于继承了Student,PrimaryStudent创建的对象的原型是:

new PrimaryStudent() —-> PrimaryStudent.prototype —-> Object.prototype —-> null

必须想办法把原型链修改为:

new PrimaryStudent() —-> PrimaryStudent.prototype —-> Student.prototype —-> Object.prototype —-> null

这样,原型链对了,继承关系就对了。新的基于PrimaryStudent创建的对象不但能调用PrimaryStudent.prototype定义的方法,也可以调用Student.prototype定义的方法。

如果你想用最简单粗暴的方法这么干:

  1. PrimaryStudent.prototype =Student.prototype;

是不行的!如果这样的话,PrimaryStudent和Student共享一个原型对象,那还要定义PrimaryStudent干啥?

我们必须借助一个中间对象来实现正确的原型链,这个中间对象的原型要指向Student.prototype。为了实现这一点,参考道爷(就是发明JSON的那个道格拉斯)的代码,中间对象可以用一个空函数F来实现:

  1. // PrimaryStudent构造函数:
  2. functionPrimaryStudent(props){
  3. Student.call(this, props);
  4. this.grade = props.grade ||1;
  5. }
  6. // 空函数F:
  7. function F(){
  8. }
  9. // 把F的原型指向Student.prototype:
  10. F.prototype =Student.prototype;
  11. // 把PrimaryStudent的原型指向一个新的F对象,F对象的原型正好指向Student.prototype:
  12. PrimaryStudent.prototype =new F();
  13. // 把PrimaryStudent原型的构造函数修复为PrimaryStudent:
  14. PrimaryStudent.prototype.constructor =PrimaryStudent;
  15. // 继续在PrimaryStudent原型(就是new F()对象)上定义方法:
  16. PrimaryStudent.prototype.getGrade =function(){
  17. returnthis.grade;
  18. };
  19. // 创建xiaoming:
  20. var xiaoming =newPrimaryStudent({
  21. name:'小明',
  22. grade:2
  23. });
  24. xiaoming.name;// '小明'
  25. xiaoming.grade;// 2
  26. // 验证原型:
  27. xiaoming.__proto__ ===PrimaryStudent.prototype;// true
  28. xiaoming.__proto__.__proto__ ===Student.prototype;// true
  29. // 验证继承关系:
  30. xiaoming instanceof PrimaryStudent;// true
  31. xiaoming instanceof Student;// true

用一张图来表示新的原型链:

注意,函数F仅用于桥接,我们仅创建了一个new F()实例,而且,没有改变原有的Student定义的原型链。

如果把继承这个动作用一个inherits()函数封装起来,还可以隐藏F的定义,并简化代码:

  1. function inherits(Child,Parent){
  2. var F =function(){};
  3. F.prototype =Parent.prototype;
  4. Child.prototype =new F();
  5. Child.prototype.constructor =Child;
  6. }

这个inherits()函数可以复用:

  1. functionStudent(props){
  2. this.name = props.name ||'Unnamed';
  3. }
  4. Student.prototype.hello =function(){
  5. alert('Hello, '+this.name +'!');
  6. }
  7. functionPrimaryStudent(props){
  8. Student.call(this, props);
  9. this.grade = props.grade ||1;
  10. }
  11. // 实现原型继承链:
  12. inherits(PrimaryStudent,Student);
  13. // 绑定其他方法到PrimaryStudent原型:
  14. PrimaryStudent.prototype.getGrade =function(){
  15. returnthis.grade;
  16. };

class继承

在上面的章节中我们看到了JavaScript的对象模型是基于原型实现的,特点是简单,缺点是理解起来比传统的类-实例模型要困难,最大的缺点是继承的实现需要编写大量代码,并且需要正确实现原型链。

有没有更简单的写法?有!

新的关键字class从ES6开始正式被引入到JavaScript中。class的目的就是让定义类更简单。

我们先回顾用函数实现Student的方法:

  1. functionStudent(name){
  2. this.name = name;
  3. }
  4. Student.prototype.hello =function(){
  5. alert('Hello, '+this.name +'!');
  6. }

如果用新的class关键字来编写Student,可以这样写:

  1. classStudent{
  2. constructor(name){
  3. this.name = name;
  4. }
  5. hello(){
  6. alert('Hello, '+this.name +'!');
  7. }
  8. }

比较一下就可以发现,class的定义包含了构造函数constructor和定义在原型对象上的函数hello()(注意没有function关键字),这样就避免了Student.prototype.hello = function () {…}这样分散的代码。

最后,创建一个Student对象代码和前面章节完全一样:

  1. var xiaoming =newStudent('小明');
  2. xiaoming.hello();
  • class继承
    用class定义对象的另一个巨大的好处是继承更方便了。想一想我们从Student派生一个PrimaryStudent需要编写的代码量。现在,原型继承的中间对象,原型对象的构造函数等等都不需要考虑了,直接通过extends来实现:
  1. classPrimaryStudent extends Student{
  2. constructor(name, grade){
  3. super(name);// 记得用super调用父类的构造方法!
  4. this.grade = grade;
  5. }
  6. myGrade(){
  7. alert('I am at grade '+this.grade);
  8. }
  9. }

注意PrimaryStudent的定义也是class关键字实现的,而extends则表示原型链对象来自Student。子类的构造函数可能会与父类不太相同,例如,PrimaryStudent需要name和grade两个参数,并且需要通过super(name)来调用父类的构造函数,否则父类的name属性无法正常初始化。

PrimaryStudent已经自动获得了父类Student的hello方法,我们又在子类中定义了新的myGrade方法。

ES6引入的class和原有的JavaScript原型继承有什么区别呢?实际上它们没有任何区别,class的作用就是让JavaScript引擎去实现原来需要我们自己编写的原型链代码。简而言之,用class的好处就是极大地简化了原型链代码。

你一定会问,class这么好用,能不能现在就用上?

现在用还早了点,因为不是所有的主流浏览器都支持ES6的class。如果一定要现在就用上,就需要一个工具把class代码转换为传统的prototype代码,可以试试Babel这个工具。  

摘抄来源:http://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000


原文地址:https://www.cnblogs.com/luolei/p/7076256.html