GithubHelp home page GithubHelp logo

articles's People

Contributors

vxee avatar

Watchers

 avatar  avatar

Forkers

yoghurtxu

articles's Issues

js 模拟实现new

什么是new?

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一

通过new 运算符创建的实例可以拥有构造函数里的属性,同时可以访问到构造函数原型(Function.prototype)中的属性。

假设有这样一个构造函数:

function Person (name, age) {
    this.name = name;
    this.age = age;

    this.habit = 'read books';
}

Person.prototype.home = 'Earth';

Person.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}

我们来写一个函数模拟实现new方法

function objectFactory(){
    var obj = new Object();
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    Constructor.apply(obj,arguments);
    return obj;
}
var person = objectFactory(Person, 'Jack', '18');
  1. 创建了一个空对象obj
  2. 通过shift方法将arguments的第一个参数即传入的构造函数移除并返回给Constructor这个变量
  3. 设置obj的原型指向构造函数的原型,这个obj就能访问构造函数原型中的属性。
  4. 使用apply,改变构造函数的this为obj,这样obj就可以拥有构造函数的一些属性。
  5. 返回obj

构造函数有返回值怎么办

构造函数返回一个对象
function Person (name, age) {
    this.strength = 60;
    this.age = age;
    return {
        name: name,
        habit: 'Games'
    }
}

var person = new Person('Jack', '18');

console.log(person.name) // Jack
console.log(person.age) // undefined

可以看到只能访问返回对象中的属性

构造函数返回基本类型的值
function Person (name, age) {
    this.strength = 60;
    this.age = age;
    return 'i am a person';
}

var person = new Person('Jack', '18');
console.log(person.name) // Jack
console.log(person.age) // 18

这是就和没有返回值的效果一样,person能访问到原型对象和构造函数中的属性。

因此我们要给模拟实现的new方法加上对构造函数返回类型的判断。

function objectFactory(){
    var obj = new Object();
    Constructor = [].shift.call(arguments);
    // obj.__proto__ = Constructor.prototype;
    Object.setPrototypeOf(obj, Constructor.prototype);
    var result = Constructor.apply(obj,arguments);
    // 判断result只能是object类型
    return result !== null && typeof result === 'object' ? result : obj;
}

Git学习

最近看到一个网址http://iissnan.com/progit/html/zh/ch1_0.html。准备开始系统地学习一下Git的使用。在此记录一下。

Git基础

在工作目录中初始化新仓库

$ git init

当前目录下会出现.git文件夹

$ git add

对文件进行跟踪。把目标文件快照放入暂存区域,也就是 add file into staged area,同时未曾跟踪过的文件标记为需要跟踪。

$ git add *.c
$ git add README
$ git commit -m 'initial project version'

从现有仓库中克隆

克隆仓库的命令格式为 git clone [url]

检查当前文件状态

$ git status

跟踪新文件

$ git add filename

忽略文件

首先创建一个.gitignore文件

$ cat .gitignore
# 此为注释 – 将被 Git 忽略
# 忽略所有 .a 结尾的文件
*.a
# 但 lib.a 除外
!lib.a
# 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO
/TODO
# 忽略 build/ 目录下的所有文件
build/
# 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
doc/*.txt
# ignore all .txt files in the doc/ directory
doc/**/*.txt

查看已暂存和未暂存的文件

$ git diff

此命令比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。

$ git diff --cached

查看已经暂存起来的文件和上次提交时的快照之间的差异。

提交更新

$ git commit -m 'some message'

跳过使用暂存区域

$ git commit -a -m 'added new benchmarks'

Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤。

移除文件

$ git rm [filename]

如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f

想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,仅是从跟踪清单中删除。

$ git rm --cached [filename]

移动文件

$ git mv file_from file_to

查看提交历史

$ git log

可选参数:
-p: 展开显示每次提交的内容差异
-2: 仅显示最近的两次更新

 git log -p -2

单词层面的对比

git log -U1 --word-diff

简化log输出

git log --pretty=oneline

撤销操作

修改最后一次提交

$ git commit --amend

取消已暂存的文件

git reset HEAD <file>

取消对文件修改

$ git checkout -- [filename]

远程仓库的使用

查看远程仓库

$ git remote -v

添加远程仓库

$ git remote add [shortname]  [url]

从远程仓库抓取数据

$ git fetch [remote-name]

推送数据到远程仓库

$ git push [remote-name] [branch-name]

查看远程仓库名称

$ git remote show [remote-name]

远程仓库的删除和重命名

$ git remote rename [old name] [new name]

Git 分支

创建分支

$ git branch [name]

HEAD是一个指向你正在工作中的本地分支的指针,创建了一个分支后并不会自动切换到这个分支中去。

切换分支

$ git checkout [name]

可以合并成一条命令,创建分支并切换

$ git checkout  -b [name]

分支合并

$ git merge [branchname]

推送本地分支

$ git push (远程仓库名) (分支名)

跟踪远程分支

$ git checkout -b [分支名] [远程名]/[分支名]

删除远程分支 即省略本地分支的名字

$ git push [远程名] :[分支名]

#git工具

交互式暂存

$ git add -i

储藏

只想切分支,不想提交正在进行的工作

$ git stash

显示储藏列表

$ git stash list

应用存储

$ git stash apply [名字]

background-clip属性和background-origin属性

background-clip和background-origin取值可以为border-box,padding-box,content-box.
可以使用background-clip改变背景的填充方式。
background在盒子模型中,它布满整个元素的盒子区域,并不是从padding就开始填充,从border就开始。
给div添加样式可以明显看到。因为默认的background-origin是border-box.
image
需要注意的是background-color从元素的边框左上角起到元素的边框右下角。
background-origin的作用是让图片能在边框上显示
background-origin: border;
image
同样设置background-clip: border;看看是什么效果
image
发现图片从padding-box的左上定点开始显示
总结一下:
background-clip规定背景的显示范围,是从border-box,还是padding-box或者content-box.
background-origin规定背景从哪里开始绘制,决定background-position的参照点是border-box还是padding-box或者是content-box的左上角。
所以当设置

background-clip: padding-box;
background-origin: border-box;

会出现背景图片被裁剪的效果,因为显示区域是从padding-box开始,虽然从border-box开始绘制。

函数节流和防抖

函数节流是指一定时间内js方法只执行一次。

function throttle(fn, interval){
    var last,
        timer,
        interval = interval || 200;
    return function(){
        var th = this,
            args = arguments;
        var now = new Date();
        if(last && now - last < interval){
            clearTimeout(timer);
            timer = setTimeout(function(){
                last = now;
                fn.apply(th, args);
            }, interval);
        }else{
            last = now;
            fn.apply(th,args);
        }
    }
}

它和防抖动最大的区别就是,节流函数不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而防抖动只是在最后一次事件后才触发一次函数。
原理是通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器。
通常用在resize(),onScroll()

函数防抖是指频繁触发的条件下,只有要足够的空闲时间,才执行代码一次。

function debounce(fn, delay){
    var timer,
        delay = delay || 200;
    return function(){
        var th = this;
        var args = arguments;
        if(timer){
            clearTimeout(timer);
        }
        timer = setTimeout(function(){
            timer = null;
            fn.apply(th,arg);
        },delay);
    }
}

每一次事件被触发,都会清除当前的定时器,然后重新设置超时调用。
这就会导致每一次高频事件都会取消前一次的超时调用,导致事件处理程序不能被触发。

通常用在验证码输入监听keydown事件。

函数柯里化

1.简单的柯里化

const sub_curry = function (fn) {
  const args = [].slice.call(arguments, 1);
  return function () {
    const newArgs = args.concat([].slice.call(arguments));
    return fn.apply(this, newArgs);
  };
};

使用

const add = function(a, b) {
  return a + b;
}
sub_curry(add, 1, 2); // 3

2.复杂的柯里化

const sub_curry = function (fn) {
  const args = [].slice.call(arguments, 1);
  return function () {
    const newArgs = args.concat([].slice.call(arguments));
    return fn.apply(this, newArgs);
  };
};

const curry = function (fn, length) {
  length = fn.length || length;

  const slice = Array.prototype.slice;

  return function () {
    if (arguments.length < length) {
      console.log("length", length);
      const combined = [fn].concat(slice.call(arguments));
      return curry(sub_curry.apply(this, combined), length - arguments.length);
    } else {
      return fn.apply(this, arguments);
    }
  };
};

使用

const fn0 = function (a, b, c, d, e) {
  return [a, b, c, d, e];
};

const fn1 = curry(fn0);
fn1("a", "b")("c", "d", "e"); // ["a", "b", "c", "d", "e"];

3.优美的柯里化

var curry = fn =>
    judge = (...args) =>
        args.length === fn.length
            ? fn(...args)
            : (arg) => judge(...args, arg)

SCSS学习

嵌套属性

在同一个命名空间中设置属性

.funky {
  font: {
    family: fantasy;
    size: 30em;
    weight: bold;
  }
}

// 编译后
.funky {
  font-family: fantasy;
  font-size: 30em;
  font-weight: bold;
}

命名空间也可以有自己的属性值

.funky {
  font: 20px/24px fantasy {
    weight: bold;
  }
}

/* 编译后 */
.funky {
  font: 20px/24px fantasy;
    font-weight: bold;
}

注释

SASS支持标准的多行注释/* *///
多行注释会被保留在输出的css中,而单行注释会被删除。

使用插值语句,可以将变量输出到多行注释中。如:

$version: "1.2.3";
/* This CSS is generated by My Snazzy Framework version #{$version}. */

SassScript

交互式shell

在命令行中输入sass -i可以进行测试

变量 $

$width: 5em;

/* 使用 */
#main {
  width: $width;
}

变量仅在它定义的选择嵌套层级的范围内可用
定义变量的时候可以后面带上!global标志,在这种情况下,变量在任何地方可见

#main {
  $width: 5em !global;
      width: $width;
}

#sidebar {
  width: $width;
}

/* 编译为 */
#main {
  width: 5em;
}

#sidebar {
  width: 5em;
}

数据类型

支持7种主要的数据类型:

  • 数字
  • 文本字符串
  • 颜色
  • 布尔值
  • 空值
  • 值列表(list)
  • maps

变量默认 !default

如果分配给变量的值后面加了!default标志意味着该变量如果已经赋值,那么它不会被重新赋值。如果尚未赋值,那么会被赋予新的给定值。
如果变量是null,将视为未赋值。

@规则和指令

@import

用于导入扩展名是.scss.sass的样式文件。
以下情况不会导入Sass文件

  • 如果文件的扩展名是.css
  • 如果文件以http:// 开始
  • 如果文件名是url()
  • 如果@import中包含任何的媒体查询

@extend

@extend告诉一个选择器的样式应该继承另一选择器

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.seriousError {
  @extend .error;
  border-width: 3px;
}

/* 编译为 */
.error, .seriousError {
  border: 1px #f00;
  background-color: #fdd;
}

.seriousError {
  border-width: 3px;
}

@at-root

可以使一个或多个规则被限定输出在文档的根层级上,而不是被嵌套在其父选择器下。

.parent {
  ...
  @at-root {
    .child1 { ... }
    .child2 { ... }
  }
  .step-child { ... }
}

/* 输出 */
.parent { ... }
.child1 { ... }
.child2 { ... }
.parent .step-child { ... }

@Degug

用来调试Sass文件

@debug 10em + 12em;

输出

Line 1 DEBUG: 22em

@warn

可以跟踪错误,也可以使用--quiet或者:quiet关闭警告
用法示例:

@mixin adjust-location($x, $y) {
  @if unitless($x) {
        @warn "Assuming #{$x} to be in pixels";
        $x: 1px * $x;
      }
      @if unitless($y) {
    @warn "Assuming #{$y} to be in pixels";
        $y: 1px * $y;
      }
      position: relative; left: $x; top: $y;
}

@if

p {
  @if 1 + 1 == 2 { border: 1px solid;  }
  @if 5 < 3      { border: 2px dotted; }
  @if null       { border: 3px double; }
}

编译为:

p {
  border: 1px solid; }

@for

用于重复输出一组样式。
@for $var from <start> through <end>
@for $var from <start> to <end>
两者的区别是through能取到end,而to取不到。

@each

@each $var in <list or map>

@each $animal in puma, sea-slug, egret, salamander {
      .#{$animal}-icon {
        background-image: url('/images/#{$animal}.png');
  }
}

输出

.puma-icon {
  background-image: url('/images/puma.png'); }
.sea-slug-icon {
  background-image: url('/images/sea-slug.png'); }
.egret-icon {
  background-image: url('/images/egret.png'); }
.salamander-icon {
  background-image: url('/images/salamander.png'); }

多重赋值

@each $animal, $color, $cursor in (puma, black, default),
                                      (sea-slug, blue, pointer),
                                      (egret, white, move) {
      .#{$animal}-icon {
        background-image: url('/images/#{$animal}.png');
        border: 2px solid $color;
        cursor: $cursor;
  }
}

编译为:

.puma-icon {
  background-image: url('/images/puma.png');
  border: 2px solid black;
  cursor: default; }
.sea-slug-icon {
  background-image: url('/images/sea-slug.png');
  border: 2px solid blue;
  cursor: pointer; }
.egret-icon {
  background-image: url('/images/egret.png');
  border: 2px solid white;
  cursor: move; }

同时也支持键值对的列表

@each $header, $size in (h1: 2em, h2: 1.5em, h3: 1.2em) {
  #{$header} {
        font-size: $size;
  }
}

编译为:

h1 {
  font-size: 2em; }
h2 {
  font-size: 1.5em; }
h3 {
  font-size: 1.2em; }

@while

$i: 6;
    @while $i > 0 {
  .item-#{$i} { width: 2em * $i; }
      $i: $i - 2;
}

编译为:

.item-6 {
  width: 12em; }

.item-4 {
  width: 8em; }

.item-2 {
  width: 4em; }

混入指令(Mixin Directives)

定一个large-text

@mixin large-text {
  font: {
    family: Arial;
    size: 20px;
    weight: bold;
  }
  color: #ff0000;
}

引入混合样式@include

.page-title {
  @include large-text;
  padding: 4px;
  margin-top: 10px;
}

编译为

.page-title {
  font-family: Arial;
  font-size: 20px;
  font-weight: bold;
  color: #ff0000;
  padding: 4px;
  margin-top: 10px; }

谈谈http1、http2、https

什么是http1、http2

http从http1到http1.1再到http2,目前用的最多的应该是http1.1了。但是http1.1相对http2来说,并不安全,谷歌浏览器在访问一些http的网站时,会提示你这不是一个安全的连接。

那么http2相对http1.1有什么优势呢?

先来说一说http1.0和http1.1的区别。http1.0协议使用非持久连接,在非持久连接下,一个tcp连接只传输一个对象。http1.1协议默认使用持久连接,一个tcp连接上可以传送多个http请求和响应,减少了建立和关闭连接的消耗和延迟。持久连接的实现也是依靠增加了新的请求头Connection,值为keep-alive时,客户端通知服务器返回这次结果后保持连接;值为close时,则关闭。
http1.0不支持Host请求头字段,WEB浏览器无法使用主机头名来明确表示要访问服务器上的哪个WEB站点,这样就无法使用WEB服务器在同一个IP地址和端口号上配置多个虚拟WEB站点。
在HTTP 1.1中增加Host请求头字段后,WEB浏览器可以使用主机头名来明确表示要访问服务器上的哪个WEB站点,这才实现了在一台WEB服务器上可以在同一个IP地址和端口号上使用不同的主机名来创建多个虚拟WEB站点。

那么http2相对http1.1有什么优势呢?

http2最大的特点是多路复用,对用一个域的服务器只建立一次tcp连接,一次可加载多个资源,使用二进制帧传输,同时会对http头部进行压缩。

1. 多路复用
多路复用的优势在于可以用同一个连接传输多个资源,因此在http1.1做的一些优化操作就没有必要了,如雪碧图,合并css和js文件等。
2. server push
通常,在第一个资源html文件没有加载时,即客户端收到服务器发来的html,没有解析到<img>``<script>标签时,是不会开始向服务器请求数据的。但是http2可以提前将客户端需要的资源主动发送过来,加快了资源的加载时间
3. 报文头压缩和二进制编码
使用二进制传输在于它更接近计算机存储形式,传统的文本流传输形式存在一些解析上的复杂性,如对消息长度(Message Length)的解析要分为5种情况。
报文头压缩是第一次发送完整的报文头,而第二次只要发送必要的请求头字段即可,这个的实现要求服务器和客户端共同维护一个报头表。

http2的兼容性如何?

对IE来说只有windows10的IE11支持
如果浏览器不支持http2也没事,因为在建立http连接时,浏览器会发送一个client hello 包,这个包里会说明它是否支持http2。

https

当客户端和服务器之间传输数据时,都会经过一个中间人,可能是运营商,也可能是不怀好意的人,由于采用http连接,不加密,因此都有可能窃取或者篡改你的数据再发给服务器。在一些博客或者网站页面上出现弹窗广告就很有可能是被劫持了。

中间人攻击的两种方式

1. 域名污染
由于我们访问域名需要先进行域名解析,即向DNS服务器请求某个域名的IP地址,在经过DNS的中间链点可能会进行抢答,返回一个错误的IP地址,IP地址就指向中间人。
2. ARP欺骗
路由器会通过APR广播查询某个IP的设备的物理地址,由于是广播的,因此所有机器都可以收到,这时就可以回应一个假的信息。
https是解决中间人攻击的唯一方法。这时候就要讲到https的连接方法。

https连接方法

通俗的来讲,服务器发送证书给浏览器,浏览器检查证书正确,并检查证书中对应的主机名是否正确,如果正确则双方加密数据后发送给对方。
https = http + tls

https的不足

  1. 建立https需要花费一点时间(~0.3)
  2. 数据需要加密和解密,占用一定的CPU
  3. 加密后数据比原来大,占用更多带宽

koa源码阅读

koa源码解读

application.js

koa的入口文件。

定义了一个类Application继承自nodejs的eventEmitter(有on,emit方法来注册和触发事件)。

listen(...args)
封装了原生nodejs的http.createServer方法。

toJSON()
only这个模块返回一个this中属性为'subdomainOffset','proxy', 'env'的对象。

use(fn)
做了两件事,假如传入的方法是构造器写法,则用koa-convert这个方法包装一下,转成async/await写法,并用depd模块给出一个提示信息,提示3版本后不再支持构造器写法。接着把这个方法放入middleware数组中。

callback()
首先要讲到compose,这个方法将多个中间件函数合并成一个大的中间件函数,然后调用这个中间件函数就可以依次执行添加的中间件函数,执行一系列的任务。

JavaScript设计模式与开发实践【第一部分】

今天开始阅读《JavaScript设计模式与开发实践》,对于设计模式的学习一直渴望已久。

设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。

其实平时在工作中不知不觉在使用某些设计模式,只是我们不知道而已。

动态类型语言和静态类型语言

  • 静态类型语言在编译时便已确定变量的类型,而动态类型语言的变量类型要到程序运行的时 候,待变量被赋予某个值之后,才会具有某种类型。
  • 静态类型语言的优点首先是在编译时就能发现类型不匹配的错误,编辑器可以帮助我们提前 避免程序在运行期间有可能发生的一些错误。并对程序进行一些优化工作,提高程序执行速度。
  • 动态类型语言对变量类型的宽容给实际编码带来了很大的灵活性。由于无需进行类型检测,我们可以尝试调用任何对象的任意方法,而无需去考虑它原本是否被设计为拥有该方法。
    这里就有一个 鸭子类型 的概念。

“如果它走起 路来像鸭子,叫起来也是鸭子,那么它就是鸭子。”

利用鸭子类型的**,我们 不必借助超类型的帮助,就能轻松地在动态类型语言中实现一个原则:“面向接口编程,而不是 面向实现编程”。

多态

多态的最根本好处在于,你不必再向对象询问“你是什么类型”而后根据得到的答案调用对象的某个行为——你只管调用该行为就是了,其他的一切多态机制都会为你安排妥当。

JavaScript不会进行类型检查,很容易实现多态,只要你具有这个行为就好。而静态类型语言需要被设计为可以向上转型:当给一个类变量 赋值时,这个变量的类型既可以使用这个类本身,也可以使用这个类的超类。

封装

封装的目的是将信息隐藏,封装应该被视为“任何形式的封装”,也就是说,封装不仅仅是隐藏数据,还包括隐藏实现细节、设计细节以及隐藏对象的类型等。

原型模式

在 JavaScript中,每个对象都是从 Object.prototype 对象克隆而来的,这样的话, 我们只能得到单一的继承关系,即每个对象都继承自 Object.prototype 对象,这样的对象系统显然是非常受限的。
但是我们可以动态地指向其他对象。这样一来,当对象 a 需 要借用对象 b 的能力时,可以有选择性地把对象 a 的构造器的原型指向对象 b,从而达到继承的效果。如下代码就是常用的原型链继承方式:

var obj = {
    name: 'sven'
};

var A = function(){};
A.prototype = obj;

var a = new A();
console.log(a.name); //  sven

JavaScript设计模式与开发实践【第二部分】

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等。

实现单例模式

var Singleton = function(name){
    this.name = name;
    this.instance = null;
}
Singleton.prototype.getName = function(){
    console.log(this.name);
}
Singleton.getInstance = function(name){
    if(!this.instance){
        this.instance = new Singleton(name);
    }
    return this.instance;
}
var a = Singleton.getInstance('svent1');
var b = Singleton.getInstance('sven2');
console.log(a === b); // true

用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。

惰性单例

惰性单例指的是在需要的时候才创建对象实例。

// fn是具体的操作逻辑
var getSingle = function(fn){
    var result;
    return function(){
        return result || (result = fn.apply(this,arguments));
    }
}

策略模式

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

没有很通用的模式代码,只有针对具体需求,将不同的算法或者行为被封装在各个策略类中,Context 将请求委托给这些策略对象,这些策略对象会根据请求返回不同的执行结果,这样 便能表现出对象的多态性。

代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。
代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之 后,再把请求转交给本体对象。

保护代理和虚拟代理

代理可以直接拒绝掉一些请求,称为保护代理。
虚拟代理把一些开销很大的对象延迟到真正需要它的时候才创建。

var myImage = (function () {
      var imgNode = document.createElement('img');
      document.body.appendChild(imgNode);

      return {
        setSrc: function (src) {
          imgNode.src = src;
        }
      }
})();

var proxyImage = (function(){
      var img = new Image;
      img.onload = function(){
        myImage.setSrc(this.src);
      }
      return {
        setSrc: function(src){
          myImage.setSrc('./loading.png');  // loading 占位图
          img.src = src;
        }
      }
})();
proxyImage.setSrc('http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg');

通过 proxyImage 间接地访问 MyImage。proxyImage 控制了客户对 MyImage 的访问,并且在此过程中加入一些额外的操作,比如在真正的图片加载好之前,先把 img 节点的 src 设置为一张本地的 loading 图片。

css中的层叠

在默认情况下元素没有设置z-index,该如何判断哪个元素在上,哪个在下?是html中的位置前后吗?其实并不是。有一个层叠顺序图。
image
那么会疑惑,什么是层叠上下文呢?
这是HTML元素的三维概念,HTML 元素依据其自身属性按照优先级顺序占用层叠上下文的空间。
如何触发一个元素形成堆叠上下文?

  • 根元素 (HTML),
  • z-index 值不为 "auto"的 绝对/相对定位,
  • 一个 z-index 值不为 "auto"的 flex 项目 (flex item),即:父元素 display: flex|inline-flex,
  • opacity 属性值小于 1 的元素(参考 the specification for opacity),
  • transform 属性值不为 "none"的元素,
  • mix-blend-mode 属性值不为 "normal"的元素,
  • filter值不为“none”的元素,
  • perspective值不为“none”的元素,
  • isolation 属性被设置为 "isolate"的元素,
  • position: fixed
  • 在 will-change 中指定了任意 CSS 属性,即便你没有直接指定这些属性的值
  • -webkit-overflow-scrolling 属性被设置 "touch"的元素

在层叠上下文中,其子元素同样也按照上面解释的规则进行层叠。 特别值得一提的是,其子元素的 z-index 值只在父级层叠上下文中有意义。意思就是父元素的 z-index 低于父元素另一个同级元素,子元素 z-index 再高也没用。

react-router学习

由于我看的是4.X版本,有些用法和2、3版本有点区别。

Link 需要从react-router-dom中去引用。

Route不允许这样嵌套。

<Route path="/" component={Home} >
	<Route path="about" component={About} />
	<Route path="inbox" component={Inbox} />
	<Route render={() => <h6>404</h6>} />
</Route>

应该是平级的书写方式

<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/inbox" component={Inbox} />
<Route render={() => <h6>404</h6>} />

可以嵌套一个Switch来只匹配一个组件渲染。

import { Route } from 'react-router';
import { Switch } from 'react-router-dom';

<Switch>
	<Route exact path="/" component={Home} />
    <Route path="/about" component={About} />
    <Route path="/inbox" component={Inbox} />
    // 当没有任何匹配时,会渲染一个404
    <Route render={() => <h6>404</h6>} />
</Switch>

路径匹配语法

<Route path="/hello/:name">         // 匹配 /hello/michael 和 /hello/ryan
<Route path="/hello(/:name)">       // 匹配 /hello, /hello/michael 和 /hello/ryan
<Route path="/files/*.*">           // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg

withRouter

import { withRouter } from 'react-router';

const ShowTheLocation = ({ match, location, history}) => {
    return (
    	<div>You are now at {location.pathname}</div>
    );
}
export default withRouter(ShowTheLocation);

withRouter是一个hoc,可以把路由的一些信息当作props传递到组件中,并返回一个新的组件。

js 模拟实现call和apply

众所周知,call和apply可以使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。这两个方法的区别是apply只能接收指定this和一个参数数组,而call可以接受若干个参数。

call

举个例子:

var foo = {
    value: 1
};
function bar(){
    console.log(this.value);
}
bar.call(foo); // 1

可以看到call方法绑定了this为foo,然后执行了bar方法

模拟实现

在调用call时,把foo改成这样:

var foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
};

foo.bar(); // 1

我们可以给foo方法添加一个bar属性,然后在执行完foo.bar方法后把bar属性删除

Function.prototype.call2 = function(context){
    // 首先要获取调用call的函数,用this可以获取
    context.fn = this;
    // 执行函数
    context.fn();
    // 删除属性
    delete context.fn;
}

var foo = {
    value: 1
};
function bar(){
    console.log(this.value);
}
bar.call(foo); // 1

call函数还能接收参数

实现如下,主要用到了evel()拼成一个函数,其中的args会自动调用Array.toString方法

Function.prototype.call2 = function(context){
    context.fn = this;
    var args = [];
    for(var i = 1; i < arguments.length; i++){
        args.push('arguments[' + i + ']');
    }
    eval('context.fn(' + args + ')');
    delete context.fn;
}

// 测试
var foo = {
    value: 1
};
function bar(newValue){
    this.value = newValue;
    console.log(this.value);
}
bar.call(foo,2); // 2

目前实现的call方法还存在两个问题:

  • call()可能会指定this为null,此时this指向window
  • 调用call的函数是可以有返回值的
    实现代码如下:
Function.prototype.call2 = function(context){
    context = context || window;
    context.fn = this;
    var args = [];
    for(var i = 1; i < arguments.length; i++){
        args.push('arguments[' + i + ']');
    }
    var result = eval('context.fn(' + args + ')');
    delete context.fn;
    return result;
}

// 测试
var foo = {
    value: 1
};
function bar(newValue){
    this.value = newValue;
    return {
        value: this.value
    };
}
console.log(bar.call(foo,2)); // {value: 2}

apply

这里直接参考别人实现的apply代码:

Function.prototype.apply2 = function (context, arr) {
    var context = context || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn;
    return result;
}

多列等高布局

以下列的布局为例

<div id="container">
    <div class="left">多列等高布局左</div> 
    <div class="right">多列等高布局右</div>
</div>

1.flex布局实现

.container{
    display: flex;
}
.left, .right {
    flex: 1;
}

2.利用正padding和负margin相抵实现

#container {
      width: 200px;
      overflow: hidden;
}
.left, .right{
      width:40%;
      float: left;
      padding-bottom: 5000px;
      margin-bottom: -5000px;
      border: 1px solid red;
}

3.父容器设置背景色

 #container {
            width: 400px;
            margin: 0 auto;
            background-color: deeppink;
            overflow: hidden;
        }
        .left, .right{
            width: 200px;
            float: left;
            font-size: 16px;
            line-height: 24px;
            color: #444;
        }
        .right {
            background-color: blue;
        }

4.table-cell 实现

#container {
            width: 400px;
            margin: 0 auto;
            overflow: hidden;
            display: table;
            background:#eee;
        }

        .left,
        .right {
            display: table-cell;
            width: 200px;
            float: left;
            font-size: 16px;
            line-height: 24px;
            color: #444;
        }
        .left {
            background-color: deeppink;
        }
        .right {
            background-color: blue;
        }

从lie.js源码分析Promise实现

Promise的实现有很多库,如jQuery的deferred,还有很多提供polyfill的,如es6-promise,lie等,它们的实现都基于Promise/A+标准,这也是ES6的Promise采用的。
为了了解Promise的内部实现机制,通过引入一个lie.js来分析,如果用原生的promise不能进入调试,因此借助lie.js来分析。
首先

npm install lie --save-dev

接着需要HTML

<!doctype html>
<html>
    <head>
        <script src="./node_modules/lie/dist/lie.js"></script>
        <script src="./test.js"></script>
    </head>
    <body>
    </body>
</html>

test.js代码如下

console.log(Promise);
new Promise(resolve => {
    console.log(1);
    resolve(3);
    Promise.resolve().then(()=> console.log(4))
}).then(num => {
    console.log(num)
});
console.log(2);

在浏览器中查看时会发现
image
说明已成功用lie.js的promise覆盖了原生的promise
可以看到

// Promise函数接受一个function
function Promise(resolver) {
  if (typeof resolver !== 'function') {
    throw new TypeError('resolver must be a function');
  }
  this.state = PENDING; // 设置状态值为进行中
  this.queue = [];
  this.outcome = void 0;
  if (resolver !== INTERNAL) {
    safelyResolveThenable(this, resolver);
  }
}

Promise接收两个参数,分别是resolve和reject的方法

let resolver = function(resolve, reject) {
    if (success) resolve();
    else reject();
};

new Promise(resolver);

然后将这个resolver方法传入一个处理函数中
image
接着会执行tryToUnwrap方法,会开始执行之前resolve里定义的方法。这时执行到resolve(1)
image
首先会将3传入,然后执行handlers.resolve方法
image
image

这个函数设置了self的state状态为FULFILLED(完成),并且把结果outcome设置为调resolve传进来的值,这里是3,如果resolve传来是一个Promise的话就会进入到Promise链处理,最后返回一个promise对象。
接下来看一下then()方法
image
then同样也接收两个回调方法,并会重新创建一个promise对象,由于在resolver里面已经把state置成fulfilled完成态了,所以它会执行unwrap函数,并传递成功回调、以及resolve给的结果outcome(还有一个参数promise,主要是用于返回,形成then链)。

看一下unwrap方法
image

他用了immediate方法,这个方法也是使用了immediate库,它可以实现一个nextTick的功能,即在当前代码逻辑单元同步执行完了之后立刻执行,相当于setTimeout 0,但是它又不是直接用setTimeout 0实现的。
来看一下Mutation的实现方式

  if (Mutation) {
    var called = 0;
    var observer = new Mutation(nextTick);
    var element = global.document.createTextNode('');
    // 监听节点的data属性的变化
    observer.observe(element, {
      characterData: true
    });
    scheduleDrain = function () {
      // 让data属性发生变化,在0/1之间不断切换,
      // 进而触发observer执行nextTick函数
      element.data = (called = ++called % 2);
    };
  }

MutationObserver可以用它来监听DOM结点的变化,如增删、属性变化等。
immediate实现是这样的

  if (Mutation) {
    var called = 0;
    var observer = new Mutation(nextTick);
    var element = global.document.createTextNode('');
    // 监听节点的data属性的变化
    observer.observe(element, {
      characterData: true
    });
    scheduleDrain = function () {
      // 让data属性发生变化,在0/1之间不断切换,
      // 进而触发observer执行nextTick函数
      element.data = (called = ++called % 2);
    };
  }

使用nextTick回调注册一个observer观察者,然后创建一个DOM节点element,成为observer的观察对象,观察它的data属性。当需要执行nextTick函数的时候,就调一下scheduleDrain改变data属性,就会触发观察者的回调nextTick。它是异步执行的,在当前代码单元执行完之后立刻之行,但又是在setTimeout 0之前执行的。
这个时候,就可以回答为什么上面代码的输出顺序是123,而不是132了。第一点可以肯定的是1是最先输出的,因为new一个Promise之后,传给它的resolver同步执行,所以1最先打印。执行了resolve(3)之后,就会把当前Promiser对象的state改成完成态,并记录结果outcome。然后跳出来执行then,把传给then的成功回调给immediate在nextTick执行,而nextTick是使用Mutation异步执行的,所以3会在2之后输出。
接下来会研究下nextTick的执行过程。
研究了一下发现用到了队列。

我们来看看自己怎么实现一个Promise!

手写Promise

首先 Promise是一个类 于是就用到了class来定义

class Promise{

    constructor(executor){
        // 初始化state为等待态
        this.state = 'pending';
        // 成功的值
        this.value = undefined;
        // 失败的原因
        this.reason = undefined;
        // 成功存放的数组
        this.onResolvedCallbacks = [];
        // 失败存放的数组
        this.onRejectedCallbacks = [];
        let resolve = value => {
            if(this.state === 'pending'){
                this.state = 'fulfilled';
                this.value = value;
            }
        };
        let reject = reason => {
            if(this.state === 'pending'){
                this.state = 'rejected';
                this.reason = reason;
            }
        };
        try{
            executor(resolve,reject);
        } catch (err) {
            reject(err);
        }
    }
    then(onFulfilled, onRejected){
        if(this.state === 'fulfilled') {
            onFulfilled(this.value);
        };
        if(this.state === 'rejected') {
            onRejected(this.reason);
        }
    }
}

let p = new Promise((resolve, reject) =>{
    console.log(1);
    resolve('success');
})
p.then(resolve => {
    console.log(resolve);
},reject => {
    console.log(reject);
})

可以看到输出了1 success 看起来是简单实现了Promise
之前一直疑惑不解的如何将成功的value值传给onFulfilled(),原来是将值存在Promise对象一个属性中,同样有一个属性存放当前Promise的执行状态,如果是fulfilled就将resolve的value值传给onFulfilled(),其实整个过程还是一个同步的执行顺序。
那么如何实现异步呢
但是当我们将resolve放在定时器setTimeout中,then时状态还是pending,我们就需要在then调用的时候将成功和失败存到各自的数组,一旦reject或者resolve就调用他们。

class Promise{

    constructor(executor){
        // 初始化state为等待态
        this.state = 'pending';
        // 成功的值
        this.value = undefined;
        // 失败的原因
        this.reason = undefined;
        // 成功存放的数组
        this.onResolvedCallbacks = [];
        // 失败存放的数组
        this.onRejectedCallbacks = [];
        let resolve = value => {
            if(this.state === 'pending'){
                this.state = 'fulfilled';
                this.value = value;
                // 一旦resolve执行,调用成功数组的函数
                this.onResolvedCallbacks.forEach(fn=>{
                    fn();
                })
            }
        };
        let reject = reason => {
            if(this.state === 'pending'){
                this.state = 'rejected';
                this.reason = reason;
                this.onRejectedCallbacks.forEach(()=>{
                    fn();
                })
            }
        };
        try{
            executor(resolve,reject);
        } catch (err) {
            reject(err);
        }
    }
    then(onFulfilled, onRejected){
        if(this.state === 'fulfilled') {
            onFulfilled(this.value);
        };
        if(this.state === 'rejected') {
            onRejected(this.reason);
        }
        // 当状态state为pending时
        if(this.state === 'pending') {
            // onFulfilled传入到成功数组
            this.onResolvedCallbacks.push(()=>{
                onFulfilled(this.value);
            })
            // onRejected传入到失败数组
            this.onRejectedCallbacks.push(()=>{
                onRejected(this.value);
            })
        }

    }
}

let p = new Promise((resolve, reject) =>{
    console.log(1);
    setTimeout(()=>{
        resolve('success');
    },100);
})
p.then(resolve => {
    console.log(resolve);
},reject => {
    console.log(reject);
})
console.log('3');

发现输出了1 3 success 看起来解决了resolve在setTimeout中执行的问题 但是并没有解决.then异步执行的问题,因此在方法中加上setTimeout来模拟实现。实际上是有区别的,任务队列分为macrotasks和microtasks,promise中的then方法的函数会被推入到microtasks中,setTimeout中的函数被推入到macrotasks中。任务队列中,在每一次事件循环中,macrotask只会提取一个执行,而microtask会一直提取,直到microsoft队列为空为止。当主线程执行完后会首先检查microtasks队列并完成里面的所有任务后再执行macrotask的任务。因此promise中的方法会先于setTimeout中执行。
完整代码

class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // onRejected如果不是函数,就忽略onRejected,直接扔出错误
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        // 异步
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'rejected') {
        // 异步
        setTimeout(() => {
          // 如果报错
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          // 异步
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          // 异步
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0)
        });
      };
    });
    // 返回promise,完成链式
    return promise2;
  }
}

js bind方法实现

bind方法可以用来给一个方法绑定上下文环境对象,以及重新给方法传参数。
bind的另一个简单使用是使一个函数拥有预设的初始参数。我们称为偏函数

function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

由于bind方法在并不是在所有的浏览器上都支持,因此我们考虑自己实现bind方法。
首先我们可以给目标函数指定作用域来简单实现bind

Function.prototype.bind = function(context){
    self = this;
    return function(){
        return self.apply(context, arguments);
    }
}

这样实现的bind方法就只能接受一个上下文环境变量的参数,不能同时接受参数。因此我们修改一下。

Function.prototype.bind = function(context){
    var slice = Array.prototype.slice,
        _args = slice.call(arguments,1),
        self = this;
    return function(){
        var _inargs = slice.call(arguments);
        return self.apply(context, _args.concat(_inargs));
    }
}

现在bind可以绑定对象,同时也能在绑定对象时传递参数。
但是bind还有一个特点:

一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
并看不懂什么意思 = = 其实就是bind返回的函数还能用做构造函数。bind 时指定的 this 值会失效,但传入的参数依然生效。

举个例子:

var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}

bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'daisy');

var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin

我们可以通过修改函数的返回原型来实现,代码如下:

Function.prototype.bind = function(context){
    var slice = Array.prototype.slice,
        _args = slice.call(arguments,1),
        self = this,
        fBound = function(){
            var _inargs = slice.call(arguments);
            // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
            // 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
            // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
            return self.apply((this instanceof fBound ? this : context), _args.concat(_inargs));
        };
        // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
        fBound.prototype = self.prototype;
        return fBound;
}

bound.prototype = this.prototype这么写的话,修改返回函数原型对象(bound.prototype)的同时把绑定函数的原型对象(this.prototype也同时修改了。因此用匿名函数做中转,this.protptype 就安全了。
还有几个小问题的解决:

  • 调用bind不是函数
  • bind兼容性的问题
    最终的完整代码如下:
fakeBind = function (context) {
    if (typeof this !== "function") {
        throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }
    var slice = Array.prototype.slice,
        _args = slice.call(arguments, 1),
        self = this,
        F = function () {},
        bound = function () {
            var _inargs = slice.call(arguments);
            return self.apply((this instanceof F ? this : context), _args.concat(_inargs));
        };
    F.prototype = self.prototype;
    bound.prototype = new F();
    return bound;
}

Function.prototype.bind = Function.prototype.bind || fakeBind;
ES6版实现
Function.prototype.fakeBindES6 = function(context, ...rest) {
  if (typeof this !== "function") {
    throw new Error("Bind must be called on a function");
  }
  var self = this;
  return function inner(...args) {
    if (this instanceof inner) {
      // 当返回的内层函数作为构造函数使用,bind 时绑定的 this 失效。
      // 即此处直接执行绑定函数,而不使用 apply 对 this 进行绑定 
      return new self(...rest, ...args);
    }
    // 当作为普通函数调用,this 指向传入的对象
    return self.apply(context, rest.concat(args));
  };
};

谈谈ES5中对象的创建和继承

对象的创建

对象是js中最常用到的一种类型。有两种方式可以创建一个Object实例,第一种是new 操作符➕Object构造函数,如

var person = new Object();
person.name = 'nike';

也可以用对象字面量表示法。如:

var person = {
    name: 'nike'
}

用以上的两种方法创建对象,存在缺点,使用用一个接口创建很多对象会产生大量重复的代码。因此就有了工厂模式。

工厂模式

自己定义一个函数来封装以特定接口创建对象的细节。如:

function createPerson(name,age){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.sayName = function(){
        console.log(ths.name);
    }
    return o;
}
var person1 = createPerson('nike',20);
var person2 = createPerson('rose',20);

工厂模式虽然解决了创建多个相似对象的问题,但是却不容易知道一个对象的类型。

构造函数模式

可以使用原生构造函数或者是自定义的构造函数。

function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    }
}
var person1 = new Person('nike',20);
var person2 = new Person('rose',20);
console.log(person1.constructor == Person) // true
console.log(person2 instanceof Person) // true

创建自定义构造函数意味着将来可以将它的实例标识为一种特定的类型。
考虑到将一些共同的方法也定义在构造函数中不是很妥。

原型模式

我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

function Person(){
}
Person.prototype.name = 'nike';
Person.prototype.age = 20;
Person.prototype.sayName = function(){
    console.log(this.name);
}
var person1 = new Person();
person1.sayName(); // 'nike'
var person2 = new Person();
console.log(person1.sayName === person2.sayName) // true

原型链的一个问题是,原型对象中的属性都是被所有实例所共享,当实例修改时,所有实例都会发生改变,所以实例应该要有一些自己私有的属性。

组合使用构造函数和原型模式

function Person(name, age){
    this.name = name;
    this.age = age;
}
Person.prototype = {
    constructor: Person,
    sayName: function(){
        console.log(this.name);
    }
}
var person1 = new Person('nike', 20);
person1.sayName(); // 'nike'

动态原型模式

为构造函数添加方法是在经过检查后发现没有这个方法,然后动态添加。

function Person(name, age){
    this.name = name;
    this.age = age;

    if(typeof this.sayName !== 'function'){
        Person.prototype.sayName = function(){
            console.log(this.name);
        }
    }
}

继承

原型链继承

function SuperType(){
    this.prototype = true;
}
SuperType.prototype.getSuperValue = function() {
    return this.prototype;
}
function SubType(){
    this.subproperty = false;
}

// 继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
    return this.subproperty;
}
var instance = new SubType();
console.log(instance.getSuperValue()) // true

缺点: 无法实现多继承,包含引用类型值的原型属性会被所有实例共享。子类无法向父类传递参数。

构造继承

通过在子类中使用call(),apply()执行父类的构造函数。

function SuperType(){
    this.colors = ["red","blue"];
}
function SubType(){
    SuperType.call(this);
}
var instance = new SubType();
instance.colors.push('black');
console.log(instance.colors); // [ 'red', 'blue', 'black' ]
var instance2 = new SubType();
console.log(instance2.colors); // [ 'red', 'blue' ]

使用call()或者apply()就可以实现子类向父类传递参数。
缺点: 只能继承父类的属性和方法,不能继承原型类上的属性和方法。

组合继承

function SuperType(name){
    this.name = name;
    this.colors = ["red","blue"];
}
SuperType.prototype.sayName = function(){
    console.log(this.name);
}
function SubType(name, age){
    // 继承属性
    SuperType.call(this, name);
    this.age = age;
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
    console.log(this.age);
}
var instance = new SubType('nike', 20);
instance.colors.push('black');
console.log(instance.colors); // [ 'red', 'blue', 'black' ]
instance.sayName(); // nike
instance.sayAge(); // 20

优点:可以继承父类的属性和方法,也能继承原型链上的属性和方法
缺点: 调用了两次父类构造函数,第一次是在创建子类原型时,第二次是在子类构造函数内部,生产了两份实例

寄生组合继承

function SuperType(name){
    this.name = name;
    this.colors = ["red","blue"];
}
SuperType.prototype.sayName = function(){
    console.log(this.name);
}
function SubType(name,age){
    // 继承属性
    SuperType.call(this, name);
    this.age = age;
}
(function(){
    // 创建一个没有实例方法的类
    var Super = function(){};
    Super.prototype = SuperType.prototype;
    // 将实例作为子类的原型
    SubType.prototype = new Super();
})()

这种方式只调用了一次父类构造函数,也避免了在subType.prototype上创建不必要的属性。推荐使用此方法。

vue-router简单实现

vue-router简单实现

通过配置路由对应的模版文件,监听当前路由去渲染相应的组件,当点击跳转链接时,用h5的window.history.pushState在历史记录里放入一条记录,并使得当前url变更为点击的url,该方法不会刷新页面,通过添加监听事件popstate来改变当前路由,然后重新渲染对应的组件。

redux源码阅读

redux主要对外提供了5个方法,分别是

  • createStore
  • combineReducer
  • bindActionCreators
  • applyMiddleware
  • compose

首先来看一下createStore方法

Function createStore(reducer, preloadedState, enhancer)

这个方法创建一个store来存放应用中的所有state,应用中有且仅有一个store。

第一个参数是一个reducer方法,第二个参数是初识时的state,也可以不填。

简化一下createStore方法

createStore = (reducer,  preloadedState, enhancer) => {
    let state;
    let listeners = [];
    
    getState = () => state;
    
    // 返回一个取消监听state改变的方法
    subscribe = listener => {
        let isSubscribed = true;
        listeners.push(listener);
        
        return unsubscribe = () => {
            if(!isSubscribed) {
                return;
            }
            isSubscribed = false;
            const index = listeners.indexOf(listener);
      	    listeners.splice(index, 1);
        }
    }
    
    dispatch = action => {
        state = reducer(state, action);
        
    	for (let i = 0; i < listeners.length; i++) {
      		const listener = listeners[i];
      		listener();
    	}
        return action;
    };
    
    // 初始化store
    dispatch({});
    
    return {
        getState,
        dispatch,
        subscribe
    }

}

接下来看一下combineReducers这个方法。

当应用变得复杂时,可以考虑将reducer函数拆分成多个单独的函数。这个函数可以让你拥有多个reducer,同时保持各自负责逻辑块的独立性。

简化一下代码,看看它的实现。

combineReducers = reducers => {
    // 参数reducers是一个reducer的对象
    const finalReducerKeys = Object.keys(reducers);
    return combination =>  (state = {}, action) => {
        let hasChanged = false;
        for (let i = 0; i < finalReducerKeys.length; i++) {
      		const key = finalReducerKeys[i];
      		const reducer = reducers[key];
      		const previousStateForKey = state[key];
      		const nextStateForKey = reducer(previousStateForKey, action);
      		
      		nextState[key] = nextStateForKey;
      		hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    	}
    	return hasChanged ? nextState : state;
    }
}

看一下bindActionCreators方法的实现。

这个方法把一个value为不同action creator的对象,转成拥有同名key的对象,同时使用dispatch对每个action creator进行包装,这样在调用action中的方法时不用再加上dispatch(fn),直接fn();

bindActionCreator = (actionCreator, dispatch) => {
  return () => {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

bindActionCreators = (actionCreators, dispatch) => {
	// actionCreators是一个对象或者一个方法
	// {
	//   addTodo: Function,
	//   removeTodo: Function
	// }
    if (typeof actionCreators === 'function') {
    	return bindActionCreator(actionCreators, dispatch);
    }
    const keys = Object.keys(actionCreators);
    const boundActionCreators = {}
    for (let i = 0; i < keys.length; i++) {
    	const key = keys[i];
    	const actionCreator = actionCreators[key];
    	if (typeof actionCreator === 'function') {
      		boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
    	}
    }
    return boundActionCreators;
}

惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。

compose函数可以从右到左来组合多个函数。这是函数式编程中的方法。

applyMiddleware方法有点难懂。

看一下源码

applyMiddleware = (...middlewares) => (createStore) => (...args) => {
    const store = createStore(...args);
    const middlewareAPI = {
        getState: store.getState,
        dispatch: (...args) => dispatch(...args)
    };
    // 一个中间层的方法数组,每个方法传递两个参数 getState 和 dispatch
    const chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);
    
    // 返回 一个经过多个中间层处理的store对象
    return {
        ...store,
        dispatch
    }; 
}

之所以能这样使用applyMiddleware是因为在createStore方法里传入了自身函数

function createStore(reducer, preloadedState, enhancer) {
    ...
    // 这里的enhancer通常是middlewares方法
    if (typeof enhancer !== 'undefined') {
      return enhancer(createStore)(reducer, preloadedState)
    }
    ...
}

webpack hmr初探

最初想了解一下react-hot-loader这个库,想知道这个库有什么用处。

目前我的认知是redux+webpackhot module replacement来实现当本地代码修改后,不会整个页面刷新,而只是更新我们修改的地方,从而可以保留我们更改之前的页面状态。

我觉得这是一个很酷的功能,解决了平时每次修改完代码后整个页面刷新,再测一些问题的时候又要再次去复现问题调试,用了react-hot-loader可以保留我们上次页面修改前的状态。但是我也了解到,这个库目前还存在几个没有解决的问题,毕竟做到状态保留在某些情况下还是有点困难。

我尝试着让我们工程的打包支持这个功能,然后又重新去读了一下打包的代码。有以下几点收获:

  1. 了解了webpack-merge,这个库可以用来合并webpack的一些配置数据,有一个merge.smart()方法在合并时会智能得合并一些配置。
  2. 目前我们用的是webpack-serve,但是看了一下官方已经不维护了,推荐使用webpack-dev-serve,后续我看一下怎么从webpack-serve迁移到webpack-dev-serve
  3. 了解了hmr的实现原理

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.