手打,学习,来自阮一峰的博客
想要学 node.js 与最新的框架 ,最好先学ES6

字符串的扩展

includes(),stratsWith(),endsWith()

ES5中,JavaScript有indexOf方法与lastIndexOf()方法, 可以用来确定一个字符串是否包含在另一个字符串中. ES6又提供了三种新方法.

includes(): 返回布尔值, 表示是否找到了参数字符串.
startsWith(): 返回布尔值, 表示参数字符串是否在原字符串的头部
endsWith(): 返回布尔值, 表示参数字符串是否在源字符串的尾部

var s = "Hellow world!";
s.startsWith('Hellow')//true
s.endsWith('!')//true
s.includes('o')//true     

这三个方法都支持第二个参数, 表示开始搜索的位置

var s = 'Hello world!';
s.startsWith('word', 6) //true
s.endsWith('Hello', 5) //true
s.includes('Hello', 6) //false
上面代码表示, 使用第二个参数n时,endsWith的行为与其他两个方法有所不同. 它针对前n个字符, 而其它两个方法针对从第n个位置直到字符串结束

repeat()

repeat方法返回一个新字符串, 表示将原字符串重复n次

'x'.repeat(3)//"xxx"
'hello'.repeat(2) //"hellohello"
'na'.repeat(0) //""

如果是小数,会被取整

'na'.repeat(2.9)//"nana"

如果repeat的参数时负数或者Infinity或undefined, 会报错.

'na'.repeat(Infinity)
//RangeErrow
'na'.repeat(-1)
//RangeError

但是, 如果蚕食是0 到-1之间的小数, 则等同于0, 这是因为会先进行取整原酸, 0 到-1之间的小数, 取整以后等于-0, repeat视同为0

'na'.repeat(-0.9)   //"" 

参数NaN等同于0

'na'.repeat(NaN)//""

如果repeat的参数时字符串, 则会先转换成数字.

'na'.repeat('na')//""
'na'.repeat('3')//"nanana"

padStart(),padEnd()

ES7推出了字符串补全长度的功能.如果某个字符串不够指定长度, 会在头部或尾部补全. padStrat用于头部补全, padEnd用于尾部补全

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

上面代码中,padStart和padEnd一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。

如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。

'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'

如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。

'abc'.padStart(10, '0123456789')
// '0123456abc'

如果省略第二个参数,则会用空格补全长度。

'x'.padStart(4) // '   x'
'x'.padEnd(4) // 'x   '

padStart的常见用途是为数值补全指定位数。下面代码生成10位的数值字符串。

'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"

另一个用途是提示字符串格式。

'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

模板字符串

传统的JavaScript语言,输出模板通常是这样写的。

$('#result').append(
  'There are <b>' + basket.count + '</b> ' +
  'items in your basket, ' +
  '<em>' + basket.onSale +
  '</em> are on sale!'
);

上面这种写法相当繁琐不方便,ES6引入了模板字符串解决这个问题。

$('#result').append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

// 普通字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is
 not legal.`

console.log(`string text line 1
string text line 2`);

// 字符串中嵌入变量
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

上面代码中的模板字符串,都是用反引号表示。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。

号,则前面要用反斜杠转义。

var greeting = `\`Yo\` World!`;

如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。

$('#list').html(`
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`);

上面代码中,所有模板字符串的空格和换行,都是被保留的,比如

    标签前面会有一个换行。如果你不想要这个换行,可以使用trim方法消除它。

$('#list').html(`
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`.trim());

模板字符串中嵌入变量,需要将变量名写在${}之中。

function authorize(user, action) {
  if (!user.hasPrivilege(action)) {
    throw new Error(
      // 传统写法为
      // 'User '
      // + user.name
      // + ' is not authorized to do '
      // + action
      // + '.'
      `User ${user.name} is not authorized to do ${action}.`);
  }
}

大括号内部可以放入任意的JavaScript表达式,可以进行运算,以及引用对象属性。

var x = 1;
var y = 2;

`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"

`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"

var obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// 3

模板字符串之中还能调用函数。

function fn() {
  return "Hello World";
}

`foo ${fn()} bar`
// foo Hello World bar

如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。

如果模板字符串中的变量没有声明,将报错。

// 变量place没有声明
var msg = `Hello, ${place}`;
// 报错

由于模板字符串的大括号内部,就是执行JavaScript代码,因此如果大括号内部是一个字符串,将会原样输出。

`Hello ${'World'}`
// "Hello World"

模板字符串甚至还能嵌套。
const tmpl = addrs => <table> ${addrs.map(addr =>
${addr.first}
${addr.last}
).join('')} </table>;

上面代码中,模板字符串的变量之中,又嵌入了另一个模板字符串,使用方法如下。

const data = [
    { first: '<Jane>', last: 'Bond' },
    { first: 'Lars', last: '<Croft>' },
];

console.log(tmpl(data));
// <table>
//
//   <tr><td><Jane></td></tr>
//   <tr><td>Bond</td></tr>
//
//   <tr><td>Lars</td></tr>
//   <tr><td><Croft></td></tr>
//
// </table>

如果需要引用模板字符串本身,在需要时执行,可以像下面这样写。

// 写法一
let str = 'return ' + '`Hello ${name}!`';
let func = new Function('name', str);
func('Jack') // "Hello Jack!"

// 写法二
let str = '(name) => `Hello ${name}!`';
let func = eval.call(null, str);
func('Jack') // "Hello Jack!"

手打,学习,来自阮一峰的博客
想要学 node.js 与最新的框架 ,最好先学ES6

数组的解构赋值

基本用法

ES6允许按一定的模式, 从数组和对象中提取值, 对变量进行赋值, 这被称为解构(DEstructuring).
以前, 为变量赋值, 只能直接指定值.

var a = 1;
var b = 2;
var c = 3;

ES6允许写成下面这样

var [a, b, c] = [1, 2, 3];

上面代码表示, 可以从数组中提取值, 按照对应位置, 对变量赋值.

本质上, 这种写法属于”模式匹配”, 只要等号两边的模式相同, 左边的变量就会被赋予对应的值. 下面是一些使用嵌套数组进行解构的例子.

let [foo,[[bar],baz]] = [1,[[2],3]];
foo //1
bar //2
baz //3

let [ , , third] = ['foo', 'bar', 'baz'];
third //'baz'

let [x, , y] = [1, 2, 3];
x //1
y //3

let [head, ...tail] = [1, 2, 3, 4];
head //1
tail //[2, 3, 4]

let [x , , ...z] = ['a'];
x //'a'
y //undefiend
z //[]

如果结构不成功, 变量的值就等于undefined

var [foo] = [];
var [bar, foo] = [1]; //bar 1,foo undefined

以上两种情况都属于解构不成功, foo的值都会等于undefined.

另一种情况是不完全结构, 即等号左边的模式, 只匹配一部分的等号右边的数组. 这种情况下, 解构依然可以成功.

let [x, y] = [1, 2, 3];
x //1
y //2

let [a, [b], d] = [1, [2, 3],4];
a //1
b //2
d //4

上面连个例子, 都属于不完全结构, 但是都可以成功.

如果等号的右边不是数组(或者严格的说, 不是可遍历的结构, 参见<< Iterator >> 一章), 那么将会报错

//报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefiend;
let [foo] = null;
let [foo] = {};

上面的表达式都会报错, 因为等号右边的值, 要么转为对象以后不具备Iterator接口(前五个表达式), 要么本身就不具备Iterator接口(最后一个表达式).

解构赋值不仅使用于var命令, 也适用于let和const命令.

对于Set(ES6新特性)结构, 也可以使用数组的结构赋值

let [x, y, z] = new Set(['a', 'b', 'c']); 

事实上, 只要某种数据结构具有Iterator接口, 都可以采用数组形式的结构赋值.

function* fibs() {
    var a = 0;
    var b = 1;
    while(true){
        yield a;
        [a, b] = [b, a + b];
    }
}        
var [first, second, third, fourth, fifth, sixth] = fibs();
sixth //5

上面代码中, fibs是一个Generator函数, 原生具有Iterator接口. 结构赋值会依次从这个接口获取值

默认值

结构赋值允许指定默认值

var [foo = true] = [];
foo //true
[x, y = 'b'] = ['a']; //x = 'a', y = 'b'
[x, y = 'b'] = ['a', undefiend]; //x='a',y='b'

上面代码中, 如过一个数组成员是null, 默认值就不会生效, 因为null不严格等于undefined.

如果默认值是一个表达式, 那么这个表达式是惰性求值的, 即只有在用到的时候, 才会求值

function f(){
    console.log('aaa';)
}
let [x = f()] = [1];

上面代码中, 因为x能取到值, 所以函数f根本不会执行. 上面的代码其实等价于下面的代码

let x;
if([1][0] === undefined){
    x = f();
} else {
    x = [1][0];
}

默认值可以引用结构赋值的其他变量, 但该变量必须已经声明

let [ x = 1, y = x] = []; //x=1;y=1;  
let [ x= 1, y = x] = [2]; //x=2;y=2
let [x = 1, y = x] = [1, 2]; //x=1; y=2;
let [x = y, y = 1] = []; //报错

上面最后一个表达式之所以会报错, 是因为x用到默认值y时,y还没有声明

对象的结构赋值

结构赋值不仅可以用于数组, 还可以用于对象

var {foo, bar} = { foo:'aaa', bar: 'bbb'};
foo //"aaa"
bar //"bbb"

对象的结构与数组有一个重要的不同. 数组的元素是按次序排列的, 变量的取值由它的位置决定; 而对象的属性没有次序, 变量必需要与属性同名, 才能取到正确的值.

var {baz} = {foo: 'aaa',bar :'bbb'};
baz // undefiend        

上边这个例子变量baz没有对应的同名属性, 导致取不到值, 最后等于undefined

如果变量名与属性名不一致, 必须写成下面这样

var { foo: baz} = { foo: "aaa" ,bar: "bbb"};
baz // "aaa"

let obj = { first : 'hello', last: 'world'};
let {first: f, last: l} = obj;
f //"hellow"
l //"world"

这实际上说明, 对象的结构赋值是下面形式的简写(参见<< 对象的扩展 >>一章)

var { foo : foo, bar: bar} = { foo : "aaa", bar : "bbb"};

也就是说, 对象的解构赋值的内部机制, 是先找到同名属性, 然后再赋值给对应的变量. 真正被赋值的是后者, 而不是前者.

var { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined

上面代码中,真正被赋值的是变量baz,而不是模式foo。

注意,采用这种写法时,变量的声明和赋值是一体的。对于let和const来说,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。

let foo;
let {foo} = {foo: 1}; // SyntaxError: Duplicate     declaration "foo"

let baz;
let {bar: baz} = {bar: 1}; // SyntaxError:  Duplicate declaration "baz"

上面代码中,解构赋值的变量都会重新声明,所以报错了。不过,因为var命令允许重新声明,所以这个错误只会在使用let和const命令时出现。如果没有第二个let命令,上面的代码就不会报错。

let foo;
({foo} = {foo: 1}); // 成功

let baz;
({bar: baz} = {bar: 1}); // 成功

和数组一样,解构也可以用于嵌套结构的对象。

var obj = {
  p: [
    "Hello",
    { y: "World" }
  ]
};

var { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

注意,这时p是模式,不是变量,因此不会被赋值。

var node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
};

var { loc: { start: { line }} } = node;
line // 1
loc  // error: loc is undefined
start // error: start is undefined

上面代码中,只有line是变量,loc和start都是模式,不会被赋值。

下面是嵌套赋值的例子。

let obj = {};
let arr = [];

({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar:  true });

obj // {prop:123}
arr // [true]

hw注: 最好还是不要使用嵌套

对象的解构也可以指定默认值。

var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var { message: msg = "Something went wrong" } = {};
msg // "Something went wrong"

默认值生效的条件是,对象的属性值严格等于undefined。

var {x = 3} = {x: undefined};
x // 3

var {x = 3} = {x: null};
x // null

上面代码中,如果x属性等于null,就不严格相等于undefined,导致默认值不会生效。

如果解构失败,变量的值等于undefined。

var {foo} = {bar: 'baz'};
foo // undefined

如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。

上面代码中,等号左边对象的foo属性,对应一个子对象。该子对象的bar属性,解构时会报错。原因很简单,因为foo这时等于undefined,再取子属性就会报错,请看下面的代码。

var _tmp = {baz: 'baz'};
_tmp.foo.bar // 报错

如果要将一个已经声明的变量用于解构赋值,必须非常小心。

// 错误的写法

var x;
{x} = {x: 1};
// SyntaxError: syntax error

上面代码的写法会报错,因为JavaScript引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免JavaScript将其解释为代码块,才能解决这个问题。

// 正确的写法
({x} = {x: 1});

上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。关于圆括号与解构赋值的关系,参见下文。

解构赋值允许,等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。

({} = [true, false]);
({} = 'abc');
({} = []);

上面的表达式虽然毫无意义,但是语法是合法的,可以执行。

对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。

let { log, sin, cos } = Math;

上面代码将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。

字符串的解构赋值

字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';
len // 5

数值和布尔值的结构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。

解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

函数参数的结构赋值

function add([x, y]){
    return x + y;
}

add([1, 2]); //3

上面代码中, 函数 add 的参数表面上是一个数组, 但在传入参数的那一刻, 数组参数就被解构成变量 x 和 y. 对于函数内部的代码来说, 他们能感受到的参数就是 x 和 y.

[[1,2], [3, 4]].map(([a, b]) => a + b);
//[3,7]    

函数参数的解构也可以使用默认值

function move({x = 0, y +})    

函数参数的解构也可以使用默认值

function move({x =0 , y =0} = {}){
    return [x , y];
}    
move({x: 3, y:8}) ;//[3, 8]
move({x:3}); //[3, 0]
move({}); //[0, 0]
move(); //[0,0]

上面代码中, 函数move的参数是一个对象, 通过对这个对象进行解构, 得到变量 x和y的值,如果解构失败, x和y等于默认值
注意, 线面的写法会得到不一样的结果

function move({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]

上面代码是为函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。

undefined就会触发函数参数的默认值。

[1, undefined, 3].map((x = 'yes') => x);
// [ 1, 'yes', 3 ]

用途

  1. 交换变量的值

    [x, y] = [y, x];
    上面的代码交换x和y的值

  2. 从函数返回多个值
    函数只能返回一个值, 如果要返回多个值, 只要将他们房子啊数组或对象中返回. 有了解构赋值, 取出这些值就非常方便.

    //返回一个数组
    function example(){

    return [1, 2, 3];
    

    }
    var [a, b, c] = example();

    //返回一个对象

    function exa(){
    return {

    foo:1,
    bar:2
    

    };
    }
    var {foo, bar } = example();

  3. 函数的参数定义
    结构赋值可以方便地将一组参数与变量名对应起来.

    //参数是一组有次序的值
    function f([x, y, z]) {

    return [x, y, z];
    

    }
    f([1, 2, 3]);
    //参数是一组无次序的值
    function f({x, y, z}) {

    return [x, y, z];
    

    }
    f({z:3, y:2,x:1});

  4. 提取JSON数据
    结构赋值对提取JSON对象中的数据,尤其有用.

    var jsonData = {

    id: 42,
    status: "OK",
    data: [867 ,5309]
    

    } ;
    let {id, status, data:number} = jsonData;
    console.log(id, status, number);
    //42, “OK”, [857, 5309]
    上面的代码可以快速提取JSON数据的值

  5. 函数参数的默认值

    jQuery.ajax = function (url, {

    async = true,
    beforeSend = function(){},
    cache  =  true,
    //...more config
    

    }) {

    //... do stuff
    

    }
    指定参数的默认值, 就避免了在函数内部再写 var foo = config.foo || “default foo”; 这样的语句

  6. 遍历Map结构
    任何部署了Iterator接口的对象, 都可以用for …of循环遍历. Map结构原生支持Iterator接口, 配合变量的解构赋值, 获取键名和键值就非常简单.

    var map = new Map();
    map.set(‘first’,’hello’);
    map.set(‘second’,’world’);
    for( let [key, value] of map){
    console.log(key+ “ is “+ value);
    }
    // first is hello
    // second is world

    如果只想获取键名,或者只想获取键值,可以写成下面这样。

    // 获取键名
    for (let [key] of map) {
    // …
    }

    // 获取键值
    for (let [,value] of map) {
    // …
    }

  7. 输入模块的指定方法

加载模块时, 往往需要指定输入那些方法. 解构赋值使得语句非常清晰

const {SouceMapConsumer, SourceNode} = require("source-map");

手打,学习,来自阮一峰的博客
想要学 node.js 与最新的框架 ,最好先学ES6

let命令

基本用法

ES6新增了let命令, 用来声明变量. 它的用法类似var,但是所声明的变量, 只在let命令行所在的代码块内有效

{
    let a = 10;
    var b = 1;
};
a //Uncaught ReferenceError: a is not defined
b //1

我们通常使用的for循环计数器

for(let i = 0; i < 10; i++){};
console.log(i);  //Uncaught ReferenceError: i is not defined(…  

上面的代码计数器i, 只在for循环体内有效

下面的代码如果使用var,最后输出的是10

var a = [];
for(var i= 0; i < 10; i++){
    a[ i ] = function(){
        console.log(i);
    };
}    
a[6](); //10

上面代码中, 变量i是var声明的, 在全局范围内都有小. 所以每一次循环, 新的i值都会覆盖旧值, 导致最后输出的是最后一轮的i的值.

如果使用let, 声明的变量仅在块级作用域内有效, 最后输出的是6

var a = [];
for(let i = 0; i < 10;i++){
    a[i] = function(){
        console.log(i);
    };
}    
a[6](); //6

上面代码中,变量i是let声明的,当前的i只在本轮循环内有效, 所以每一次循环的i其实都是一个新的变量,所以最后输出的是6

不存在变量提升

let不像var那样会发生”变量提升”现象. 所以,变量一定要在在声明后使用,否则报错.

console.log(foo); //undefiend
console.log(bar); // Uncaught ReferenceError: bar is not defined
var foo = 2;
let bar = 2;

上面代码中, 变量foo用var命令声明,会发生变量提升,既脚本开始运行时,变量foo已经存在了,但没有值,所以会输出undefined. 变量bar用let命令声明, 不会发生变量提升. 这表示在声明它之前, 变量bar是不存在的,这时如果用到它,就会抛出一个错误.

暂时性死区

只要块级作用域内存在let命令, 他所声明的变量就”绑定”(binding)这个区域,不再受外部的影响.

var tmp = 123;
if(true){
    tmp = 'abc'; //ReferenceError
    let tmp;
}

上面代码中,存在全局变量tmp, 但是块级作用于能let又声明了一个局部变量tmp, 导致了后者绑定在这个块级作用域, 所以在let声明变量前,对tmp赋值会报错.

ES6明确规定,如果区块中存在let和const命令, 这个区块对这些命令声明的变量, 从一开始就形成了封闭作用域. 凡是在声明之前就使用这些变量, 就会报错.

总之, 在代码块内, 使用let命令声明变量之前, 该变量都是不可用的. 这在于发生, 称为”暂时性死区”(temporal dead zone, 简称 TDZ)

if(true){
    //TDZ开始
    tem = 'abc'; //ReferenceError
    console.log(tmp); //ReferenceError

    let tmp; //TDZ结束
    console.log(tmp); //undefiend

    tmp = 123;
    console.log(tmp); //123
}

上面代码中, 在let命令声明变量tmp之前, 都属于tmp的”死区”

“暂时性死区”也意味着typeof不再是一个百分百安全的操作;

typeof x; //ReferenceError
let x;

上面代码中, 变量x使用let命令声明,所以在声明之前,都属于x的”死区”,只要用到该变量就会报错. 因此, typeof 运行时就会抛出一个 ReferenceError.
作为比较, 如果一个变量根本没有被声明, 使用typeof反而不会报错.

typeof undeclared_variable;//"undefiend"

上面代码中, undeclared_variable是一个不存在的变量名, 结果返回”undefined”. 所以, 在没有let之前, typeof运算符是百分之百安全的, 永远不会报错. 现在这一点不成立了. 这样的设计是为了让大家养成良好的变成习惯, 变量一定要在声明后使用, 否则就报错.

有些”死区”比较隐蔽, 不太容易发现.

function bar(x = 2, y = x){
    return [x,y];
}
bar(); //报错

上面代码中, 调用bar函数之所以报错(某些实现可能不报错), 是因为参数x默认值等于另一个参数y,而此时y还没有声明, 属于”死区”. 如果y的默认值是x,就不会报错,因为此时x已经声明了.

function bar(x = 2, y = x){
    return [x,y];
}
bar(); //[2,2]

ES6规定暂时性死区和不存在变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为. 这样的错误在ES5是很常见的, 现在有了这种规定, 避免此类错误就很容易了.

总之, 暂时性死区的本质就是, 只要一进入当前作用域, 所要使用的变量就已经存在了, 但是不可获取, 只要等到声明变量的那一行代码出现, 才可以获取和使用该变量.

let不允许重复声明

//报错
function test(){
    let a = 10;
    var a = 1;
}
//报错
function test(){
    let a = 10;
    let a = 1;
}

因此, 不能在函数内部重新声明参数

function func(arg){
    let arg;//报错
} 
function func(arg){
    {
        let arg; //不报错
    }
}

块级作用域

为什么需要块级作用域?
ES5只有全局作用域和函数作用域, 没有块级作用域,这带来很多不合理的场景.
第一种场景, 内层变量可能会覆盖外层变量.

var tmp = new Date();
function f(){
   console.log(tmp);
   if(false){
       var tmp = "hellow world";
   }
}   
f();//undefiend

上面代码中, 函数f执行后,输出结果为undefined, 原因在于变量提升, 导致内层的tmp变量覆盖了外层的tmp变量

第二种场景, 用来计数的循环变量泄露为全局变量

var s = 'hello';
for(var i = 0; i<s.length; i++){
    console.log(s[i]);
}
console.log(i); //5

上面代码中, 变量i只是用来控制循环, 但是循环结束后, 它并没有消失, 泄露成了全局变量

ES6的块级作用域

let实际上为JavaScript新增了块级作用域

function f1(){
    let n = 5;
    if(true){
        let n = 10;
    }
    console.log(n); 
}
f1(); //5

上面的函数有两个代码块, 都声明了变量n, 运行后输出5. 这表示外城代码块不收内层代码块的影响. 如果使用var变量定义n, 最后的输出值就是10

ES6允许块级作用域的任意嵌套.
{ { { { {let insane = ‘Hello World’} } } } };
上面代码使用了一个五层的块级作用域. 外层作用域无法读取内层作用域的变量.

{ { { {
    {let insane = 'Hello Word'};
    console.log(insane); //报错
    } } } } ;

内层作用域可以定义外层作用域的同名变量

{ { { {
    let insane = 'Hello World';
    {let insane = 'Hello World'}
    } } } } 

块级作用域的出现, 实际上( ⊙ o ⊙ )使得获得广泛易用的立即执行匿名函数(IIFE)不在必要了

//IIFE写法
(function(){
    var tmp = 111;
})();
//块级作用域写法
{
    let tmp = 111;
}

const命令

const 声明一个只读的常量. 一旦声明, 常量的值就不能改变

const PI = 3.1415;
PI //3.1415
PI = 3;//TypeError: Assignment to constant variable

上面代码表明改变常量的值会报错

construction声明的变量不得改变值, 这意味着, const一旦声明变量,就必须立即初始化,不能留到以后赋值

const foo;
//SystaxErrow:Missing initializer in const declaration    

上面代码表示, 对于const 来说, 只要声明不赋值, 就会报错

const的作用域与let命令相同; 只在声明所在的块级作用域内有效

if(true){
    const MAX = 5;
}
MAX //Uncaught ReferenceError: MAX is not defined

const 命令声明的常量也是不提升的, 同样存在暂时性死区, 只能在声明的位置后面使用

if(true){
    console.log(MAX); //ReferenceError
    const MAX = 5;
}

上面代码在常量MAX声明之前就调用, 结果报错

const声明的常量, 也与let一样不可重复声明

var message = "Hellow";
let age = 25;
//以下两行都会报错
const message = "Goodbye";
const age = 30;

对于符合类型的变量, 变量名不指向数据, 而是指向数据所在的地址. const命令指示保证变量名指向的地址不变, 并不保证该地址的数据不变, 所以讲一个对象声明为常量必须非常小心

const foo = {};
foo.prop = 123;
foo.prop //123
foo = {};//TypeError:"foo" is read-only

上面代码中, 常量foo储存的是一个地址,这个地址指向一个对象. 不可变的指示真个地址, 既不能把foo指向另一个地址, 但对象本身是可变的, 所以依然可以为其添加新属性.

下面是另一个例子.

const a = [];
a.push('Hellow');
a.length = 0;
a = ['Dave']; //报错

上面代码中,常量a是一个数组, 这个数组本身是可写的, 但是如果将另一个数组赋值给a, 就会报错.

如果真的想将对象冻结, 应该使用Object.freeze方法.

const foo = Object.freeze({});
//常规模式时,下面一行不起作用;
//严格模式时,该行会报错
foo.prop = 123;

上面代码中, 常量foo指向一个冻结的对象, 所以添加新属性时不起作用, 严格模式时还会报错.

除了将对象本身冻结, 对象的属性也应该冻结. 下面是一个将对象彻底冻结的函数.(既深冻结)

var constantize  = (obj) =>{
    Obj.freeze(obj);
    Object.keys(obj).forEach(key, value) =>{
        if( typeof obj[key] === 'obj'){
            constantize(obj[key]);
        }
    };
}   ;   

ES5只有两种声明变量的方法: var命令和function命令. ES6除了添加let和const命令, 另外还有两种声明变量的方法import和class命令. 所以ES6一共有6中声明变量的方法.

全局对象的属性

全局对象是最顶层的对象, 在浏览器环境指的是window对象,在Node.js指的是global对象. ES5中,全局对象的属性和全局变量是等价的

window.a = 1;
a //1
a = 2;
window.a //2

上面代码中, 全局对象的属性赋值与全局变量的赋值,是同一件事. (对Node来说,这一条支队REPL环境中使用, 模块环境之中, 全局变量必须显示的声明成global对象的属性).

未声明的全局变量, 自动成为全局属性的window的属性, 这被认为是JavaScript语言最大的设计败笔之一. 这样的设计带来了两个很大的问题, 首先是没法再编译时就报出变量未声明的错误, 只有运行时才能知道, 其次程序员很容易不知不觉地就创建全局变量(比如打字出错). 另一方面, 从语义上讲, 语言的顶层对象时一个有实体含义的对象, 也是不适合的.

ES6为了改变着一点, 一方面规定, 为了保持兼容性, var命令和function命令声明的全局变量, 依旧是全局对象的属性; 另一方面规定, let命令, const命令, class命令声明的全局变量, 不属于全局对象的属性. 也就是说, 从ES6开始, 全局变量将逐步与全局对象属性脱钩.

var a = 1;
//如果在Node的REPL环境, 可以写成global.a
//或者采用通用的方法, 写成this.a
window.a //1
let b = 1;
window.b //undefined

上面代码中, 全局变量 a 由 var命令声明, 所以它是全局对象的属性; 全局变量b由let命令声明, 所以它不是全局对象的属性, 反回 undefiend
(待验证: 在node模块中, var 全局定义变量只会在本模块生效, 若直接 赋值 会在各个引用的模块中生效)

原理

由于同源策略的限制,XmlHttpRequest只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过script标签实现跨域请求,然后在服务端输出JSON数据并执行回调函数,从而解决了跨域的数据请求。

其本质是利用了< script src=”” ></ script >标签具有可跨域的特性,由服务端返回一个预先定义好的Javascript函数的调用,并且将服务器数据以该函数参数的形式传递过来,此方法需要前后端配合完成。
它只能以GET方式请求
一般将传递的 key 命名为 callback

php服务器示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

header('Content-Type: text/javascript; charset=utf-8');

// 假设从数据库里取数据了
$arr = array(
"name"=>"js",
"age"=>20
);

// 编码处理
$json = json_encode($arr);
//获取客户端的callback函数
$callback = $_GET['callback'];
//给接收到的函数传输入据$json作为参数, 输出到html页面运行该函数
echo $callback . '(' . $json . ')';


?>

客户端示例

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
var jsonp = (function(){
var count = 0;

return function (url, callback) {
//1. 创建一个script标签
var scriptElem = document.createElement('script');

//2. 把地址传进去
var callbackName = "__callback__" + count++;
var jsonpUrl = url + "?callback=" + callbackName;
scriptElem.src = jsonpUrl;

//3. 创建一个fn函数,用于接收返回的数据
window[callbackName] = function(data){
//把数据传给想要用的人
callback(data);
//把我们创建的script标签删掉
window.document.body.removeChild(scriptElem);
};

//4.把标签放到body,把请求发出去
window.document.body.appendChild(scriptElem);
}
})();
jsonp('./js.php',function cs(data){
data = JSON.stringify(data);
console.log(data);
});

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

控制台输出:{“name”:”js”,”age”:20}
注: 请在php服务器下运行

概念

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示.迭代器模式可以吧迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素.
大部分语言都有内部迭代器的实现,ECMAScript5也增加了数组的迭代方法 forEach

内部迭代器

内部迭代器是函数的内部已经定义好了迭代器规则,它完全接手整个迭代过程,外部只需要一次初始调用
下边实现一个自己的内部迭代器

1
2
3
4
5
6
7
8
9
10
11
12
var each = function(arr, fn){
//这里只考虑arr是数组
var i, l = arr.length;
for ( i = 0; i < l; i++ ) {
//把下标和元素当做参数传递给fn函数,并提供跳出的方法,如果fn的返回值为false则提前终止循环
if ( fn.call( arr[ i ], i, arr[ i ] ) === false ) {
break;
}
}
return arr;
}
};

外部迭代器

外部迭代器必须显示地请求迭代下一个元素,我们可以手工控制迭代的过程或顺序,相比内部迭代器有更强的灵活性
下边实现一个自己的外部迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var Iterator  = function(obj){
var current = 0;
var next = function(){
current += 1;
};

var isDone = function(){
return current >= obj.length;
};

var getCurrItem = function(){
return obj[ current ];
};

return {
next:next,
isDone: isDone,
getSingle: getCurrItem
}
};

本文并非介绍line-height的四条线,也不说它是如何继承的

Demo

在 PC 端,为了实现文字居中, 我们这样写 CSS

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
* {
margin:0;
padding: 0;
}
.box {
width: 500px;
height: 200px;
line-height: 200px;
border:1px solid orange;
text-align: center;
color:black;
}
</style>
</head>
<body>
<div class="box">
测试
</div>
</body>
</html>

好,很好,非常好,简简单单就实现了文字居中,可喜可贺 咦! 我为啥要写border属性呢?无所谓啦,这种事随它去吧
真的是无所谓么
有时在移动端我们会加上这个属性

box-sizing:border-box;

看起来没有什么不一样啊!
让我修改一下border

border:50px solid black;

这他么什么鬼,画风完全不对好吧
这时候只需要将更改

line-height: 100px;

文字就再次居中了

让我恢复到原来的属性然后加上

padding:50px 0;

花还香香的
再来一条

box-sizing:border-box;


这时候也需要更改

line-height:98px;

哦!!!
不管你们懂不懂,反正我是懂了

我们都知道当 height 与 line-height 的高度相等的时候文字会垂直居中 ,这个 height 指的得是盒模型的 content 部分,不包括 padding 与 border

JS常见算法学习收集整理

排序

排序概念

排序就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来。其确切定义如下:输入:n 个记录 R1,R2,…,Rn,其相应的关键字分别为 K1,K2,…,Kn。输出:Ril,Ri2,…,Rin,使得 Ki1≤Ki2≤…≤Kin。(或 Ki1≥Ki2≥…≥Kin)。

排序算法的依据–关键字,关键字可以是数字类型,也可以是字符类型。
排序算法的稳定性

当待排序记录的关键字均不相同时,排序结果是惟一的,否则排序结果不唯一。在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生变化,则称这种排序方法是不稳定的。
排序算法的空间复杂度

若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间是O(1),则称之为就地排序(In-PlaceSou)。 非就地排序一般要求的辅助空间为O(n)。

直接插入排序

把待排序的纪录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的纪录插入完为止,得到一个新的有序序列。

设无序数组为a[0…n-1]。

1.初始时,a[0]自成1个有序区,无序区为a[1..n-1]。

2.令i=1,将a[i]插入当前的有序区a[0…i-1]中形成a[0…i]的有序区间。

3.i++并重复第二步直到i==n-1,排序完成。

一趟直接插入排序方法

具体做法:

将待插入记录 a[i]的关键字从右向左依次与有序区中记录 aj的关键字进行比较:

1.若 a[j]的关键字大于 a[i]的关键字,则将 a[j]后移一个位置;

2.若 a[j]的关键字小于或等于 a[i]的关键字,则查找过程结束,j+1 即为 a[i]的插入位置。

关键字比a[i]的关键字大的记录均已后移,所以 j+1 的位置已经腾空,只要将 a[i] 直接插入此位置即可完成一趟直接插入排序。

例如待排序数组a[0]=8,a[1]=5,a[2]=10,a[3]=12,a[4]=7,a[5]=6

第一趟:a[0]=8,有序,a[1…5]无序。

第二趟:temp=5,a[1]=5 < a[0]=8, 8往后移一位,a[1]=8,a[0]=5,a[0…1]有序,a[2…5]无序。

第三、四趟:a[2]=10 > a[1]=8,a[3]=12 > a[2]=10,有序无须移动,a[4…5]无序。

第五趟:temp=7,a[4]=7 < a[3]=12,12往后移一位,a[4]=12,依次类推…直到a[0]=5 < temp,即a[1]=7。

第六趟类比第五趟,可以得到6插入位置为a[1]=6。排序完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
var insertSort = function(array){
var i = 1,temp,len = array.length;
for(; i<len;++i){
temp = array[i];
j = i;
while(--j >= 0){
if(array[j] > temp){
array[j+1] = array[j];
}else {
break;
}
}
array[j+1]=temp;
}
return array;
};
</script>

直接插入排序算法时间复杂度:O(n^2);空间复杂度:O(1)。直接插入排序是稳定的排序方法。

冒泡排序

两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序的记录为止。按照从小到大或者从大到小的顺序进行交换,这样一趟过去后,最大或最小的数字被交换到了最后一位。

冒泡排序算法的运作过程:(从小到大排序)

设数组a[0..n-1]长度为n,

1.比较相邻的前后二个数据,如果前面数据大于后面的数据,就将二个数据交换。

2.这样对数组的第0个数据到n-1个数据进行一次遍历后,最大的一个数据就“沉”到数组第n-1个位置。

3.n=n-1,如果n不为0就重复前面二步,否则排序完成。

例子为从小到大排序,原始待排序数组| 7 | 2 | 4 | 1 | 5 |

第一趟排序(外循环)

第一次两两比较7 > 2交换(内循环)

交换前状态| 7 | 2 | 4 | 1 | 5 |

交换后状态| 2 | 7 | 4 | 1 | 5 |

第二次两两比较,7 > 4交换

交换前状态| 2 | 7 | 4 | 1 | 5 |

交换后状态| 2 | 4 | 7 | 1 | 5 |

第三次两两比较,7 > 1交换

交换前状态| 2 | 4 | 7 | 1 | 5 |

交换后状态| 2 | 4 | 1 | 7 | 5 |

第四次两两比较,7 > 5交换

交换前状态| 2 | 4 | 1 | 7 | 5 |

交换后状态| 2 | 4 | 1 | 5 | 7 |

第二趟排序(外循环)

第一次两两比较2 < 4不交换

交换前状态| 2 | 4 | 1 | 5 | 7 |

交换后状态| 2 | 4 | 1 | 5 | 7 |

第二次两两比较,4 > 1交换

交换前状态| 2 | 4 | 1 | 5 | 7 |

交换后状态| 2 | 1 | 4 | 5 | 7 |

第三趟排序(外循环)

第一次两两比较2 > 1交换

交换后状态| 2 | 1 | 4 | 5 | 7 |

交换后状态| 1 | 2 | 4 | 5 | 7 |

第二次两两比较,2 < 4不交换

交换后状态| 1 | 2 | 4 | 5 | 7 |

交换后状态| 1 | 2 | 4 | 5 | 7 |

排序完毕,输出最终结果1 2 4 5 7

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
<script>
var bubbleSort = function(array){
var i = 0,
len = array.length,
j,
temp;
for(;i < len;i++){
for(j = 0; j < len; j++){
if(array[i] < array[j]){
temp = array[j];
array[j] = array[i];
array[i] = temp;
}
}
}
return array;
};
//或者这样更好理解
function bubbleSort(arr) {
var i = arr.length, j;
var temp;
while (i > 0) {
for (j = 0; j < i - 1; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
i--;
}
return arr;
}
</script>

冒泡排序时间复杂度,最好情况:数组已有序O(n);最坏情况:数组反序O(n^2),平均时间复杂度:O(n^2)。空间复杂度,冒泡排序是原地排序,空间复杂度为O(1)。冒泡排序算法是稳定的排序算法。

直接选择排序

无序数组a[0…n-1],第一次从a[0]~a[n-1]中选取最小值,与a[0]交换,第二次从a[1]~a[n-1]中选取最小值,与a[1]交换,….,第i次从a[i-1]~a[n-1]中选取最小值,与a[i-1]交换,…..,第n-1次从a[n-2]~a[n-1]中选取最小值,与a[n-2]交换,总共通过n-1次,得到一个按关键字从小到大排列的有序序列·

直接选择排序算法过程如下:

给定n=7,数组a中的7个元素为[8,3,2,1,7,4,6]

初始状态 [ 8 3 2 1 7 4 6]

第1次,数组a[0..6]中最小的数为a[3]=1,交换a[3]<->a[0],交换结果[ 1 3 2 8 7 4 6]

第2次,数组a[1..6]中最小的数为a[2]=2,交换a[2]<->a[1],交换结果[ 1 2 3 8 7 4 6]

第3次,数组a[2..6]中最小的数为a[2]=3,交换a[2]<->a[2],交换结果[ 1 2 3 8 7 4 6]

第4次,数组a[3..6]中最小的数为a[5]=4,交换a[5]<->a[3],交换结果[ 1 2 3 4 6 8 7]

第5次,数组a[4..6]中最小的数为a[4]=6,交换a[4]<->a[4],交换结果[ 1 2 3 4 6 8 7]

第6次,数组a[5..6]中最小的数为a[6]=7,交换a[6]<->a[5],交换结果[ 1 2 3 4 6 7 8]

排序完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
var selectSort = function(array){
var i = 0,
j,
min,
temp,
len = array.length;
for(;i < len - 1;i++){
min = i;
for(j=i+1; j< len;j++){
if(array[min] > array[j]){
min = j;
}
}
temp = array[i];
array[i] = array[min];
array[min] = temp;
}
return array;
};
</script>

在直接选择排序中,共需要进行n-1次选择和交换,每次选择需要进行 n-i 次比较 (1<=i<=n-1),而每次交换最多需要3次移动,因此,总的比较次数C=(n*n - n)/2,时间复杂度O(n^2)。直接选择排序为原地排序,空间复杂度O(1)。直接选择排序不是稳定的排序算法。

希尔排序

把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

希尔排序算法过程:

先取一个正整数gap

例如数组a[49, 38, 65, 97, 26, 13, 27, 49, 55, 4]

第1次 步长 gap = 10 / 2 = 5

分成了五组(49, 13) (38, 27) (65, 49) (97, 55) (26, 4),

每组排序后变成了(13, 49) (27, 38) (49, 65) (55, 97) (4, 26)。

第1次排序结果:13 27 49 55 4 49 38 65 97 26

第2次 步长 gap = 5 / 2 = 2

分成了2组(13,49,4,38,97) (27,55,49,65,26)

每组排序后变成了(4,13,38,49,97) (26,27,49,55,65)

第2次排序结果:4 26 13 27 38 49 49 55 97 65

第3次 步长 gap = 2 / 2 = 1

分为一组,直接插入排序后,数组有序。

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
var shellSort = function(array){
var len = array.length, gap = parseInt(len/2),
i, j, tmp;
while(gap > 0){
//循环使i=gap
for(i=gap; i<len; i++){
//给临时值tmp赋值为array[i]
tmp = array[i];
//j是i减去增量
j = i - gap;
while(j>=0 && tmp < array[j]){
array[i] = array[j];
j = j - gap;

}
array[j+gap] = tmp;

}
//改变增量gap 控制循环
gap = parseInt(gap/2);
}
return array;
};
//或者这样
var shellSort = function(array){

var len=array.length;
for(var gap=Math.floor(len/2); gap>0; gap=Math.floor(gap/2)){
for(var i=gap; i<len; i++){
for(var j=i-gap; j>=0&&array[j]>array[gap+j]; j-=gap){
var temp = array[j];
array[j] = array[gap+j];
array[gap+j] = temp;
}
}
}
}

希尔排序的时间复杂度与增量序列的选取有关,例如希尔增量时间复杂度为O(n²),而Hibbard增量的希尔排序的时间复杂度为O(n^(3/2)),希尔排序时间复杂度的下界是n*log2n,希尔排序时间复杂度O(Nlog2N),空间复杂度O(1)。希尔排序不是稳定排序算法。

快速排序

首先从数组a中选取一个基准点(通常我们取中间项作为基准点),然后遍历数组,把小于基准点的项放到基准点左边集合,把大于基准点的项放到基准点右边集合。再对左边和右边两个集合重复前面的操作,直到每个子集就剩下一个元素。其实就是一个递归的思想。

依旧拿数组a=[12,3,43,11,56,90,7,66,82]举例,我们先选取一个基准点pivot=a[Math.floor(a.length/2)],即a[4]值为56,然后便利数组中的剩余项,把小于56的数组项放在左边的数组left中,把大于等于56的数组项放在右边的数组right中,第一轮操作结束后left=[12,3,43,11,7],right=[90,66,82],然后再对left和right重复以上的操作,直到left和right仅剩一项或为空时结束。最后返回left+pivot+right。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function quickSort(arr){
var len=arr.length;//获取arr的长度
if(len<=1){//如果arr的长度小于等于1则直接返回arr
return arr;
}
var pIndex=Math.floor(len/2);//获取基准点的索引下标
var pivot=arr[0];
var left=[];
var right=[];
for(var i=1; i<arr.length; i++){
if(arr[i]<pivot){//如果小于基准点就放到数组l中
left.push(arr[i]);
}else{//如果大于等于基准点就放到右边数组中
right.push(arr[i]);
}
}
return quickSort(left).concat(pivot,quickSort(right));//递归不断重复整个过程
}

快速排序算法平均时间复杂度O(nlgn),最坏O(n^2)。快速排序需要栈空间来实现递归,如果数组按局等方式被分割时,则最大的递归深度为 log n,需要的栈空间为 O(log n)。最坏的情况下在递归的每一级上,数组分割成长度为0的左子数组和长度为 n - 1 的右数组。这种情况下,递归的深度就成为 n,需要的栈空间为 O(n)。快速排序不是稳定排序算法。

感慨

算法这种东西不经常用,学起来也比较伤脑,而且学了忘学了又忘,果然对我这个特别平凡的人来说算法不太适合么
好烦 不搞了

改变

最近新入了一家公司
以前都是这样写css

1
2
3
4
5
6
7
8
9
10
11
12
13
nav ul {
margin: 0;
padding: 0;
list-style: none;
}
nav li {
display: inline-block;
}
nav a {
display: block;
padding: 6px 12px;
text-decoration: none;
}

多好啊,简单易懂,清晰明了
现在要求这样

1
2
3
nav ul { margin: 0; padding: 0; list-style: none; }
nav li { display: inline-block; }
nav a { display: block; padding: 6px 12px; text-decoration: none; }

虽然最后都要压缩,但没办法那就只能这样写了,可是以前使用emmet语法,会自动换行,这可怎么办,幸亏我大部分项目都是使用强大的webstorm写的,经过不断尝试终于在

File-->Settings-->Editor-->Code Style-->CSS

目录下的选项卡 Other 下找到了 Keep single-line blocks ,只要把此项勾选从此智能提示不换行, 顿时喜笑颜开

但是还不能满足,我要用 Less, 用Less啊,自动生成的 CSS 样式还是要换行,这可怎么办,网上各种查资料,最后发现没有办法啊没有办法,这他么是在逗我呢.

我明明记得能控制输出 CSS 的样式,难道我记错了么?这不可能!哦,真的是我记错了,支持控制输出CSS样式的是 Sass.
其实用Sass一开始我是拒绝的,虽然说比Less强大,但是我们用得着那些强大的功能么,我平时只要只用嵌套好吧,而且要装 ruby, 好烦,我只是个小前端,到底要做到哪一步啊,我记得学习 ionic 的时候还装了 Python 还有 Java Android 环境什么的,心好塞…
就在这时我发现了一个好物

node-sass

webstorm 是支持 node-sass,额至少 webstorm11 是支持的

首先你需要全局安装

npm install node-sass -g

然后在

File-->Settings-->Tools-->File Watchers

目录下点击右上角的 + ,选择 SCSS 就可以了,然后配置

Program: C:\Users\Administrator\AppData\Roaming\npm\node-sass.cmd(输入你的node-sass.cmd的路径)
Arguments: $FileName$ $FileNameWithoutExtension$.css
Working directory: $FileDir$
Output paths to refresh: $FileNameWithoutExtension$.css
'Create output from stdout' should be off(不要勾选该选项)

然后找到

C:\Users\Administrator\AppData\Roaming\npm\node_modules\node-sass\bin\node-sass (你自己的全局npm模块下)

打开在 default下(第100行)有个 output-style ,将它的值改为 compact 就可以实现我需要的输出效果了.
如果你想了解更多可以访问
来自stackOverflow

当然如果你不用 webstorm 并且有需要的话 gulp 是一个非常好的选择

果然身为程序员有问题还是要上 stack Overflow

概念

代理模式是为一个对象提供提供代用品或占位符,以便控制对他的访问
代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际访问的是替身对象