奶糖的博客

专注IT,精于前端


  • 首页

  • 关于

  • 归档

ES6 变量的解构赋值

发表于 2018-08-18
字数统计: 1,739 | 阅读时长 ≈ 7

变量的解构赋值

数组的解构赋值

  • ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

    1
    2
    3
    4
    5
    6
    7
    8
    // 以前,为变量赋值,只能直接指定值。
    let a = 1;
    let b = 2;
    let c = 3;

    ES6 允许写成下面这样。

    let [a, b, c] = [1, 2, 3]

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    let [head, ...tail] = [1, 2, 3, 4];
    head // 1
    tail // [2, 3, 4]
    // es6 解构数组中的 ... 会匹配到的等号右边数组剩余的值,将其存入数组中。
    // 如果等号右边剩余的值为空,则解构出来的是空数组
    let [head, ...tail] = [1];
    head // 1
    tail // []
    1
    2
    3
    let [foo] = [];
    let [bar, foo] = [1];
    // 如果解构不成功,值为undefined

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

    1
    2
    3
    4
    5
    let [a, b, c] = [1, 2, 3, 4]
    a // 1
    b // 2
    c // 3
    // 上面两个例子,都属于不完全解构,但是可以成功

    可以发现,解构过程中,等号左边数组的length必须小于或等于等号右边的值(不考虑undefined的情况)

  • 默认值
    解构赋值允许指定默认值。

    1
    2
    let [foo = true] = [];
    foo // true

    注意:ES6 内部使用严格相等运算符(===),判断一个位置是否有值。
    所以,只有当一个数组成员严格等于undefined,默认值才会生效。
    也就是说,等号左边的某个值,解构相应位置等号右边的数组的值时,如果该值为空或者undefined,默认值才会生效
    如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let [foo = 1, bar = 2] = [3] 
    foo // 3
    bar // 2
    ========
    let [foo1 = 1, bar1 = 2] = []
    foo // 1
    bar // 2
    ========
    let [foo2 = 1, bar2 = 2] = [3, null]
    foo // 3
    bar // null // 此时null 不全等(===)undefined,所以默认值不生效

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

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

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

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

    1
    2
    3
    4
    5
    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] = []; // ReferenceError: y is not defined
    // 最后一个表达式之所以会报错,是因为x用y做默认值时,但y还没有声明。

对象的解构赋值

  • 解构不仅可以用于数组,还可以用于对象。

    1
    2
    3
    let {foo, bar} = {foo: 'aaa', bar: 'bbb'}
    foo // "aaa"
    bar // "bbb"

    在不使用对象解构时,我们一般是如下获取对象的value的

    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
    let ajaxData = { 
    id: 10086,
    name: 'bob',
    age: '28'
    }
    var id = ajaxData.id
    var name = ajaxData.name
    var age = ajaxData.age

    使用对象解构后就很简洁了
    let ajaxData = {
    id: 10086,
    name: 'bob',
    age: '28'
    }
    let {id, name, age} = ajaxData;
    id // 10086
    name // 'bob'
    age // '28'
    // 如果要使用的变量就是等号右侧对象中的key,
    // 在上面的例子中,我要使用的就是 id, name, age, 然后把对象key对应的value赋值即可,
    // 就可以采用简写形式 let {id, name, age} = ajaxData;
    // 即 如果你最终要声明变量的key 与等号右边的对象中的key相同,可以缩写为 { key1, key2, ... }

    // 但如果我要更改变量的名称,可以写成如下形式
    let {id: a, name: b, age: c} = ajaxData;
    // 那么获取的时候就是a, b, c, 即
    a // 10086
    b // 'bob'
    c // '28'

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

    1
    2
    3
    4
    5
    6
    7
    let { foo: baz } = {foo: 'aaa'}
    baz // aaa

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

    这实际上说明,对象的解构赋值是下面形式的简写

    1
    let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    let {bar, foo} = {foo: 'aaa', bar: 'bbb'}
    foo // aaa
    bar // bbb
    // 等号两边变量次序不一致,但是取值时并没有收到影响

    let {bar, foo} = {foo: 'aaa', bar: 'bbb'}
    baz // undefined
    // 等号右边并没有对应的同名属性baz,所以取不到值,值为undefined
  • 变量的解构赋值的用途
    (1)交换变量的值

    1
    2
    3
    4
    5
    6
    let x = 1;
    let y = 2;

    [x, y] = [y, x]

    // 上面代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。

    (2)从函数返回多个值

    函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 返回一个数组
    function example() {
    return [1, 2, 3];
    }
    let [a, b, c] = example();

    // 返回一个对象
    function example() {
    return {
    foo: 1,
    bar: 2
    };
    }
    let { foo, bar } = example();

    (3)函数参数的定义

    解构赋值可以方便地将一组参数与变量名对应起来。

    1
    2
    3
    4
    5
    6
    7
    // 参数是一组有次序的值
    function f([x, y, z]) { ... }
    f([1, 2, 3]);

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

    (4)提取 JSON 数据

    解构赋值对提取 JSON 对象中的数据,尤其有用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let jsonData = {
    id: 42,
    status: "OK",
    data: [867, 5309]
    };

    let { id, status, data: number } = jsonData;

    console.log(id, status, number);
    // 42, "OK", [867, 5309]

    (5)函数参数的默认值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    jQuery.ajax = function (url, {
    async = true,
    beforeSend = function () {},
    cache = true,
    complete = function () {},
    crossDomain = false,
    global = true,
    // ... more config
    } = {}) {
    // ... do stuff
    };

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

    (6)输入模块的指定方法

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

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

ECMAScript 6总结

发表于 2018-08-18
字数统计: 1,997 | 阅读时长 ≈ 8

let 和 const 命令

let 命令

基本用法

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

    1
    2
    3
    4
    5
    6
    7
    8
    {
    let a = 10;
    var b = 1;
    }
    a // ReferenceError: a is not defined.
    b // 1
    上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。
    这表明,let声明的变量只在它所在的代码块有效。
  • for循环的计数器,就很合适使用let命令。

    1
    2
    3
    4
    for (let i = 0; i < 10; i++) {
    console.log(i);
    }
    上面的代码会依次打印出0-9, 这与var声明是不同的, 说明了每一次循环, 都是在不同的作用域内声明不同的i变量
  • 另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    for (let i = 0; i < 10; i++) {
    let i = 'abc'
    console.log(i);
    }
    // abc
    // abc
    // abc
    上面的代码会打印3次 'abc', 说明设置变量的作用域和循环体内的作用域是两个不同的作用域,
    对比下面的代码, 循环体内没有使用let 重新声明i, 意味着修改的是父作用域中的变量

    for (let i = 0; i < 10; i++) {
    i = 'abc'
    console.log(i);
    }
    // 'abc'

不存在变量提升

  • 简而言之, let 声明变量之前, 是禁止使用该变量的, 而使用 var 声明的变量, 变量会提升, 为undefined

暂时性死区

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

    1
    2
    3
    4
    5
    6
    7
    var tmp = 123;
    if (true) {
    tmp = 'abc'; // ReferenceError
    let tmp;
    }
    上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,
    导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
  • ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if (true) {
    // TDZ开始
    tmp = 'abc'; // ReferenceError
    console.log(tmp); // ReferenceError

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

    tmp = 123;
    console.log(tmp); // 123
    }
    上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。
  • “暂时性死区”也意味着typeof不再是一个百分之百安全的操作。

    1
    2
    3
    4
    5
    6
    7
    typeof abc
    let abc
    // 上面的代码在let声明之前,都属于abc变量的“死区”,只要用到该变量就会报错

    typeof abc
    var abc
    // 相比var声明,此时typeof为undefined

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

不允许重复声明

  • let不允许在相同作用域内,重复声明同一个变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 报错
    function func() {
    let a = 10;
    var a = 1;
    }

    // 报错
    function func() {
    let a = 10;
    let a = 1;
    }
  • 因此,不能在函数内部重新声明参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function func(arg) {
    let arg; // 报错
    }

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

块级作用域

为什么需要块级作用域?

  • ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。

1.0 第一种场景,内层变量可能会覆盖外层变量。

1
2
3
4
5
6
7
8
9
10
var tmp = 'abc';
function f() {
console.log(tmp);
if(false) {
var tmp = '123'
}
}
f()

// 上面代码在声明阶段,if代码块内部 tmp 变量覆盖了外层的 tmp 变量,所以导致输出undefined

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

1
2
3
4
5
6
7
8
9
var s = 'hello';

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

console.log(i); // 5

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

ES6 的块级作用域

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function f1() {
    let n = 5;
    if (true) {
    let n = 10;
    }
    console.log(n); // 5
    }
    // 上面的函数有两个代码块,都声明了变量n,运行后输出 5。
    // 这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n,最后输出的值才是 10。
  • 块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // IIFE 写法
    (function () {
    var tmp = ...;
    ...
    }());

    // 块级作用域写法
    {
    let tmp = ...;
    ...
    }

const 命令

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

    1
    2
    3
    4
    5
    6
    const PI = 3.1415;
    PI // 3.1415

    PI = 3;
    // TypeError: Assignment to constant variable.
    // 上面代码表明改变常量的值会报错。
  • const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

    1
    2
    3
    const foo;
    // SyntaxError: Missing initializer in const declaration
    // 上面代码表示,对于const来说,只声明不赋值,就会报错。
  • const的作用域与let命令相同:只在声明所在的块级作用域内有效。

    1
    2
    3
    4
    5
    if (true) {
    const MAX = 5;
    }

    MAX // Uncaught ReferenceError: MAX is not defined
  • const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

    1
    2
    3
    4
    5
    if (true) {
    console.log(MAX); // ReferenceError
    const MAX = 5;
    }
    // 上面代码在常量MAX声明之前就调用,结果报错。
  • const声明的常量,也与let一样不可重复声明。

    1
    2
    3
    4
    5
    6
    var message = "Hello!";
    let age = 25;

    // 以下两行都会报错
    const message = "Goodbye!";
    const age = 30;

const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。
对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。
但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。
因此,将一个对象声明为常量必须非常小心。
如果想将对象冻结,应该使用Object.freeze方法。

ES6 声明变量的六种方法

  • var
  • function
  • let
  • const
  • import
  • class

顶层对象的属性

  • ES6 为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性。
  • let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
  • 也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
    1
    2
    3
    4
    5
    6
    7
    var a = 1;
    // 如果在 Node 的 REPL 环境,可以写成 global.a
    // 或者采用通用方法,写成 this.a
    window.a // 1

    let b = 1;
    window.b // undefined

配得上程序员的笔记本

发表于 2018-05-08
字数统计: 344 | 阅读时长 ≈ 1

说一下我心目中的理想配置和品牌,联想或者神舟,基本思路是屏幕要大并且不反光,最佳分辨率适合显示文字(包括控制台中常用的等宽字体)。键盘手感要好,带背光,为了手感可以牺牲厚度。CPU性能要够用甚至强。续航方面,轻度使用(看文档,上网查资料,敲代码)能坚持8~10小时。如果配独显也配个中高端的,这样偶尔吃个鸡、LOL特效也能过得去。内存容量至少16G。

选中的品牌如下:

1、联想(Lenovo)

链接

1
2
3
4
5
6
7
拯救者R720 GTX1060MaxQ6G独显
15.6英寸
i7
定制i7-7700HQ
32G 256固态+1T
win10
售价:¥11998.00

1.jpg

2、ThinkPad 黑将S5 (0JCD)

链接

1
2
3
15.6英寸
16G/128G固态+1TB双硬盘
售价:¥11099.00

2.jpg

3、联想拯救者R720

链接

1
2
3
15.6英寸
i7 16G 1T+512G 独显
售价:¥7999.00

3.jpg

4、神舟(HASEE)战神ZX7-CP7S2

链接

1
2
3
4
5
GTX1060 6G独显
八代芯品,真六核处理器i7-8700配GTX1060还有256G固态,IPS屏
15.6英寸
i7-8700 16G 1T+256G SSD
售价:¥8999.00

4.jpg

5、联想(Lenovo)扬天V730-15轻薄笔记本电脑

链接

1
2
3
4
5
6
7
15.6英寸
i7-7700HQ
定制16G内存
1TB固态
独显4G
Win10
售价:¥13699.00

5.jpg

webpack2.0基本介绍

发表于 2016-08-07
字数统计: 2,360 | 阅读时长 ≈ 11

介绍

  • webpack和gulp一样是一个自动化的构建工具
    • 你不想做的东西都交给工具去做,比如混淆,压缩,移动,合并,添加浏览器兼容性词缀
  • gulp更适合做简单的流程性的操作,webpack是专门为处理SPA应用的复杂文件依赖而出现的

    • 如图

    • js引用css,js引用图片,css引用图片,css引用字体,sass引用sass
    • webpack就可以把上面的东西css都抽出来,所有的图片都抽出来
  • webpack主要用于react和vue的构建,普通的静态页面用gulp就足够了

官网

  • webpack2:https://webpack.js.org/
  • webpack1:http://webpack.github.io/docs/

webpack1和webpack2的一些区别

  • webpack2已经不支持在配置文件中自定义属性
  • webpack2中在加载器中如果用了options属性那么必须配合loader属性使用,不能用use
  • webpack2中-loader一定要写全
  • webpack2中webpack-dev-server要单独下载2.x版本,默认是下载1.x版本

快速开始

新建项目结构

安装依赖

  • npm install webpack -g
  • npm install webpack -save-dev

编写配置文件

1
2
3
4
5
6
7
8
9
10
11
var path=require('path')

module.exports = {
// 指定spa应用的入口文件
entry: path.resolve(__dirname, 'src/js/app.js'),
// 指定项目构建的输出位置
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
}
}

运行

  • 在项目根目录执行webpack可以启动默认配置文件webpack.config.js
  • 运行指定配置文件执行webpack --config webpack.develop.config.js

把运行命令放到package.json文件中

  • 启动命令还可以加很多参数webpack --config webpack.develop.config.js --progress --profile --colors
  • 这么多参数不可能都记住,所以将启动命令写到package.json的script标签中

监听代码变化自动重新构建

  • 代码的变动需要多次运行npm run develop所以我们需要自动监听代码变动,然后运行构建,于是我们用到了webpack-dev-server这个模块

webpack-deve-server介绍

  • webpack-dev-server封装了webpack和http模块
    • 所以webpack的所有命令,webpack-deve-server都可以使用
    • webpack-dev-server可以指定一个文件夹启动一个服务
      • webpack-dev-server启动的服务所有的内容都构建在内存中
      • 因为构建在内存中速度快,还有一个功能,就是没有变化的文件,不重新构建,只有变化了的文件才从新执行构建
  • 作用
    • 一是监听文件变化自动重新构建
    • 二是自动刷新浏览器,可以热更新

基本使用

  • 下载npm install webpack-dev-server@9.9.9 -save-dev,注意选择2.x版本的模块
  • 修改package.json文件中的命令webpack-dev-server --config webpack.develop.config.js --progress --profile --colors
  • 修改开发配置文件

    • webpack2中推荐所有的服务配置信息都写到配置文件中的devServer属性中,不要写在package.json中
    • 基本配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      devServer: {
      // 指定启动服务的更目录
      contentBase: __dirname + '/src',
      // 指定端口号
      port: 8080,
      host: 'localhost',
      // 以下信息可有可无,为了完整
      inline: true,
      historyApiFallback: true,
      noInfo: false,
      // stats: 'minimal',
      // publicPath: publicPath
      },

热更新

  • 可以不刷新浏览器更新
  • 修改配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
devServer: {
// 指定启动服务的更目录
contentBase: __dirname + '/src',
// 指定端口号
port: 8080,
host: 'localhost',
// 启用热更新
hot: true,
// 以下信息可有可无,为了完整
inline: true,
historyApiFallback: true,
noInfo: false,
// stats: 'minimal',
// publicPath: publicPath
},
  • 需要配合一个内置插件一起使用
1
2
3
4
5
var webpack=require('webpack')

plugins: [
new webpack.HotModuleReplacementPlugin(),
]

Loaders(加载器)

  • 在真正构建之前做一些预处理操作就叫加载器

处理es6、es7、jsx语法加载器

  • npm install babel-loader --save-dev
  • npm install babel-core babel-preset-es2015 babel-preset-react -save-dev
  • npm install babel-preset-stage-0 babel-preset-stage-1 babel-preset-stage-2 babel-preset-stage-3 --save-dev
  • npm install babel-plugin-transform-runtime -save-dev这个插件的作用是支持es7特性
  • 修改配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader"
}
]
}
]
}
  • 创建.babelrc文件
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"presets": [
"es2015",
"react",
"stage-0",
"stage-1",
"stage-2",
"stage-3"
],
"plugins": [
"transform-runtime"
]
}

处理css文件引用的加载器

  • npm install style-loader css-loader -save-dev
  • 修改配置文件
1
2
3
4
5
 // 处理在js中引用css文件
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},

处理scss或者less文件引用的加载器

  • npm install sass-loader less-loader node-sass -save-dev
  • 修改配置文件
1
2
3
4
5
6
7
8
9
  // 处理在js中引用scss文件
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader'],
},

处理图片引用的加载器

  • npm install url-loader file-loader -save-dev
  • 修改配置文件
1
2
3
4
5
// 处理图片操作  25000bit ~3kb
{
test: /\.(png|jpg|jpeg|gif)$/,
use: 'url-loader'
},

处理字体文件引用的加载器

  • npm install url-loader file-loader -save-dev
  • 修改配置文件
1
2
3
4
5
// 处理字体文件
{
test: /\.(eot|woff|ttf|woff2|svg)$/,
use: 'url-loader'
}

发布策略

  • 简单的说就是将开发阶段的配置文件复制一份到部署的配置文件中,然后删删改改就可以了
  • 把热更新插件删除,devServer属性删除
  • 修改图片处理和字体处理加载器
1
2
3
4
5
6
7
8
9
10
 // 处理图片操作  25000bit ~3kb
{
test: /\.(png|jpg|jpeg|gif)$/,
use: 'url-loader?limit=25000&name=images/[name].[ext]'
},
// 处理字体文件
{
test: /\.(eot|woff|ttf|woff2|svg)$/,
use: 'url-loader?limit=100000&name=fonts/[name].[ext]'
}
  • 接下来运行npm run publish就发布了
  • 不过要注意所有的css,js,图片都构建到一个bundle.js文件中了

Plugins(插件)

  • 在真正构建之后做一些后处理操作就叫插件

删除插件

  • npm install clean-webpack-plugin -save-dev
  • 使用
1
2
3
4
5
6
var CleanPlugin = require('clean-webpack-plugin');

plugins: [
// 删除文件夹的插件
new CleanPlugin(['dist'])
]

抽取公共js插件

  • 使用
1
2
3
4
5
6
7
8
9
10
 entry: {
app: path.resolve(__dirname, 'src/js/app.js'),
vendors: ['vue','vue-router','vuex']
},


plugins: [
// 分离第三方应用的插件
new webpack.optimize.CommonsChunkPlugin({name: 'vendors', filename: 'vendors.js'}),
]

提取样式文件插件

  • npm install extract-text-webpack-plugin@4.4.4 -save-dev注意下载2.x版本
  • 使用
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
// 1、抽取css的第三方插件
var ExtractTextPlugin = require("extract-text-webpack-plugin");

// 2、处理在js中引用css文件
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallbackLoader: "style-loader",
loader: "css-loader"
})
},
// 处理在js中引用scss文件
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallbackLoader: "style-loader",
loader: "css-loader!sass-loader"
})
},

//3、加一个插件
plugins: [
// 删除文件夹的插件
new ExtractTextPlugin("app.css"),
]

自动生成html插件

  • npm install html-webpack-plugin -save-dev
  • 在根目录创建template.html
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>webpack学习</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
  • 修改配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var HtmlWebpackPlugin = require('html-webpack-plugin');

plugins: [
// 自动生成html插件
new HtmlWebpackPlugin({
template: './src/template.html',
htmlWebpackPlugin: {
"files": {
"css":["app.css"],
"js": ["vendors.js", "bundle.js"]
}
},
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
}
}),
]

压缩插件

  • 修改配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
plugins: [
// 压缩混淆js代码插件
new webpack.optimize.UglifyJsPlugin({
beautify: false,
mangle: {
screw_ie8: true,
keep_fnames: true
},
compress: {
warnings: false,
screw_ie8: true
},
comments: false
}),
]

定义生产环境插件

  • 修改配置文件
1
2
3
4
5
6
7
8
plugins: [
// 在构建的过程中删除警告
new webpack.DefinePlugin({
'process.env':{
NODE_ENV:'"production"'
}
})
]

自动打开浏览器插件

  • npm install open-browser-webpack-plugin -save-dev
  • 使用
1
2
3
4
5
6
7
// 自动打开浏览器插件
var OpenBrowserPlugin = require('open-browser-webpack-plugin')

plugins: [
// 删除文件夹的插件
new OpenBrowserPlugin({url: 'http://localhost:8080/', browser: 'chrome'})
]

高级

和gulp的集成

  • npm install gulp del gulp-sequence -save-dev
  • 编写配置文件
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
// gulp的任务是控制执行流程,webpack的任务是处理复杂引用的依赖

var gulp = require('gulp');
// 删除文件和目录
var del = require('del');
// 按顺序执行
var gulpSequence = require('gulp-sequence');
// 引入webpack的本地模块
var webpack = require("webpack");
// 引入wbpack的配置文件
var webpackConfig = require("./webpack.publish.config.js");

gulp.task('default', ['sequence'], function () {
console.log("项目构建成功");
});

// 流程控制
gulp.task('sequence', gulpSequence('clean','webpack'));

// 删除文件和文件夹
gulp.task('clean', function (cb) {
//del('dist);// 如果直接给dist的目录,项目启动的顺序还有清除结果会报错,所以要写的更详细一些
del(['dist/*.js', 'dist/*.css', 'dist/images', 'dist/fonts,','dist/*.html']).then(function () {
cb()
});
});


//写一个任务,在gulp中执行webpack的构建
// gulp 负责任务流程部分的操作,webpack负责复杂模块系统的引用分离工作
gulp.task('webpack', function (cb) {
// 执行webpack的构建任务
webpack(webpackConfig, function (err, stats) {

if (err) {
console.log("构建任务失败");
} else {
cb();
}

});
});

和eslint的集成

其他

组件库按需加载处理

react中的antd按需加载配置

1
2
3
4
5
6
7
npm install babel-plugin-import -save-dev
{
"presets": ["es2015", "react","stage-0","stage-1","stage-2","stage-3"],
"plugins": [
["import", { "libraryName": "antd","style": "css" }]
]
}

vue总的element按需加载配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
npm install babel-plugin-component-save-dev
{
"presets": [
"es2015",
"react",
"stage-0",
"stage-1",
"stage-2",
"stage-3"
],
"plugins": [
"transform-runtime",
[
"component",
[
{
"libraryName": "element-ui",
"styleLibraryName": "theme-default"
}
]
]
]
}

git使用手册

发表于 2016-07-15
字数统计: 2,922 | 阅读时长 ≈ 11
  • 版本控制
  • SVN
  • Git
  • Github

版本控制

问题1:历史记录

问题2:多人协作

解决问题:软件

1
2
3
4
5
版本  用户  说明                   日期
1 张三 删除了软件服务条款5 7/12 10:38
2 张三 增加了License人数限制 7/12 18:09
3 李四 财务部门调整了合同金额 7/13 9:51
4 张三 延长了免费升级周期 7/14 15:17

什么是版本控制?

版本管理就是管理更新的历史记录,
它给我们提供了一些在软件开发过程中必不可少的功能,例如:

  • 记录一款软件添加或更改源代码的过程
  • 回滚到特定阶段,恢复误删除的文件
  • 合并多人协作的文件等
  • 多人协同,文件传输

版本控制分类

  • 集中式
    • SVN
  • 分布式
    • Git

SVN

SVN 全称 Apache Subversion,是一个开放源代码的集中式版本管理系统。
在 2000 年由 CollabNet 开发,现已发展成为 Apache 软件基金会的一个开源项目。

环境安装

SVN 交互协作流程

集中式版本管理 - SVN

集中式

早期的版本管理就是以 Apache Subversion 为代表的集中式版本管理,
集中式版本管理将所有的数据集中存放在服务器中,这是有便于统一管理的优点。
但是一旦开发者所处的环境不能连接服务器,就无法获取最新源代码,开发也就无法进行。
服务器宕机时也是同样的道理,而且万一服务器故障导致数据丢失,
恐怕开发者就再也见不到最新的源代码了。

简而言之:

  • 中央服务器好比是一个图书馆
  • 你要改一本书,必须先从图书馆借出来(checkout)
  • 然后回到家自己改,改完了,再放到图书馆(commit)

一些术语

  • 源代码库(repository):源代码统一存放的地方
  • 检出(checkout):当你手上没有源代码的时候,就需要从 responsive checkout 一份
  • 提交(commit):当你已经修改了代码,就需要 commit 到 repository
  • 更新(update):当你已经 checkout 了一份源代码,Update 一下就可以和 repository 上的源代码同步,你手上的代码就会有最新的变更

使用 VisualSVN 搭建 SVN 服务器

SVN 服务器:运行 Subversion 服务的计算机。

为了方便,我们这里使用比较流行的图形化工具 VisualSVN
来搭建我们的 SVN 服务。

安装完毕之后,基本使用流程如下:

  • 创建用户
  • 创建版本仓库
  • 设定用户权限

使用 TortoiseSVN 作为 SVN 客户端

SVN 客户端:用户通过SVN客户端同SVN服务器交互

这里我们使用最流行的 TortoiseSVN

https://DESKTOP-40UMEJI:8443/svn/jd

https://192.168.133.25:8443/svn/jd

TortoiseSVN 客户端基本操作流程

  • 检出项目:checkout
    • 在没有源代码的前提下,需要通过 tortoise-svn 客户端下载
  • 提交修改:commit
    • 帮你记录当前开发的软件的状态
  • 更新文件或目录:update(更新)
    • 别的开发人员在已有源代码的前提下可以通过 update 更新服务器上最新的版本
  • 查看版本日志:log(日志)

关于冲突

假设 A、B 两个用户都在版本号为 100 的时候,更新了 kingtuns.txt 这个文件,
A 用户在修改完成之后提交 kingtuns.txt 到服务器, 这个时候提交成功,
这个时候 kingtuns.txt 文件的版本号已经变成 101 了。
同时B用户在版本号为 100 的 kingtuns.txt 文件上作修改, 修改完成之后提交到服务器时,
由于不是在当前最新的 101 版本上作的修改,所以导致提交失败。

良好的使用习惯就是,提交之前,先更新。

为了避免冲突,别人的文件你最好不要动,
万一你要修改公共的文件或者是别人的文件,
跟别人最好口头沟通好,就是你改动的时候,
别人最好不要去改动,这样才能最大程度上避免冲突的问题。

多人协作时,同个目录或同个文件需要不同成员共同开发,
这个时候 commit 和 update 就可能出现冲突。

  • 两个程序员只要不是修改了同一行程序,SVN 可以通过 update 自动合并修改
  • 但是如果两个程序员修改了同一行程序, SVN 会提示文件 conflict,需要手动确定

如何解决?

第一种解决方法:手动合并冲突的内容

第二种解决方法:每次修改某个文件的时候对文件上锁,这样你在修改的过程中别人就无法更新这个文件

建议:

  • 一个文件最好同一时间只被一个人修改提交
  • 多跟团队成员沟通
  • 不要随便去修改别人的文件

版本管理使用建议

  • 不要频繁的提交版本
    • 一般有比较成熟的功能模块的时候,再去提交
    • 修复了功能性 bug 的时候再去提交
    • 提交的代码最好无 bug
  • 每次 commit 之前都要 update
    • 因为你在编辑这个文件的时候,可能比人已经编辑并提交了某个版本
    • 所以先 update,目的是为了检查一下服务器上有没有最新版,如果有,直接更新
      • 更新的过程中如果遇到冲突,不要慌,去手动解决
  • 每次 commit 的时候都务必要写提交日志
    • 这个提交日志就好比你保存副本的时候加的一个标记
    • 目的是为了日后做版本的回退查找以及查看记录更新状态

使用总结

  • 版本控制管理系统
  • 源代码仓库 repository
  • 检出代码 checkout
  • 更新最新源代码 update
  • 提交修改 commit

其它

  • 清除svn保存的username用户名和paasword密码(windows和linux)
  • 菜鸟教程 - SVN 教程

Git

维基百科 - Git

学习资源介绍

  • Git教程 - 廖雪峰
  • Pro Git
  • git - 简明指南
  • 猴子都能懂的GIT入门

Git 简介

  • 是什么
    • Git 也是一个版本控制管理软件
  • 有什么用,可以解决什么问题
    • 保存历史记录
    • 多人协作
  • 有了 SVN,为啥要学 Git
    • Git 火
    • Git 相对于 SVN 来说,更强大,用户也非常多
  • 怎么用
  • Git 的诞生
    • http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/00137402760310626208b4f695940a49e5348b689d095fc000

Git 使用交互流程

git交互模型

安装和配置 Git 环境

  • 下载地址:https://git-scm.com/

git-bash 常用命令

  • pwd
  • ls
    • ls -a
  • cd
  • mkdir
  • clear
  • rmdir
    • 只能删除空目录
  • rm
    • rm 文件名
    • rm -rf 目录名
      • 注:很强大,可以删除非空目录,以及一些比较顽固的文件或者目录

初始化配置

1
2
3
4
5
6
7
8
# 设置用户名
git config --global user.name "你的名字"
# 配置用户邮箱
git config --global user.email "你的常用邮箱"
# 设置 gitk 图形查看工具中文显示默认编码(防止乱码)
git config --global gui.encoding utf-8
# 查看配置列表项
git config --list

基本使用

  • git init
    • 初始化一个 Git 仓库
  • git status
    • 查看当前工作区、暂存区、本地仓库的状态
  • git add
  • git commit
    • 示例:git commit -m "日志说明" --author="操作者姓名 <邮箱>"
    • 执行 git commit 的时候,Git 会要求具有用户名和邮箱的参数选项
    • 可以通过 git config 命令配置一下用户名和邮箱
  • git log
  • gitk

总结:操作 Git 的基本工作流程就是先修改文件,然后执行 git add 命令。
git add 命令会把文件加入到暂存区,接着就可以执行 git commit 命令,将文件存入文档库,
从而形成一次历史记录。

  • 问题1:关于 Git-bash 中文问题
  • Git for Windows Unicode Support
  • 问题2:执行 commit 的时候一大堆的信息
  • 问题3:配置 user.name 和 user.email 问题

工作区、暂存区、本地仓库

版本回退

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
# git rm --cached <file>
# 恢复暂存区的指定文件到工作区
$ git checkout [file]

# 恢复某个commit的指定文件到暂存区和工作区
$ git checkout [commit] [file]

# 恢复暂存区的所有文件到工作区
$ git checkout .

# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
$ git reset [file]

# 重置暂存区与工作区,与上一次commit保持一致
$ git reset --hard

# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
$ git reset [commit]

# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
$ git reset --hard [commit]

# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
$ git reset --keep [commit]

# 新建一个commit,用来撤销指定commit
# 后者的所有变化都将被前者抵消,并且应用到当前分支
$ git revert [commit]

# 暂时将未提交的变化移除,稍后再移入
$ git stash
$ git stash pop

远程同步

  • remote
  • push
  • pull

在线仓库托管服务

一个不知道 github、stackoverflow 的程序员想想都是可悲的

  • github
  • 码云
  • coding

Github

Github 就是程序员的新浪微博
它可以让你使用社交化的方式进行编程协作、

- 点赞
- 评论
- 转发
- etc.

主要作用:可以免费在线托管你的仓库
可以实现多人协作
提供了一个可视化界面(Web Page)让你能直观清晰的了解你的项目源代码

基本使用

  • 注册
  • 登陆
  • 创建远程仓库
  • 通过 git clone 命令下载远程仓库到本地
    • git clone 会自动帮你把远程仓库下载到本地,不需要再去 git init 了
    • 通过 clone 下来的仓库,git 有一个远程仓库地址列表,git 默认会把你 clone 的地址起一个别名:origin
    • 然后你执行 push 的时候实际上就是将本地的版本提交到 origin 上
  • 在本地进行操作,通过 git commit 形成历史记录
  • 通过 git push 将本地仓库中的历史记录提交到远程仓库

本地已有仓库,需要提交到线上

如果是 git init 出来的仓库,进行 push 提交的时候就不知道要往哪里 push。

所以,这里通过 remote 相关命令进行设置:

1
2
3
4
5
6
# 查看所有的远程仓库信息
git remote show
# 根据别名查看指定的远程仓库信息
git remote show 远程仓库地址别名
# 添加远程仓库信息
git remote add 别名 远程仓库地址

通过上面的 git remote add 添加完远程仓库地址信息之后,还不能直接 git push,必须在每一次
push 的时候加上 git push 仓库地址别名 master 就可以提交了。

如果想要省略 git push 后面需要指定的 仓库地址别名 master 可以通过下面的命令修改:

1
git push --set-upstream heima master

这样就可以直接使用 git pish 进行提交而不需要指定 heima master 了

目标

  1. 能概述什么是版本控制并举例
  2. 能概述 SVN 的协作交互流程(远程仓库、用户、本地仓库)
  3. 能掌握 SVN 基本的交互使用(checkout、update、commit)
  4. 能概述 Git 的协作交互流程(远程仓库、用户、本地仓库)
  5. 能掌握 Git 的基本使用(init、status、add、commit、log、clone、push、remote)

面向对象编程重点回顾

发表于 2016-06-21
字数统计: 1,865 | 阅读时长 ≈ 7

递归

在函数内部直接或者间接的调用自己就是递归

递归的要素

  1. 自己调用自己
  2. 要有结束条件

化归思想

化繁为简,化难为易的过程,化是转化,归是归结。

练习:

  • 求前n项和
  • 求n!
  • 求n的m次方(求幂)
  • 斐波那契数列

递归获取后代元素

1
2
3
4
5
6
7
8
9
10
11
function getChildren(ele){
var result = [];
var children = ele.children;
for(var i = 0; i < children.length; i++){
var child = children[i];
result.push(child);
var temp = getChildren(child);
result = result.concat(temp);
}
return result;
}

作用域

什么是作用域?

变量的作用范围就是作用域

什么是词法作用域?(词法作用域又叫静态作用域)

代码在写好的时候,根据代码的书写结构,就可以确定变量的作用范围,这种作用域,就是词法作用域

什么是块级作用域?

使用代码块限定的作用域就是块级作用域,js中没有块级作用域

作用域链

函数可以创建作用域,函数中又可以声明函数,这样就形成了作用域套作用域的连式结构,称作作用域链

变量的搜索原则

  1. 现在使用变量的作用域中进行查找,如果找到了就直接使用
  2. 如果没有找到,就去上级作用域链中进行查找,如果找到了就直接使用
  3. 如果没有找到,就沿着作用域链一直向上查找,直到找到全局作用域

JS代码执行过程

  1. 预解析
  2. 执行

变量提升

在js代码预解析的阶段,会将所有的变量声明以及函数声明提升到其所在的作用域的最顶上,这就是变量提升(Hoisting)

变量提升的特殊情况

  1. 函数同名:都提升,后面的会覆盖前面的
  2. 函数与变量同名: 只提升函数,忽略变量声明
  3. 变量提升分作用域
  4. 变量提升分段(script标签)
  5. 条件式函数声明: 会被当做函数表达式处理,只提升函数名称,函数体不提升
  6. 形参的赋值是在变量提升过程之前

闭包

闭包的概念

可以访问独立数据的函数!

闭包要解决的问题

在函数外部无法访问函数内部声明的变量

闭包的基本模型

1
2
3
4
5
6
7
function outer(){
var data = "";
function inner(){
return data;
}
return inner;
}

闭包的原理

作用域,变量的访问规则,上级作用域中无法直接访问下级作用域中的变量,但是下级作用域可以直接访问上级作用域中变量

闭包的用途

  1. 保护变量,增加对于变量赋值的时候的校验逻辑
  2. 给函数新增一个私有的变量

for循环注册点击事件以及for循环setTimeout的问题

缓存

将数据进行临时的存储,以提升访问效率

常见的缓存

  1. 浏览器缓存
  2. 硬件缓存
  3. CDN
  4. 数据库缓存

    缓存解决递归实现的斐波那契数列存在的性能问题(重点)

实现jQuery缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createCache(){
var cache = {};
var keys = [];
return function(key, value){
if(value != undefined){
cache[key] = value;
keys.push(key);
if(keys.length > 20){
delete cache[keys.shift()];
}
}
return cache[key];
}
}

jQuery的缓存源码

1
2
3
4
5
6
7
8
9
10
function createCache(){
var keys = [];
function cache(key, value){
if(keys.push(key + " ") > 20){
delete cache[keys.shift()];
}
return (cache[key + " "] = value);
}
return cache;
}

沙箱

1
2
3
4
5
(function(window){
//变量声明
//功能代码
//如果需要,使用window向外界暴露接口
})(window)

传参的目的

  1. 利于代码压缩
  2. 实现逻辑隔离

沙箱的用途

  1. 框架
  2. 组件
  3. 插件

函数的调用模式

函数调用模式

1
2
函数名();
//this--->window

方法调用模式

1
2
对象.方法名();
//this---->调用该方法的对象

构造函数调用模式

1
2
new 函数名();
//this----> new创建出来的实例

上下文调用模式

工厂模式和寄生模式创建对象(只是调用模式不同)

上下文调用模式

call

1
2
3
4
函数名.call(this要指向的对象, arg1..argN);
//1. 调用函数
//2. 将this指向第一个参数
//3. 将第二个及之后的所有的参数,作为实参依次传递给函数

apply

1
2
3
4
函数名.apply(this要指向的对象, 数组或者伪数组);
//1. 调用函数
//2. 将this指向第一个参数
//3. 将第二个参数中的数组或者伪数组,拆解开,将所有的元素依次作为实参依次传递给函数

注意事项

  1. 如果第一个参数为null或者Undefined,this指向window
  2. 如果第一个参数为值类型的数据,就会将该数据转换成对应的引用类型的数据,再讲this指向该引用类型数据

tip

一般情况下apply方法的传参特性会非常常用!!!

练习

  1. 转换伪数组
  2. 求数组最大值

forEach和map

forEach

1
2
3
4
5
数组.forEach(function(value, index, arr){})
//遍历数组,执行回调函数
//value:当前正在遍历的元素
//index:当前正在遍历的元素的索引
//arr:当前正在遍历的数组

map

1
2
3
4
5
数组.map(function(value, index, arr){})
//遍历数组,执行回调函数
//value:当前正在遍历的元素
//index:当前正在遍历的元素的索引
//arr:当前正在遍历的数组

map的返回值

将每次执行回调函数的返回值,组合成一个新的数组,作为map方法的返回值!

map的练习

  1. 将数组中的元素转成字符串
  2. 求最大值
  3. 生成随机数

Object.defineProperty

用来给对象添加属性的

1
2
3
4
Object.defineProperty(obj, propertyName, descriptor);
//obj 就是要给谁添加属性
//propertyName 要添加的属性名
//descriptor 属性的描述信息,也就是特性集合,是一个对象哦~

descriptor对象中的属性

  • writable 默认为false,表示属性是否可写
  • configurable 默认为false, 表示属性是否可以配置(删除)
  • enumerable 默认为false, 表示属性是否可以被遍历(for-in)
  • value 用来设置属性的值
  • get和set方法,和writable一样都可以用来设置属性是否可以写

严格模式

如何开启严格模式

1
2
"use strict"
'use strict'

严格模式中的特点

  1. 声明变量必须使用var
  2. 对象的属性不能重名
  3. 函数的形参名不能重复
  4. 八进制常量不允许使用
  5. eval有自己的作用域

案例(歌曲管理,Tab栏切换)

事件兼容性处理

三种注册事件的方式

  1. onclick简单形式
  2. addEventListener(type, handler, useCapture)
  3. attachEvent(type, handler)

addEventListener和attachEvent的区别

  1. 回调函数中获取事件对象的方式不一样,addEventListener通过回调函数的参数获取,attachEvent通过window.event获取
  2. 回调函数中的this指向不一致,addEventListener中this指向当前dom对象,attachEvent中this指向window

兼容性处理的通用函数

1
2
3
4
5
6
7
8
9
10
11
function registerEvent(ele, type, handler){
if(ele.addEventListener){
ele.addEventListener(type, handler);
}else if(ele.attachEvent){
ele.attachEvent("on" + type, function(){
handler.call(ele, window.event);
})
}else{
ele["on" + type] = handler;
}
}

面向对象编程第六篇--笔记

发表于 2016-04-12
字数统计: 878 | 阅读时长 ≈ 4

闭包优化缓存解决递归实现的斐波那契数列存在的性能问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createFib(){
var arr = [];
function fib(n){
var num = arr[n];
if(!num){
if(n <= 2){
num = 1;
}else{
num = fib(n - 1) + fib(n -2);
}
arr[n] = num;
}
return num;
}
}

jQuery缓存实现

1
2
3
4
5
6
7
8
9
10
11
12
13
function createCache(){
var cache = {};
var keys = [];
return function(key, value){
if(value != undefined){
cache[key] = value;
if(keys.push(key) > 20){
delete cache[keys.shift()];
}
}
return cache[key];
}
}

jQuery缓存源码分析

1
2
3
4
5
6
7
8
9
10
function createCache(){
var keys = [];
function cache(key, value){
if(keys.push(key + " ") > 20){
delete cache[keys.shift()];
}
return (cache[key + " "] = value);
}
return cache;
}

沙箱模式

一个封闭的隔离的环境!

在JS中通过函数来创建沙箱

1
2
3
4
5
6
7
8
(function(window){
//变量的声明

//主要的功能代码

//如果需要,就通过window对象向外界暴露接口

})(window)

将window对象作为参数的目的

  1. 便于代码压缩
  2. 实现逻辑上的隔离,外部不访问里面的东西,里面也不访问外面的东西

沙箱的应用

  1. 框架
  2. 组件
  3. 插件

函数的调用模式

函数调用模式

函数名();
this —> window

方法调用模式

对象.方法名();
this —> 调用该方法的对象

构造函数调用模式

new 函数名();
this —> new创建出来的实例

上下文调用模式

两种创建对象的方式

工厂模式

1
2
3
4
5
6
7
8
function createObj(name, age){
var obj = new Object();
obj.name = name;
obj.age = age;
return obj;
}

var p = createObj("bob", 18);

寄生模式

1
2
3
4
5
6
7
8
function Person(name, age){
var obj = new Object();
obj.name = name;
obj.age = age;
return obj;
}

var p = new Person("bob", 18);

工厂模式和寄生模式都可以用来创建对象,但是调用模式不一样,工厂模式是函数调用模式,寄生模式是构造函数调用模式!

上下文调用模式

call

1
2
3
4
5
函数名.call(obj, arg1...argN);
//功能:
//1. 调用函数
//2. 改变this指向为第一个参数中的对象
//3. 将第二个及以后的所有参数,依次的传递给函数作为实参

apply

1
2
3
4
5
函数名.apply(obj, 数组or伪数组);
//功能:
//1. 调用函数
//2. 改变this指向为第一个参数中的对象
//3. 将第二个参数的数组或者伪数组,中的元素一一拆解,依次传递给函数作为实参

注意事项

  1. 如果call和apply的第一个参数为null或者undefined,this将会指向window
  2. 如果call和apply的第一个参数为值类型的数据,会将值类型的数据转换成其对应的引用类型的数据,this指向该引用类型的数据

tips: 一般情况下apply的传参特性会被使用的居多(转换伪数组为真数组,求最大值)

forEach和map的使用

都可以用来遍历数组

forEach

1
2
3
4
数组.forEach(function(value, index, arr){})
//value:正在遍历的数组元素
//index: 正在遍历的数组元素的索引
//arr: 正在遍历的数组

map

1
2
3
4
数组.map(function(value, index, arr){})
//value:正在遍历的数组元素
//index: 正在遍历的数组元素的索引
//arr: 正在遍历的数组

map是有返回值的,他的返回值是,将每次调用回调函数之后的返回值拼接成一个新的数组,将这个数组返回,作为map的返回值!

面向对象编程第五篇--笔记

发表于 2016-04-09
字数统计: 778 | 阅读时长 ≈ 3

作用域链

函数可以创建作用域,函数中又可以声明函数,这样就形成了作用域套作用域的链式结构,称作作用域链

变量搜索原则

  1. 现在使用变量的作用域中进行查找,如果找到了,就直接使用
  2. 如果没有找到,就去上一级作用域链中进行查找,如果找到了,就直接使用
  3. 如果没有找到,就沿着作用域链依次向上查找,直到找到全局作用域

绘制作用域链图

  1. 首先绘制0级作用域链
  2. 在全局作用域中查找变量以及函数的声明,用小方块的形式将其画在0级作用域线上
  3. 再从0级作用域链中的函数中引出下一级作用域链
  4. 重复以上步骤!!!

闭包

什么是闭包

闭包就是指可以访问独立的数据的函数!!!

闭包要解决的问题

函数内部声明的变量无法再函数外部使用

闭包的基本模型

1
2
3
4
5
6
7
function outer(){
var data = "";
function inner(){
return data;
}
return inner;
}

如何使用闭包返回多个函数

在函数内部创建一个对象,在对象中封装多个函数,返回对象,在外部就可以使用对象,访问各个方法

闭包的原理

上级作用域无法访问下级作用域中的变量,但是下级作用域可以访问上级作用域的变量

闭包的用途

  1. 可以用来保护数据, 就是变量被保护在外部函数中,想要修改变量只能通过闭包函数,可以在闭包函数中添加一些校验逻辑,以保证数据的合理性以及安全性
  2. 可以为函数新增私有的变量

使用闭包解决for循环中注册点击事件的问题

问题

点击事件在触发的时候,访问的i变量已经变成了最后一个值!!

解决方案

通过闭包来创建一个函数,该函数拥有自己独立的一个变量j,在循环的时候,将当前的i的值保存一份交给j,再将该闭包函数注册给点击事件,这样在点击事件触发的时候,就不会去访问全局的i,而是使用自己的j

使用闭包解决for循环中setTimeout代码执行的问题

缓存

将数据临时的存储起来以提高访问效率

浏览器缓存(重点)

硬件缓存

CDN(重点)

数据库缓存

使用缓存解决递归实现的斐波那契数列存在的性能问题

性能问题

由于递归实现的斐波那契数列中,存在大量的重复计算,导致性能低下

如何解决

使用缓存,将每次计算后的结果进行保存,在下次获取该数据的时候,就直接去缓存中拿,而避免了重复的计算

面向对象编程第四篇

发表于 2016-04-02
字数统计: 432 | 阅读时长 ≈ 1

面向对象复习

递归

什么是递归

在函数直接或者间接的调用自己,就是递归

递归的两个要素

  1. 自己调自己
  2. 结束条件(必需)

化归思想

化繁为简,化难为易的过程, 转化 归结 的简称

前n项和

n的m次方

斐波那契数列

递归获取后代元素(重点!!)

词法作用域

作用域就是变量起作用的范围

在代码写好的时候,根据代码的书写结构,就可以确定变量的作用域,这种作用域就是词法作用域

词法作用域又叫静态作用域

js中没有块级作用域(代码块限定的作用域叫做块级作用域)

变量提升(hoisting)

JS代码执行分两个阶段:1.预解析阶段 2.执行阶段

在预解析阶段,会对代码中所有的变量声明以及函数声明做提升操作,提升到其所在的作用域的最顶上!

特殊情况

  1. 函数同名的时候,会全部提升,但是后面的会覆盖掉前面的
  2. 函数和变量同名的情况,只会提升函数,忽略掉变量的声明
  3. 变量提升是分作用域的
  4. 变量提升是分段(script标签)的
  5. 条件式函数声明(在条件语句中的函数声明),会将条件式函数声明视为函数表达式做处理,只会提升函数名,函数体不提升
  6. 函数中形参赋值的过程是 在变量提升之前!

函数中的形参,就相当于是在函数中声明一个局部的变量

面向对象编程第三篇

发表于 2016-03-18
字数统计: 776 | 阅读时长 ≈ 3

instanceof关键字

判断构造函数的原型是不是在对象的原型链上
1
对象 instanceof 构造函数

创建函数的三种方式

函数声明

1
2
3
function 函数名(参数列表){
//函数体
}

函数表达式

1
2
3
var 函数名 = function (参数列表){
//函数体
}

Function

1
var 函数名 = new Function();

Function的使用

1
2
3
4
5
6

var func = new Function(); //创建空函数

var func1 = new Function(methodBody); //创建有函数体的函数,methodBody是一个字符串

var func2 = new Function(arg1, arg2...argN, methodBody) //创建一个以methodBody为函数体,之前所有的参数为形参名的函数, 所有的参数都是字符串!!

如何解决Function的函数体过长的问题?

  1. 字符串拼接
  2. 使用模板引擎
  3. 使用反引号

eval的使用

eval是个函数,可以将字符串转换成代码并立即执行!

1
eval(符合js语法规范的字符串);

使用eval处理JSON格式数据的时候的问题

当使用eval处理JSON格式数据的时候,eval会将JSON格式字符串中的大括号{},当做代码段来解析,所以会报错!

解决方案:

  1. 将变量声明也同时放在eval中

    1
    2
    3
    var jsonStr = "{key: value}";

    eval("var obj=" + jsonStr);
  2. 在json格式的字符串前后拼接小括号

    1
    2
    3
    var jsonStr = "{key: value}";

    var obj = eval("(" + jsonStr + ")");

JSON2

Function和eval的异同

  1. 都能将字符串转换成代码
  2. Funciton创建出来的是函数,需要手动去调用,代码才会执行,而eval会直接将字符串转成代码并执行

问题:

  1. 执行效率的问题
  2. 安全性问题(XSS)

函数的原型链

如果将函数当做一个对象对待的话,那么他也是由构造函数创建出来的,所以这个函数对象,也会有相应的原型链

js中所有的函数的构造函数都是Function, 包括Function本身

完整的原型链绘制

Object和Function的关系

可以通过instanceof关键字来确定两者的关系, Object和Function互为实例!!

arguments对象

arguments是函数内部的一个对象,他是一个伪数组
在函数调用的时候,会将函数的实参,依次的存入这个伪数组中。

arguments.length 属性,表示函数在调用的时候,传入的实参的个数
arguments.callee 属性,指的就是arguments对象所在的函数,一般用来在匿名函数中做递归

函数对象的成员

  • arguments
  • caller 函数的调用环境,如果是在全局,则为null,如果是在其他函数当中调用的,那么caller为所在的那个函数
  • length 函数的形参的个数
  • name 函数的名称 函数声明和函数表达式的函数名为正常的函数名,new Function创建出来的函数名为 anonymous

静态成员以及实例成员

  • 静态成员: 通过构造函数访问的成员就是静态成员
  • 实例成员: 通过实例去访问的成员就是实例成员
12
奶糖的博客

奶糖的博客

擅长各项前端技能,深入研究移动端开发与前端性能,目前就职于亿欧网盟。非专业视觉设计师。此为博客一枚。

13 日志
3 标签
© 2016 — 2019 奶糖的博客
本站访客数: