面向对象基础概念

对象、类、实例

一起事物皆为对象,对象是一个自包含的实体,用一组可识别的特性和行为来标识。类就是具有相同的属性和功能的对象的抽象的集合,可以认为类是创建某一类对象的模板。实例是根据类实现的一个真实的对象,实现的过程称为实例化。

构造方法与方法重载(overload)

构造方法又叫构造函数,其实就是对类进行初始化。根据参数的不同,可以由多种构造方法,方法重载提供了创建同名的多个方法的能力(参数不同)。

1
2
3
4
5
6
7
public class Cat{
private string name = "";
public Cat(){}
public Cat(string name){
this.name = name;
}
}

属性及方法

修饰符

类的访问修饰符只有public、abstract等,不能使用private、protected等修饰符。属性和方法都有访问修饰符,常见的修饰符有(C#):

  • private 私有,只允许本类中的成员访问,子类、实例不能访问;
  • protected 保护访问, 允许本类、子类访问,实例不能访问;
  • internal 内部访问,指向本项目内访问,其他项目不能访问;
  • public 共有访问,不受任何限制;

set、get访问器

属性有两个方法get、set。get访问器返回与声明的属性相同的数据类型;set访问器有一个隐式参数,用关键字value表示,进行赋值。使用构造器的好处有控制读写(是否可读、可写、对值的范围进行控制等)。

1
2
3
4
5
6
7
8
9
10
private int shoutNum = 3;
public int ShouNum
{
get{ return shouNum; }
set{
if(value<=10){
shouNum = value;
}
}
}

面向对象三大特性

封装

每个对象都包含它能进行操作所需要的所有信息,这个特性称为封装。适当的封装,可以将对象使用接口的程序实现部分隐藏起来,同时确保用户无法任意更改对象内部的重要的数据。封装有这两层的概念,指的是方法的实现。使用封装的好处有:减少耦合、类内部实现可以自由修改、类具有清晰的对外接口。

继承

继承定义了类如何相互关联,共享特性。子类(派生类)不但继承了父类(基类)的所有特性,还可以定义新的特性。继承的特性有:

  • 子类拥有父类非private的属性和功能;
  • 子类具有自己的属性和功能,即子类可以扩展父类没有的属性和功能;
  • 子类还可以以自己的方式实现父类的功能(方法重写);
1
2
3
4
5
6
class Cat:Animal
{
//对于构造方法,不能被继承,只能被调用
public Cat(string name):base(name){}
pulic string Shout(){//...}
}

多态(override)

多态表示不同的对象可以执行相同的动作,但要通过它们自己的实现代码来执行。
在C#中,多态的实现方法为:父类通过virtual关键词,将成员声明为虚拟的,表示这个方法/属性可以被重写;然后,子类可以选择使用override关键字,来重写方法。不同的语言实现多态的方法有差别。

1
2
3
4
5
6
7
8
9
10
class Animal{
....
public virtual string Shout(){} //在C#中,要声明为虚函数,才可以重写
}
class Cat:Animal{
...
public override string Shout(){
... //重写的方法
}
}

抽象类和接口

类是对对象的抽象,抽象类是对类的抽象,接口是对行为的抽象。如果行为跨越不同类的对象,可使用接口,对于一些相似的类对象,用继承抽象类。从设计角度看,抽象类是从子类中发现了公共的东西,泛化出父类,然后子类继承父类,而接口是根本不知子类的存在,方法如何实现还不确认,预先定义。
抽象类和接口还有一些其他的具体的区别:

  • 抽象类可以给出一些成员的实现,接口却不包含成员的实现。
  • 抽象类的抽象成员可被子类部分实现,接口的成员需要实现类完全实现。
  • 一个类只能继承一个抽象类,但可以实现多个接口等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//抽象类不能实例化,只是类的抽象
abstract class Animal
{

protected abstract string getShoutSound();
}

//接口,应该是现有接口,再由类
interface IChange
{

string ChangeThing(string thing);//此为继承接口的类要实现的方法
}
class MachineCat:Cat,IChange
{

...
public string ChangeThing(string thing) //不需要override
{ //方法的实现 }
}

集合和泛型

集合ArrayList是使用大小可按需动态增加的数组,可以通过泛型为其制定集合中的类型。泛型是具有占位符(类型参数)的类、结构、接口和方法,这些占位符是类、接口、接口和方法所存储和使用的一个或多个类型的占位符。泛型集合类可以将类型参数用作为它所存储的对象的类型的占位符。使用方法如: List arrayAnimal。

Web应用

所谓Web应用,指的是使用web技术构建的业务繁杂、逻辑较多、交互频繁的单页Web应用,它在一个界面上集成多种功能,甚至整个系统就只有一个页面,所有的业务功能都是它的子模块,通过特定的方式挂接到主界面。它是Ajax技术的进一步升华,把Ajax的无刷新机制发挥到极致,因此能早就与桌面程序媲美的流畅用户体验。

Web应用需要解决的问题

根据徐飞前辈的分类,Web应用中要解决的问题分为三类:状态、组织、效率。原文

状态

在一个业务界面中,我们可能会根据某些数据去生成一块界面,然后根据界面上的某些操作,改变一些数据,进而影响界面另外一部分。所以抽象出来就有两种关系:数据改变造成界面改变、界面改变造成数据改变。能够描述界面当前状态的数据,就称为状态。如果不对状态进行抽象,则可能造成逻辑的混乱,所以,不同的框架采用不同的方式进行了处理。

组织

所谓组织,指两个方面,一方面是模块关系,一方面是业务模型。
目前解决模块关系的问题的共识是组件化。整个应用形成倒置的组件树,每个组件提供对外接口,然后内部只关注自己的实现。最原始的组件,其功能可以单独开发测试,然后逐级拼装成更复杂的组件,直到整个应用。每一级都是易装配,可追踪,可管控的。组件化的目标有分治和复用,其中分治的价值更大。由于组件设计需要更高的成本和更高的难度,所以对web应用“全组件化”是有一定的难度的,可综合考虑。
所谓业务模型指的是所处领域中的业务数据、规则、流程的集合。目前解决业务模型的方法有flux、redux之类的架构。

效率

效率分为开发效率和运行效率。开发效率与选用的框架、技术以及组件化等都有很大的关系。运行效率主要因素有数据变更检测方式和创建、修改DOM。数据变更检测方式和框架有很大的关系,创建DOM的过程中,各个框架之间没有太大差异,而在修改DOM的时候,React的虚拟DOM有很大的优秀,其他框架内部实现也在逐渐借鉴。

常见的架构

目前在Web应用中,常用的架构有MVC、MVP、MVVM三种。下面这两篇文章讲的很好。
Scaling Isomorphic Javascript Code
MVC,MVP 和 MVVM 的图示

开发框架与类库

通常,我们会开始用类库和框架来解决构建动态应用的问题。类库是一些函数的集合,它可以帮助我们写Web应用,但起主导作用的是我们的代码,如jQuery。而框架是一种特殊的、已经实现了的Web应用,它只需要填充具体的业务逻辑,其主导作用的是框架。

  • ExtJS可以称为第一代单页应用框架的典型,它封装了各种UI组件,用户主要使用JavaScript来完成前端的开发以及布局。然而,随着功能的逐渐增加,ExtJS的体积变得日益庞大,性能也变得越来越差。
  • React可以认为是MVC中的V视图,其产生的意义是:解决构建随时间数据不断变化的大规模应用程序的问题。其主要特点是组件化(也可以使用其独有的jsx)和虚拟DOM树(只会更新需要更新的DOM,节省大量的DOM修改的时间)。React推崇的是单向数据流:给定原始界面,施加一个变化,就可以推导出顶一个状态(界面或数据的更新)
  • Angular是为了克服HTML在构建应用上的不足而设计的。它采用了MVVM的架构,采用了数据与界面的双向数据绑定,当界面发生变化时,按照配置规则去更新相应的数据,然后,在根据配置好的规则,把数据更新到界面状态。
  • Vue是一个构建数据驱动的web界面的库。Vue.js的目标是通过尽可能简单的API实现相应的数据绑定和组合的视图组件。它只聚焦于视图层,非常容易与其他库或已有项目整合。其核心是一个响应的数据绑定系统,它让数据与DOM保持同步变得非常简单。与其他框架的对比
  • jQuery是一个优秀的JavaScript库,它是轻量级的js库,兼容CSS3以及各种浏览器。jQuery是用户能更方便地处理HTML、events、实现动画效果和AJAX交互。

flexbox

本文大量参考了Helen Emerson的《Using the CSS3 flexbox layout》,如有需要,请移步此文。

Flexbox简述

Flexbox的布局是一个用于页面布局的CSS3模块,目前为止,大部分的浏览器都支持或部分支持此功能,支持情况请参考caniuse网站。Flexbox可以简单快速的创建一个具有弹性功能的布局,当在一个小屏幕上显示的时候,Flexbox可以让元素在伸缩容器中进行自由扩展和收缩,从而调整整个布局。

伸缩容器和伸缩项目

一个flexbox布局由伸缩容器和其中的伸缩项目组成。伸缩容器是一个HTML标签元素,并且“display”属性显示设置“flex”属性值,伸缩容器中的所有子元素都会自动变成伸缩项目(flex items)。

伸缩方向与换行(flex-flow)

伸缩容器有一个CSS属性“flex-flow”用来决定伸缩项目的布局方式。常见的flex-flow的值有row、column、row wrap等,其伸缩项目排列方式如下:

flex-flow:row
flex-flow:column
flex-flow:row wrap

1
2
3
4
5
.container{
display: flex;
flex-flow: row; //还有columen、warp、row warp等
min-width: 1000px; //还可以使用max-widht等属性设置最大/最小宽度/高度等
}

伸缩项目设置

伸缩容器中的所有子元素都将自动变成伸缩项目,仅需要设置伸缩项目的尺寸即可。设置其尺寸可以使用widht、height等固定伸缩项目的宽和高,也可以使用flex属性(还可以结合min-widht等属性)动态改变伸缩项目的尺寸。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.container{
display: flex;
flex-flow: row wrap;
max-width: 1500px;
}
.left{
flex: 1;
min-width: 200px;
max-widht: 400px;
}
.main{
width: 600px;
}
.right{
flex: 2;
min-widht: 200px;
max-width: 600px;
}


在以上容器中,为left right都设置flex,最小/最大宽度,而mian设置了固定宽600px。其排列规则如下(在chrome中验证):

  1. 当宽度足够宽时,先去除main的600px固定宽,left和right宽度比为1:2(超过max-widht,长度自动设为max-width的值)。
  2. 当宽度不足时,优先满足left的min-widht和mian的widht。如果无法满足right的min-widht,right转到第二行。一次类推。
  3. 所以,可以结合min-widht、max-widht和flex动态调整元素大小,并使元素不至于过大或者过小。
  4. 计算规则为先按照flex分配元素尺寸(如果只有一个flex,则其填充剩余空白),然后参考最大最小尺寸,确定元素尺寸。每个元素都自动有一个隐式的最小尺寸,这里需要注意一下。

react初步认识

React起源于Facebook的内部项目,其设计思想及其独特,属于革命性创新,性能出众,代码逻辑简单。其主要优点有:

  1. 虚拟DOM,在必要的时候才将组件渲染到真正的DOM上,减少频繁的DOM操作造成的性能问题。
  2. 组件化,React都是关于构建可复用的组件。事实上,通过React唯一要做的事情就是构建组件。得益于其良好的封装性,组件使代码复用、测试和关注分离更加简单。

简单应用

<!DOCTYPE html>
<html>
  <head>
    <title>Hello React</title>
    <script src="http://fb.me/react-0.13.0.js"></script>
    <script src="http://fb.me/JSXTransformer-0.13.0.js"></script>
  </head>
  <body>
    <div id="example"></div>
    <script type="text/jsx">
      var HelloWorld = React.createClass({
          render: function() {
            return (
                  <p>
                    Hello, <input type="text" placeholder="Your name here" />!
                    It is {this.props.date.toTimeString()}
                  </p>
            );
          }
        });

        setInterval(function() {
              React.render(
                <HelloWorld date={new Date()} />,
                document.getElementById('example')
              );
        }, 500);

    </script>
  </body>
</html>

官方例子

React提供了一个官方例子英文中文,从如何拆分组件树、如何利用React创建静态版本、如何识别最小的state、如何确认state的声明周期到添加方向数据流,较完整的展现了react的应用。

ECMAScript6中的异步操作

Generator函数

Generator函数是ES6提供的一种异步编程解决方案,我们可以将Generator函数理解成一个状态机,执行这个函数会返回一个遍历器对象。Generator函数内部使用yield语句,没执行一次函数的next方法,执行到下一个yield,直到函数结束,从而达到异步调用函数的目的。此外,yield本身没有返回值,next方法可以带一个参数,该参数将被当做上一个yield语句的返回值,实现在generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//一般与for of结合的使用方法,不需要使用next方法
var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a) {
var length = a.length;
for (var i = 0; i < length; i++) {
var item = a[i];
if (typeof item !== 'number') {
yield* flat(item); //在generator函数中调用另一个generator函数,要使用yield*
} else {
yield item;
}
}
};
for (var f of flat(arr)) {
console.log(f);
} //1,2,3,4,5,6

//通过next函数注入不同的值
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true } 5+24+13=42

//回调函数的改写
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();

loader.next();// 加载UI
loader.next(); //// 卸载UI

//部署Ajax操作
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response); //将response作为值传入
});
}
var it = main();
it.next(); //执行request操作

Promise对象

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。Promise独享的状态(pending、resolved、rejected)不受外界影响,只有异步操作的结果可以决定当前是哪一种状态。一旦状态发生改变,就不再变。如果某些事件不断反复发生,一般阿狸说,使用stream模式比部署Promise更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//基本用法
var promise = new Promise(function(resolve, reject) { //接收一个函数,函数有两个参数
// ... some code

if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) { //接收两个函数,分别在成功和失败的时候执行
// success
}, function(value) {
// failure
});

//异步加载图片
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
var image = new Image();

image.onload = function() {
resolve(image);
};

image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};

image.src = url;
});
}

//generator和promise的结合
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
var g = function* () {
try {
var foo = yield getFoo();
console.log(foo);
} catch (e) {
console.log(e);
}
};
function run (generator) {
var it = generator();

function go(result) {
if (result.done) return result.value;

return result.value.then(function (value) {
return go(it.next(value));
}, function (error) {
return go(it.throw(error));
});
}

go(it.next());
}
run(g);

异步操作和Async函数

ES6之前,异步编程的方法,大概有:回调函数、事件监听、发布/订阅、Promise对象,ES6则应用generator函数是异步编程进入全新的阶段,ES7的Async函数更是提出了异步编程的终极解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//读取文件
//1.回调函数
fs.readFile('/etc/passwd', function (err, data) {
if (err) throw err;
console.log(data);
});

//2.Promise
fs.readFile(fileA, function (err, data) {
fs.readFile(fileB, function (err, data) {
// ...
});
}); //读入A后,读取B,则会出现多重嵌套

//3.协程
function *asnycJob() {
// ...其他代码
var f = yield readFile(fileA);
// ...其他代码
}

//4.generator
var fs = require('fs');

var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) reject(error);
resolve(data);
});
});
};

var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

//5.Async
var asyncReadFile = async function (){
var f1 = await readFile('/etc/fstab'); //await表示紧跟后面的表达式需要等待结果
var f2 = await readFile('/etc/shells'); //返回Promise对象,如果不是,则转为promise
console.log(f1.toString());
console.log(f2.toString());
};

ECMAScript6中的Class

本质上,ES6只是ES5的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性,Class和let一样,不存在变量提升。ES6实现了类的继承,类的静态方法,但是目前没有规定类的静态属性,在ES7中,有关于静态属性的提议。

Class类基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//ES5的写法
function Point(x,y){
this.x = x;
this.y = y;
}

Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
}

//ES6的写法
Class Point { //其实它可以看做构造函数的另一种写法。
constructor(x, y) {
this.x = x; //显示定义在本身,而不是原型上
this.y = y;
}
toString() { //class这种写法,方法是不可枚举的
return '(' + this.x + ', ' + this.y + ')';
}
}

Class的继承

Class之间可以通过extends关键字实现继承。ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

1
2
3
4
5
6
7
8
9
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color; //this是继承父类的this对象,然后对其进行加工
}
toString() {
return this.color + ' ' + super.toString(); // super代表父类实例,调用父类的toString()
}
}

类的prototype和proto属性

大多数浏览器的ES5实现之中,每一个对象都有proto属性,指向对应的构造函数的prototype属性。Class作为构造函数的语法糖,同时有prototype属性和proto属性,因此同时存在两条继承链。一、子类的proto属性,表示构造函数的继承,总是指向父类。二、子类prototype属性的proto属性,表示方法的继承,总是指向父类的prototype属性。

1
2
3
4
class A {}
class B extends A {}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

Class的静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。父类的静态方法,可以被子类继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'

var foo = new Foo();
foo.classMethod() //typeeror static方法不会被实例继承

Class Bar extends Foo{
static classMethod() {
return super.classMethod()+'too'; //可以从super对象上调用
}
}
Bar.classMethod() //'hello' static方法可以被子类继承,

ECMAScript6新属性方法等

ECMAScript6中添加了一些新的结构、属性、方法等,下面将进行简单的说明。

Symbol

ES5的对昂属性名都是字符串,容易造成属性名的冲突。ES6引入了一种新的原始数据类型Symbol,表示独一无二的值,它是JavaScript的第七种数据类型,通过Symbol函数生成。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。如下:

1
2
3
4
5
6
7
8
var mySymbol = Symbol();
var a = {[mySymbol]: 'Hello!'}; //symbol属性不能使用.运算符
a[mySymbol] //"Hello!" symbol属性不能使用.运算符

var s1 = Symbol.for('foo'); //必须都是用for,且输入值相同,才相等
var s2 = Symbol.for('foo');
s1 == s2 //true 使用symbol.for可以实现两个symbol相同
Symbol.keyFor(s2) //"foo" sysmol.for()有登记制,symbol()没有,登记的会返回字符串参数

Set和Map数据结构

Set结构

ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set的方法有四个:set.add(value)、set.delete(value)、set.has(value)、set.clear()清除所有成员。如果要读取或者遍历可以将set通过Array.from(set)转换为数组。

1
2
3
4
5
6
var set = new Set([1,2,3,3,4,"4",NaN,NaN]);
alert(set.size); //6 在set内部,4不等于“4”(不会发生类型转换),NaN等于NaN,对象总是不等于对象{}不等于{}

function dedupe(array){ //数组去重的方法
return Array.from(new Set(array));
}

WeakSet

WeakSet与Set类似,也是不重复的值的集合,但是它与Set有两个区别。一、WeakSet的成员只能是对象。二、WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用。无法引用WeakSet的成员,因此WeakSet是不可遍历的。

1
2
var a = [[1,2],[3,4]]; //数组的成员只能是对象,不能是变量
var ws = new WeakSet(a);

Map结构

JavaScript的对象(Object),本质上是键值对的集合(Hash结构),但是只能用字符串当作键。ES6提供了Map数据结构,类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当做键,是一种更完善的Hash结构实现。
Map原生提供三个遍历器生成函数和一个遍历方法。entries()、keys()、values()、forEach()。
Map可以与数组、对象、Json相互转换。

1
2
3
4
5
6
7
8
9
10
var m = new Map();
var o = {p:"hello tecent"};
m.set(o,"content");
m.get(o); //"content"

m.size //1
m.has(o); //true
m.delete(o); //true
m.has(o); //false
m.clear(); //全部清空

WeakMap

WeakMap与Map结构基本类似,唯一的区别是它只接受对象做为键名(null除外),而且键名所指向的对象,不计入垃圾回收机制。其设计目的在于,键名是对象的弱引用。

Iterator遍历器与for…of循环

JavaScript原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6又添加了Map和Set。Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for…of循环,Iterator接口主要供for…of消费。其遍历过程如下:

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

Iterator接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for…of循环。当使用for…of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口。在ES6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象(字符串等)、Set和Map结构。另外,也可以手动为对象等添加Iterator接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let arr = ['a', 'b'];
let iter = arr[Symbol.iterator](); //默认的Iterator接口部署在数据结构的Symbol.iterator属性
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: undefined, done: true }

let arr =['b','c'];
['a',…arr,'d'] //['a','b','c','d'] 扩展运算符会调用默认的iterator接口

var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) { //for in循环,只获得对象的键名
console.log(a); // 0 1 2 3
}
for (let a of arr) { //for of循环,获得对象的键值,只返回具有数字索引的属性
console.log(a); // a b c d
}

var map = new Map();
for(var [name,value] of map)

ECMAScript6为数据类型添加的新方法

ECMAScript6在2015年6月正式发布。它的目标,是是的JavaScript语言可以用来编写复杂的大型应用陈旭,称为企业级开发语言。ECMAScript是JavaScript的规格,后者有时前者的一种实现。

let和const命令

let命令

ES6新增let命令,用来声明变量,用法类似于var。区别为:

  • let所声明的变量,只在let命令所在的代码块内有效。
  • let不存在“变量提升”现象,即其变量的定义是在JavaScript的代码执行阶段。
  • let存在暂时性死区,即又要块级作用域内存在let命令,它所声明的变量就“绑定”这个区域,不再受外部的影响。
  • let实际上为JavaScript新增了块级作用域;
    如此便解决了块级局部变量以及for循环中局部变量的问题。解决了如下一个大问题:
1
2
3
4
5
6
7
var a=[];
for(let i=0;i<10;i++){
a[i]=function(){
console.log(i); //i为局部变量,
}
}
a[5](); //5,每次的i都是一个新的变量

const命令

const用来定义常量,一旦声明,常量的值就不能改变。

  • 严格模式下,重新赋值会报错,常规模式下,赋值无效不报错;
  • const声明时不赋值则不能赋值,常量为undefind。严格模式下,const声明时必须赋值,否则报错;
  • const存在块级作用域。

变量的结构赋值

只要等号两边的模式相同,左边的变量就会被赋予对应的值。可以用于交换变量的值、从函数返回多个值、函数参数定义、解析jsondata等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var [foo,[[bar],baz]] = [1,[[2],3]]; 
bar //2

var [a,b] = [1]; //a=1,b=undefined
var [a,b] = [1,2,3]; //解构不完全,但可以成功

//设置默认值
var [c=1,d=2,e=3] = [undefined,null,5]; //1,null,5

//用途
[x,y] = [y,x]; //交换变量的值

function example(){
return [1,2,3];
}
var [a,b,c] = example(); //从函数返回多个值

function f([x,y,z]){……}
f([1,2,3]); //函数参数定义

//解析jsondata等

字符串的扩展

String类型主要添加了遍历字符、字符串的头尾判断、字符串重复、字符的有关Unicode表示等。

1
2
3
4
5
6
7
8
9
10
for(let codePoint of 'foo'){
console.log(codePoint); //分别显示 f o o ,遍历字符
}

var s='Hello world';
s.startWith('Hello'); //true
s.endWith('Hello',5); //true,可以添加第二个参数,表示搜索的位置。
s.includes('Hello'); //true

'hello'.repeat(2); //"hellohello"

数值的扩展

新增了是否为无穷、NaN判断,是否为整数,安全整数,另外,parseInt()、parseFloat()方法也放到了Number对象的下面。Math对象的话则添加了获取整数部分,返回正负值,立方根、对数等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0b111110111 === 503 // 0b二进制 true
0o767 === 503 // 0o八进制 true

Number.isFinite(0.8); //true 是否为非无穷,检查Infinate特殊值
Number.isNaN(NaN); //true 是否为NaN,检查NaN特殊值

Number.parseInt('12.34'); //12 将全局方法parseInt()、parseFloat(),移植到Number对象上
Number.parseFloat('12.45#'); //12.45

Number.isInteger(3.0); //true 是否为整数,JavaScript中,整数和浮点数使用同样的存储方法,所以33.0视为一个值
Number.isSafeInteger(Math(2,53)-1); /true,JavaScript能够精确表示-2^53到-2^53间的整数(不含端点),超过则无法精确表示这个值

Math.trunc(4.1); //4 用于取出一个数的小数部分,返回整数部分
Math.sign(-5); //-1 参数为正数返回+1 其他返回+00、+0、-1和NaN
Math.cbrt(2); //1.2599210498948734 返回一个数的立方根

//还有对数方法、关于32位无符号数的方法等

数组的扩展

新增了将类数组转换为数组、将一组值转换为数组的方法,通过find()查找第一个符合条件的数组成员,通过fill填充数组,还可以通过entries()等方法返回键值对数组等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let arrayLike={'0':'a','1':'b','2':'c',length:3};
var arr1 = [].slice.call(arrayLike); //['a','b','c'] ES5的方法
let arr2 = Array.from(arrayLike); //['a','b','c'] ES6的方法
Array.from('abcde'); //['a','b','c','d','e']
Array.from([1, 2, 3], (x) => x * x); //[1,4,9] 接收第二个参数,对每个元素进行处理

Array.of(3,11,8); //[3,11,8] 将一组值,转换为数组,主要目的是弥补数组构造函数Array()的不足

[1,4,-5,10].find((n) => n<0); //-5 用于找出第一个符合条件的数组成员

['a','b','c'].fill(7,1,2); //['a',7,'c'] 第一个参数表示填充值,剩下两个表示填充起始位置和结束位置

//entries() keys() values()用于遍历数组,分别表示遍历键值对、键、值
for(let [index,elem] of ['a','b'].entries()){
console.log(index,elem);
} //0 "a" 1 "b"

[1,2,3].includes(2); //true 是否包含给定数值

函数参数的扩展

函数中新增了设置参数默认值、rest参数等功能,并且新增了箭头函数,尾递归优化等,并且可以通过函数的length属性获取其无默认值参数的个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function Point(x = 0, y = 0) { //设置参数默认值
this.x = x;
this.y = y;
}
var p = new Point(5); //p.x=5,p.y=0

//通常定义了默认值的参数,应该是函数的尾参数,因为输入f(,)这种没有赋值的函数会出错
function f(x,y=1){
return [x,y];
}
f(2); //[2,1] 这样就可以省略尾部的有默认值的参数

(function(a,b,c=1){}}).length //2 返回没有指定默认值的参数个数

//指定参数是否可以省略
function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}
foo(); //会抛出错误,因为mustBeProvided默认值为抛出错误

//变量数量不定,rest参数。values变量表示一个数组
function add(...values) {
let sum = 0;

for (var val of values) {
sum += val;
}

return sum;
}
add(2, 5, 3) // 10

//箭头函数
var f = v => v*v; //f(2)等于4
var sum = (num1,num2) => num1+num2 //等同于下面
var sum = function (num1,num2){ return num1+num2; }

//尾递归,只保留一个调用记录(严格模式下开启),复杂度O(1)
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120

对象的扩展

对象的扩展对象合并、属性遍历、返回对象键值数组等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Object.is(NaN,NaN) //true 只要两个值一样,就相等,不同于===
Object.is("12",12) //false 不进行自动转换类型,不同于==

//对象合并,常用于为对象添加属性和方法以及克隆对象等
var target = { a: 1, b: 1 }; //合并对象,只是浅复制
var source1 = { b: 2, c: 2 };
Object.assign(target, source1); //{ a:1, b: 2, c: 2}将后面的对象都复制到第一个对象

//属性的遍历
for……in //遍历对象自身和继承的可枚举属性
Object.keys(obj) //返回一个数组,包含对象自身(不含继承的)所有可枚举属性
Object.getOwnPropertyNames(obj) //返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)

//返回对象的键值对数组、键数组和值数组
Object.values(obj); //返回obj对象的值数组
Object.keys(obj); //返回obj对象的键数组
Object.entries(obj); //[["a","1"],["b",2]] 返回obj对象的值数组

同源策略与跨域

同源策略

同源策略为JavaScript文件只能访问与其同源的资源和数据。同源策略浏览器最核心也是最基本的安全功能,其目的是为了防止恶意网站窃取数据。所谓同源是指协议、域名、端口都相同。IE关于同源不要求端口相同。

同源禁止的行为

考虑同源,要考虑三个部分:不同源的服务器、不同源的窗口和文档、不同源的存储数据。一般情况下,同源策略禁止以下行为:

  • 禁止JavaScript访问不同源的Cookie、LocalStorage以及IndexDB等存储数据。
  • 禁止读取和操作与JavaScript脚本不同源的窗口和文档的大多数属性。
  • 禁止XMLHttpRequest生成的HTTP请求与不同源的服务器通信。(如果允许,就可以在控制台写个ajax提交到任意的服务器了)

同源策略允许的行为

同源策略控制了不同源之间的交互,但是并非限制了所有不同源的交互。通常允许以下行为:

  • 通常允许跨域写操作,例如链接、重定向以及表单提交。特定少数的HTTP请求需要添加preflight。
  • 通常允许跨域资源嵌入。例如利用script、link、img、iframe、video、audio等标签嵌入不同域的资源(可以设置资源是否可以被其他域访问)。这种内嵌资源的获取和访问是由浏览器来完成的,所以是安全的。此外,通过< script>标签等引入的JavaScript、CSS、图像等静态资源也被认为是与包含它们的界面所在的域同源。
  • 通常不允许跨域读操作。但常可以通过内嵌资源进行读取访问,如调用内嵌脚本的方法(JSONP)。

防盗链

严格来说,同源策略用于JavaScript脚本,所以使用script、img等标签进行跨域资源嵌入是与同源策略无关的。那么别人也就可以在他们的页面上引用我们的服务器的图片了,但是如果我们不想给他们用呢?在请求头中,有个字段为referer,采用url的格式表示发送请求的网页或文件,我们可以在服务器代码中检查referer的值,来决定是否让这些网站使用。这就是防盗链的原理。

跨域

跨域利用一定的方法和技术,使JavaScript脚步可以访问获取原本不同源的服务器、窗口和文档、存储数据的内容。常用的方法有设置相同的域、跨域资源共享、JSONP、跨文档信息、WebSockets等。

变更源

同源限制的是不同源内容之间的访问,那么我们只需要将两个页面的源设成一致就可以相互访问了。
实现方法:通过JavaScript,将A页面和B页面的document.domain设置为相同的域名。
限制及适用范围:

  • 同个网页的一级域名需要相同。如http://store.company.com/dir/other.html的域名可以设置为`document.domain = “company.com”`,但是不能设置为othercompany.com。另外赋值时,会以null覆盖原来的端口号,所以赋值时要带上端口号
  • 此方法只适用于Cookie和窗口,LocalStorage和IndexDB无法通过此方法规避同源,而要使用PostMessage。
  • 也可以在服务器设置Cookie时,指定Cookie的所属域名为一级域名,这样只有一级域名相同,就可以访问该Cookie。

CORS跨域资源共享

跨域资源共享是一种网络浏览器的技术规范,它为Web服务器定义了一种方式,允许网页从不同域访问其资源。而这种访问是同源策略所禁止的。它定义了一种浏览器和服务器交互的方式来确定是否允许跨域请求。跨域资源共享的基本思想,是通过自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是否应该成功。
默认情况下,XHR对象只能访问与包含它的页面位于同一域中的资源,所以通过xhr.open()打开的是相对路径的url,如果浏览器支持CORS,则可以将url设置为指向其他域资源的绝对路径的url。将请求发送给服务器后,服务器会根据这个头部信息(Origin,说明请求的源)决定是否给予响应,可以接受的话,就在Access-Control-Allow-Origin头部中回发相同的源信息(如果是公共资源,可以回*),如果没有这个头部或者有这个头部但源信息不匹配,浏览器就会驳回请求。
与JSONP相比,CORS无疑更加先进:

  1. JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
  2. 使用CORS,开发者可以使用普通的xhr对象发起请求和获得数据,比起JSONP有更好的错误处理。

JSONP

JSONP是JSON with padding的简写,是应有JSON的一种方法。JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的JSON数据。JSONP是通过动态< script>元素来使用的,使用时可以为src属性指定一个跨域URL。
总的来说,JSONP是一种在客户端定义函数,在服务器端传入数据并传递到客户端调用该函数的一种方式。所以JSONP要求服务器和客户端的密切配合,并且只能实现GET请求。且数据格式要为可以通过script标签传递的JSONP格式。
实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//客户端
<body>
<div id="divCustomers"></div>
<script type="text/javascript">
function callbackFunction(result, methodName)
{

var html = '<ul>';
for(var i = 0; i < result.length; i++)
{
html += '<li>' + result[i] + '</li>';
}
html += '</ul>';
document.getElementById('divCustomers').innerHTML = html;
}
</script>

<script type="text/javascript" src="http://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunction"></script>
</body>

//服务器端
<?php
header('Content-type: application/json');
//获取回调函数名
$jsoncallback = htmlspecialchars($_REQUEST ['jsoncallback']);
//json数据
$json_data = '["customername1","customername2"]';
//输出jsonp格式的数据
echo $jsoncallback . "(" . $json_data . ")";
?>

Web Sockets

Web Sockets的目标是在一个单独的持久连接上提供双工、双向通信。使用标准的HTTP服务器无法实现Web Sockets,只要支持这种协议的专门服务器才能正常工作。

1
2
3
4
5
6
var socket=new WebSocket("ws://www.example.com/server.php");
socket.send(JSON.stringify(message));
socket.onmessage=function(event){
var data = event.data;
//处理数据
}

window.postMessage

HTML5为解决文档间的通信问题,引入了一个全新的API,它为window对象新增了一个window.postMessage方法,允许跨域的窗口通信。
父窗口可以使用window.open()或iframe等打开一个不同源的子窗口,则父窗口和子窗口可以进行通信。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
//父窗口http://a.com脚本
var popup = window.open('http://b.com', 'title');
popup.postMessage('hello b', 'http://b.com');//向子窗体发送信息
window.addEventListener('message', function(e){ console.log(e); });

//子窗口http://b.com脚本
window.opener.postMessage('hello a', 'http://a.com');//向父窗体发送信息
window.addEventListener('message', function(event){
if(event.origin = "http://a.com"){
console.log(event.data);
}
});

总结

同源策略是限制JavaScript脚本访问不同源的资源的一种安全措施,是web安全的基石。所以script、img等标签不受同源策略的限制(因为其资源的请求是浏览器发起的,是安全的),但是可以通过防盗链技术(检查请求头的referer)来限制我们的资源被其他网页的使用的权限。
同源策略限制JavaScript脚本访问不同源的存储数据(Cookie、LocalStorage、IndexDB)、窗口和文档(其绝大部分属性)以及服务器资源(XMLHttpRequest发起的请求要同源),以防止恶意的网址获取用户的信息。但是,实现合理的跨域请求也是必须的。
为了使同一个一级域名下网站间进行相互访问,可以将其子网址的域名通过document.domain统一设为一级域名,子网站间就不再受同源限制了。为了使Ajax发起不同源的请求,可以使用跨域资源共享方法,设置Orign和Access-Control-Allow-Origin头部来指定该服务器信任的网站,也可以使用JSONP,但它有很大的限制。此外,也可使用WebSocket来实现客户端与服务器端的通信,它不实行同源策略。为了不同网页间进行信息传输,则可以使用postMessage方法来实现。

Ext中Ajax技术详解

Ajax技术的核心是XMLHttpRequest对象。原生JavaScript实现Ajax的流程为:创建XHR对象实例,实例使用open()方法(设定发送请求类型、URL以及是否异步),然后使用send()方法传送相应数据,即完成请求。接下来,通过检查xhr实例的状态,判断是否返回所需值,并对response进行解析,执行相应操作即完成了Ajax通信过程。ExtJS以及jQuery等库都对Ajax过程进行了不同程度的封装,以简化异步请求过程。

ExtJS的Ajax使用方法简介

ExtJS的封装比较完全,目前ExtJS进行异步通信的常规手段都是采用Model+Store的方式,对组件内容进行动态更新,已经比较少使用Ext.Ajax的方法了。但前一种方式只是对Ext.Ajax方法的进一步封装,其实现原理类似,故此处仍然先对Ext.Ajax进行说明。

1
2
3
4
5
6
7
8
9
10
11
12
//以下是主要的属性和方法,此外还有failure等
Ext.Ajax.request({
url: 'page.php', //设置url,可以带请求参数
method: "get", //设置请求方法
params: { //设置要发送的参数,可发送多个参数
id: 1
},
success: function(response){ //请求的数据返回成功则执行

var text = response.responseText;
// process server response here
}
});

ExtJS的Ext.Ajax.request底层实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
request : function(options) {

//初始化
options = options || {};
var me = this,
scope = options.scope || window,
username = options.username || me.username,
password = options.password || me.password || '',
async,
requestOptions,
request,
headers,
xhr;
if (me.fireEvent('beforerequest', me, options) !== false) {

requestOptions = me.setOptions(options, scope);

if (me.isFormUpload(options)) {
me.upload(options.form, requestOptions.url, requestOptions.data, options);
return null;
}

// 如果设置了自动取消异步,则调用abort()方法,取消异步请求
if (options.autoAbort || me.autoAbort) {
me.abort();
}

async = options.async !== false ? (options.async || me.async) : false;
// 创建一个XHR对象实例,此处多了用户名和密码两个参数
// 函数进行性能检测,ie返回的是xdr实例,存在xhr返回xhr实例
xhr = me.openRequest(options, requestOptions, async, username, password);

// 如果不是XDR,则设置相应头部
if (!me.isXdr) {
headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
}

// 创建请求实例
request = {
id: ++Ext.data.Connection.requestId, //自动设置id以进行区分
xhr: xhr,
headers: headers,
options: options,
async: async,
binary: options.binary || me.binary,
timeout: setTimeout(function() {
request.timedout = true;
me.abort(request);
}, options.timeout || me.timeout)
};

me.requests[request.id] = request;
me.latestId = request.id;

// 绑定statechange事件函数,状态发生改变则发生相应事件
if (async) {
if (!me.isXdr) {
xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
}
}

//如果是XDR,则使用一下方法绑定request
if (me.isXdr) {
me.processXdrRequest(request, xhr);
}

// 开始进行请求
xhr.send(requestOptions.data);
if (!async) {
return me.onComplete(request);
}
return request;
} else {
Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
return null;
}
},

ExtJS中load()与reload()

ExtJS可以通过store的load()和reload()方法隐式实现Ajax请求及响应,为对Ajax的应用级别的封装。下面为load()、reload()流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//reload方法,为更改最新属性,再次使用load()方法
reload: function(options) {
var o = Ext.apply({}, options, this.lastOptions);
return this.load(o);
},


load: function(options) {
var me = this,
operation = {
action: 'read'
};

// ……

if (me.fireEvent('beforeload', me, operation) !== false) {
me.loading = true;
//调用read方法
me.proxy.read(operation, me.onProxyLoad, me);
}

return me;
},

//调用Ajax请求
read: function() {
return this.doRequest.apply(this, arguments);
},

doRequest: function(operation, callback, scope) {

//……

Ext.Ajax.request(request);

return request;
},