JS进阶 -- 工程化之 webpack

代码链接

概述

工程化包括了自动化 + 模块化 + 性能优化,前端语言较多并且更新频率较快。

1
2
3
CSS ==> Less ==> Sass ==> Scss ==> Stylus(结合Less + Sass)
JS ==> coffee ==> ES6 ==> babel ==> TypeScript ==> Elm
HTML ==> Jade ==> Pug ==> slim

当我们面对这么多语言的时候,Webpack 应运而生,Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler),它主要为我们解决了工程化的问题。在本篇文章中将要介绍 SCSS + babel + Webpack

Sass | Scss

Scss 是 CSS 的预处理语言 | 扩展,Sass makes CSS fun again 这是Scss 要做的事情。Scss添加了嵌套规则、变量、混入、选择器继承,还能和命令行工具 | Web框架插件将其转换为格式良好的标准 CSS

Sass 与 Scss 区别

Sass 使用换行 + 缩进进行工作

1
2
body
background: red

Scss 使用分号 + 花括号进行工作

1
2
3
body{
background: red;
}

安装 Scss

Scss 是有 Ruby 社区创作出来的,所以要使用 gem 命令,但是 node 将 Scss 重写了一遍,所以可以使用 node-sass

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
npm install -g node-sass  // 安装
npm uninstall node-sass -g // 卸载

解决问题 4 连
npm update
npm install
nodejs node_modules/node-sass/scripts/install.js
npm rebuild node-sass

解决问题 7 连 查看 node-sass 是否存在
npm -v
node -v
node -p process.versions
node -p process.platform
node -p process.arch
node -p "require('node-sass').info"
npm ls node-sass

解决问题 3 连 重新生成 node_modules + package.json
npm -rf node_modules
rm package.json
rm package-lock.json
npm install

npm cache clean // 清除缓存
npm cache clean --force // 清除缓存
npm install --unsafe-perm
npm config set sass_binary_site http://npm.taobao.org/mirrors/node-sass/ // 切换淘宝源
npm i -g node-sass --save_binary_path=/home/wubowen/Downloads/Linux-x64-48-binding.node // 通过 .node 包安装
npm install -g cnpm --registry=https://registry.npm.taobao.org // 安装 cnpm
cnpm install -g node-sass // cnpm 安装

运行配置文件
. ~/.bash_profile
. ~/.npmrc
source ~/.profile
source .npmrc

安装成功之后便可以使用 node-sass 了

1
2
3
4
node-sass [options] <input> [output]
示例
node-sass src/style.scss dist/style.css
node-sass src/style.scss dist/style.css -w // Watch a directory or file

babel

Babel is a JavaScript compiler。它是一款可以将 JS 语法编译为 ES5(IE能读)的编译器

安装 + 使用

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
npm install --save-dev babel-cli babel-preset-env
touch .babelrc
编辑 .babelrc
{
"presets": ["env"]
}
npm init // 创建 package.json
npm install --save-dev babel-cli
package.json 多出一行
{
"devDependencies": {
+ "babel-cli": "^6.0.0"
}
}

在 package.json 中 +
{
"name": "my-project",
"version": "1.0.0",
+ "scripts": {
+ "build": "babel src -d lib"
+ },
"devDependencies": {
"babel-cli": "^6.0.0"
}
}

npm run build // 运行 package.json 中的 "build": "babel src -d lib" 这句话
./node_modules/.bin/babel src -d lib // 将 src 中的文件翻译到 lib 文件中

之后 index.html 引用 dist 中的文件

自动监控
./node_modules/.bin/babel path1 -d path2 --watch

watch

watch 命令行工具可以监控文件的改动

1
2
npm i -g watch-cli
watch -p 'path1' -c 'cp path1 path2' // 只要 path1 中的文件改动就会将 path1 中的文件复制到 path2 中

watch 也可以用于 img

1
watch -p 'src/img/**/*' -c 'cp -r src/img dist/img'

前端目录规范

src ==> source 目录 ==> 未经翻译的代码
dist ==> distribution 目录 ==> 待发布的代码
node_modules ==> 第三方包
vendors ==> 第三方代码(jQuery)

Webpack

通过之前的介绍,我们使用
scss ==> node-sass ==> css
ES6 JS ==> babel ==> ES5(IE 使用)
HTML + img ==> watch ==> 复制
我们通过这么多的命令才能实现我们的自动化,那么为什么不使用一种工具,具有以上三者的作用呢,所以 Webpack 应运而生

Webpack 核心

入口(entry)

entry 指示 webpack 使用哪个模块来作为构建其内部依赖图的开始,进入进口后,webpack 会找出有哪些模块和库是入口直接或间接依赖的,每个依赖项随即被处理,最后输出到 bundles 的文件中
通过在 webpack 配置中配置 entry 属性来指定起点

1
2
3
4
webpack.config.js
module.exports = {
entry: './path/to/my/entry/file.js'
}

出口(output)

output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件

1
2
3
4
5
6
7
8
webpack.config.js
module.exports = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve( __dirname, 'dist' )
filename: 'my-first-webpack-bundle.js'
}
}

loader

loader 让 webpack 能够去处理非 JavaScript 文件(webpack 自身只能理解 JavaScript)。webpack loader 将所有类型的文件,转换为应用程序的依赖图可以直接引用的模块。多数 loader 可以通过选项(options)自定义
loader 实现的目标

  • 识别出应该被对应的 loader 进行转换的那些文件(使用 text 属性)
  • 转换这些文件,使其能被添加到依赖图中(使用 use 属性)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    webpack.config.js
    module.exports = {
    entry: './path/to/my/entry/file.js',
    output: {
    path: path.resolve( __dirname, 'dist' )
    filename: 'my-first-webpack-bundle.js'
    },
    module: {
    rules: [
    {
    // 遇到匹配的文件,使用 loader 进行转换之后再进行打包
    text: /\.txt$/, // 匹配的文件
    use: 'raw-loader' // 使用的 loader
    use: {
    loader: 'babel-loader',
    options: {
    presets: [ '@babel/present-env' ]
    }
    }
    }
    ]
    }
    }
插件(plugins)

loader 被用于转换某些类型的模块,而插件则用于执行范围更广的任务。插件的范围包括:从打包优化 + 压缩,一直到重新定义环境中的变量
想使用一个插件,需要 require() 插件,然后把它添加到 plugins 数组中,多数插件可以通过选项 options 自定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件
const path = require('path');

const config = {
entry: './path/to/my/entry/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-first-webpack.bundle.js'
},
module: {
rules: [
{ test: /\.txt$/, use: 'raw-loader' }
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};

module.exports = config;

webpack 使用

注意:npm i 找不到的module

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
mkdir webpack-demo
cd webpack-demo
npm init // 初始化
一路回车
ls ==> package.json
npm install --save-dev webpack // package.json 中多了依赖 webpack
touch webpack.config.js // 创建配置文件并编辑
vi webpack.config.js
****************
const path = require('path');
module.exports = {
entry: './src/js/',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist/js/')
}
};
*****************

mkdir src src/css src/js
touch src/css/main.scss src/js/index.js src/js/main.js src/index.html

****************
index.html 中引入 css,不引 scss
<link rel="stylesheet" href="./css/main.css">
***************
npx webpack // 将 src 中的 JS 文件转换 + 复制到 dist/js/

使用 babel-loder

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
index.js + main.js

***********************
let a = 1
console.log(a)

let b = 1
console.log(b)
**********************

npm install --save-dev babel-loader babel-core babel-preset-env webpack
// 使用 webpack 3.x babel-loader 7.x | babel 6.x,注意 github 点击[链接](https://github.com/babel/babel-loader/tree/7.x)
vi webpack.config.js

**************************
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist/js/')
},
+ module: {
+ rules: [
+ {
+ test: /\.js$/, // 如果文件是 .js 结尾
+ exclude: /(node_modules|bower_components)/,
+ use: { // 使用 babel-loader 转换
+ loader: 'babel-loader',
+ options: { // 选项,参数 env
+ presets: ['env']
+ }
+ }
+ }
+ ]
+ }
***************************

npx webpack

模块化

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
touch src/js/app.js
// app.js 调用 main.js + index.js
vi app.js

***************************
import a from './main'
import b from './index'

a()
console.log('app')
b()
**************************

index.js

********************************
let b = 1
function fn() {
console.log(b)
}

export default fn
*******************************

main.js

**********************************
export default function(){
let a =1
console.log(a)
}
***********************************

webpack.config.js

********************************
module.exports = {
- entry: './src/js/',
+ entry: './src/js/app.js',
}
********************************

rm -rf dist
npx webpack // 看终端 log 便可以看到,webpack 将三个 js 文件都转换并复制到了 dist/js 中

使用 scss-loader

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
npm install sass-loader node-sass webpack --save-dev
vi webpack.config.js

********************************
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env']
}
}
- }
+ },
+ {
+ test: /\.scss$/, // 1. 发现后缀 .scss 文件
+ use: [
+ {
+ loader: "style-loader" // 4. 使用 style-loader 将 JS 字符串 ==> <style> 标签(creates style nodes from JS strings)
+ }, {
+ loader: "css-loader" // 3. 使用 css-loader 将 css ==> JS 字符串(translates CSS into CommonJS)
+ }, {
+ loader: "sass-loader" // 2. 使用 sass-loader 将 sass ==> css (compiles Sass to CSS)
+ }
+ ]
+ }
]
}
*********************************

vi app.js

**************************
+ import '../css/main.scss'
**************************

npx webpack

使用 postcss-loader

postcss-loader 能够使得 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
npm i -D postcss-loader
touch postcss.config.js
vi postcss.config.js

*****************************
module.exports = {
//parser: 'sugarss',
plugins: {
'postcss-import': {},
// 'postcss-cssnext': {},
// 'cssnano': {}
}
}
*******************************

vi webpack.config.js

************************
......
{
test: /\.scss$/,
use: [
{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader", // translates CSS into CommonJS
+ options: { importLoaders: 1 }
+ }, {
+ loader: 'postcss-loader',
+ options: { // 如果没有 options 则不会添加后缀,先尝试不添加 options,如果 postcss-loader 不工作,再添加 options
+ plugins: () => [
+ require('autoprefixer')
+ ]
+ }
},{
loader: "sass-loader" // compiles Sass to CSS
}
]
}
......
***********************

npx webpack

webpack 总结

  • webpack 将前端思想完全转变,将 CSS 写入 JS 中,使用 JS 生成

JS进阶 -- 面向对象(三)三面大旗 + 基础概念 + 总结

概述

面向对象是一种思维定势(套路),当遇到任何需求,都是用这一套思维解决。本篇我们进一步了解面向对象,主要了解面向对象的三面大旗,即封装 + 继承 + 多态,还有一些关于面向对象的概念

三面大旗

封装

封装即是隐藏细节,可以是 A 对 A 隐藏细节,也可以是 A 对 B 隐藏细节。任何一个函数都是封装

  • A - A ==> 封装函数,解决自己的思想负担,需要使用时只需要调用自己封装的函数即可
  • A - B ==> 例如一些第三方库(jQuery 的作者对于使用 jQuery 的人),A 给 B 提供 API,B 使用 API,有助于合作 + 交流

面向对象可以实现封装,但不代表只有面向对象可以封装

继承

多态

多态使得一个东西灵活,如果没有多态,则经常要进行类型转换,指一个东西拥有两种甚至更多种属性
示例:div 同时是 Node + Element,可以调用两者任意的 API

1
2
div.childNodes  // 子代节点 ==> div 作为 Node 使用
div.child // 元素节点 ==> div 作为 Element 使用

JavaScript 是动态语言,所有东西都是多态,所有东西都有多个状态

基础概念

面向对象是把一组数据结构和处理它们的方法组成对象(Object),把相同行为的对象归纳为类(Class),通过类的封装(Encapsulation)隐藏内部细节,通过继承(Inheritance)实现类的特化(Specialization)/泛化(Generalization),通过多态(Polymorphism)实现基于对象类型的动态分派(Dunamic Dispatch)
面向对象是一套经验(套路)。它可以使得我们更快、更好的进行开发

Namespace 命名空间

它是一种可以捆绑功能的容器,经常是一个对象,可以为这个对象添加属性和方法,这个对象就是命名空间,例如 window.jQuery

Class 类

定义对象的特征,它是对象属性和方法的模板定义

Object 对象

类的一个实例

Property 属性

对象的特征,比如大小

Method 方法

对象的能力,比如行走

Constructor 构造函数

对象初始化的瞬间,被调用的方法,通常它的名字和包含它的类一致

Inheritance 继承

一个类可以继承另一个类的特征

Encapsulation 封装

把数据和相关的方法绑定在一起使用的方法

Abstraction 抽象

结合复杂的继承、方法和属性的对象可以模拟现实的模型

Polymorphism 多态

不同的类可以定义为相同的方法或属性

总结

JavaScript 实现面向对象

JavaScript 中的面向对象都是对于复杂类型(Object)而言的,JavaScript 中 Object 又主要分为 普通对象 + Array + Function,JavaScript 中没有类的概念,所以 JavaScript 中使用原型(__proto__,原型链)实现继承

代码层面的面向对象

1
2
3
4
5
6
7
8
function Person( name ){
this.name = name
}
Person.prototype.say = function(){
console.log( ` My name is ${ this.name } ` )
}
var xiaoming = new Person( 'xiaoming' )
xiaoming.say()

这就是代码层面的面向对象,function Person(){} 就是调用的函数, Person.prototype.say = function(){} 就是你生成的新对象的原型方法

面向对象解决的问题

  1. 组件化。使得代码划分明确,作用清晰
  2. 调用时只需要 new,即可把公共属性 + 私有属性继承
  3. 每一个 new 之间互不影响,之后便可以添加自己的属性(也可覆盖公共属性)

相关知识点

命名空间

1
var app = app || {}

我们先来了解下 &&|| 这两个操作符的含义。这两个操作符返回的是一个 truey | falsey 值

&& 操作符

&& 操作符

|| 操作符

|| 操作符

所以当我们使用 var app = app || {} 这种写法是最佳实践,因为如果在这个语句之前,app 就有了定义,那么这个语句不会覆盖之前的定义。

JS进阶 -- 面向对象(二)构造函数 + 继承

概述

本篇主要讲述构造函数和继承

构造函数

编程

编程主要分为函数式编程面向对象编程

  • 函数式编程 ==> 推崇函数

  • 面向对象编程 ==> 推崇,没有函数概念

    JavaScript

    JavaScript 中的可以理解为构造函数

  • 类 ==> 如果一个东西返回一个 Object 就叫做类

  • 构造函数 ==> 如果一个函数返回一个 Object 就叫做构造函数

因为 JavaScript 中没有类的概念,所以 JavaScript 中的构造函数即可以看做是类,JavaScript 中 new 是构造函数的典范

继承

继承可以实现代码复用,JavaScript 中使用原型(__proto__,原型链)实现继承,实例化对象的 __proto__ 指向构造函数的 prototype

1
对象.__proto__ === 函数.prototype   // 很重要

分类

  1. 对象之间的继承 ==> __proto__

  2. 函数与对象之间的继承通过 this 参数(关键字)传导

  3. 关键字 new 形成构造函数,从而使用函数创建对象,使得新创建的对象可以继承构造函数上的属性

模拟继承的具体方式

  • 共有属性 ==> prototype 属性
  • 私有属性 ==> 构造函数上的属性

==> 继承 === 共有属性 + 私有属性 === prototype 属性 + 构造函数上的属性

1
2
3
4
5
6
7
8
9
10
11
function Person( name ){
this.name = name
}
Person.prototype.think = true
Person.prototype.foot = 'two'
Person.prototype.say = function(){
console.log( 'I can say' )
}
Person.prototype.walk = function(){
console.log( 'I can walk' )
}
new 模拟继承
1
2
3
4
5
let xiaoming = new Person( 'xiaoming' )
xiaoming.a = 1
xiaoming.xxx = function(){
console.log( 'xxx' )
}

对象 xiaoming 具有 Person 的私有属性 + 共有属性,还有自身的属性
new 模拟继承

call + Object.create() + new 模拟继承

new 模拟的继承仅仅有一层,如果我们要模拟多层,即 xiaoming 继承 StudentStudent 继承 Person
示例图
其中 Student 继承 Person构造函数与构造函数的继承(类与类的继承)

1
2
3
4
5
6
7
8
9
10
11
12
13
function Student( name, school ){
Person.call( this, name ) // 继承 Person 属性 === Person.bind( this ).( name )
this.school = school
}
Student.prototype = Object.create( Person.prototype ) // 继承 Person 方法
Student.prototype.learn = function(){
console.log( `${ this.name } learn in ${ this.school }` )
}
let xiaoming = new Student( 'xiaoming', 'qinghua' )
xiaoming.a = 1
xiaoming.xxx = function(){
console.log( 'xxx' )
}

对象 xiaoming 既继承了 Student 的共有属性 + 私有属性,又继承了 Person 的共有属性 + 私有属性,还有自己的属性
call + Object.create() + new 实现继承

模拟构造函数与构造函数的继承(类与类的继承)

上面中模拟构造函数与构造函数的继承(类与类的继承)使用了 Object.create()。模拟构造函数与构造函数的继承(类与类的继承)即是实现

1
Student.prototype.__proto__ = Person.prototype

  • 方案1:前提 ==> Person 为空(没有私有属性),若 Person 不为空,则 Student 上会多出 Person 上的私有属性

    1
    2
    3
    Student.prototype = new Person()
    ==> Student.prototype === this
    ==> Student.prototype.__proto__ === Person.prototype
  • 方案2:ES3 ==> not care Person 上的私有属性

    1
    2
    3
    function emptyPerson() {}
    emptyPerson.prototype = Person.prototype
    Student.prototype = new emptyPerson()
  • 方案3:ES5 API

    1
    Student.prototype = Object.create( Proson.prototype )
  • 方案4:ES6 ==> class + extends ==> prototype(共有属性)必须是函数

    1. class 用法:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      class Example{
      constructor(options){
      this.name = options.name
      }
      say() { }
      walk() { }
      }
      == 等价于 ==
      function Example(options) {
      this.name = options.name
      }
      Example.prototype.say = function() { }
      Example.prototype.walk = function() { }
    2. extends 用法:

      1
      2
      3
      4
          class Student extends Person === Student.prototype = Object.create(Person.prototype)
      super(options) === Example.call(this,options)
      ```
      3. 具体实现

      class Student extends Person{

      constructor(options){
          super(options)   // 继承私有属性
      }
      

      }

      1
      2
      3
      4
      5
      6
          4. ` class + extends ` 不伦不类
      ` Student ` 是 ` class `(类) ==> JavaScript 中没有类这个数据类型
      ` Student ` 是函数 ==> Student 并不能 ` call `

      ## 相关知识点
      ` Object.create() ` 的实现

function inherit( base ){
function fn(){}
fn.prototype = base
return new fn()
}
```

JS进阶 -- 面向对象(一)代码 + 函数 + this + new

概述

本篇我们借助 MVC 来初步理解面向对象,之后理解一下 函数 + this + new

代码

初始

当我们使用 MVC 思想将代码模块化后,每个 js 文件中代码没有重复,很精简,但是当我们跨文件观察我们的 js 时,发现每个 js 文件都有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let view = document.querySelect( 'xxx' )
let model = {
init: function(){},
fetch: function(){},
save: function(){}
}
let controller = {
view: null,
model: null,
init: function(){
this.view = view
this.model = model
this.model.init()
this.init()
this.bingEvents()
}
}

这是一种跨文件的重复,如果我们抽丝剥茧便可以对重复的地方进行分类,在全局范围内创造一个模板,每一个重复的地方都是由全局模板制造出来的

进阶

我们创建一个 base 文件目录,里面有

1
2
3
4
5
6
base/View.js
window.View = function(){}
base/Model.js
window.Model = function(){}
base/Controller.js
window.Controller = function(){}

之后每个文件的重复的地方都使用全局变量 View()、Model() 和 Controller()

函数 + this

函数是一种可执行代码组成的对象,this 仅仅是函数的一个参数而已

特点

I/O + 一顿操作 + 隐藏细节,所有的 I( input ) === this + arguments

函数 <==> this <==> 对象

JavaScript 通过 this 连接 ObjectFunction,函数和对象没有一丢丢关系

1
2
3
4
5
6
let obj = {
name: 'xxx',
sayName: function() {
console.log( this.name )
}
}

obj.sayName 仅仅存储了函数的地址而已 ==> 函数是一等公民 ==> 函数是一个独立的,既不是附属品,也不是方法

判断 this 的值

new 操作符

概述

new 操作符主要是一个语法,它主要做了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(name) {
this.name = name
}

当使用 let xiaoming = new Person( 'xiaoming' ) 时,等价于

let xiaoming = function(options) {
// 1. var temp = {}
// 2. this = temp
this.name = 'xiaoming'
// 3. 构造函数.prototype = { constructor: 构造函数 }
// 4. this.__proto__ = 构造函数.prototype
// 5. return this
}

请注意:new 返回值的类型是 Object
浏览器运行结果

说明:如果人为在 Person 中增加 return,两种情况

  • return 基本类型 ==> 无效 + 无视
  • return 复杂类型(对象) ==> 返回复杂类型(对象)

new 的实现

var 对象 = new 函数

方法一:通过 Object.create() 实现 new
1
2
3
4
5
6
function _new(){
let constructor = arguments[0]
let obj = Object.create( constructor.prototype )
constructor.apply( obj, [].slice.call( auguments, 1 ))
return obj
}

通过 Object.create() 实现 new

方法二:通过 Object.setPrototypeOf() 实现 new
1
2
3
4
5
6
7
function _new(){
let constructor = arguments[0]
let obj = {}
Object.setPrototypeOf( obj, constructor.prototype )
constructor.apply( obj, [].slice.call( arguments, 1 ))
return obj
}

通过 Object.setPrototypeOf() 实现 new

相关知识点

创建一个函数过程

  1. 创建一个函数对象

  2. 创建一个原型对象

  3. 函数对象会有一个 prototype 属性,指向对应的原型对象

  4. 原型对象中有一个 constructer 属性,指向对应的构造函数

函数

总结篇(四) -- 进阶

概述

本篇主要讲述声明前置、引用类型、函数作用域链、闭包、HTTP、Web安全和性能优化

声明前置

在进入一个执行环境后,先把 varfunction 声明的变量前置,再去顺序执行代码,如果出现同名,则覆盖。如果一个变量已经有值(包括函数)再 var 无效

引用类型

引用类型存储的是变量的地址,赋值仅仅是地址的拷贝

1
2
3
4
5
6
var a = 1
function increase( num ){
return num += 1
}
increase( a )
console.log( a ) // 1

JavaScript 中引用类型即对象,对象存储方式是,栈内存中存储地址,堆内存中存储数据

1
2
3
4
5
6
var obj = { a: 1 }
function increase( obj ){
obj.a += 1
}
increase( obj )
console.log( obj.a ) // 2

函数作用域链

函数在执行过程中,先从自己内部找变量,之后去创建当前函数所在的作用域中寻找,依次向上

1
2
3
4
5
6
7
8
9
10
var a = 1
function fn1(){
function fn2(){
cnsole.log( a )
}
var a = 2
return fn2
}
var fn = fn1()
fn() // 2

1
2
3
4
5
6
7
8
9
10
var a = 1
function fn1(){
var a = 2
return fn2
}
function fn2(){
console.log( a )
}
var fn = fn1()
fn() // 1

闭包

一个函数引用了外部的变量,这个变量 + 函数 === 闭包,外部作用域中的变量不能被释放,可以通过函数来操作这个变量

1
2
3
4
5
6
7
8
function bindName( name ){
return function( action ){ // 此函数 + name(argument[0])就是闭包
console.log( ` ${ name } is ${ action }ing ` )
}
}
var doing = bindName( 'xxx' )
doing( 'work' ) // xxx is working
doing( 'play' ) // xxx is playing

HTTP

HTTP(Hypertext Transfer Protocol)超文本传输协议,它是一种应用层的协议,主要规定了电脑之间传输内容的协议

  • 应用层协议:HTTP、DNS、SSH
  • 传输层协议:TCP、UDP
  • 网络层协议:IP
  • URI ==> 统一资源标识符
  • URL ==> 统一资源定位符

    HTTP 报文(HTTP Message)

    请求报文(Request Message)
    请求报文
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 请求行
    method path HTTP/1.1
    // 请求头
    key: value
    Host: 域名
    connection: 决定当前的事务完成之后,是否会关闭网络连接,如果该值是 keep-alive ,网络连接就是持久的,不会关闭,使得对同一服务器的请求可以继续在该连接上完成
    Pragma: no-cache ==> 与 Cache-Control: no-cache 效果一致,强制要求缓存服务器在返回缓存的版本之前将请求提交到源头服务器进行验证
    Cache-Control: no-cache ==> 同上
    User-Agent: 用户代理软件的应用类型、操作系统、软件开发商以及版本号
    Accept: 告知客户端可以处理的内容类型
    Referer: 当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的
    Accept-Encoding: 将客户端能够理解的内容编码方式--通常是某种压缩算法--进行通知
    Accept-Language: 客户端声明它可以理解的自然语言,以及优先选择的语言
    If-None-Match: 条件式请求首部。对于 GET 和 HEAD 请求方法来说,当且仅当服务器上没有任何资源的 ETag 属性值与这个首部中列出的相匹配的时候,服务器端会才返回所请求的资源,响应码为 200
    Cookie:
响应报文(Response Message)

响应报文

1
2
3
4
5
6
7
8
9
10
// 响应行
HTTP/1.1 HTTP-Status-Code
// 响应头
Cache-Control: 被用于在 HTTP 请求和响应中通过指定指令来实现缓存机制
Content-Encoding: 对特定媒体类型的数据进行压缩
Content-Type: 实际返回的内容的内容类型
Expires: 指定一个日期 | 时间,在这个 日期 | 时间之后,HTTP 响应被认为是过时的
Server: 处理请求的源头服务器所用到的软件相关信息
Set-Cookie: 服务器端向客户端发送 Cookie
Strict-Transport-Security: 安全功能,告知浏览器只能通过 HTTPS 访问当前资源,禁止 HTTP 方式

使用 Cookie + Session 可以使得无状态的 HTTP 请求可以记录稳定的状态信息

  • Cookie 是存储在浏览器里的一小段数据
  • Session 是一种让服务器能够识别某个用户的机制,或者说特指服务器存储的 Session 数据
    很多网站的静态资源使用 CDN 地址而非使用当前网站域名
    使用 CDN 加速。从 Cookie 角度上说,发送请求时都会带上 Cookie,但是 Cookie 有跨域限制,即请求相同域名才会带上 Cookie,CDN 与当前域名不一致,故请求 CDN 资源时不会带上 Cookie,使得请求加快,从而达到加速的目的
Session
  • Session 在生产环境中保存在数据库中
  • Session 是基于 Cookie 实现的

Web 安全

  1. 传输数据安全
  2. 浏览器安全机制
  3. 常见攻击: XSS 攻击、CSRF 攻击、点击挟持

数据传输安全

只要使用 HTTP 协议,无论做任何安全措施都是徒劳的,只有 HTTPS 协议才能保证数据在传输过程中的安全

数据加密

  1. 对称加密:加密和解密都是用同一密钥
    • 特点: 速度快
    • 常见算法:AES
  2. 非对称加密:加密和解密使用不同的密钥,成为公钥和私钥。数据用公钥加密后必须用私钥解密,数据用私钥加密后必须用公钥解密。
    • 特点:速度慢,CPU 开销大
    • 常见算法:RSA
  3. Hash:把任意长度数据经过处理变成一个长度固定且唯一的字符串,无法反向解密成原始数据
    • 特点:验证数据完整性
    • 常见算法:MD5、SHA1、SHA256

攻击方式(传输过程中)

  • 拒绝服务:攻击者让目标机器停止提供服务
  • 信息泄露:攻击者获取用户信息
  • 中间人攻击:攻击者通过各种手段,在服务端发出的 HTTP | HTML 报文与显示屏呈现出的 Web 页面之间做一些手脚,篡改网页的部分或者全部内容,从而改变用户在浏览器视窗中看到的内容
  • 重放攻击: 攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程,破坏认证的正确性

HTTPS 实现安全数据传输

主要方案
  1. 服务器生成一对公钥和私钥,公钥给浏览器
  2. 浏览器做对称加密,产生密钥,使用公钥加密密钥,发送给服务器
  3. 服务器接收到公钥加密的密钥,服务器使用私钥解密,从而得到密钥
  4. 服务器和浏览器使用密钥进行交流
确保浏览器得到的是真正的公钥

根 CA + 上级 CA + CA 机构

根 CA 担保上级 CA,上级 CA 担保 CA 机构

当一个网站要使用 HTTPS 时先需在一些国际认证的 CA 机构填写网站信息申请证书,而这些 CA机构还有上层 CA,最终有一个根 CA

上述三者都做对称加密,分别得到

  • 公钥根CA + 私钥根CA
  • 公钥上级CA + 私钥上级CA
  • 公钥CA机构 + 私钥CA机构

私钥根CA 加密 公钥上级CA
私钥上级CA 加密 公钥CA机构
私钥CA机构 加密 公钥

浏览器都会内置根 CA 和一些顶级 CA 的证书

浏览器所要做的

  1. 根据 公钥根CA 解密 私钥根CA
  2. 根据解密出的 私钥根CA 解密 公钥上级CA
  3. 根据解密出的 公钥上级CA 解密 私钥上级CA
  4. 根据解密出的 私钥上级CA 解密 公钥CA机构
  5. 根据解密出的 公钥CA机构 解密 私钥CA机构
  6. 根据解密出的 私钥CA机构 解密 公钥

至此浏览器得到真正的 公钥
12306 网站
说明了 12306 网站的 HTTPS 证书没有机构为其担保,所以浏览器提示连接不安全

XSS 攻击

XSS 攻击是一种安全漏洞,攻击者利用这种漏洞在网上注入恶意的客户端代码,详细请看

避免方式
  1. 所有用户输入的地方都是不安全的,所以不要把用户提交的东西作为 HTML 去运行,即不使用 innerHTML
  2. JavaScript 中不要使用 eval()

CSRF(跨站请求伪造) 攻击

原理

攻击者构造网站后台某个功能接口的请求地址,诱导用户去点击或用特殊方法让该请求地址自动加载,用户在登录状态下这个请求被服务端接受后会被误以为是用户合法的操作,对于 GET 形式的接口地址可轻易被攻击,对于 POST 形式的接口地址也不是百分百安全,攻击者可诱导用户带 Form 表单可用 POST 方式提交参数的页面

避免方式
  1. 用户提交数据时 + 验证步骤
  2. 后端给前端传输的数据中,生成随机数,把随机数使用 Set-Cookie 写入到 Cookie 中,同时把随机数写到 Form 表单中(<input type='hidden' CSRF='随机数'),当用户提交表单时,服务器收到请求后查看 Form 表单中随机数是否 === Cookie 中随机数

性能优化

性能优化主要分为加载优化 + 体验优化两方面

加载优化
  1. 服务器网速要好,宽带够,使用 CDN
  2. 把所有资源压缩,尤其是图片压缩,选择合适的图片大小,HTML、CSS、JS 压缩
  3. 减少请求,生产环境资源打包合并
  4. 后端接口速度加快
  5. 利用缓存,能重复利用的都重复利用,Cache-Control + ETag
体验优化
  1. 只给用户暂时需要的东西,按需加载 + 懒加载
  2. 代码性能优化
  3. CSS3 动画优先,其次是 JS 动画,transform > position + left > margin-left

总结篇(三) -- 你知道 log 出来的 this 是什么么

概述

函数中的 this 是一个很重要的知识点,如果能清楚的知道各种场景下函数中 this 所代表的值,那么对于我们去理解库 | 框架是很有帮助的,也可以让我们去运用一些更加高级的思想

this

this 的使用情况大致分为两种

  • 函数中
  • 对象的方法中
    1
    this === call | apply | bind 的第一个参数

在判断 this 的值的时候,进行相应的转化是很有必要的

严格模式 + 非严格模式

示例一

1
2
3
4
5
'use strict'
function fn( a, b ){
console.log( this )
}
fn( 1, 2 )

上述中的 this 会打印出什么呢?

1
fn( 1, 2 ) === fn.call( undefined, 1, 2 ) === fn.apply( undefined, [1, 2] )

答案

  • 严格模式 ==> this === undefined
  • 非严格模式 ==> this === undefined == 浏览器转化 ==> window

对象方法

示例二

1
2
3
4
5
6
7
8
9
10
11
12
let obj = {
fn: function( a, b ){
console.log( this )
},
child: {
fn2: function(){
console.log( this )
}
}
}
obj.fn( 1, 2 )
obj.child.fn2()

上述中的 this 会打印出什么呢?

1
2
obj.fn( 1, 2 ) === obj.fn.call( obj, 1, 2 ) === obj.fn.apply( undefined, [ 1, 2 ] )  ==> this === obj
obj.child.fn2() === obj.child.fn2.call( obj.child ) ==> this === obj.child

示例三

1
2
3
4
5
6
7
8
9
let arr = []
for( let i = 0; i < 3; i++ ){
arr[ i ] = function(){
console.log( this )
}
}
let fn = arr[ 0 ]
arr[ 0 ]
fn()

上述中的 this 会打印出什么呢?

1
2
arr[ 0 ] ==> arr 对象{ 0: fn, 1: fn, 2: fn } ==> arr.0 ==> arr.0.call( this ) ==> this === arr
fn() ==> fn.call( undefined ) ==> this === undefined == 浏览器转化 ==> window

bind

bind用法在之前有讲过,这个 API 就是返回一个函数 + 绑定 this

示例四

1
2
3
4
5
6
7
let obj = { a: 1 }
function fn(){
console.log( this )
console.log( this.a )
}
let newFn = fn.bind( obj )
newFn()

上述中的 this 会打印出什么呢?

1
newFn() === fn.bind( obj )() ==> this === obj ==> this.a === 1

示例五

1
2
3
4
5
6
7
8
9
10
11
12
let obj = {
container: document.querySeletor( 'body' )
bind: function(){
console.log( this )
this.container.addEventListener( 'click', this.sayHello )
this.container.addEventListener( 'click', this.sayHello.bind( this ) )
},
sayHello: function(){
console.log( this )
}
}
obj.bind()

上述中的 this 会打印出什么呢?

1
2
3
bind 中的 ` this ` ==> obj.bind.call( obj ) ==> this === obj
this.container.addEventListener( 'click', this.sayHello ) 当点击事件触发时,sayHello 打印出的 this ==> this === 绑定事件的元素(this.container) ==> this === obj.container
this.container.addEventListener( 'click', this.sayHello.bind( this ) ) 当点击事件触发时,sayHello 打印出的 this ==> bind 绑定了 this[ this === obj ] ==> sayHello 打印出的 this === obj

箭头函数

箭头函数没有 this + arguments

示例六

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let obj = {
fn1: function(){
console.log( this )
},
fn2() {
console.log( this )
},
fn3: () => {
console.log( this )
}
}
obj.fn1()
obj.fn2()
obj.fn3()

上述中的 this 会打印出什么呢?

1
2
3
4
obj.fn1() === obj.fn1.call( obj ) ==> this === obj
obj.fn2() === obj.fn2.call( obj ) ==> this === obj
fn1 的写法 === fn2 的写法
obj.fn3() === obj.fn3.call( 与 obj 同级 this ) ==> this === window

示例七

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let obj = {
init() {
console.log( this )
let prop = {
init: () => {
console.log( this )
},
bind() {
console.log( this )
}
}
prop.init()
prop.bind()
}
}
obj.init()

上述中的 this 会打印出什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let obj = {
init() {
console.log( this ) // 2. this === obj
let prop = {
init: () => {
console.log( this ) // 4. this === prop 同级 this ==> this === obj
},
bind() {
console.log( this ) // 6. this === prop
}
}
prop.init() // 3. init() 是箭头函数 ==> prop.init.call( prop 同级 this )
prop.bind() // 5. bind 不是箭头函数 ==> prop.bind.call( prop )
}
}
obj.init() // 1. === obj.init.call( obj )

示例八

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
let obj = {
x: console.log( this )
fn1() {
console.log( this )
setTimeout( function(){
console.log( this )
}, 10 )
},
fn2() {
console.log( this )
setTimeout( () => {
console.log( this )
}, 20 )
},
fn3() {
console.log( this )
setTimeout( function(){
console.log( this )
}.bind( this ), 30 )
},
fn4: () => {
console.log( this )
setTimeout( () => {
console.log( this )
}, 40 )
}
}
obj.fn1()
obj.fn2()
obj.fn3()
obj.fn4()

obj.x 的值是什么?

上述中的 this 会打印出什么呢?

1
2
3
let obj = {
x: console.log( this ) // console.log( this ) 的值是 undefined ==> obj.x === undefined 其中的 this === window
}

fn1()

1
2
3
4
5
6
7
8
9
10
11
12
13
fn1() {
console.log( this ) // 2. this === obj
setTimeout( function(){
console.log( this )
}, 10 )
// 3. 等价于
// function fn(){
// console.log( this ) // 5. this === undefined == 浏览器转化 ==> window
// }
// 过 10s 执行函数 fn
// fn() // 4. fn 非箭头函数 fn.call( undefined )
}
obj.fn1() // 1. fn1 非箭头函数 obj.fn1.call( obj )

fn2()

1
2
3
4
5
6
7
8
9
10
11
12
13
fn2() {
console.log( this ) // 2. this === obj
setTimeout( () => {
console.log( this ) // 5. this === 上级 this ==> this === obj
}, 20 )
// 3. 等价于
// () => {
// console.log( this )
// }
// 过 20s 执行箭头函数
// 箭头函数() // 4. 箭头函数 箭头函数.call( 上级this )
}
obj.fn2() // 1. fn2 非箭头函数 obj.fn2.call( obj )

fn3()

1
2
3
4
5
6
7
8
9
10
11
12
13
fn3() {
console.log( this ) // 2. this === obj
setTimeout( function(){
console.log( this )
}.bind( this ), 30 )
// 3. 等价于
// function fn(){
// console.log( this ) // 5. this === obj
// }.bind( this )
// 过 30s 执行函数 fn
// fn.bind(this) // 4. fn 非箭头函数 + bind 绑定 this[ this === obj ]
}
obj.fn3() // 1. fn3 非箭头函数 obj.fn3.call( obj )

fn4()

1
2
3
4
5
6
7
8
9
10
11
12
13
fn4: () => {
console.log( this ) // 2. this === obj 同级 this ==> this === window
setTimeout( () => {
console.log( this )
}, 40 )
// 3. 等价于
// () => {
// console.log( this ) // 5. this === window
// }
// 过 40s 后执行箭头函数
// 箭头函数() // 4. 箭头函数 箭头函数.call( 上级 this )
}
obj.fn4() // 1. fn4 箭头函数 obj.fn4.call( obj 同级 this )

总结

判断函数中 this 步骤

  1. 查看调用函数的类型
  2. 箭头函数
    • 箭头函数.call( 上级 this )
    • obj.箭头函数.call( obj 同级 this )
  3. 非箭头函数
    • 非箭头函数.call( undefined )
    • obj.非箭头函数.call( obj )

前端基础系列(四) -- 内存

概述

电脑开机之后就把硬盘中的数据传输到内存中,例如操作系统,把硬盘中的操作系统读到内存中即是开机。内存特点是:存储数据快,一旦断电,数据就丢失了。与内存相对应的是外存,表示无论断电与否,数据都存在,外存的存储速度慢。

示例

Chrome 浏览器打开之后大约占用了 1G 的内存,分别分配给每个页面,每个页面大约占用 100M ,其中包括了:

1. HTML + CSS
2. JavaScript
3. 网络 HTTP
4. 其他

所以 JavaScript 最多占用约 100M 的内存。

内存分类

JS 将内存划分为两个大区,分别是代码区(存储代码)数据区(存储数据)

1
let  a = 1  // 变量 a 存储在代码区  数字 1 存储在数据区 二者以 JS 引擎关联

数据区

数据区分为 Stack(栈内存)Heap(堆内存)

  • Stack(栈内存)存储方式是以一行一行存储,类似栈,故而叫做栈内存

  • Heap(堆内存)存储方式是以堆存储

存储

  1. 数字是以 64 位浮点数进行存储
  2. 字符是以 16 位的进行存储。
  • 存储 var a = 1 时,a 在代码区,1 以64位浮点数存储在栈内存。

  • 存储 var b = 2 时,b 在代码区,2 以64位浮点数存储在栈内存。

注意:b = a 时,将 a 中的数据复制并覆盖到 b 的数据上。

  • 存储 var a = true 时,a 在代码区,true 转化为 1 ,之后以64位浮点数存储在栈内存。

  • 存储 var obj1 = {} 时,obj1 在代码区,在栈内存中会存有一个以64位浮点数的地址,引用的是堆内存中的一个地址,obj1 中的数据将存储在堆内存中的一个地址,如果在后面在 添加 obj1 的属性 ,继续放在堆内存中。

  • 存储 var obj2 = {} 时,obj2 在代码区,在栈内存中会存有一个以64位浮点数的地址,引用的是堆内存中的一个地址,obj2 中的数据将存储在堆内存中的一个地址。

注意:obj2 = obj1 时,和 b = a 做的事情完全相同,即将 obj1 中的数据地址复制并覆盖到 obj2 的数据地址上。

数据类型存储方法:

JavaScript 中有 7 种数据类型

  1. 简单数据类型(6种)直接存储在 Stack(栈内存)

  2. 复杂数据类型(Object)在 Stack(栈内存)中存储 Heap(堆内存)的地址

    所有的变量和对象的关系都是引用关系。

套路:

1
2
3
4
5
var a = { n: 1 };  
var b = a;
a.x = a = { n:2 }; //当浏览器运行这句话时,首先确定 a 的值,之后执行 { n:2 },之后执行 a = { n:2 },之后执行 a.x (此时的 a 是之前确定的 a )。
alert( a.x ); //undefined
alert( b.x ); //[object Object]

内存图

基本概念

垃圾回收

如果一个对象没有被引用,就是垃圾,将会被回收(释放内存)。

内存泄露

由于浏览器的 BUG ,使得应该被标记为垃圾的东西,没有被标记为垃圾,造成内存被永久占用,除非把浏览器关闭。
解决方法:在关闭页面之前把所有的事件设置为 null 。

浅拷贝&深拷贝

基本数据类型
1
2
3
4
var a = 1;
var b = a;
b = 2; // b 变不影响 a,即是深拷贝
alert( a ); // 1

对于基本类型,所有的赋值 “ = “ 都是深拷贝

所以在谈及深拷贝和浅拷贝时是不考虑基本类型的,因为基本类型的赋值都是深拷贝

复杂数据类型(Object)

浅拷贝:

1
2
3
4
var a = { name : 'a' };
var b = a;
b.name = 'b' ; // b 变致 a 变,即是浅拷贝
alert( a.name ); // 'b'

深拷贝:b 复制 a 的所有数据,但是地址不同,最终的结果就是 b 变是 b 自己的事情,与 a 无关,即 b 变不影响 a

前端基础系列(三) -- 算法 + 数据结构基础

结构化编程

  1. 一行一行执行
  2. 有条件控制语句 if…else…
  3. 有循环控制语句 while(exp) do…

    伪代码

    流程图

算法

  1. 输入:一个算法必须有零个或以上输入量。
  2. 输出:一个算法应有一个或以上输出量,输出量是算法计算的结果。
  3. 明确性:算法的描述必须无歧义,以保证算法的实际执行结果是精确地 匹配要求或期望,通常要求实际运行结果是确定的。
  4. 有限性:依据图灵的定义,一个算法是能够被任何图灵完备系统模拟的一串运算,而图灵机只有有限个状态、有限个输入符号和有限个转移函数(指令)。而一些定义更规定算法必须在有限个步骤内完成任务。
  5. 有效性:又称可行性。能够实现,算法中描述的操作都是可以通过已经实现的基本运算执行有限次来实现。

数据结构

即数据的结构

哈希(Hash)

键值对: { '键' : '值' } ==> Array / Object

  • 计数排序:计数排序使用一个额外的数组 arrhash),其中第 i 个元素是待排序数组 Arr 中值等于 i 的元素的个数。然后根据数组 arr 来将 Arr 中的元素排到正确的位置(用到了桶,但是每个桶中只有相同的数字,空间浪费)(所有的桶是Hash,桶里是队列,先进先出)。
    复杂度:n + max
    缺点:

    1. 需要一个哈希表示计数工具。
    2. 无法对小数和负数排序
    
  • 桶排序:将数组分到有限数量的桶里。每个桶再个别排序,此时可以用其他排序方法(所有的桶是Hash,桶里是队列,先进先出

  • 基数排序:只有十个桶(0 - 9),先排个位,之后十位,依次到最高位(所有的桶是Hash,桶里是队列,先进先出
    说明:比较排序的极限 n*logN

队列(queue)

  • 特点:先进先出
  • 可以用数组实现

栈(stack)

  • 特点:先进后出
  • 可以用数组实现

链表(Linked List)

  • 是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大

  • 数组无法直接删除中间的一项,但是链表可以,链表是动态数组

  • Hash 实现链表,head 表示第一个 Hash ,所有的 Hash 都是节点(node

  • 是一种抽象数据类型(ADT)或是实作这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点组成一个具有层次关系的集合

  • 特点

    1. 每个节点有零个或多个子节点
    2. 没有父节点的节点称为根节点
    3. 每一个非根节点有且只有一个父节点
    4. 除了根节点外,每个子节点可以分为多个不相交的子树
  • 术语

    1. 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
    2. 深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
    3. 节点的度:一个节点含有的子树的个数称为该节点的度;
    4. 树的度:一棵树中,最大的节点的度称为树的度;
    5. 叶节点终端节点:度为零的节点;
  • 二叉树(Binary tree):每个节点最多含有两个子树的树称为二叉树

    1. 二叉树的第 i 层至多拥有2的( i-1 )次幂个节点数
  • 完全二叉树:对于一颗二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树

    1. 在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则此二叉树为完全二叉树(Complete Binary Tree)

    2. 具有n个节点的完全二叉树的深度为log以2为底n的对数+1。深度为k的完全二叉树,至少有2的k次幂个节点,至多有2的( k+1 )次幂 - 1个节点

  • 满二叉树:所有叶节点都在最底层的完全二叉树

    1. 一棵深度为k,且有2的( k+1 )次幂 - 1个节点的二叉树,称为满二叉树(Full Binary Tree)

    2. 每一层上的节点数都是最大节点数

说明:用数组存储满二叉树和完全二叉树,用Hash存储其他的树

算法和数据结构结合

  1. 我们要解决一个跟数据相关的问题
  2. 分析这个问题,想出对应的数据结构
  3. 分析数据结构,想出算法
    数据结构和算法是互相依存、不可分开的

分类:

  1. 分治法:把一个问题分区成互相独立的多个部分分别求解的思路。这种求解思路带来的好处之一是便于进行并行计算。前端主要使用分治法

  2. 动态规划法:当问题的整体最优解就是由局部最优解组成的时候,经常采用的一种方法

  3. 贪婪算法:常见的近似求解思路。当问题的整体最优解不是(或无法证明是)由局部最优解组成,且对解的最优性没有要求的时候,可以采用的一种方法

  4. 线性规划法:见词条

  5. 简并法:把一个问题通过逻辑或数学推理,简化成与之等价或者近似的、相对简单的模型,进而求解的方法

排序算法

  • 冒泡排序:它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
    这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端,故名。
  • 选择排序:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
  • 插入排序:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
  • 基数排序:将整数按位数切割成不同的数字,然后按每个位数分别比较。将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
  • 快速排序:快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
    步骤为:
    1. 从数列中挑出一个元素,称为”基准”(pivot),
    2. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
    3. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
      递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

前端基础系列(二) -- 命令行基础

概述

命令行操作会解放我们的鼠标,让我们更加流畅的进行我们想要的操作

文件与文件夹(目录)

  • ~ ==> 用户目录
  • / ==> 所有硬盘
  • . ==> 当前目录
  • .. ==> 父级目录
  • $ ==> 已准备好
  • directory ==> 目录文件夹
  • file ==> 文件
  • make ==> 新建
  • remove ==> 删除
  • move ==> 移动
  • list ==> 枚举
  • link ==> 链接(windows不支持)
  • find ==> 查找
  • echo ==> 发出回音、重复
  • touch ==> 触摸
  • change ==> 改变
  • copy ==> 复制

缩写规则

删除元音字母(A E I O U),保留前两个到三个辅音

  • mkdir ==> make directory ==> 创建目录
  • rm ==> remove ==> 删除
  • mv ==> move ==> 移动、重命名
  • cp ==> copy ==> 复制
  • ls ==> list ==> 枚举
  • cd ==> change directory ==> 改变目录
  • -rf ==> 用于删除目录,其中:
    • -r ==> 文件夹的递归操作
    • -f ==> 强制,否则每个文件都要询问

常见命令

  • cd ==> 进入目录

  • pwd ==> 显示当前目录

  • mkdir 目录名 ==> 创建目录

  • mkdir -p 目录路径 ==> 创建目录(eg.:mkdir -p demo/outter/inner),如果目录路径有特殊字符(eg.:空格)要加引号。所以坚决避免有特殊字符

  • whoami ==> 我是谁

  • ls ==> 查看路径

  • ls -a ==> 查看路径(显示所有文件【包括隐藏文件】)

  • ls -l ==> 查看路径(包括详细信息)

  • ls -al | ls -la ==> ls -a + ls -l

  • touch 文件名 ==> 创建文件

  • touch 文件名(已存在的文件名) ==> 改变文件更新时间

  • cp 源路径 目标路径 ==> 复制文件

  • cp -r 源路径 目标路径 ==> 复制目录

  • mv 源路径 目标路径 ==> 移动节点

  • rm 文件路径 ==> 删除文件

  • rm -f 文件路径 ==> 强制删除文件

  • rm -r 目录路径 ==> 删除目录

  • rm -rf 目录路径 ==> 强制删除目录

  • curl -L http://www.baidu.com > baidu.html ==> 下载文件

  • df -kh ==> 磁盘占用

  • du -sh ==> 当前目录大小

  • du -h ==> 各文件大小

Git

使用 git 三种方式

  1. 只在本地上使用
  2. 将本地仓库上传到github
  3. 下载github上的仓库

git命令

1
2
3
4
5
6
7
git clone 'SSH地址' ==> 下载仓库
git init ==> 初始化本地仓库 .git 目录
git status -sb ==> 显示当前所有文件状态
git add . ==> 把当前目录('.' 表示当前目录)里面的变动添加到【暂存区】
git commit -m '信息' ==> 将 'add' 的内容【正式提交】到本地仓库,并添加注释信息
git commit --amend -m '信息' ==> 修改上次的注释信息
git log ==> 历史变动

git status -sb ==> -s(summary):显示总结 + -b(branch):显示分支
start css/style.css ==> 使用默认编辑器打开 style.css
如果有新的变动,需要一次执行

1
2
git add .
git commit -m '信息'

  • git status -sb ==> 显示当前所有文件状态,其中:
    ?? ==> 表示待处理
    A ==> 表示添加
    M ==> 表示这个文件被修改了(Modified)

本地使用

1
2
3
4
git init
mkdir + touch
git add .
git commit -m '信息'

将本地仓库上传到github

create a new repository on the command line
创建一个新仓库在命令行中
push an existing repository from the command line
添加一个现有的仓库从命令行
:existing — 现有

直接在github创建一个仓库然后下载本地

1
2
3
4
5
6
7
8
9
10
create a new repository
repository name
Decription
Initilize this repository with a README
Add .gitignore : Node
Add a license : MIT License
clone or download
Use SSH ==> git@github.com开头地址
在要粘贴的文件夹 git bash here
git clone '地址'

上传更新

1
2
3
4
1. git add 文件路径
2. git commit -m '信息'
3. git pull
4. git push

:在命令行中输入命令是区分大小写

github 上删除 node_modules

1
2
3
git rm -r --cached node_modules  // --cached不会把本地的.idea删除
git commit -m 'delete node_modules'
git push -u origin master

命令行技巧

~/.bashrc

~/.bashrc 文件的功能很强大

自动运行

  1. touch ~/.bashrc
  2. start ~/.bashrc
  3. 编辑 ~/.bashrc,内容为 cd ~/Desktop,重启 Git Bash,默认就进入桌面目录了
    可以用 ~/.bashrc 在进入 Git Bash 前执行任何命令,十分方便

alias

  1. ~/.bashrc 里新增一行 alias b="echo 'bowen is awesome'"
  2. 运行 source ~/.bashrc,作用是执行 ~/.bashrc
  3. 运行b,就会看到 bowen is awesome
  4. 也就是说,现在 b 就是 echo 'bowen is awesome' 的缩写了
常见命令缩写
1
2
3
4
5
6
7
alias la='ls -a'
alias ll='ls -l'
alias gst='git status -sb'
alias ga='git add'
alias ga.='git add .'
alias gc='git commit'
alias gc.='git commit .'

保存退出,然后运行 source ~/.bashrc

相关知识点

  • 绝对路径: 以 / 开头的路径就是绝对路径
  • 隐藏文件以 . 开头
  • d — 目录、r — 可读、w — 可写、x — 可执行
    rwx(管理员权限)
    r-x(用户所在组权限)
    r-x(任意用户权限)
    更多信息

前端基础系列(一) -- 编程基础

概述

操作系统运行于硬件之上,浏览器运行于操作系统之上,HTML | CSS | JavaScript运行于浏览器之上,HTML | CSS | JavaScript 的所有数据都来自于服务器

存储数据

如何存储0和1

1 — 充电
0 — 不充电
充完电之后立刻放电
cpu — 表示每秒钟可以充多少次电

如何储存数字

十进制 — 十六进制

0—0 1—1 2—2 3—3 4—4 5—5 6—6 7—7 8—8

9—9 10—A 11—B 12—C 13—D 14—E 15—F

二进制 — 十六进制(4位,不足位数补0)

1
2
3
4
5
0000---0    0001---1    0010---2    0011---3    0100---4    

0101---5 0110---6 0111---7 1000---8 1001---9

1010---A 1011---B 1100---C 1101---D 1110---E 1111-F

如何存储字符(二进制)

ASCII

如何存储中文(十六进制)

GB2312 ==> GBK

如何存储所有字符(32位)

Unicode字符集 ==> UTF-8(是一种编码方式,不是字符集,减少内存使用)
图片

图片

编码问题

javascript使用了Unicode字符集,但是没有使用UTF-8编码(ES6解决了)。ES5只能表示两个字节以内的字符

GitHub 的使用

新建库

图片

库的名字

图片

创建

图片

  • 在本地新建一个文件夹,之后用 git bash 打开,在 git bash 中输入
    图片

代表成功。此处若出现问题,则需要配置git

1
2
3
4
5
git config --global user.name 'your username' 
git config --global user.email 'your email'
git config --global push.default simple
git config --global core.quotepath false
git config --global core.editor "vim"

图片

git push -u origin master出现错误,则需要进行下面的配置,点击 settings

图片

点击 SSH and GPG keys

图片

图片

图片

注意添加自己的邮箱,回车。

图片

将生成的文件打印

在 git bash 中输入 cat ~/.ssh/id_rsa.pub 将输出的东西复制

图片

title 随意填写,将刚刚复制的粘贴到 Key

图片

之后再一次在 git bash 中输入git push -u origin master 即可,出现下面的即表示成功。

图片

图片

图片

之后运用 vs Code 编辑在本地创建的文件夹

图片

  • 进行提交
    图片
    图片

点击 推送,之后刷新 github 页面,即可看到更新

图片

图片

之后填写内容,提交推送,刷新 github

在 github 上查看HTML文件

图片

图片

图片

先在浏览器中粘贴地址,并加上文件名,之后复制地址!

图片

图片