黄东璐

黄东璐 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

黄东璐 发布了文章 · 9月11日

git 的一些基本操作

一、关于撤销

1.commit错了,想撤销怎么办?

git reset --soft HEAD^

这样就回退到了add的状态

2.如果只是commit的注释写错了,想要修改怎么办?

git commit --amend

此时会进入到默认的vim编辑器,修改完注释保存就好了

3.撤销所有的已经add的文件

git reset HEAD .

4.撤销add的某个文件或文件夹

git reset HEAD  -filename

最后,附上一张常用操作的图
image.png

查看原文

赞 0 收藏 0 评论 0

黄东璐 发布了文章 · 9月11日

关于babel的一点学习

1.babel-core

当我们在webpack中使用babel的时候,首先要安装babel-core,这是babel编译库的核心包

npm install babel-core --save-dev

2.babel-loader

之后,webpack中对js文件,我们要进行编译,就需要配置,在webpack中,需要用到babel-loader来使用bebel

npm install babel-loader --save-dev

所以,在webpack.config.js代码中,要这样写:

rules: [
  {
    test: /\.js$/,
    use: {
      loader: 'babel-loader'
    },
    exclude: '/node_modules/'
  }
]

3.babel-preset-xxx(xxx代表很多选项)

(1)babel-preset-es2015

按照es6标准进行编译,同理,如果按照es7标准进行编译,则安装babel-preset-es2016

(2)babel-preset-env

一般来说,如果你想用最新的规范做编译,直接安装babel-preset-env就可以了,它包含了 babel-preset-es2015, babel-preset-es2016, and babel-preset-es2017,等价于babel-preset-latest,可以编译所有最新规范中的代码

使用编译规则来配置webpack,在babel-loader中新增配置参数:

rules: [
  {
    test: /\.js$/,
    use: {
      loader: 'babel-loader',
      options: {
        presets: ['env']        //也可以写成presets: ['babel-preset-env']
      },
      exclude: '/node_modules/'
    }
  }
]

4.babel-polyfill

babel官网上写了很明确一句话,babel只负责对语法进行编译当我们写箭头函数,babel会帮你把它编译成普通函数,这没有任何问题,但是,比如说我们代码里使用了promise,babel打包出来的代码其实还是promise,在低版本浏览器里,promise并不支持,但是babel并不会帮你处理,因为这不是语法编译层面需要做的事情。不转换新的API包括,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象。

于是,如果我们要让打包出来的代码能兼容低版本浏览器,还要考虑到promise,Set这样的新语法低版本浏览器不兼容的问题,这时候babel-polyfill就出场了。你只需要全局安装一下babel-polyfill:

npm install --save-dev babel-polyfill

然后在项目中使用一下它,代码就不存在刚才提到的兼容性问题了

import 'babel-polyfill';

但是,直接用babel-polyfill会有一些坑,第一个坑是污染全局环境,比如说低版本浏览器没有Set,但是babel-polyfill会在全局变量里加一个Set。再一个问题是,会造成代码冗余,举个例子,多个模块用到Promise,每个模块里都有可能独立存在一个对Promise做兼容性的代码。所以,使用babel-polyfill可以解决兼容性问题,但并不是最佳方案,于是,出现了babel-plugin-transform-runtime,使用这个插件,就可以解决上面的问题了。

5.babel-plugin-transform-runtime

安装:

npm install babel-plugin-transform-runtime --save-dev
npm install babel-runtime --save-dev

更改webpack的配置:

rules: [
  {
    test: /\.js$/,
    use: {
      loader: 'babel-loader',
      options: {
        presets: ['env'],        //也可以写成presets: ['babel-preset-env']
        plugins: ['transform-runtime']
      },
      exclude: '/node_modules/'
    }
  }
]
  1. .babelrc文件

==============

好了,到此为止,整个babel的配置差不多了,最后我们可以在工程目录下创建一个.babelrc文件,把关于babel的配置放进去:

{
  presets: [['babel-preset-env', {
    targets: {
      browser: ['> 1%', 'last 2 versions']
    }
  }]],
  plugins:['transform-runtime']
}

然后,webpack.config.js可以精简为:

rules: [
  {
    test: /\.js$/,
    use: {
      loader: 'babel-loader',
        },
    exclude: '/node_modules/'
    }
]
查看原文

赞 0 收藏 0 评论 0

黄东璐 发布了文章 · 9月11日

Vue和React的区别

一、监听数据变化的实现原理不同

  • Vue通过getter/setter以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
  • React默认是通过比较引用的方式进行的,如果不优化(pureComponent/shouldComponentUpdate)可能导致大量不必要的VDOM得重新渲染

为什么 React 不精确监听数据变化呢?这是因为 Vue 和 React 设计理念上的区别,Vue 使用的是可变数据,而React更强调数据的不可变。所以应该说没有好坏之分,Vue更加简单,而React构建大型应用的时候更加鲁棒。

二、数据流的不同

image.png

  • 大家都知道Vue中默认是支持双向绑定的。在Vue1.0中我们可以实现两种双向绑定:

    • 父子组件之间,props可以双向绑定
    • 组件与DOM之间可以通过v-model双向绑定
  • 在 Vue2.x 中去掉了第一种,也就是父子组件之间不能双向绑定了(但是提供了一个语法糖自动帮你通过事件的方式修改),并且 Vue2.x 已经不鼓励组件对自己的 props 进行任何修改了
  • 然而React从诞生之初就不支持双向绑定,React一直提倡的是单向数据流,他称之为 onChange/setState()模式。不过由于我们一般都会用 Vuex 以及 Redux 等单向数据流的状态管理框架,因此很多时候我们感受不到这一点的区别了。

三、HOC和mixins

在Vue中我们组合不同功能的方式是通过mixin,而在React中我们通过HOC(高阶组件)。React 最早也是使用 mixins 的,不过后来他们觉得这种方式对组件侵入太强会导致很多问题,就弃用了 mixinx 转而使用 HoC,关于mixin究竟哪里不好,可以参考React官方的这篇文章 Mixins Considered Harmful。而 Vue 一直是使用 mixin 来实现的。

但是为什么Vue不采用HOC的方式来实现呢?

因为高阶组件本质就是高阶函数,React的组件是一个纯粹的函数,所以高阶函数对React来说非常简单。但是Vue就不行了,Vue中组件是一个被包装的函数,并不简单的就是我们定义组件的时候传入的对象或者函数。比如我们定义的模板怎么被编译的?比如声明的props怎么接收到的?这些都是vue创建组件实例的时候隐式干的事。由于vue默默帮我们做了这么多事,所以我们自己如果直接把组件的声明包装一下,返回一个高阶组件,那么这个被包装的组件就无法正常工作了。

四、组件通信的区别

image.png
在Vue 中有三种方式可以实现组件通信

  • 父组件通过 props 向子组件传递数据或者回调,虽然可以传递回调,但是我们一般只传数据,而通过 事件的机制来处理子组件向父组件的通信
  • 子组件通过 事件 向父组件发送消息
  • 通过 V2.2.0 中新增的 provide/inject 来实现父组件向子组件注入数据,可以跨越多个层级。

在React中,也有对应的三种方式

  • 父组件通过 props 可以向子组件传递数据或者回调
  • 可以通过 context 进行跨层级的通信,这其实和 provide/inject 起到的作用差不多。

可以看到,React 本身并不支持自定义事件,Vue中子组件向父组件传递消息有两种方式:事件和回调函数,而且Vue更倾向于使用事件。但是在 React 中我们都是使用回调函数的,这可能是他们二者最大的区别。

五、渲染模版的不同

在表层上, 模板的语法不同

  • React是通过JSX渲染模版
  • 而Vue是通过一种拓展的HTML语法进行渲染

但其实这只是表面现象,毕竟React并不必须依赖JSX。在深层上,模板的原理不同,这才是他们的本质区别:

  • React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都是通过JS语法实现的
  • Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要 v-if 来实现

react中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以我们import 一个组件完了之后,还需要在 components 中再声明下,这样显然是很奇怪但又不得不这样的做法。

六、Vuex和Redux的区别

从表面上来说,store 注入和使用方式有一些区别。在 Vuex 中,$store 被直接注入到了组件实例中,因此可以比较灵活的使用:

  • 使用dispatch和commit提交更新
  • 通过mapState或者直接通过this.$store来读取数据

在 Redux 中,我们每一个组件都需要显示的用 connect 把需要的 props 和 dispatch 连接起来。

另外 Vuex 更加灵活一些,组件中既可以 dispatch action 也可以 commit updates,而 Redux 中只能进行 dispatch,并不能直接调用 reducer 进行修改。

从实现原理上来说,最大的区别是两点:

  • Redux 使用的是不可变数据,而Vuex的数据是可变的。Redux每次都是用新的state替换旧的state,而Vuex是直接修改
  • Redux 在检测数据变化的时候,是通过 diff 的方式比较差异的,而Vuex其实和Vue的原理一样,是通过 getter/setter来比较的(如果看Vuex源码会知道,其实他内部直接创建一个Vue实例用来跟踪数据变化)

七、diff算法不同

两者流程思维上是类似的,都是基于两个假设(使得算法复杂度降为O(n)):

  • 不同的组件产生不同的 DOM 结构。当type不相同时,对应DOM操作就是直接销毁老的DOM,创建新的DOM。
  • 同一层次的一组子节点,可以通过唯一的 key 区分。

但两者源码实现上有区别:

  • Vue基于snabbdom库,它有较好的速度以及模块机制。Vue Diff使用双向链表,边对比,边更新DOM
  • React主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。

image.png

八、事件机制不同

Vue

  • Vue原生事件使用标准Web事件
  • Vue组件自定义事件机制,是父子组件通信基础
  • Vue合理利用了snabbdom库的模块插件

React

  • React原生事件被包装,所有事件都冒泡到顶层document监听,然后在这里合成事件下发。基于这套,可以跨端使用事件机制,而不是和Web DOM强绑定。
  • React组件上无事件,父子组件通信使用props
查看原文

赞 0 收藏 0 评论 0

黄东璐 发布了文章 · 9月11日

理解JavaScript中的设计模式

一、什么是设计模式

在软件工程中,设计模式是软件设计中常见问题可重用的解决方案。设计模式代表着经验丰富的软件开发人员使用的最佳实践。设计模式可以被认为是编程模板。

二、为什么要使用设计模式

许多程序员要么认为设计模式是浪费时间,要么他们不知道如何恰当地应用它们。 但是使用适当的设计模式可以帮助你编写更好,更易理解的代码,并且代码可以轻松维护,因为它更容易理解。

最重要的是,设计模式为软件开发人员提供了一些沟通上的便利。 它们会立即向学习你代码的人显示你的代码的意图。

例如,如果你在项目中使用装饰者模式,那么新程序员将立即知道该代码正在做什么,并且他们可以更专注于解决业务问题,而无需花费精力去理解你的代码正在做什么。

现在我们知道了什么是设计模式,以及它们为什么重要,让我们深入研究 JavaScript 中使用的各种设计模式。

三、各种设计模式

1.模块模式

  • 模块是一段独立的代码,因此我们可以在不影响其他代码的情况下单独更新模块。 模块还允许我们为变量创建单独的作用域来避免命名空间的污染。 当它们与其他代码段分离时,我们也可以在其他项目中重用模块。
  • 模块是任何现代 JavaScript 应用程序不可或缺的一部分,有助于保持代码清洁,分离和组织。 有许多方法可以在JavaScript 中创建模块,其中一种是模块模式。
  • Bit之类的平台可以帮助将模块和组件转换为共享的构建块,可以与任何项目共享,发现和开发。 通过零重构,它是一种快速且可扩展的方式来共享和重用代码。
  • 与其他编程语言不同,JavaScript 没有访问修饰符的特性,也就是说,你不能将变量声明为私有(private)或公开(public)。 因此模块模式也常常被用于模拟封装的概念。
  • 此模式使用IIFE(立即调用的函数表达式),闭包和函数作用域来模拟此概念。 例如:
const myModule = (function() {
  
  const privateVariable = 'Hello World';
  
  function privateMethod() {
    console.log(privateVariable);
  }
 
  return {
    publicMethod: function() {
      privateMethod();
    }
  }
 
})();
 
myModule.publicMethod();

由于上面的代码是IIFE,代码会立即执行,返回的对象被分配给 myModule 变量。 由于闭包,即使在IIFE完成之后,返回的对象仍然可以访问 IIFE 内定义的函数和变量。

因此,在 IIFE 中定义的变量和函数对外部作用域来说基本上是隐藏的,因此它们对 myModule 变量是私有的。

执行代码后,myModule 变量如下所示:

const myModule = {
  publicMethod: function() {
    privateMethod();
  }
};

因此,我们可以调用 publicMethod(),转而调用 privateMethod()。例如:

// Prints 'Hello World'
module.publicMethod();

2.暴露模块模式(Revealing Module Pattern)

暴露模块模式是 Christian Heilmann 对模块模式略微的改进版本。 模块模式的问题是我们必须创建新的公开函数来调用私有函数和变量。

在暴露模块模式中,我们将返回的对象的属性映射到我们想要公开的私有函数。 这就是为什么它被称为暴露模块模式的原因。 例如:

const myRevealingModule = (function() {
  
  let privateVar = 'Peter';            //相当于是私有变量
  const publicVar  = 'Hello World';
 
  function privateFunction() {    //相当于是私有方法
    console.log('Name: '+ privateVar);
  }
  
  function publicSetName(name) {
    privateVar = name;
  }
 
  function publicGetName() {
    privateFunction();
  }
 
  /** reveal methods and variables by assigning them to object     properties */
 
return {
    setName: publicSetName,
    greeting: publicVar,
    getName: publicGetName
  };
})();
 
myRevealingModule.setName('Mark');
 
// prints Name: Mark
myRevealingModule.getName();

这种模式使我们更容易理解我们可以公开访问哪些函数和变量,这有助于代码的可读性。

代码执行后,myRevealingModule 如下所示:

const myRevealingModule = {
  setName: publicSetName,
  greeting: publicVar,
  getName: publicGetName
};

我们可以调用 myRevealingModule.setName('Mark'),来引用内部的 publicSetName ,以及调用myRevealingModule.getName() ,来引用内部的 publicGetName 。例如:

myRevealingModule.setName('Mark');
 
// prints Name: Mark
myRevealingModule.getName();

暴露模块模式相较于模块模式的优点:

  • 我们可以修改 return 语句中的一行代码,来将成员从 public(公开) 更改为 private(私有) ,反之亦然
  • 返回的对象不包含函数定义,所有右侧表达式都在 IIFE 中定义,使代码清晰易读。

3.ES6 模块(ES6 Modules)

在ES6之前,JavaScript 没有内置的模块系统,所以开发人员必须依赖第三方库或模块模式来实现模块化。但是在 ES6 中,JavaScript 拥有了原生的模块系统。

ES6 模块存储在单独的文件中。每个文件只能有一个模块。默认情况下,模块中的所有内容都是私有的。函数、变量和类使用 export 关键字来向外公开。模块内的代码总是在 严格模式(strict mode) 下运行。

(1)导出模块

导出函数和变量声明有两种方法:

  • 1) 通过在函数和变量声明前添加 export 关键字。例如:
// utils.js
export const greeting = 'Hello World';
 
export function sum(num1, num2) {
  console.log('Sum:', num1, num2);
  return num1 + num2;
}
 
export function subtract(num1, num2) {
  console.log('Subtract:', num1, num2);
  return num1 - num2;
}
 
// This is a private function
 
function privateLog() {
  console.log('Private Function');
}
  • 2) 通过在代码末尾添加 export 关键字,并包含我们要导出的函数和变量的名称.例如:
// utils.js
function multiply(num1, num2) {
  console.log('Multiply:', num1, num2);
  return num1 * num2;
}
function divide(num1, num2) {
  console.log('Divide:', num1, num2);
  return num1 / num2;
}
// This is a private function
function privateLog() {
  console.log('Private Function');
}
export {multiply, divide};

(2)导入模块

与导出模块类似,有两种方法可以使用 import 关键字导入模块。 例如:

  • 1) 一次导入多个项目
// main.js
 
// importing multiple items
import { sum, multiply } from './utils.js';
 
console.log(sum(3, 7));
console.log(multiply(3, 7));
  • 2)导入所有的模块
// main.js
 
// importing all of module
import * as utils from './utils.js';
 
console.log(utils.sum(3, 7));
console.log(utils.multiply(3, 7));

(3)导入/导出模块可以使用别名

如果要避免命名冲突,可以在导出和导入时使用别名。例如:

  • 1)重命名导出
// utils.js
 
function sum(num1, num2) {
  console.log('Sum:', num1, num2);
  return num1 + num2;
}
 
function multiply(num1, num2) {
  console.log('Multiply:', num1, num2);
  return num1 * num2;
}
 
export {sum as add, multiply};
  • 2)重命名导入
// main.js
 
import { add, multiply as mult } from './utils.js';
 
console.log(add(3, 7));
console.log(mult(3, 7));

4.单例模式(Singleton Pattern)

Singleton(单例) 是一个只能实例化一次的对象如果不存在,则单例模式会创建类的新实例如果存在实例,则它只返回对该对象的引用。 对构造函数的任何重复调用总是会获取相同的对象

JavaScript 一直支持单例模式。 我们只是不称他们为单例,我们称之为 对象字面量。 例如:

const user = {
  name: 'Peter',
  age: 25,
  job: 'Teacher',
  greet: function() {
    console.log('Hello!');
  }
};

因为 JavaScript 中的每个对象占用一个唯一的内存位置,当我们调用 user 对象时,我们实际上是返回对该对象的引用

如果我们尝试将用户变量复制到另一个变量并修改该变量。 例如:

const user1 = user;
user1.name = 'Mark';

我们会看到的结果是两个对象都被修改,因为 JavaScript 中的对象是通过引用而不是通过值传递的。所以内存中只有一个对象。例如:

// prints 'Mark'
console.log(user.name);
 
// prints 'Mark'
console.log(user1.name);
 
// prints true
console.log(user === user1);

可以使用构造函数实现单例模式。例如:

let instance = null;
 
function User() {
  if(instance) {        //先判断instance是否存在,如果存在则返回实例本身(不会创建新的实例);如果该对象不存在,则将该变量分配给instance变量
    return instance;
  }
 
  instance = this;
  this.name = 'Peter';
  this.age = 25;
  
  return instance;
}
 
const user1 = new User();
const user2 = new User();
 
// prints true
console.log(user1 === user2);

调用此构造函数时,它会检查 instance 对象是否存在。 如果该对象不存在,则将该变量分配给 instance 变量。如果对象存在,它只返回该对象。

单例模式也可以使用模块模式实现。 例如:

const singleton = (function() {
  let instance;
  
  function init() {
    return {
      name: 'Peter',
      age: 24,
    };
  }
 
  return {
    getInstance: function() {
      if(!instance) {            //如果实例已存在,则返回该实例
        instance = init();        //如果实例不存在,则实例话该对象
      }
      
      return instance;
    }
  }
})();
 
const instanceA = singleton.getInstance();
const instanceB = singleton.getInstance();            //返回的是instanceA
 
// prints true
console.log(instanceA === instanceB);

在上面的代码中,我们通过调用 singleton.getInstance 方法创建一个新实例。 如果实例已存在,则此方法仅返回该实例,如果实例不存在,则通过调用 init() 函数创建新实例

5.工厂模式(Factory Pattern)

  • 工厂模式是一种使用工厂方法创建对象的设计模式,而不指定创建对象的确切的类或构造函数。
  • 工厂模式用于在不公开实例化逻辑的情况下创建对象。当我们需要根据特定条件生成不同的对象时,可以使用此模式。例如:
class Car{
  constructor(options) {
    this.doors = options.doors || 4;
    this.state = options.state || 'brand new';
    this.color = options.color || 'white';
  }
}
 
class Truck {
  constructor(options) {
    this.doors = options.doors || 4;
    this.state = options.state || 'used';
    this.color = options.color || 'black';
  }
}
 
class VehicleFactory {
  createVehicle(options) {
    if(options.vehicleType === 'car') {
      return new Car(options);
    } else if(options.vehicleType === 'truck') {
      return new Truck(options);
      }
  }
}

在这里,我创建了一个 Car 和 Truck 类(带有一些默认值),用于创建新的 car 和 truck 对象。 我已经定义了一个 VehicleFactory 类,用于根据 options 对象中收到的 vehicleType 属性创建并返回一个新对象。

const factory = new VehicleFactory();
 
const car = factory.createVehicle({
  vehicleType: 'car',
  doors: 4,
  color: 'silver',
  state: 'Brand New'
});
 
const truck = factory.createVehicle({
  vehicleType: 'truck',
  doors: 2,
  color: 'white',
  state: 'used'
});
 
// Prints Car {doors: 4, state: "Brand New", color: "silver"}
console.log(car);
 
// Prints Truck {doors: 2, state: "used", color: "white"}
console.log(truck);

我创建了一个 VehicleFactory 类的新对象 factory 。之后,我们可以通过调用 factory.createVehicle 并,传递一个带有 carType 属性 options 对象,且值为 car 或 truck 的来创建一个新的 Car 或 Truck 对象。

6.装饰者模式

装饰者模式用于扩展对象的功能,而无需修改现有的类或构造函数。 此模式可用于向对象添加功能,而无需它们修改底层代码。

function Car(name) {
  this.name = name;
 
  // Default values
  this.color = 'White';
}
 
// Creating a new Object to decorate
const tesla= new Car('Tesla Model 3');
 
// Decorating the object with new functionality
 
tesla.setColor = function(color) {
  this.color = color;
}
 
tesla.setPrice = function(price) {
  this.price = price;
}
 
tesla.setColor('black');
tesla.setPrice(49000);
 
// prints black
console.log(tesla.color);

这种模式的一个更实际的例子是:

比方说,汽车的成本取决于它的功能数量。 如果没有装饰者模式,我们必须为不同的功能组合创建不同的类,每个类都有一个成本方法来计算成本。 例如:

class Car() {
}
 
class CarWithAC() {
}
 
class CarWithAutoTransmission {
}
 
class CarWithPowerLocks {
}
 
class CarWithACandPowerLocks {
}

但是使用装饰者模式,我们可以创建一个基类 ·Car`,并使用装饰者函数将不同配置的成本计算方法添加到其对象中。例如:

class Car {
  constructor() {
    // Default Cost
    this.cost = function() {
      return 20000;
    }
  }
}
 
// Decorator function
function carWithAC(car) {
  car.hasAC = true;
  const prevCost = car.cost();
  car.cost = function() {
    return prevCost + 500;
  }
}
 
// Decorator function
function carWithAutoTransmission(car) {
  car.hasAutoTransmission = true;
   const prevCost = car.cost();
  car.cost = function() {
    return prevCost + 2000;
  }
}
 
// Decorator function
function carWithPowerLocks(car) {
  car.hasPowerLocks = true;
  const prevCost = car.cost();
  car.cost = function() {
    return prevCost + 500;
  }
}

首先,我们创建一个基类 Car,用于创建 Car 对象。 然后,然后我们为了不同的功能创建了装饰者函数,并将 Car 对象作为参数传递。 然后我们覆盖该对象的成本函数,该函数返回汽车的更新成本,并向该对象添加新属性以指示添加了哪个特征。

class Car {
  constructor() {
    // Default Cost
    this.cost = function() {
      return 20000;
    }
  }
}
 
// Decorator function
function carWithAC(car) {
  car.hasAC = true;
  const prevCost = car.cost();
  car.cost = function() {
    return prevCost + 500;
  }
}
 
// Decorator function
function carWithAutoTransmission(car) {
  car.hasAutoTransmission = true;
   const prevCost = car.cost();
  car.cost = function() {
    return prevCost + 2000;
  }
}
 
// Decorator function
function carWithPowerLocks(car) {
  car.hasPowerLocks = true;
  const prevCost = car.cost();
  car.cost = function() {
    return prevCost + 500;
  }
}

首先,我们创建一个基类 Car,用于创建 Car 对象。 然后,然后我们为了不同的功能创建了装饰者函数,并将 Car 对象作为参数传递。 然后我们覆盖该对象的成本函数,该函数返回汽车的更新成本,并向该对象添加新属性以指示添加了哪个特征。

要添加新功能,我们可以执行以下操作:

const car = new Car();
console.log(car.cost());
 
carWithAC(car);
carWithAutoTransmission(car);
carWithPowerLocks(car);

最后,我们可以像这样计算汽车的成本:

// Calculating total cost of the car
console.log(car.cost());
查看原文

赞 0 收藏 0 评论 0

黄东璐 发布了文章 · 9月11日

中间人攻击

一、什么是中间人攻击

中间人攻击(Man-in-the-MiddleAttack,简称“MITM_攻击_”)是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方 直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻 击中,攻击者可以拦截通讯双方的通话并插入新的内容。中间人攻击是一个(缺乏)相互认证的攻击。大多数的加密协议都专门加入了一些特殊的认证方法以阻止中间人攻击。例如,SSL协议可以验证参与通讯的一方或双方使用的证书是否是由权威的受信 任的数字证书认证机构颁发,并且能执行双向身份认证。

二、中间人攻击的过程

1.客户端发送请求到服务端,请求被中间人截获

2.服务器向客户端发送公钥

3.中间人截获公钥,保留在自己手上。然后自己生成一个(伪造的)公钥,发给客户端。

4.客户端收到伪造的公钥后,生成加密的hash值发给服务器

5.中间人获得加密hash值,用自己的私钥解密获得真密钥。同时生成假的加密hash值,发给服务器。

6.服务器用私钥解密获得假密钥。然后加密数据传输给客户端。

image.png

三、防止中间人攻击的办法

1.使用受信任的CA证书

2.使用一次性密钥

查看原文

赞 0 收藏 0 评论 0

黄东璐 发布了文章 · 9月11日

输入url到显示页面的过程

整个过程包括两个子过程:导航渲染

一、导航

  1. 用户输入url并回车
  2. 浏览器进程检查url,组装协议,构成完整的url
  3. 浏览器进程通过进程间通信(IPC)把url请求发送给网络进程
  4. 网络进程接收到url请求后检查本地缓存是否缓存了该请求资源,如果有则将资源返回给浏览器进程
  5. 如果没有,网络进程向服务器发起http请求(网络请求),请求过程如下:

    1. 进行DNS解析,获取服务器的ip地址,端口

      1. 本地域名服务器查看本地缓存
      2. 根域名服务器
      3. 顶级域名服务器
      4. 权威域名服务器
    2. 利用ip地址和端口构建进程间通信的接口socket套接字,后续TCP端到端的传输的端也是指的是socket套接字
    3. 建立tcp连接(三次握手)
    4. 构造请求行、请求头和请求体,利用建立好的tcp连接发送构建好的请求信息
    5. 服务器收到请求信息,构造响应行、响应头和响应体,利用建立好的tcp连接发送给客户端
    6. 客户端浏览器的网络进程接收响应头和响应信息,并解析响应内容
  6. 网络进程解析响应流程

    1. 检查状态码,如果是301/302,则需要重定向,从Location自动读取地址,重新进行第4步操作,如果是200,则继续处理请求
    2. 200响应处理

      1. 检查响应的Content-Type,如果是字节流类型,则将该请求提交给下载管理器,该导航流程结束,不再进行后续的渲染。如果是text/html类型,则通知浏览器进程准备渲染进程准备进行渲染
  7. 准备渲染进程

    1. 浏览器进程检查当前的url是否和之前打开的页面的url主域名是否相同,如果相同,则复用之前页面的渲染进程。如果不同,则创建新的渲染进程
  8. 传输数据,更新状态

    1. 渲染进程准备好后,浏览器进程向渲染进程发送“提交文档”信号
    2. 渲染进程接收到信号后建立“管道”与网络进程进行数据的传输
    3. 数据传输完毕后,渲染进程发送“确认提交”信号给浏览器进程
    4. 浏览器进程接收到确认信号后更新浏览器界面的状态:安全信息、地址栏url、前进后退的历史状态、更新web页面等

二、渲染

  1. 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。(使用栈来实现,遇到“<p"就入栈,遇到"</p"就出栈
  2. 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。构造渲染树
  3. 创建布局树,并计算元素的布局信息。
  4. 对布局树进行分层,并生成分层树
  5. 为每个图层生成绘制列表,并将其提交到合成线程。
  6. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
  7. 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
  8. 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。

image.png

查看原文

赞 0 收藏 0 评论 0

黄东璐 发布了文章 · 9月11日

XSS攻击

一、概述

Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。

为了和 CSS 区分,这里把攻击的第一个字母改成了 X,于是叫做 XSS。

XSS 的本质是:恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。

而由于直接在用户的终端执行,恶意代码能够直接获取用户的信息,或者利用这些信息冒充用户向网站发起攻击者定义的请求。

二、XSS的危害

其实归根结底,XSS的攻击方式就是想办法“教唆”用户的浏览器去执行一些这个网页中原本不存在的前端代码

  • 窃取网页浏览中的cookie值
    在网页浏览中我们常常涉及到用户登录,登录完之后服务端会返回一个cookie值。这个cookie值相当于一个令牌,拿着这张令牌就等同于证明了你是某个用户。

    如果你的cookie值被窃取,那么攻击者很可能能够直接利用你的这张令牌不用密码就登录你的账户。如果想要通过script脚本获得当前页面的cookie值,通常会用到document.cookie

    试想下如果像空间说说中能够写入xss攻击语句,那岂不是看了你说说的人的号你都可以登录(不过某些厂商的cookie有其他验证措施如:Http-Only保证同一cookie不能被滥用)

  • 劫持流量实现恶意跳转

    这个很简单,就是在网页中想办法插入一句像这样的语句:

<script>window.location.href="http://www.baidu.com";</script>

三、XSS攻击的分类

1.反射型XSS(非持久型XSS)

也就是攻击相对于访问者而言是一次性的,具体表现在我们把我们的恶意脚本通过url的方式传递给了服务器,而服务器则只是不加处理的把脚本“反射”回访问者的浏览器而使访问者的浏览器执行相应的脚本

也就是说想要触发漏洞,需要访问特定的链接才能够实现。

2.储存型XSS(持久型XSS)

它与反射型XSS最大的不同就是服务器再接收到我们的恶意脚本时会将其做一些处理。

例如储存到数据库中,然后当我们再次访问相同页面时,将恶意脚本从数据库中取出并返回给浏览器执行这就意味着只要访问了这个页面的访客,都有可能会执行这段恶意脚本,因此储存型XSS的危害会更大。

还记得在文章开头提到的留言板的例子吗?那通常就是储存型XSS。当有人在留言内容中插入恶意脚本时,由于服务器要像每一个访客展示之前的留言内容,所以后面的访客自然会接收到之前留言中的恶意脚本而不幸躺枪。

这个过程一般而言只要用户访问这个界面就行了,不像反射型XSS,需要访问特定的URL。

四、防范手段

  • 首先是过滤。对诸如<script>、<img>、等标签进行过滤。
  • 其次是编码。像一些常见的符号,如<>在输入的时候要对其进行转换编码,这样做浏览器是不会对该标签进行解释执行的,同时也不影响显示效果。
  • 最后是限制。通过以上的案例我们不难发现xss攻击要能达成往往需要较长的字符串,因此对于一些可以预期的输入可以通过限制长度强制截断来进行防御。
查看原文

赞 0 收藏 0 评论 0

黄东璐 发布了文章 · 9月10日

HTTPS对称加密和非对称加密

//未完成

查看原文

赞 0 收藏 0 评论 0

黄东璐 发布了文章 · 9月10日

CSRF攻击

一、CSRF攻击

CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。

一张图说明CSRF:
image.png
通过这6个步骤达到攻击的目的。

简单来讲就是攻击者(黑客,钓鱼网站)盗用了你的身份,以你的名义发送恶意请求,这些请求包括发送邮件、发送消息、盗取账号、购买商品、银行转账,从而使你的个人隐私泄漏和财产损失。

以下条件二者缺一不可:

  • 登录受信任的网站A,并在本地生成Cookie
  • 在不登出A的情况下,访问危险网站B

CSRF防御方法:

1.验证HTTP Referer 字段

根据HTTP协议,在HTTP request头部有一个Referer字段它记录了该HTTP请求所在的地址,表示HTTP请求从哪个页面发出的。如果攻击者要实行CSRF攻击,那么他只能在自己的站点构造请求,此时Referer的指就是指向黑客自己的网站。因此,若想防御CSRF攻击,目标站点只要验证每一个请求的Referer值。

这种方法的好处就是简单易行,只需要在后台添加一个拦截器来检查Referer即可。然而这种方法并不是万无一失的,Referer的值是由浏览器提供的,一些低级的浏览器可以通过某种方式篡改Referer的值,这就给了攻击者可乘之机;而一些高级浏览器处于安全考虑,可以让用户设置发送HTTP请求时不再提供Referer值,这样当他们正常访问支付宝网站时,因为没有提供Referer值而被误认为CSRF攻击,拒绝访问。实际应用中通常采用第二种方法来防御CSRF攻击。

2.添加token验证

CSRF攻击之所以能够成功,是因为攻击者可以完全伪造用户的请求,该请求中所有的用户验证信息都存在cookie中,因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的cookie来通过安全验证。要防止CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于cookie之中。可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务器建立一个拦截器来验证这个token,如果请求中没有token或者token不正确,则认为可能是CSRF攻击而拒绝该请求。
现在业界一致的做法就是使用Anti CSRF Token来防御CSRF

  • 用户访问某个表单页面
  • 服务端生成一个Token,放在用户的Session中,或者浏览器的Cookie中
  • 在页面表单附带上Token参数
  • 用户提交请求后,服务端验证表单中的Token是否与用户Session(或Cookie)中的Token一致,一致为合法请求,不是则非法请求。

3.验证码

验证码,强制用户必须与应用进行交互,才能完成最终请求。通常情况下,验证码能够很好的遏制CSRF攻击。但是出于用户体验考虑,网站不能给所有的操作都加上验证码。因此验证码只能作为一种辅助手段。

4.尽量使用POST,限制GET

GET接口能够直接将请求地址暴露给攻击者,所以要防止CSRF一定最好不要用GET。当然POST并不是万无一失,攻击者只需要构造一个form表单就可以,但需要在第三方页面做,这样就增加了暴露的可能性。

5.在HTTP头部添加自定义属性

这种方法也是使用token并验证但是它是把token放在HTTP请求头部中。通过使用AJAX我们可以在我们的请求头部中添加我们的自定义属性,但是这种方法要求我们将整个站的请求全部改成AJAX,如果是新站还好,老站的话无疑是需要重写整个站点的,这是很不可取的。

查看原文

赞 0 收藏 0 评论 0

黄东璐 发布了文章 · 9月10日

Cookie的SameSite属性

Cookie的SameSite属性用来限制第三方Cookie,从而减少安全风险

它可以设置三个值:

  • Strict
  • Lax
  • None

1.Strict

Strict 最为严格,完全禁止第三方Cookie,跨站点时,任何情况下都不会发送Cookie。换言之,只有当前网页的URL与请求目标一致,才会带上Cookie。

Set-Cookie: CookieName=CookieValue; SameSite=Strict;

这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。

2.Lax

Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。

Set-Cookie: CookieName=CookieValue; SameSite=Lax;

导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。
image.png
设置了Strict或Lax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。

3.None

Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。

下面的设置无效:

Set-Cookie: widget_session=abc123; SameSite=None

下面的设置有效:

Set-Cookie: widget_session=abc123; SameSite=None; Secure            //需要同时设置Secure属性:Cookie只能通过https协议发送

Chrome 新增Cookie设置

Chrome80为用户引入了两个独立设置:

  • SameSite by default cookies(默认Cookies): 设置后,所有未指定的SameSite属性的Cookie将自动强制使用SameSite = Lax
  • Cookie without SameSite must be secure(没有SameSite的Cookie必须是安全的):设置后,没有SameSite属性或具有SameSite属性的Cookie Set-Cookie: SameSite = None 需要是安全的。 在此上下文中,安全是指所有浏览器请求都必须遵循 HTTPS 协议。不符合此要求的 Cookie 将被拒绝。所有网站都应使用HTTPS来满足此要求。
查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 5 次点赞
  • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 4月24日
个人主页被 224 人浏览