最近本身在休假,打算闭门几天将《JavaScript高级程序设计》(第3版)这本良心教材再回顾一遍。目前本身进入前端领域近两年,如今重读并记录下这本教材的“硬”知识点 😊 。javascript
ECMAScript 函数不能像传统意义上那样实现重载。而在其余语言(如Java)中,能够为一个函数编写两个定义,只要这两个定义的签名(接受的参数类型和数量)不一样便可[p66]。ECMAScript的类型是松散形的,没有签名,因此是没有重载的。css
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
。html
ECMAScript 中全部的函数的参数都是按值传递
的[p70]。也就是说,把函数外部的值复制给函数内部的参数,就是把值从一个变量复制到另外一个变量同样。**基本类型值的传递如同基本类型变量的复制同样,而引用类型值的传递,则如同引用类型变量的复制同样。**下面分开例子介绍两种不一样类型为何是按值传递。前端
基本类型这个按值传递比较好理解,直接复制变量的值传递:vue
function addTen(num){
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
console.log(result); // 30
console.log(count); // 20 ,没有变化哈
复制代码
有些人认为引用类型的传参是按照引用来传的,那暂且认为他们的理解是正确的,那下面的示例结果怎么解析呢?java
function setName(obj){
obj.name = '嘉明';
obj = new Object();
obj.name = '庞嘉明';
}
var person = new Object();
setName(person);
console.log(person.name); // '嘉明',为啥不是'庞嘉明'呢?
复制代码
若是是按照引用传的话,那么新建的对象obj = new Object()
应该是指向堆内容的对象啊,那么改变它本有的name
属性值应该生效,然而并无生效。因此它也是按值传递
滴。node
解析器在向执行环境中加载数据时,对函数声明和函数表达式并不是一视同仁[p111]。解析器会率先读取函数声明,并使其执行任何代码以前可用(能够访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解析。jquery
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()
。这两个方法的用途都是在特定的做用域中调用函数,实际上等于设置函数体内this对象的值[116]。call和apply在对象中仍是挺有用处的。web
apply()方法和call()方法的做用是相同的,区别在于接收参数的方式不一样。ajax
apply()方法接收两个参数:一个是在其中运行函数的做用域,另外一个是参数数组,这里的参数数组能够是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
复制代码
call()方法接收的第一个参数和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个步骤:
构造函数解决了重复实例话问题(也就是建立多个类似对象的问题)和解决了对象识别的问题。可是,像上面那样,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(); // nicholas
var person2 = new Person();
person2.sayName(); // nicholas
console.log(person1.sayName == person2.sayName); // true
复制代码
能够有关系图以下:
上面的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
复制代码
上面代码中原型链以下:
原型链继承带来两个问题:一是原型实际上变成了另外一个类型的实例,因而,原先的实例属性也就变成了如今原型的属性,共享了属性。二是在建立子类型的实例时,不能在没有影响全部对象实例的状况下向超类型的构造函数传递参数。
借用构造函数解决原型链继承带来的不能向构造函数传递仓鼠的问题。这里使用到了apply()或者call()方法在新建立的对象上执行构造函数。
function SuperType(){
this.colors = ['red','blue','green'];
}
function SubType(){
// 继承SuperType
SuperType.call(this); // SuperType.apply(this)同效
}
var instance1 = new SubType();
instance1.color.push('black');
console.log(instance1.colors); // 'red,blue,green,black'
var 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(); // 29
var 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.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name, age){
SuperType.call(this, name); // 只在这调用了一次超类型的构造函数
this.age = 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(); // 3
baz = 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)=>{ // 执行tasks
setTimeout(()=>{
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'){ // 标准模式下的低版本ie
pageWidth = document.documentElement.clientWidth;
pageHeight = document.documentElement.clientHeight;
}else{ // 混杂模式下的chrome
pageWidth = 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;
复制代码
为绘制上下文应用变换,会致使使用不一样的变换矩阵应用处理,从而产生不一样的结果。[p453]
可经过下面的方法来修改变换矩阵:
关于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中是必须的。
能够这么理解: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对象用于在后台与服务器交换数据。它是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]。注:部分文字和代码引用自前端常见跨域解决方案(全)
CORS(Cross-Origin Resource Sharing,跨资源共享)定义了在必须访问跨资源时,浏览器与服务器应该如何沟通。其背后的基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,仍是应该失败。 复杂的跨域请求应当考虑使用它。
普通跨域请求:只服务端设置Access-Control-Allow-Origin便可,前端无需设置,若是要带cookie请求:先后端都要设置。
1.前端设置
1.) 原生ajax
function createCORSRequest(method,url){ // 兼容处理,ie8/9须要用到window.XDomainRequest
var xhr = new XMLHttpRequest();
// 前端设置是否带cookie
xhr.withCredentials = true;
if("withCredentials" in xhr){ // 其余的用到withCredentials
xhr.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("user=admin");
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', // 后端容许发送Cookie
'Access-Control-Allow-Origin': 'http://www.domain1.com', // 容许访问的域(协议+域名+端口)
'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本没法读取cookie
});
res.write(JSON.stringify(postData));
res.end();
});
});
server.listen('8080');
console.log('Server is running at port 8080...');
复制代码
JSONP是JSON with padding(填充式JSON或参数式JSON)的简写,是应用JSON的一种新方法,在后来的web服务中很是流行。简单的跨域请求用JSONP便可。
一般为了减轻web服务器的负载,咱们把js,css,img等静态资源分离到另外一台独立域名的服务器,在html页面中再经过相应的标签从不一样域名下加载静态资源,而被浏览器容许,基于此原理,咱们能够经过动态建立script,再请求一个带参网址实现跨域通讯。
1.前端实现
1.)原生实现
<script> var script = document.createElement('script'); script.type = 'text/javascript'; // 传参并指定回调执行函数为onBack script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack'; 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.js
this.$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 protocol 是 HTML5一种新的协议。它实现了浏览器与服务器全双工通讯,同时容许跨域通信。
原生的WebSocket API使用起来不太方便,示例中使用了socket.io,它很好的封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
1.前端代码
<div>user input:<input type="text"></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); }; </script>
复制代码
2.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 建立平滑的动画[p682]。在此以前都是使用setTimeout或者setInterval实现,requestAnimationFrame与它们相比:
使用的示范以下:
<div id="num">1</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);
}
复制代码
原文连接请戳这里