什么软件做网站,百度旗下的外贸建站公司,上海服装外贸公司,网络公司排名最新排名最近自己在休假#xff0c;打算闭门几天将《JavaScript高级程序设计》(第3版)这本良心教材再回顾一遍。目前自己进入前端领域两年多#xff0c;现在重读并记录下这本教材的“硬”知识点 。本文原文链接请戳 重读《JavaScript高级程序设计》link.jianshu.com函数没有重载ECM…最近自己在休假打算闭门几天将《JavaScript高级程序设计》(第3版)这本良心教材再回顾一遍。目前自己进入前端领域两年多现在重读并记录下这本教材的“硬”知识点 。本文原文链接请戳 重读《JavaScript高级程序设计》link.jianshu.com函数没有重载ECMAScript 函数不能像传统意义上那样实现重载。而在其他语言如Java中可以为一个函数编写两个定义只要这两个定义的签名接受的参数类型和数量不同即可[p66]。ECMAScript的类型是松散形的没有签名所以是没有重载的。function load(num){return num 100;
}
function load(num,name){return num 200;
}
var result load(100); // 300
# 后面的函数声明覆盖掉前面的函数声明
基本的数据类型基本类型值指的是简单的数据段而引用类型指那些可能由多个值构成的对象[p68]。这里指出来的基本的数据类型是说的es5的哈Undefined,Null,Boolean,Number和String。传递参数ECMAScript 中所有的函数的参数都是按值传递的[p70]。也就是说把函数外部的值复制给函数内部的参数就是把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样而引用类型值的传递则如同引用类型变量的复制一样。下面分开例子介绍两种不同类型为什么是按值传递。基本类型值基本类型这个按值传递比较好理解直接复制变量的值传递function addTen(num){num 10;return num;
}
var count 20;
var result addTen(10);
console.log(count); // 20 没有变化哈
console.log(result); // 30
引用类型值有些人认为引用类型的传参是按照引用来传的那暂且认为他们的理解是正确的那下面的示例结果怎么解析呢function setName(obj){obj.name 嘉明;obj new Object();obj.name 庞嘉明;
}
var person new Object();
setName(person);
console.log(person.name); // 嘉明为啥不是庞嘉明呢
如果是按照引用传的话那么新建的对象obj new Object()应该是指向堆内容的对象啊那么改变它本有的name属性值应该生效然而并没有生效。所以它也是按值传递滴。函数声明与函数表达式解析器在向执行环境中加载数据时对函数声明和函数表达式并非一视同仁[p111]。解析器会率先读取函数声明并使其执行任何代码之前可用可以访问至于函数表达式则必须等到解析器执行到它所在的代码行才会真正被解析。console.log(sum(10 , 10)); // 20
function sum(num1 , num2){return num1 num2;
}
console.log(sum(10 , 10)); //TypeError: sum is not a function
var sum function(num1 , num2){return num1 num2;
}
apply和call每个函数都包含两个非继承而来的方法apply()和call()。这两个方法的用途都是在特定的作用域中调用函数实际上等于设置函数体内this对象的值[116]。call和apply在对象中还是挺有用处的。apply()方法和call()方法的作用是相同的区别在于接收参数的方式不同。applyapply()方法接收两个参数一个是在其中运行函数的作用域另一个是参数数组这里的参数数组可以是Array的实例也可以是arguments对象类数组对象。function sum(num1 , num2){return num1 num2;
}
function callSum1(num1,num2){return sum.apply(this,arguments); // 传入arguments类数组对象
}
function callSum2(num1,num2){return sum.apply(this,[num1 , num2]); // 传入数组
}
console.log(callSum1(10 , 10)); // 20
console.log(callSum2(10 , 10)); // 20
callcall()方法接收的第一个参数和apply()方法接收的一样变化的是其余的参数直接传递给函数。换句话说在使用call()方法时传递给函数的参数必须逐个列举出来。function sum(num1 , num2){return num1 num2;
}
function callSum(num1 , num2){return sum.call(this , sum1 , sum2);
}
console.log(callSum(10 , 10)); // 20
创建对象虽然Object构造函数或者对象字面量都可以用来创建单个对象但是这些方式有个明显的缺点使用同一个接口创建很多对象会产生大量的重复代码。[p144]工厂模式工厂模式就是造一个模子产生一个个对象。function createPerson(name , age ,job){var o new Object();o.name name;o.age age;o.job job;o.sayName function(){alert(this.name);};return o;}var person1 createPerson(nicholas , 29 , software engineer);var person2 createPerson(greg , 27 , doctor);
工厂模式解决了创建多个相似对象的问题解决创建对象时产生大量重复代码但是没有解决对象识别的问题即怎么知道一个对象的类型是Person还是Animal啊。构造函数模式下面使用构造函数创建特定类型的对象。这里是Person类型function Person(name , age , job){ // 注意构造函数的首字母为大写哦this.name name;this.age age;this.job job;this.sayName function(){alert(this.name);}
}var person1 new Person(nicholas , 29 , software engineer);
var person2 new Person(greg , 27 , doctor);alert(person1.constructor Person); // true 可以理解为person1的创造者是Person也就是对象的类型Person
在创建Person的新实例必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤创建一个新对象将构造函数的作用域赋给新对象因此this指向了这个新对象执行构造函数中的代码为这个新对象添加属性返回新对象构造函数解决了重复实例话问题也就是创建多个相似对象的问题和解决了对象识别的问题。但是像上面那样person1和person2共有的方法实例化的时候都创建了这未免多余了。当然可以将共有的方法提取到外面像这样function Person(name , age , job){this.name name;this.age age;this.job job;this.sayName sayName;
}
function sayName(){alert(this.name);
}
var person1 new Person(nicholas , 29 , software engineer);
var person2 new Person(greg , 27 , doctor);
将sayName提取出来就成了全局的方法了然而这里只有Person类创建对象的时候才使用到这样就大才小用了吧所以提取出来到全局方法这种操作不推荐。原型模式创建的每个函数都有一个prototype原型属性这个属性就是一个指针指向一个对象而这个对象的用途就是包含可以由特定类型的所有实例共享的属性和方法。function Person(){
}
Person.prototype.name nicholas;
Person.prototype.age 29;
Person.prototype.sayName function(){alert(this.name);
};var person1 new Person();
person1.sayName(); // nicholasvar person2 new Person();
person2.sayName(); // nicholasconsole.log(person1.sayName person2.sayName); // true
可以有关系图如下life/learn/read/javascript/prototype_object上面的Person.prototype不建议使用字面量来写Person.prototype{},虽让效果一样但是这里重写了原本Person.prototype的对象因此constructor属性会指向Ohject而不是Person。当然也是可以处理的啦将指向指正确并指定construtor的枚举属性为enumerable: false。原型模式解决了函数共享的问题但是也带了一个问题实例化中对象的属性是独立的而原型模式这里共享了。组合使用构造函数模式和原型模式创建自定义类型的最常见的方式就是组合使用构造函数模式和原型模式。构造函数模式用于定义实例属性而原型模式用于定义方法和共享属性。function Person(name , age ,job){this.name name;this.age age;this.job job;this.friends [shelby , court];
}
Person.prototype.sayName function(){alert(this.name);
}var person1 new Person(nicholas , 29 , software engineer);
var person2 new Person(greg , 27 , doctor);person1.friends.push(van);
console.log(person1.friends); // shelby,court,van
console.log(person2.friends); // shelby,court
console.log(person1.friends person2.friends); // false
console.log(person1.sayName person2.sayName); // true
动态原型模式其他的OO语言比如java创建对象的类中是包含了自身的属性、方法和共有的属性、方法如下小狗的例子public class Dog{int age;public Dog(String name ){this.age age;System.out.println(小狗的名字是: name);}public void setAge(int age){age age;}public int getAge(){System.out.println(小狗的年龄为: age);return age;}public static void main(String []args){/* 创建对象 */Dog dog new Dog(tom);/* 通过方法来设定age */dog.setAge(2);/* 调用另外一个方法获取age */dog.getAge();/* 也可以通过 对象.属性名 获取 */System.out.println(变量值: dog.age);}
}为了看起来是类那么一会事动态原型模式把所有信息都封装在了构造函数中而通过在构造函数中初始化原型仅在必要的情况下又保持了同时使用构造函数和原型的优点。如下:function Person(name , age ,job){// 属性this.name name;this.age age;this.job job;// 方法if(typeof this.sayName ! function){Person.prototype.sayName function(){alert(this.name);}}
}
var friend new Person(nicholas , 29 , software engineer);
friend.sayName();
寄生构造函数模式在前面几种模式都不适应的情况下可以用寄生构造函数模式数据结构中就使用到哈寄生构造函数模式可以看成是工厂模式和构造函数模式的结合体。其基本思想是创建一个函数该函数的作用仅仅是封装创建对象的代码然后再返回新创建的对象。function Person(name , age , job){var o new Object();o.name name;o.age age;o.job job;o.sayName function(){alert(this.name);}return o;
}var friend new Person(nicholas, 29 , software engineer);
friend.sayName(); // nicholas
关于寄生构造函数模式需要说明返回的对象与构造函数或者与构造函数的原型属性直接没有什么关系也就是说构造函数返回的对象与构造函数外部创建的对象没有什么区别。为此不能依赖instanceof操作符来确定对象类型。由于存在上面的问题建议在可以使用其他模式的情况下不要使用这种模式。稳妥构造函数模式稳妥对象适合在一些安全的环境中这些环境中会禁止使用this和new或者防止数据被其他应用程序如Mashup程序改动时使用。稳妥构造函数遵循与寄生构造函数类似的模式但是有两点不同意识新创建对象的实例方法不引用this二是不使用new操作符调用构造函数。function Person(name , age , job){// 创建要返回的对象var o new Object();// 可以在这里定义私有的变量和函数// 添加方法o.sayName function(){alert(name); // 不使用this.name};// 返回对象return o;
}var friend Person(nicholas, 29 , software engineer); // 不使用new
friend.sayName(); // nicholas
继承许多的OO语言都支持两种继承方法接口继承和实现继承。接口继承只继承方法签名而实现继承则继承实际的方法。由于函数没有签名在ECMAScript中无法实现接口继承。ECMAScript只支持实现继承而且实现主要是依靠原型链来实现的。[p162]原型链原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。回顾下构造函数、原型和实例的关系: 每个构造函数都有一个原型对象原型对象都包含一个指向构造函数的指针而实例都包含一个指向原型对象的内部指针。function SuperType(){this.property true;
}
SuperType.prototype.getSuperValue function(){return this.property;
}
function SubType(){this.subProperty false;
}// 继承了SuperType,重点哦
SubType.prototype new SuperType();SubType.prototype.getSubValue function(){return this.subProperty;
}var instance new SubType();
console.log(instance.getSuperValue()); // true
上面代码中原型链如下life/learn/read/javascript/prototype_chain原型链继承带来两个问题一是原型实际上变成了另一个类型的实例于是原先的实例属性也就变成了现在原型的属性共享了属性。二是在创建子类型的实例时不能在没有影响所有对象实例的情况下向超类型的构造函数传递参数。借用构造函数借用构造函数解决原型链继承带来的不能向构造函数传递仓鼠的问题。这里使用到了apply()或者call()方法在新创建的对象上执行构造函数。function SuperType(){this.colors [red,blue,green];
}
function SubType(){// 继承SuperTypeSuperType.call(this); // 使用SuperType.apply(this)同效
}var instance1 new SubType();
instance1.color.push(black);
console.log(instance1.colors); // red,blue,green,blackvar instance2 new SubType();
console.log(instance2.colors); // red,blue,green
上面的例子中我在父类型构造函数中没有传参数看者感兴趣的话可以自己加下参数来实验一番咯。借用构造函数解决了原型链继承的确定但是又没有接纳原型链的优点共享。下面的组合继承结合了原型链和借用构造函数容纳了两者的优点。组合继承组合继承的思路是使用原型链实现对原型属性和方法的继承而通过借用构造函数来实现对实例属性的继承。function SuperType(name){this.name name;this.colors [red,blue,green];
}
SuperType.prototype.sayName function(){console.log(this.name);
}
function SubType(name,age){// 继承属性SuperType.call(this,name);this.age age;
}// 继承方法
SubType.prototype new SuperType();
SubType.prototype.constructor SubType; // 避免重写构造函数指向错误
SubType.prototype.sayAge function(){console.log(this.age);
}var instance1 new SubType(nicholas , 29);
instance1.colors.push(black);
console.log(instance1.colors); // red,blue,green,black
instance1.sayName(); // nicholas
instance1.sayAge(); // 29var instance2 new SubType(greg , 27);
console.log(instance2.colors); // red,blue,green
instance2.sayName(); // greg
instance2.sayAge(); // 27
组合继承避免了原型链和借用构造函数的缺陷融合了它们的优点成为了JavaScript中最常用的继承模式。而且instanceof和isPrototypeOf()也能够用于识别基于组合继承创建的对象。原型式继承原型式继承是借助原型可以基于已有的对象创建新对象同时还不必因此创建自定义的类型。function object(o){ // 传入一个对象function F(){};F.prototype o;return new F();
}var person {name : nicholas,friends: [shelby,court,van]
};var anotherPerson object(person);
anotherPerson.name greg;
anotherPerson.friends.push(rob);var yetAnotherPerson object(person);
yetAnotherPerson.name linda;
yetAnotherPerson.friends.push(barbie);console.log(person.friends); // shelby,court,van,rob,barbie
寄生式继承寄生式继承是与原型继承紧密相关的一种思路。寄生式继承的思路与寄生构造函数和工厂模式类似即是创建了一个仅用于封装继承过程的函数该函数在内部以某种方式来增强对象最后再像真的做了所有工作一样返回对象。function object(o){ // 传入一个对象function F(){};F.prototype o;return new F();
}
function createAnother(original){var clone object(original);clone.sayHi function(){console.log(hi);};return clone;
}
var person {name : nicholas,friends : [shelby,court,van]
}
var anotherPerson createAnother(person);
anotherPerson.sayHi(); // hi
上面的例子中新对象anotherPerson不仅具有person的所有属性和方法而且还有了自己的sayHi()方法。寄生组合式继承组合继承是JavaScript最常用的继承模式不过它也有自己的不足。组合继承最大的问题就是无论什么情况下都会调用两次超类型构造函数一次是在创建子类型原型的时候另一次是在子类型构造函数内部。寄生组合式继承能够解决这个问题。所谓寄生组合式继承即通过借用构造函数来继承属性通过原型链的混成形式来继承方法。其背后的基本思路是不必为了指定子类型的原型而调用超类型的构造函数我们所需要的无非就是超类型的原型的一个副本而已。寄生组合式继承的基本模式如下function inheritPrototype(subType,superType){var prototype Object(superType.prototype); // 创建对象prototype.constructor subType; // 增强对象防止下面重写constructor属性subType.prototype prototype; // 指定对象}
一个完整的例子如下,相关插图见书[p173]:function inheritPrototype(subType,superType){var prototype Object(superType.prototype);prototype.constructor subType;subType.prototype prototype;}
function SuperType(name){this.name name;this.color [red,blue,green];
}
SuperType.prototype.sayName function(){alert(this.name);
}
function SubType(name, age){SuperType.call(this,age); // 只在这调用了一次超类型的构造函数
}inheritPrototype(SubType , SuperType);SubType.prototype.sayAge function(){console.log(this.age);
}var instance new SubType(nicholas , 29);
上面的例子的高效处体现在它只调用了一次SuperType构造函数并且避免了在SubType.prototype上创建不必要的多余的属性。与此同时原型链还能保持不变因此还能正常使用instanceof和inPrototypeOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。闭包闭包是指有权访问另一个函数作用域中的变量的函数。我的理解是函数内的函数使用到外层函数的变量延长变量的生存时间造成常驻内存。例子见下function foo(){var a 2;return function(){a 1;console.log(a);}
}var baz foo();baz(); // 3
baz(); // 4
baz(); // 5
baz(); // 6
上面的例子中外部的函数foo()执行完成之后正常的情况下应该销毁a变量的但是内部的返回的匿名函数使用到该变量不能销毁。如果需要销毁的话可以改写成下面function foo(){var a 2;return function(){a 1;console.log(a);}
}
var baz foo();
baz(); // 3baz null; // 将内部的匿名函数赋值为空
从闭包说起谈到了闭包这让我想起了不久前刷知乎看到一篇文章。自己整理如下for(var i 0 ; i 5; i){setTimeout(function(){console.log(i);},1000)
}
console.log(i);// 5,5,5,5,5,5
上面的代码是输出了6个5而这6个5是这样执行的先输出全局中的console.log(i)然后是过了1秒种后瞬间输出了5个5为什么用瞬间这个词呢怕看者理解为每过一秒输出一个5。解读上面的代码的话可以通过狭义范围(es5)的理解同步 异步 回调 回调也是属于异步的范畴所以我这里指明了狭义啦。先是执行同步的for,遇到异步的setTimeout(setTimeout和setInterval属于异步哈)后将其放入队列中等待接着往下执行全局的console.log(i)将其执行完成后执行异步的队列。追问1:闭包改写上面的代码期望输出的结果为5 0,1,2,3,4。改造的方式一for(var i 0; i 5; i){(function(j){setTimeout(function(){console.log(j);},1000);})(i);
}
console.log(i);// 5,0,1,2,3,4
上面的代码巧妙的利用IIFE(Immediately Invoked Function Expression:声明即执行的函数表达式)来解决闭包造成的问题闭包的解析看上面。方法二利用js中基本类型的参数传递是按值传递的特征改造代码如下var output function(i){setTimeout(function(){console.log(i);},1000);
};
for(var i 0; i 5; i){output(i); // 这里传过去的i值被复制了
}
console.log(i);// 5,0,1,2,3,4
上面改造的两个方法都是执行代码后先输出5然后过了一秒种依次输出0,1,2,3,4。如果不要考虑全局中的console.log(i)输出的5而是循环中输出的0,1,2,3,4。你还可以使用ES6的let块级作用域语法,实现超级简单:for(let i 0; i 5; i){setTimeout(function(){console.log(i);},1000);
}// 0,1,2,3,4
上面是过了一秒钟后依次输出0,1,2,3,4。这种做法类似于无形中添加了闭包。那么如果使用ES6语法的话会怎样实现5,0,1,2,3,4呢追问2:ES6改造刚开始的代码使得输出的结果是每隔一秒输出0,1,2,3,4大概第五秒输出5。在不使用ES6的情况下for(var i 0; i 5; i){(function(j){setTimeout(function(){console.log(j);},1000*j);})(i);
}
setTimeout(function(){console.log(i);
},1000*i);// 0,1,2,3,4,5
上面的代码简单粗暴但是不推荐。看题目是每隔一秒输出一个值再回调实现最后的5输出这个时候应该使用ES6语法来考虑应该使用Promise方案const tasks [];
for(var i 0; i 5; i){// 这里的i声明不能改成let改成let的话请看下一段代码((j){tasks.push(new Promise((resolve){ // 执行taskssetTimeout((){console.log(j);resolve(); // 这里一定要resolve,否则代码不会按照预期执行},1000*j);}))})(i);
}Promise.all(tasks).then((){ // 执行完tasks回调setTimeout((){console.log(i);},1000);
});// 符合要求的每隔一秒输出
// 0,1,2,3,4,5
如果是使用let我的改造如下const tasks [];
for (let i 0; i 5; i) {tasks.push(new Promise((resolve) {setTimeout(() {console.log(i);resolve();}, 1000 * i);}));
}Promise.all(tasks).then(() {setTimeout(() {console.log(tasks.length);}, 1000);
});// 0,1,2,3,4,5
上面的代码比较庞杂可以将其颗粒话模块化。对上面两段代码的带var那段进行改造后如下const tasks []; // 这里存放异步操作的Promise
const output (i) new Promise((resolve) {setTimeout((){console.log(i);},1000*i);
});// 生成全部的异步操作
for(var i 0; i 5; i){tasks.push(output(i));
}
// 异步操作完成之后输出最后的i
Promise.all(tasks).then(() {setTimeout(() {console.log(i);},1000);
});// 符合要求的每隔一秒输出
// 0,1,2,3,4,5
追问3:ES7既然ES6的Promise可以写那么ES7是否可以写呢从而让代码更加简洁易读那就使用到到了异步操作的async await特性啦。// 模拟其他语言中的sleep实际上可以是任何异步操作
const sleep (time) new Promise((resolve) {setTimeout(resolve , time);
});(async () {for(var i 0; i 5; i){await sleep(1000);console.log(i);}await sleep(1000);console.log(i);
})();// 符合要求的每隔一秒输出
// 0,1,2,3,4,5
浏览器窗口位置IE、Safari、Opera和Chrome都提供了screenLeft和screenTop属性分别表示浏览器窗口相对于屏幕左上角和上边的位置[p197]。Firefox则以screenX和screenY属性来表示。为了兼容各个浏览器可以入下面这样写var leftPos (typeof window.screenLeft number)?window.screenLeft : window.screenX;
var topPos (typeof window.screenTop number)? window.screenTop : window.screenY;
浏览器窗口大小由于浏览器厂商以及历史的问题无法确认浏览器本身的大小但是可以取得视口的大小[p198]。如下var pageWidth window.innerWidth,pageHeight window.innerHeight;if(typeof pageWidth ! number){if(document.compatMode CSS1Compat){ // 标准模式下的低版本iepageWidth document.documentElement.clientWidth;pageHeight document.documentElement.clientHeight;}else{ // 混杂模式下的chromepageWidth document.body.clientWidth;pageHeight document.body.clientHeight;}
}
上面的示例可以简写成下面这样var pageWidth window.innerWidth || document.documentElement.clientWidth || document.body.clientHeight;
var pageHeight window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
canvas中的变换为绘制上下文应用变换会导致使用不同的变换矩阵应用处理从而产生不同的结果。[p453]可通过下面的方法来修改变换矩阵:rotation(angle):围绕原点旋转图像angle弧度scale(scaleX,scaleY)translate(x,y): 将坐标原点移动到(x,y)。执行这个变换后坐标(0,0)会变成之前由(x,y)表示的点。JSON关于JSON最重要的是要理解它是一种数据格式不是一种编程语言。对象字面量和JSON格式比较先来看下对象字面量demo写法var person {name : nicholas,age : 29
};# 上面的代码也可以写成下面的
var person {name : nicholas,age : 29
};
而上面的对象写成数据的话就是下面这样了{name: nicholas ,age: 29
}# 可到网站 https://www.bejson.com/ 验证⚠️ 与JavaScript对象字面量相比JSON对象又两个地方不一样。首先没有声明变量(JSON中没有变量的概念)。其次没有分号(因为这不是JavaScript语句所以不需要分号)。留意的是对象的属性必须加双引号(不是单引号哦)这在JSON中是必须的。stringify()和parse()可以这么理解JSON.stringify()是从一个object中解析成JSON数据格式而JSON.parse()是从一个字符串中解析成JSON数据格式。var person {name: nicholas,age: 29
};var jsonText JSON.stringify(person);console.log(jsonText);// {name:nicholas,age:29}
var strPerson {name:nicholas,age:29};
var jsonText JSON.parse(strPerson);console.log(jsonText); // { name: nicholas, age: 29 }
XMLHttpRequest对象XMLHttpRequest对象用于在后台与服务器交换数据。它是Ajax技术的核心[p571]。XMLHttpRequest对象能够使你在不重新加载页面的情况下更新网页在页面已加载后从服务器请求数据在页面已加载后从服务器接收数据在后台向服务器发送数据XMLHttpRequest的使用# 创建XHR对象 open()准备发送 send()传送数据// 创建对象对浏览器做兼容
function createXHR(){if(typeof XMLHttpRequest ! undefined){ // IE7和其他浏览器支持return new XMLHttpRequest();}else if(typeof ActiveXObject ! undefined){if(typeof arguments.callee.activeXString ! string){var versions [MSXML2.XMLHttp.6.0,MSXML2.XMLHttp.3.0,MSXML2.XMLHttp]; // 低版的ie可能遇到三种不同版本的XMR对象var i , len;for(i 0,len versions.length; i len ; i){try{new ActiveXObject(version[i]);arguments.callee.activeXString versions[i];break;}catch (ex){// 跳过}}}return new ActiveXObject(arguments.callee.activeXString);}else{throw new Error(No XHR object available.);}
}
var xhr createXHR();// 准备发送数据
xhr.open(get,path/to/example.txt,false);// 非异步异步的话第三个参数改为true// 传送数据
xhr.send(null); // get方法不需要传数据// 判断状态嘛获取服务器返回的数据
if((xhr.status 200 xhr.status 300) || xhr.status 304){console.log(xhr.responseText);
}else{console.log(Request was nsuccessful : xhr.status);
}
跨域解决方案何为跨域呢只要访问的资源的协议、域名、端口三个不全相同就可以说是非同源策略而产生了跨域了这是狭义的说法。广义的说法通过XHR实现Ajax通信的一个主要限制来源于跨域的安全策略默认情况下,XHR对象只能访问包含它的页面位于同一个域中的资源[p582]。注部分文字和代码引用自前端常见跨域解决方案全CORSCORSCross-Origin Resource Sharing跨资源共享定义了在必须访问跨资源时浏览器与服务器应该如何沟通。其背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通从而决定请求或响应是应该成功还是应该失败。 复杂的跨域请求应当考虑使用它。普通跨域请求只服务端设置Access-Control-Allow-Origin即可前端无需设置如果要带cookie请求前后端都要设置。1.前端设置1.) 原生ajaxfunction createCORSRequest(method,url){ // 兼容处理ie8/9需要用到window.XDomainRequestvar xhr new XMLHttpRequest();// 前端设置是否带cookiexhr.withCredentials true;if(withCredentials in xhr){ // 其他的用到withCredentialsxhr.open(method,url,true);}else if(typeof XDomainRequest ! undefined){xhr new XDomainRequest();xhr.open(method , url);}else{xhr null;}return xhr;
}// get请求
var request createCORSRequest(get,http://www.somewhere-else.com/page/);
if(request){request.onload function(){// 对request.responseText 进行处理 };request.send();
}// post请求带cookie
var requestXhr createCORSRequest(post,http://www.somewhere-else.com/page/);
requestXhr.setRequestHeader(Content-Type,application/x-www-form-urlencoded);
requestXhr.send(useradmin);
xhr.onreadystatechange function() {if (xhr.readyState 4 xhr.status 200) {alert(xhr.responseText);}
};
2.)jquery ajax上面写了一大堆原生的看得头都有点大了还是使用jquery ajax 比较舒服:$.ajax({...xhrFields: {withCredentials: true // 前端设置是否带cookie},crossDomain: true, // 会让请求头中包含跨域的额外信息但不会含cookie...
});
3.) vue框架在vue-resource封装的ajax组建中加入以下代码:Vue.http.options.credentials true;
2.服务器设置若后端设置成功前端浏览器控制台上就不会出现跨域报错的信息反之说明没有成功。1. java后台/** 导入包import javax.servlet.http.HttpServletResponse;* 接口参数中定义HttpServletResponse response*/
response.setHeader(Access-Control-Allow-Origin, http://www.domain1.com); // 若有端口需写全协议域名端口
response.setHeader(Access-Control-Allow-Credentials, true);2.) node后台var http require(http);
var server http.createServer();
var qs require(querystring);server.on(request, function(req, res) {var postData ;// 数据块接收中req.addListener(data, function(chunk) {postData chunk;});// 数据接收完毕req.addListener(end, function() {postData qs.parse(postData);// 跨域后台设置res.writeHead(200, {Access-Control-Allow-Credentials: true, // 后端允许发送CookieAccess-Control-Allow-Origin: http://www.domain1.com, // 允许访问的域协议域名端口Set-Cookie: la123456;Path/;Domainwww.domain2.com;HttpOnly // HttpOnly:脚本无法读取cookie});res.write(JSON.stringify(postData));res.end();});
});server.listen(8080);
console.log(Server is running at port 8080...);
JSONPJSONP是JSON with padding(填充式JSON或参数式JSON)的简写是应用JSON的一种新方法在后来的web服务中非常流行。简单的跨域请求用JSONP即可。通常为了减轻web服务器的负载我们把js,css,img等静态资源分离到另一台独立域名的服务器在html页面中再通过相应的标签从不同域名下加载静态资源而被浏览器允许基于此原理我们可以通过动态创建script再请求一个带参网址实现跨域通信。1.前端实现1.原生实现scriptvar script document.createElement(script);script.type text/javascript;// 传参并指定回调执行函数为onBackscript.src http://www.domain2.com:8080/login?useradmincallbackonBack;document.head.appendChild(script);// 回调执行函数function onBack(res){console.log(JSON.stringify(res));}
/script服务器返回如下返回时即执行全局函数onBack({status: true,user:admin})
2.jquery ajax$.ajax({url: http://www.domain2.com:8080/login,type: get,dataType: jsonp, // 请求方式为jsonp jsonpCallback: onBack, // 自定义回调函数名data: {}
});
3.vue.jsthis.$http.jsonp(http://www.domain2.com:8080/login,{params: {},jsonp: onBack
}).then((res){console.log(res);
});
2.后端nodejs代码的示范var qs require(querystring);
var http require(http);
var server http.createServer();server.on(request,function(req,res){var params qs.parse(req.url.split(?)[1]);var fn params.callback;// jsonp返回设置res.writeHead(200,{Content-Type:text/javascript});res.write(fn (JSON.stringify(params)));res.end();
});server.listen(8080);
console.log(Server is running at port 8080 ...);
⚠️ jsonp缺点只能实现get一种请求。WebSocket协议跨域WebSocket protocol 是 HTML5一种新的协议。它实现了浏览器与服务器全双工通信同时允许跨域通讯。原生的WebSocket API使用起来不太方便示例中使用了socket.io它很好的封装了webSocket接口提供了更简单、灵活的接口也对不支持webSocket的浏览器提供了向下兼容。1.前端代码divuser inputinput typetext/div
script src./socket.io.js/script
script
var socket io(http://www.domain2.com:8080);// 连接成功处理
socket.on(connect, function() {// 监听服务端消息socket.on(message, function(msg) {console.log(data from server: --- msg); });// 监听服务端关闭socket.on(disconnect, function() { console.log(Server socket has closed.); });
});document.getElementsByTagName(input)[0].onblur function() {socket.send(this.value);
};
/script2.node socket后台var http require(http);
var socket require(socket.io);// 启http服务
var server http.createServer(function(req, res) {res.writeHead(200, {Content-type: text/html});res.end();
});server.listen(8080);
console.log(Server is running at port 8080...);// 监听socket连接
socket.listen(server).on(connection, function(client) {// 接收信息client.on(message, function(msg) {client.send(hello msg);console.log(data from client: --- msg);});// 断开处理client.on(disconnect, function() {console.log(Client socket has closed.); });
});
requestAnimationFrame()帧动画requestAnimationFrame 创建平滑的动画[p682]。在此之前都是使用setTimeout或者setInterval实现requestAnimationFrame与它们相比不需要时间间隔会贴切浏览器的刷新频率在切换到另外的页面时会停止运行使用的示范如下div idnum1/div
// 兼容浏览器
(function(){var lastTime 0;var vendors [webkit,moz,ms,-o];for(var x 0;x vendors.length !window.requestAnimationFrame; x){window.requestAnimationFrame window[vendors[x] RequestAnimationFrame];window.cancelAnimationFrame window[vendors[x] cancelAnimationFrame] || window[vendors[x] CancelRequestAnimationFrame];}if(!window.requestAnimationFrame){window.requestAnimationFrame function(callback){var currTime new Date().getTime();var timeToCall Math.max(0, 16 - (currTime - lastTime));var id window.setTimeout(function(){callback;},timeToCall);lastTime currTime - timeToCall;return id;}}if(!window.cancelAnimationFrame){window.cancelAnimationFrame function (id){clearTimeout(id);}}
})();// 简单的计数
var num 1,timer;
fn();
function fn(){document.getElementById(num).innerText num;timer requestAnimationFrame(fn);
}
document.onclick function(){cancelAnimationFrame(timer);
}原文链接请戳这里