javascript 高级程序设计_重读《JavaScript高级程序设计》

33964928b658d439acd4beecfa151189.png

最近自己在休假,打算闭门几天将《JavaScript高级程序设计》(第3版)这本良心教材再回顾一遍。目前自己进入前端领域两年多,现在重读并记录下这本教材的“硬”知识点 。

本文原文链接请戳

重读《JavaScript高级程序设计》​link.jianshu.com
c9a68d55ef7e8eee1a800f0ac562a40a.png

函数没有重载

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,NumberString

传递参数

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()方法的作用是相同的,区别在于接收参数的方式不同。

apply

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

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个步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(因此this指向了这个新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  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(); // nicholasvar person2 = new Person();
person2.sayName(); // nicholasconsole.log(person1.sayName == person2.sayName); // true

可以有关系图如下:

8cc8ad72e6065a9d84272b8242ecb39b.png

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

上面代码中原型链如下:

fedca327ade636e520be10ef9990c632.png

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,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(); // 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]。注:部分文字和代码引用自前端常见跨域解决方案(全)

CORS

CORS(Cross-Origin Resource Sharing,跨资源共享)定义了在必须访问跨资源时,浏览器与服务器应该如何沟通。其背后的基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。 复杂的跨域请求应当考虑使用它。

普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无需设置,如果要带cookie请求:前后端都要设置。

1.前端设置

1.) 原生ajax

function 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("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

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';// 传参并指定回调执行函数为onBackscript.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协议跨域

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()帧动画

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);
}

原文链接请戳这里

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/289562.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[异常解决] ubuntu上安采用sudo启动的firefox,ibus输入法失效问题解决

采用sudo启动的应用是root权限的应用&#xff0c; ibus失效是因为ibus的初始配置采用user权限: 而root下运行的firefox输入法的配置还是停留在默认情况~ 解决方案是在shell下以root权限运行ibus&#xff0c;然后进行配置&#xff1a; 1、shell下输入&#xff1a;sudo ibus-setu…

Avalonia-.NET 的跨平台 UI 框架

简介Avalonia 是 dotnet 的跨平台 UI 框架&#xff0c;提供灵活的样式系统并支持 Windows、Linux、macOS 等多种操作系统。Avalonia 已经成熟并且可以投入生产。我们还在测试版中支持 iOS、Android&#xff0c;并在早期阶段通过 WASM 支持浏览器。使用在WPF中&#xff0c;当你在…

Skype for TV停止支持 三星确认今年6月移除该应用

微软热门视频聊天和语音通话服务Skype自今天开始将不再支持智能电视。用户无法在智能电视上登录或者重置密码。在停止支持之后&#xff0c;由电视制造厂商自行决定移除Skype应用或者继续提供非支持版本。三星已经确认将于2016年6月2日开始在旗下的智能电视中移除Skype服务&…

CactiEZ V10.1 中文版 Cacti中文解决方案+使用教程(2)

配置被监控的Windows主机说明&#xff1a;要使用CactiEZ监控一台Windows主机&#xff0c;需要在被监控的主机上面安装snmp&#xff08;简单网络管理协议&#xff09;1、下面开始安装配置snmp开始-设置-控制面板-添加或删除程序-添加删除Windows组件找到管理和监视工具&#xff…

AgileConfig 1.6.0 发布 - 支持服务注册与发现

大家好&#xff0c;好久没有输出博文了&#xff0c;一是因为比较忙&#xff0c;另外一个原因是最近主要的精力是在给 AgileConfig 添加一个新的功能&#xff1a;服务注册与发现。先说说为什么会添加这个功能。我自己的项目是用 Consul 来做为服务注册发现组件的。自从我上线了 …

快速排序算法_Python实现快速排序算法

排序是算法的入门知识&#xff0c;应用广泛&#xff0c;且在程序员面试中&#xff0c;经常被提及&#xff0c;其中最常考的两大排序算法为快速排序与归并排序&#xff0c;本篇将使用Python语言来分析了解快速排序算法。思想 快速排序是一种非常高效的排序算法&#xff0c;采用 …

用户反馈KB3189866累积更新出现卡在95%进度情况

伴随着本月的补丁星期二活动&#xff0c;微软面向Windows 10推出了三款累积更新。和以往相同&#xff0c;部分用户反馈称无法安装本次更新并导致出现各种问题。很多读者反馈称KB3189866累积更新会在45%或95%的时候卡住不动&#xff0c;无法执行和完成安装。 在尝试过几种临时解…

UVa1607 poj1435 UVaLive1686 Gates

填坑系列(p.246) 由函数连续性得满足二分性 1 #include<cstdio>2 #include<cstring>3 #include<cstdlib>4 #include<algorithm>5 #include<iostream>6 7 using namespace std;8 9 void setIO(const string& s) { 10 freopen((s ".…

c语言入门数组,C语言入门之数组(2)

二维数组前面介绍的数组只有一个下标&#xff0c;称为一维数组&#xff0c; 其数组元素也称为单下标变量。在实际问题中有很多量是二维的或多维的&#xff0c; 因此C语言允许构造多维数组。多维数组元素有多个下标&#xff0c; 以标识它在数组中的位置&#xff0c;所以也称为多…

IOS开发--TextField

2019独角兽企业重金招聘Python工程师标准>>> //初始化text field的位置和大小UITextField *text [[UITextField alloc] initWithFrame:CGRectMake(20, 20, 130, 130)];//设置边框样式//样式有很多种&#xff0c;点进去查看text.borderStyle UITextBorderStyleRoun…

IOS之学习笔记十五(协议和委托的使用)

1、协议和委托的使用 1)、协议可以看下我的这篇博客 IOS之学习笔记十四(协议的定义和实现) https://blog.csdn.net/u011068702/article/details/80963731 2)、委托可以叫代理&#xff0c;实现协议的类的对象可以叫委托对象或者代理对象 3)、关键就是我们在控制器里类(获取数…

Dapr 源码解析 系列文章汇总

Dapr背后的架构模式是符合未来架构趋势&#xff08;多运行时架构&#xff09;和云原生发展趋势的。知乎 iyacontrol 对 Dapr的意义有一个很好的概括&#xff1a;对于小公司&#xff0c;甚至没有基础架构和中间件团队的公司&#xff0c;Dapr 提供了开箱即用的基础设施功能&#…

cstring判断包含字符串_Python字符串方法之-解决判断问题

19、endswith()描述&#xff1a;判断字符串是否以指定字符或子字符串结尾。语法&#xff1a;str.endswith("suffix", start, end) 或str[start,end].endswith("suffix") 用于判断字符串中某段字符串是否以指定字符或子字符串结尾。—> bool 返回值为布尔…

Angular变化检测机制

2019独角兽企业重金招聘Python工程师标准>>> 在使用Angular进行开发中&#xff0c;我们常用到Angular中的绑定——模型到视图的输入绑定、视图到模型的输出绑定以及视图与模型的双向绑定。而这些绑定的值之所以能在视图与模型之间保持同步&#xff0c;正是得益于Ang…

nx二次开发c语言,NX二次开发-UFUN API函数编程基础

1.NXOpen C 的函数函数名称的约定NX Open C 共有2类名称约定&#xff1a;一个是标准的NX Open C 的函数名称约定&#xff1b;另一个是以前版本的原有的名称约定。1.标准名称约定【格式】UF__【说明】(1)UF:User Funciton的简写&#xff0c;表示该函数为NX Open C 函数。(2)&…

JMeter压力测试入门教程[图文]

Apache JMeter是Apache组 织开发的基于Java的压力测试工具。用于对软件做压力测试&#xff0c;它最初被设计用于Web应用测试但后来扩展到其他测试领域。 它可以用于测试静态和动态资源例如静态文件、Java小服务程序、CGI脚本、Java 对象、数据库&#xff0c; FTP服务器, 等等。…

.NET6之MiniAPI(二十九):UnitTest

MiniAPI的单元测试与asp.net web api的单元测试大体是相同的&#xff08;毕竟都是asp.net core&#xff09;&#xff0c;只是在小细节上有一些差异&#xff0c;文章中会说到这点。本文测试框架是XUnit&#xff0c;Mock框架是Moq&#xff0c;关于这两个框架和库的学习&#xff0…

vue data数据修改_Vue 超清晰思维导图(7张),详细知识点梳理!

Vue思维导图目录MVC与MVVM的区别Vue基本代码结构Vue指令Vue组件class和style动态绑定computed计算属性EventBusfilter过滤器方法Vue是一套构建用户界面的框架&#xff0c;只关注视图层&#xff0c;它不仅易于上手&#xff0c;还便于与第三方库或既有项目整合。&#xff08;Vue有…

界面连接数据库

1、获取本机的SQL Server服务器名 private void Form2_Load(object sender, EventArgs e){listBox1.Items.Clear();SQLDMO.Application SQLServer = new SQLDMO.Application();SQLDMO.NameList strServer = SQLServer.ListAvailableSQLServers();if (strServer.Count > 0){f…

Xamarin效果第二十篇之GIS中加载三维白模

在前面文章中简单玩了玩GIS的基本操作、Mark相关、AR和测距,今天再次分享一下N年前就像玩耍的效果;啥也不说了都在效果里:再来看看手机端的效果:1、关于效果我也是偶然见看到了别人实现:https://blog.csdn.net/arcgis_all/article/details/769991042、关于实现就是在三维场景图…