Giter Club home page Giter Club logo

learntolearn's People

Contributors

naseeihity avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

learntolearn's Issues

gulp配置

gulpfile.js

var gulp = require('gulp');
var minimist = require('minimist');

// //文件合并
// var concat = require('gulp-concat');
// //文件重命名
// var rename = require('gulp-rename');
// //代码混淆
// var uglify = require('gulp-uglify');
// //代码映射
// var source_map = require('gulp-sourcemaps');
// //css压缩
// var minify_css = require('gulp-clean-css');
// //静态文件MD5后缀
// var rev = require('gulp-rev');
// //文件替换
// var rev_collector = require('gulp-rev-collector');
//本地开发环境
// var connect = require('gulp-connect');
// var watch = require('gulp-watch');

//使用gulp-load-plugins自动命名gulp的相关包,通过package.json映射,驼峰命名
var $ = require('gulp-load-plugins')();

// 代理到远程服务器
var proxy = require('http-proxy-middleware');
// 文件操作
var fs = require('fs');
// 自动打开浏览器
var open = require('open');
//删除文件
var del = require('del');



// 导入配置文件
var cfg = require('./build/gulp_config.json');
var JS = cfg.js;
var CSS = cfg.css;
var HTML = cfg.html;
var REV = cfg.rev;
var js_css_src = [].concat(JS.src, CSS.src);

//默认任务开始入口
gulp.task('default', ['clean'], function() {
    //首先清空dist文件夹,再依次执行任务
    gulp.start('pack-js','pack-css');
});

//清空已有输出
gulp.task('clean', function() {
    return del(['dist/']);
});

//js文件合并压缩
gulp.task('pack-js', function() {
    return gulp.src(JS.src)               //需要合并的js文件
    .pipe($.sourcemaps.init())                //初始化sourcemap
    .pipe($.concat(JS.concat.main))               //合并js文件并命名
    .pipe(gulp.dest(JS.dest))          //将文件写入文件夹
    .pipe($.rename(JS.concat_min.main))           //设置混淆后的文件名
    .pipe($.uglify())                         //代码混淆
    .pipe($.sourcemaps.write('./maps/'))           //输出sourcemap
    .pipe(gulp.dest(JS.dest));         //输出最终的结果
});

//css文件合并压缩并增加md5后缀
gulp.task('pack-css', function() {
    return gulp.src(CSS.src)
        .pipe($.sourcemaps.init())
        .pipe($.concat(CSS.concat.main))
        .pipe($.cleanCss())                 //css代码压缩
        .pipe($.sourcemaps.write('./maps/')) 
        .pipe(gulp.dest(CSS.dest))
        .pipe($.rename(CSS.concat_min.main))
        .pipe($.rev())                        //增加md5后缀
        .pipe(gulp.dest(CSS.dest))
        .pipe($.rev.manifest())               //输出映射表用户替换增加md5后缀文件的引用
        .pipe(gulp.dest(REV.dest));
});

//md5后缀替换
gulp.task('rev', function() {
    return gulp.src([REV.src, HTML.index])    //设置映射表位置,以及需要进行路径名替换的文件
    .pipe(rev_collector())                  //替换
    .pipe(gulp.dest(REV.destH));        //输出
});

var devCfg = require('./build/dev_config.json');
var devLocal = devCfg.local;
var devProxy = devCfg.proxy;
var knownOptions = {
    string: 'host',
    default: { host: ''}//不设置时host默认指为true
};
var optionCL = minimist(process.argv.slice(2), knownOptions);

var proxyHost = optionCL.host || devProxy.host;
var PROXY_TARGET = (devCfg.https ? "https://" : "http://") 
                        + proxyHost + ":" + devProxy.port + devProxy.uri;

//本地服务
gulp.task('webserver', function() {
    $.connect.server({
        port: devLocal.port,
        root: devLocal.root,
        livereload: true,
        // nodev8.8以上版本无法自动开启https[https://github.com/AveVlad/gulp-connect/issues/236]
        https: false,
        // 为true时将采用默认配置
        // https:{
        //     key: fs.readFileSync(devLocal.key),
        //     cert: fs.readFileSync(devLocal.cert)
        // },
        fallback: devLocal.index,   //index重定向
        middleware: function(connect, opt) {
            return [
                proxy(devProxy.uri,  {
                    target: PROXY_TARGET,
                    changeOrigin:true
                }),
                // 可以代理到多个远程服务器
                proxy('/otherServer', {
                    target: 'https://IP:Port',
                    changeOrigin:true
                })
            ]
        }
    });
    // open('http' + devLocal.host + devLocal.port);
});

//将对处理后的文件的watch提出,使得各个任务更加纯粹
gulp.task('livereload', function() {
    gulp.src(js_css_src)
      .pipe($.watch(js_css_src))
      .pipe($.connect.reload());
});

//使用gulp-watch实现只重新编译被更改过的文件
gulp.task('watch', ['webserver', 'livereload'], function() {
    $.watch(JS.src, function() {
        gulp.start('pack-js');
    });
    $.watch(CSS.src, function() {
        gulp.start('pack-css');
    });
    //html文件不做任何处理直接重载
    $.watch(HTML.src).pipe($.connect.reload());
});

//雪碧图生成
gulp.task('sprite', function () {
    var spriteData = gulp.src('./src/imgs/*').pipe($.spritesmith({
      imgName: 'sprite.png',
      cssName: 'sprite.css'
    }));
    return spriteData.pipe(gulp.dest('./dist/sprite/'));
});

//gulp-imagemin未能成功启用
//gulp-imagemin: Couldn't load default plugin "optipng"
// gulp.task('pack-img', function() {
//     return gulp.src('./src/imgs/*')
//         .pipe(imagemin())
//         .pipe(gulp.dest('./dist/images/'));
// });

// Others
//gulp-autoprefixe   css自动加浏览器前缀
//gulp-base64 将图片转为base64,无请求,但会增大css文件大小
//postcss  引入css处理的各种工具
//lazypipe 创建流的工程方法,便于重用

package.json

{
  "name": "demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "gulp && gulp rev",
    "start": "gulp && gulp watch --host"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "del": "^3.0.0",
    "fs": "^0.0.1-security",
    "gulp": "^3.9.1",
    "gulp-clean-css": "^3.9.2",
    "gulp-concat": "^2.6.1",
    "gulp-connect": "^5.0.0",
    "gulp-load-plugins": "^1.5.0",
    "gulp-rename": "^1.2.2",
    "gulp-rev": "^8.1.1",
    "gulp-rev-collector": "^1.2.3",
    "gulp-sourcemaps": "^2.6.2",
    "gulp-uglify": "^3.0.0",
    "gulp-watch": "^4.3.11",
    "gulp.spritesmith": "^6.8.0",
    "http-proxy-middleware": "^0.17.4",
    "minimist": "^1.2.0",
    "open": "^0.0.5"
  },
  "dependencies": {}
}

dev_config.json

{
    "local": {
        "port": "3000",
        "key": "",
        "cert": "",
        "index": "./src/html/index.html",
        "root": ["./"],
        "host": "://localhost:"
    },
    "proxy": {
        "uri": "/api",
        "host": "3.121.1.111",
        "port": "1884"
    },
    "https": true    
}

gulp_conf.json

{
    "js": {
        "src": [
            "./src/js/*.js"
        ],
        "concat": {
            "main": "index.js"
        },
        "concat_min": {
            "main": "index.min.js"
        },
        "dest": "./dist/js/"
    },
    "css": {
        "src": [
            "./src/css/*.css"
        ],
        "concat": {
            "main": "style.css"
        },
        "concat_min": {
            "main": "style.min.css"
        },
        "dest": "./dist/css/"
    },
    "html": {
        "index": "./src/html/index.html",
        "src": [
            "./**/*.html"
        ],
        "dest": ""
    },
    "rev": {
        "src": "./dist/rev/*.json",
        "dest": "./dist/rev",
        "destH": "./src/html/"
    }
}

前端测试工具Jest——基础

准备

// 安装
npm install --save-dev jest

// 配置
{
  "scripts": {
    "test": "jest"
  }
}

// 执行
yarn test

匹配器

基本用法

test('a demo', () => {
    <!-- 期望与匹配器 -->
    expect(something).toBe(something);
});

常用匹配器

  • toBe 使用Object.is来判断对象
  • toEqual 可以用来判断对象键值相等
  • toBeCloseTo(number, numDigits) 用来判断浮点数相等
  • toBeNull
  • toBeUndefined
  • toBeDefined
  • toBeTruthy
  • toBeFalsy
  • toMatch 字符串正则匹配
  • toThrow 测试抛出错误
  • not
  • toHaveBeenCalled 确保一个mock函数被执行
  • toHaveBeenCalledTimes(number)
  • toHaveBeenCalledWith(arg1, arg2, ...)
  • toHaveBeenLastCalledWith(arg1, arg2, ...)
  • toBeGreaterThan
  • toBeGreaterThanOrEqual
  • toBeLessThan
  • toBeLessThanOrEqual
  • toBeInstanceOf(Class)
  • toContain(item)
  • toContainEqual(item)
  • toHaveLength(number)
  • toMatchObject(object)
  • toHaveProperty(keyPath, value)
  • toMatchSnapshot(optionalString)快照匹配
  • toThrowErrorMatchingSnapshot

匹配器扩展

  1. expect.extend({matchers}) 添加自己的匹配器
const diff = require('jest-diff');
expect.extend({
  toBe(received, expected) {
    const pass = Object.is(received, expected);

    const message = pass
      ? () =>
          this.utils.matcherHint('.not.toBe') +
          '\n\n' +
          `Expected value to not be (using Object.is):\n` +
          `  ${this.utils.printExpected(expected)}\n` +
          `Received:\n` +
          `  ${this.utils.printReceived(received)}`
      : () => {
          const diffString = diff(expected, received, {
            expand: this.expand,
          });
          return (
            this.utils.matcherHint('.toBe') +
            '\n\n' +
            `Expected value to be (using Object.is):\n` +
            `  ${this.utils.printExpected(expected)}\n` +
            `Received:\n` +
            `  ${this.utils.printReceived(received)}` +
            (diffString ? `\n\nDifference:\n\n${diffString}` : '')
          );
        };

    return {actual: received, message, pass};
  },
});
  1. expect.anything() 匹配null,undefined以为的任意值

  2. expect.assertiongs(number) 确认断言数量,用于确保异步代码中的断言被调用

    • expect.hasAssertions()
  3. expect.addSnapshotSerializer(serializer) 添加一个格式化特定应用程序数据结构的模块

  4. .resolves包裹一个完成的promise,以实现链式调用其他的匹配器

test('resolves to lemon', () => {
  // make sure to add a return statement
  return expect(Promise.resolve('lemon')).resolves.toBe('lemon');
});

test('resolves to lemon', async () => {
  await expect(Promise.resolve('lemon')).resolves.toBe('lemon');
  await expect(Promise.resolve('lemon')).resolves.not.toBe('octopus');
});
  1. .rejects包裹一个拒绝的promise
test('rejects to octopus', () => {
  // make sure to add a return statement
  return expect(Promise.reject(new Error('octopus'))).rejects.toThrow(
    'octopus',
  );
});

test('rejects to octopus', async () => {
  await expect(Promise.reject(new Error('octopus'))).rejects.toThrow('octopus');
});

测试异步代码

回调形式

test('the data is peanut butter', done => {
  function callback(data) {
    expect(data).toBe('peanut butter');
    // 必须有
    done();
  }

  fetchData(callback);
});

Promises

test('the data is peanut butter', () => {
  expect.assertions(1);
  // 不可忽略return,否则测试会在fetchData之前完成
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});

test('the fetch fails with an error', () => {
  expect.assertions(1);
  return fetchData().catch(e => expect(e).toMatch('error'));
});

test('the data is peanut butter', () => {
  expect.assertions(1);
  return expect(fetchData()).resolves.toBe('peanut butter');
});

test('the fetch fails with an error', () => {
  expect.assertions(1);
  return expect(fetchData()).rejects.toMatch('error');
});

Async/Await

test('the data is peanut butter', async () => {
  expect.assertions(1);
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
  expect.assertions(1);
  try {
    await fetchData();
  } catch (e) {
    expect(e).toMatch('error');
  }
});

test('the data is peanut butter', async () => {
  expect.assertions(1);
  await expect(fetchData()).resolves.toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
  expect.assertions(1);
  await expect(fetchData()).rejects.toMatch('error');
});

测试条件的建立和拆除

重复的建立

  • beforeEach
  • afterEach
beforeEach(() => {
  initializeCityDatabase();
});

afterEach(() => {
  clearCityDatabase();
});

beforeEach(() => {
  // return a promise   
  return initializeCityDatabase();
});

一次性建立

  • beforeAll
  • afterAll
beforeAll(() => {
  return initializeCityDatabase();
});

afterAll(() => {
  return clearCityDatabase();
});

// 也可以将一次性的建立限制到作用域内,调用顺序符合作用域嵌套规则
describe('matching cities to foods', () => {
  // Applies only to tests in this describe block
  beforeEach(() => {
    return initializeFoodDatabase();
  });

  test('Vienna <3 sausage', () => {
    expect(isValidCityFoodPair('Vienna', 'Wiener Schnitzel')).toBe(true);
  });

  test('San Juan <3 plantains', () => {
    expect(isValidCityFoodPair('San Juan', 'Mofongo')).toBe(true);
  });
});

执行顺序

在一个测试文件中,Jes会t在执行任何实际测试之前执行所有描述处理程序(测试套件describe)。

使用test.only来验证一个测试用例是否是单独失败的

Mock

const mock = jest.fn();

mock 函数

function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}

const mockCallback = jest.fn();
forEach([0, 1], mockCallback);

// The mock function is called twice
expect(mockCallback.mock.calls.length).toBe(2);

// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

mock返回值

const filterTestFn = jest.fn();

// Make the mock return `true` for the first call,
// and `false` for the second call
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);

const result = [11, 12].filter(filterTestFn);

console.log(result);
// > [11]
console.log(filterTestFn.mock.calls);
// > [ [11], [12] ]

学习 React 系列(三)

类型检查

只会在开发模式进行

import PropTypes from 'prop-types';
class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

// PropTypes 包含所有的基本类型
Greeting.propTypes = {
  name: PropTypes.string
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,
  
  // 可以被渲染的任何元素
  optionalNode: PropTypes.node,
  // React 元素
  optionalElement: PropTypes.element,
  
  optionalMessage: PropTypes.instanceOf(Message),
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),
  
  // 对象,数组内部的元素类型都可以设置
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
  
  // 通过 isRequired 属性设置必传项
  requiredFunc: PropTypes.func.isRequired,
  
  // 你甚至可以在这里直接设置参数校验
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },
  
  // PropTypes.element设置只允许一个子元素
  children: PropTypes.element.isRequired,
  
  // 设置默认值
  Greeting.defaultProps = {
    name: 'Stranger'
  };
};

ref

获取 DOM 元素,编写非控制的组件。

什么时候用?

  1. 处理焦点事件,文本选择,媒体播放时
  2. 出发即时动画
  3. 集成第三方 DOM 操作库
  4. 使用前多一步思考,这个状态是不是应该提升
class AutoFocusTextInput extends React.Component {
  componentDidMount() {
    this.textInput.focusTextInput();
  }

// this.textInput 即为对应 DOM 元素
// 元素的元素和用 class 创建的 React 元素可用
// 函数组件不可用,因为他们没有实例
  render() {
    return (
      <CustomTextInput
        ref={(input) => { this.textInput = input; }} />

    );
  }
}

子元素暴露给父元素(不推荐)

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

function Parent(props) {
  return (
    <div>
      My input: <CustomTextInput inputRef={props.inputRef} />
    </div>
  );
}

// 通过 this.inputElement 就可以获得相关元素
class Grandparent extends React.Component {
  render() {
    return (
      <Parent
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

uncontrolled component

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.value);
    event.preventDefault();
  }

// 在这种情况下,通过 defaultValue,defaultChecked来设置初始值
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input 
            type="text" 
            defaultValue="Bob"
            ref={(input) => this.input = input} 
          />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

context

取代多层传递 props,但还是尽量不要使用,如果感觉必须要用了,可能是需要 redux 了。

Portals

将子组件渲染到父组件以外的 DOM 节点,而且你在父组件中仍然能够捕获到来自 portal 的事件冒泡。

使用场景

当父组件有 overflow: hidden 或 z-index 样式时,我们希望子组件完全显示出来。如:对话框,hover 提示效果,tooltips 状态提示等。

写法

render() {
  // React does *not* create a new div. It renders the children into `domNode`.
  // `domNode` is any valid DOM node, regardless of its location in the DOM.
  return ReactDOM.createPortal(
    this.props.children,
    domNode,
  );
}

高阶组件(HOC)

一种重用组件逻辑的模式。他是一个接受组件作为参数的函数,并返回新的组件。

组件: props -> UI
高阶组件:组件 -> 组件+

约定

  1. 不要改变原始组件(即通过原型链去操作原始组件),直接使用组合
  2. 只给原始组件传递高阶组件的state 和实例方法,不传递与子组件不相关的 props
render() {
// 过滤掉与高阶函数功能相关的props属性,
// 不再传递
const { extraProp, ...passThroughProps } = this.props;

// 向包裹组件注入props属性,一般都是高阶组件的state状态
// 或实例方法
const injectedProp = someStateOrInstanceMethod;

// 向包裹组件传递props属性
return (
  <WrappedComponent
    injectedProp={injectedProp}
    {...passThroughProps}
  />
);
}
  1. 最大化使用组合
const ConnectedComment = connect(commentSelector, commentActions)(Comment);

-->

// connect是一个返回函数的函数(译者注:就是个高阶函数)
const enhance = connect(commentListSelector, commentListActions);
// 返回的函数就是一个高阶组件,该高阶组件返回一个与Redux store
// 关联起来的新组件
const ConnectedComment = enhance(CommentList);
  1. 包装显示名字以便于调试
  2. 将组件的静态方法拷贝给高阶组件
// 定义静态方法
WrappedComponent.staticMethod = function() {/*...*/}

// hnrs会自动拷贝所有非React的静态方法
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  hoistNonReactStatic(Enhance, WrappedComponent);
  return Enhance;
}

示例

// 高级组件就是一个函数,你可以传入任意数量的参数
const CommentListWithSubscription = withSubscription(
  CommentList,
  (DataSource) => DataSource.getComments()
);

const BlogPostWithSubscription = withSubscription(
  BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id)
});

// 函数接受一个组件参数……
function withSubscription(WrappedComponent, selectData) {
  // ……返回另一个新组件……
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.state = {
        data: selectData(DataSource, props)
      };
    }

    componentDidMount() {
      // ……注意订阅数据……
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }

    render() {
      // ……使用最新的数据渲染组件
      // 注意此处将已有的props属性传递给原组件
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

TODO

用原生js/React写一个TODO List,用localstorage存储

跨域问题总结

CORS(Cross-Origin Resource Sharing)

跨域资源共享,支持所有类型的HTTP请求。使用自定义的HTTP头部(包含页面的源信息,协议域名端口等),让浏览器与服务器进行沟通,从而决定请求或响应是否成功。

基本设置

//客户端HTTP请求头部
Origin: http://www.gaococ.com
//服务器端头部回发
Access-Control-Allow-Origin: http://www.gaococ.com //允许的源
Access-Control-Allow-Methods: get,post //允许的方法,用逗号隔开
Access-Control-Allow-Headers: //允许的头部
Access-Control-Max-Age: //缓存该请求的时常(s)

限制

现代浏览器都通过XHR实现了对CORS的原生支持,但会有以下限制:

不能使用setRequestHeader()设置自定义头部
不能发送和接收cookie
调用getAllResonseHeaders()方法会返回空字符串
由于同源请求和跨域请求使用了相同的接口,所以在访问本地资源时,应尽量使用相对路径,避免产生以上限制。

带凭据的请求

凭据:cooke,HTTP认证,客户端SSL证明等

// 客户端
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;

//服务器端
Access-Control-Allow-Credentials: true
兼容低版本浏览器
利用IE通过XDomainRequest实现跨域:

function createCORSRequest(method, url) {
 let xhr = new XMLHttpRequest();
 if ("withCredentials" in xhr) {
 xhr.open(method, url, true);
 } else if (typeof XDomainRequest !== "undefined") {
 xhr = new XDomainRequest();
 xhr.open(method, url);
 } else {
 xhr = null;
 }
 return xhr;
}

var request = createCORSRequest("get", "http://www.somewhere-else.com/page/");
if (request){
 request.onload = function(){
 //对 request.responseText 进行处理
 };
request.send();
}
<!-- 
 abort() :用于停止正在进行的请求。
 onerror :用于替代 onreadystatechange 检测错误。
 onload :用于替代 onreadystatechange 检测成功。
 responseText :用于取得响应内容。
 send() :用于发送请求。 -->

JSONP(JSON with padding)

参数式JSON,因为js并不能直接解析JSON,所以需要将JSON数据以参数的形式传入到函数中。JSONP一般由回调函数和数据组成,利用script标签不受同源条件的限制的原理,通过动态生成script标签的方法,将请求结果传入回调函数中实现跨域请求。但是,JSONP只支持get请求,且其安全性难以控制,以及难以判断请求是否失败。

//客户端实现
function handleResponse(response){
 alert("You’re at IP address " + response.ip + ", which is in " +
 response.city + ", " + response.region_name);
}
var script = document.createElement("script");
script.src = "http://gaococ.com/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
//服务器端
走服务器端的json方法,将其返回值传入handleResponse

JSONP的安全问题

依然还是CSRF的问题,其防御应该从以下几个方面下手

限制Referer,设置Token

严格按照JSON标准格式输出(Content-Type : application/json; charset=utf-8)
过滤callback函数名,以及里面输出的数据

postMessage

主要用于向当前页面的iframe,或者由当前页面弹出的窗口传递消息。在父页面(消息发送方)的写法如下:

var iframeWindow = document.getElementById("myframe").contentWindow;
iframeWindow.postMessage("A secret", "http://www.wrox.com");

其中postMessage的第一个参数是消息内容的字符串,第二个参数表示消息的接收者必须来自哪个域。

在子页面(消息接收方),通过异步的message事件来监听该消息,并有origin,data,source三个属性,具体写法如下:

EventUtil.addHandler(window, "message", function(event){
 //确保发送消息的域是已知的域
 if (event.origin == "http://www.wrox.com"){
 //处理接收到的数据
 processMessage(event.data);
 //可选:向来源窗口发送回执
 event.source.postMessage("Received!", "http://p2p.wrox.com");
 }
});

其中source在同域的情况下,拿到的就是window对象,但大多数情况(跨域)下拿到的都是window对象的代理,通过他并不能访问到window对象的其他信息,一般也只用他调用postMessge方法发送回执信息。

为了浏览器的兼容,postMessage()的地一个参数只传入字符串,如果想传入json数据,可以通过JSON.stringify()进行转换后传递,在接收方使用JSON.parse()再将数据格式化。

window.name

一个窗口的生命周期内,所有载入的页面都共享一个window.name,他们都对其有读写权限,即使页面刷新还依然存在,还是多配合iframe使用的。下面是一个例子,感觉这个方法还是好蠢,也不知道实际中到底会怎么用。

iframe = document.createElement('iframe');
iframe.style.display = 'none';
var state = 0;

iframe.onload = function() {
 if(state === 1) {
 var data = JSON.parse(iframe.contentWindow.name);
 console.log(data);
 iframe.contentWindow.document.write('');
 iframe.contentWindow.close();
 document.body.removeChild(iframe);
 } else if(state === 0) {
 state = 1;
 iframe.contentWindow.location = 'http://localhost:81/cross-domain/proxy.html';
 }
};

iframe.src = 'http://localhost:8080/data.php';
document.body.appendChild(iframe);

Comet(不知道和跨域有什么关系)

一种从服务器端想客户端推送数据的技术,分为长轮询和流两种实现方式。

短轮询 浏览器定时向服务器发起请求,服务器立即给出回应。

长轮询 浏览器向服务器发起请求,并保持HTTP链接,直到服务器响应,然后再发起下一次请求。

HTTP流 在页面的整个生命周期内只有一个HTTP链接,服务器周期性地向浏览器发送数据,浏览器的XHR对象通过判断readyState的值为3来取得更新后的数据。

Web Sockets

一种持久连接的全双工通信。

优势

服务器可以主动推送消息给客户端(解决http中response必须和request对应的问题)
只需要建立一次HTTP连接(性能上极大提升)
实现原理
在JS中建立Web Socket后,会发送一个HTTP请求,服务器响应后,会将HTTP协议升级为Web Socket协议(即要求服务器同时支持这两种协议),这样可以减小请求的开销。

对于Web Sockets,未加密的链接为 ws://, 加密的链接为 wss://。

使用

const socket = new WebSocket("ws://www.gaococ.com/server.php");
//同源策略不适用于websocket,必须使用绝对地址

有readyState属性来表示当前状态:
 WebSocket.OPENING (0):正在建立连接。
 WebSocket.OPEN (1):已经建立连接。
 WebSocket.CLOSING (2):正在关闭连接。
 WebSocket.CLOSE (3):已经关闭连接。

//发送数据
socket.send()//依然只能发送纯字符串数据,JSON需要Stringify。
//关闭连接
socket.close()
//接收数据
socket.onmessage = function(event){
 var data = event.data;
 //处理数据
};
//必须使用DOM0级事件
//onopen 事件,在建立连接时触发
//onerror 事件,发生错误时触发
//onclose 事件,连接关闭时触发
一个典型的WebSocket握手
//客户端
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket 
Connection: Upgrade //请求将协议升级为WebSocket协议
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //浏览器生产,验证
Sec-WebSocket-Protocol: chat, superchat //用户定义的字符串,区分同URL下不同服务需要的协议
Sec-WebSocket-Version: 13
Origin: http://example.com

//服务器
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade //协议切换成功
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

Socket.IO
他是一个WebSocket库,通过Node.Js实现了服务端基于WebSocket以及其他传输方式的封装,它提供了一个抽象层,以相同的API兼容WebSocket和其他传输方案。同时也提供了客户端的API接口。

webpack配置基础

//webpack.config.sj

var webpack = require('webpack');
var path = require('path'); 

//定义一些地址常量,下面都是约定俗称的写法

const CURRENT_PATH = path.resolve(__dirname);//当前目录
const ROOT_PATH = path.join(__dirname, '../');//上一级目录
//指定任意目录
const ASSET_PATH = path.join(__dirname, '../', 'public', 'assets');
const MODULES_PATH = path.join(ROOT_PATH, './node_modules');

module.exports = {
    //上下文(相当与设置当前目录)
    context: ROOT_PATH,
    //配置入口
    entry: {
        home: './js/home.js',
        blog: './js/blog.js'    
    },
    //配置输出
    output: {
        path: ASSET_PATH,
        filename: '[name]_bundle.js',//name会油entry中的键名替代
        publicPath: '/assets/'
    },
    //解析模块路径的相关配置
    resolve: {
        root: MODULES_PATH,
        extensions: ['', '.js', '.coffee', '.json', '.scss'],//require模块可以省略不写这些后缀
        alias: {
            AppStore : 'js/stores/AppStores.js',//后续直接 require('AppStore') 即可
      ActionType : 'js/actions/ActionType.js',
      AppAction : 'js/actions/AppAction.js'
        }//定义模块的别名,方便直接引用
    }
    //模块
    module: {
        loaders: [
        {test: /\.js$/, loader: 'babel-loader'},
        {test: /\.css$/, loader: 'style!css'},
        {test: /\.scss$/, loader: 'style!css!sass'},
        {test: /\.(jpe?g|png|gif|svg)\??.*$/, loader: 'url-loader?limit=8192&name=[name].[ext]'},
    ]//使用相关loader来加载,同时也可在文件中直接require(import)这些文件
    },
    //插件
    plugins: {
        new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery'
    }),
        new ManifestPlugin({
        fileName: 'webpack_manifest.json'//键名为插件内的方法
    })
    }
}

插件介绍

ExtractTextPlugin 分离CSS

ProvidePlugin 引入Jquery

CommonsChunkPlugin 抽取公共资源(提速)

UglifyJsPlugin 代码混淆

更多

实用功能

ecmadao

教程

webpack your bags

Hello_Linux

linux翻墙

  1. 浏览器安装SwitchyOmega插件,设置代理为本地 127.0.0.1 socks5

  2. 下载shadowsock qt5 按照ss的设置方法设置

linux中安装node(不能全局翻墙的状态下)

github.com/creationix/nvm

  1. 根据文档安装完成后,重启终端(重要!)
  2. 使用nvm install node 进行安装

linux 快捷键

  • ALT + F1: 聚焦到桌面左侧任务导航栏,可按上下键导航。
  • CTRL + ALT + T: 打开终端
  • TAB: 自动补全命令或文件名
  • CTRL + SHIFT + V: 粘贴(Linux中不需要复制的动作,文本被选择就自动被复制)
  • CTRL + SHIFT + T: 新建标签页
  • CTRL + D: 关闭标签页
  • CTRL + L: 清楚屏幕
  • CTRL + R + 文本: 在输入历史中搜索
  • CTRL + A: 移动到行首
  • CTRL + E: 移动到行末
  • CTRL + C: 终止当前任务
  • CTRL + Z: 把当前任务放到后台运行(相当于运行命令时后面加&)

Linux下挂载exfat盘

sudo su  
add-apt-repository ppa:relan/exfat  
apt-get update  
apt-get install exfat-utils  

Ubuntu 14.04下安装Qt5

sudo apt-get update
sudo apt-get install libqt4-dev libqtwebkit-dev
sudo apt-get install qt5-default libqt5webkit5-dev gstreamer1.0-plugins-base gstreamer1.0-tools gstreamer1.0-x

如果qmake还是找不到,需要在环境变量里把qmake和最新版本的qt里的qmake关联起来

which qmake # to see where it links
rm `which qmake` # IF it is linking to an old version 

Hello React Router

基本理念

在最新版本中,使用动态路由替代传统的静态路由。

静态路由

静态路由往往是作为应用初始化的一部分,在页面渲染前调用。例如 express 的写法:

app.get('/', handleIndex)
app.get('/invoices', handleInvoices)
app.get('/invoices/:id', handleInvoice)
app.get('/invoices/:id/edit', handleInvoiceEdit)

app.listen()

const appRoutes: Routes = [
  { path: 'crisis-center',
    component: CrisisListComponent
  },
  { path: 'hero/:id',
    component: HeroDetailComponent
  },
  { path: 'heroes',
    component: HeroListComponent,
    data: { title: 'Heroes List' }
  },
  { path: '',
    redirectTo: '/heroes',
    pathMatch: 'full'
  },
  { path: '**',
    component: PageNotFoundComponent
  }
];
// 靠一个全局量挂载所有路由信息,然后给每个路由分别绑定回调(组件)

动态路由

在渲染时加载路由,路由本身也作为一个组件

// 基本用法
import { BrowserRouter } from 'react-router-dom'

ReactDOM.render((
  <BrowserRouter>
    <App/>
  </BrowserRouter>
), el)

const App = () => (
  <div>
    <nav>
      <Link to="/dashboard">Dashboard</Link>
    </nav>
    <div>
      <Route path="/dashboard" component={Dashboard}/>
    </div>
  )
// 嵌套路由
const App = () => (
  <BrowserRouter>
    {/* here's a div */}
    <div>
      {/* here's a Route */}
      <Route path="/tacos" component={Tacos}/>
    </div>
  </BrowserRouter>
)

// when the url matches `/tacos` this component renders
const Tacos  = ({ match }) => (
  // here's a nested div
  <div>
    {/* here's a nested Route,
        match.url helps us make a relative path */}
    <Route
      path={match.url + '/carnitas'}
      component={Carnitas}
    />
  </div>
)

常见用法

  1. 传递参数
<li><Link to="/modus-create">Modus Create</Link></li>
<Route path="/:id" component={Child}/>

const Child = ({ match }) => (
  <div>
    <h3>ID: {match.params.id}</h3>
  </div>
)
  1. 重定向
<Redirect to="/somewhere/else"/>

<Switch>
  <Redirect from='/old-path' to='/new-path'/>
  <Route path='/new-path' component={Place}/>
</Switch>

// 当 push 为 true 时会添加新的历史记录而不是替代原有的
<Redirect push to="/somewhere/else"/>

<Redirect to={{
  pathname: '/login',
  search: '?utm=your+face',
  state: { referrer: currentLocation }
}}/>
  1. 危险跳转的中断
// when @Boolean
// message @String or Func
<Prompt
  when={formIsHalfFilledOut}
  message="Are you sure you want to leave?"
/>
  1. 404页面(没有匹配到的 url)
<Switch>
  <Route path="/" exact component={Home}/>
  <Redirect from="/old-match" to="/will-match"/>
  <Route path="/will-match" component={WillMatch}/>
  <Route component={NoMatch}/>
</Switch>
  1. 动态 url
<ul>
  <li><Link to="/about">About Us (static)</Link></li>
  <li><Link to="/company">Company (static)</Link></li>
  <li><Link to="/kim">Kim (dynamic)</Link></li>
  <li><Link to="/chris">Chris (dynamic)</Link></li>
</ul>

<Switch>
  <Route path="/about" component={About}/>
  <Route path="/company" component={Company}/>
  <Route path="/:user" component={User}/>
</Switch>
  1. 路由集中配置
const routes = [
  { path: '/sandwiches',
    component: Sandwiches
  },
  { path: '/tacos',
    component: Tacos,
    routes: [
      { path: '/tacos/bus',
        component: Bus
      },
      { path: '/tacos/cart',
        component: Cart
      }
    ]
  }
]

// wrap <Route> and use this everywhere instead, then when
// sub routes are added to any route it'll work
const RouteWithSubRoutes = (route) => (
  <Route path={route.path} render={props => (
    // pass the sub-routes down to keep nesting
    <route.component {...props} routes={route.routes}/>
  )}/>
)

All Kinds Of Tips

  1. 文字过长后用...代替

      <p>this is a line of words which is too looooooong</p>
      p{
        white-space: nowrap;//不换行
        overflow: hidden;
        text-overflow: ellipsis;
          //用...代替过长的文字,如果是clip则为直接剪裁,只有firefox 9+支持以string作为其值
      }
    

    注意该块或其父元素需要设置width或者(max-width)

    对于现代浏览器已可以实现多行文字用...代替过长的文字

    .box {
      display: -webkit-box;
      -webkit-line-clamp: 3;//设置行数
      -webkit-box-orient: vertical;
      overflow: hidden;
    }
    

Ruby on Rails在View层的写法

所有的Ruby语句都写在<% %><%= %>内。<% %> 标签用来执行 Ruby 代码,没有返回值,例如条件判断、循环或代码块。<%= %> 用来输出结果。注意前后都有_空格_。

  1. <%= yield %>使用yield辅助方法作为占位,Rails会去找我们写好的模版填充在这里。
  2. <% provide(:title, title) %> provide用于对变量进行赋值。
  3. 选择判断等语句需要end来结束,例如:
<% @people.each do |person| %>
  Name: <%= person.name %><br>
<% end %>
  1. 使用render加载局部视图:

    <%= render "menu" %>
    #会渲染名为 _menu.html.erb 的文件
    
  2. 使用 content_for 补充某些代码模版块。

    home.html
    <html>
      <head>
        <title>Welcome!</title>
        <%= yield :special_script %>
      </head>
      <body>
        <p>Welcome! The date and time is <%= Time.now %></p>
      </body>
    </html>
    #---------
    about.html
    <p>This is a special page.</p>
    
    <% content_for :special_script do %>
      <script>alert('Hello!')</script>
    <% end %>
    # 于是在about页面的head中不仅有原来yield里的模块,还添加了新的js
    

Heroku + Nodejs + Github部署

踩坑

  1. 创建app
  2. 连接github
  3. 安装 heroku CLI
  4. 使用node-js-getting-started.git部署
  5. 修改配置文件中engines/node 与开发环境版本相同
  6. 在本地的git仓库目录下登陆heroku
  7. 设置heroku远程仓库 heroku git:remote -a name
    • 直接通过push部署时:注意只能通过master分支push,不能由本地的develop分支push到heroku的master分支
    • 同步github自动部署,代码何入master分支后自动触发部署
  8. heroku logs无法看到log 未解决,在线可以看到log
  9. 不要上传本地的package-lock或yarn-lock。因国内会自动用淘宝代理,而heroku服务器上很可能连不上,导致部署失败
  10. 使用postgresq,需掌握基本的用法,本地也装好相关软件
  11. 如何保持远程数据库和本地数据库同步
  12. 不要使用最新版的pg,使用教程中的6.x版本(最新版的api完全改变,采用新写法后本地运行正常但远程会查询数据库超时,并且会导致出错并使得应用挂掉,暂未定位出原因。)

你不知道的JavaScript(中)

你不知道的JavaScript(中)

类型

1. 内置类型

  • null
  • undefined
  • boolean
  • number
  • string
  • object(引用类型)
  • symbol(新增,属于基本类型)

2. typeof的安全防范机制

typeof对于没有声明没有赋值的变量的返回值都为undefined。如果我们有一个全局变量要在生产环境中使用,但是在线上不使用,此时就需要用typeof去检查他是否存在了。

// 这样会抛出ReferenceError错误
if (DEBUG) {
    console.log( "Debugging is starting" );
}

// 这样是安全的
if (typeof DEBUG !== "undefined") {
    console.log( "Debugging is starting" );
}

// 也可以做到,但是不能保证全局对象总是window
if (window.DEBUG) {
    // ..
}

1. 数组

  • 通过delete删除数组中的元素不会改变数组的length
  • 数组中如果有元素未赋值,其值将为undefined,但这也与a[1] = undefined是有区别的
  • a['foo'] = 1 这样的数组的值的表示方法并不会计入数组的长度,但如果键名里的字符串可以被强制类型转换成数组的话就会被当作数组索引来处理。不建议这样来使用数组

1.1 类数组

一些DOM元素列表,通过arguments对象来访问函数的参数(ES6中已经废止,用...运算符代替)。

类数组转换成数组:

var arr = Array.prototype.slice.call( arguments );
// ES6
var arr = Array.from( aruguments );

2. 字符串

  • 字符串是不可变的:字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。(而数组的成员函数都是在其原始值上操作的)

2.1 常用处理字符串的数组函数

a.join;         // undefined
a.map;          // undefined

var c = Array.prototype.join.call( a, "-" );
var d = Array.prototype.map.call( a, function(v){
    return v.toUpperCase() + ".";
} ).join( "" );

c;              // "f-o-o"
d;              // "F.O.O."

但是,我们不能使用相同的方法去实现字符串的反转(数组的reverse()函数),因为字符串的成员是不可变更的,但可以使用下面的暴力方法:

// 将字符串转成数组反转后再转会字符串,但对于复杂字符 Unicode 则需要相关的工具库来实现
var c = a
    // 将a的值转换为字符数组
    .split( "" )
    // 将数组中的字符进行倒转
    .reverse()
    // 将数组中的字符拼接回字符串
    .join( "" );

3. 数字

  • 使用tofixed()来指定小数部分的显示位数,返回值为数字的字符串形式

  • 使用toPrecision()来制定有效位数

  • 0x,0o,0b分别表示十六进制,八进制,二进制

  • 使用Number.EPSILON来比较两个浮点数是否相等(制定误差范围)

    
    function numbersCloseEnoughToEqual(n1,n2) {
        return Math.abs( n1 - n2 ) < Number.EPSILON;
    }
    var a = 0.1 + 0.2;
    var b = 0.3;
    numbersCloseEnoughToEqual( a, b );                  // true
    numbersCloseEnoughToEqual( 0.0000001, 0.0000002 );  // false
    

4. 特殊数值

  • 区别null和undefined,null不可以被当作变量来使用和赋值,而undefined可以

  • 使用void 0来得到undefined,void运算符可以使表达式不返回值

  • NaN,指数学运算没有成功,返回了错误的结果,变量仍可能是number类型的,他是唯一不等于自身的值

    var a = 2 / "foo";      // NaN
    typeof a === "number";  // true
    // 不能用NaN去判断一个变量是不是一个数字,NaN与任何值,包括自身,比较都是false
    var a = 2 / "foo";
    a == NaN;   // false
    a === NaN;  // false
    // 可以使用isNaN,但是他只是检查变量是否不是NaN也不是数字,所以有个bug
    isNaN( a ); // true
    b; "foo"
    window.isNaN( b ); // true——晕!
    // ES6的写法
    Number.isNaN(b);// false
    
  • 计算结果一旦溢出为无穷数,就无法再得到有穷数

  • -0在需要以向量形式表示一些值的时候很重要

  • 在ES6中可以使用Object.is()来判断两个值是否绝对相等(比===更加准确),主要用于判断正负0和NaN。

5. 值和引用

function foo(x) {
    x.push( 4 );
    x; // [1,2,3,4]

    // 然后
    x = [4,5,6];
    x.push( 7 );
    x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // 是[1,2,3,4],不是[4,5,6,7]
  • slice(..) 不带参数会返回当前数组的一个浅复本
  • 值的类型决定了他的传递方式

原生函数

  • 前几个不到万不得已不要使用
  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function()
  • RegExp() // 在动态定义正则表达式时会很有用
  • Date()
  • Error()
  • Symbol()
var a = new String( "abc" );
typeof a;                            // 是"object",不是"String"
a instanceof String;                 // true
Object.prototype.toString.call( a ); // "[object String]"

强制类型转换

  • toString(); // 数组的该方法被重新定义,使用‘,‘连接每一项

  • JSON.stringify(); // JSON转字符串,它并不是强制类型转换

  • 使用Date.now()来获得当前的时间戳,使用new Date(..).getTime()获得指定时间的时间戳

  • 因为~-1可以得到0,所以可以利用~和indexOf()实现将结果强制类型转换为真假值,他要比indexOf(..) >= 0之类的更加简洁

  • ~~可以用来截除Int32的小数部分,但是他在处理负数是和floor是有区别的,例如对-49.6,他会得到-49,同样使用x|0也可以达到截除小数的目的

  • 避免向praseInt传递非字符串

  • 建议使用Boolean(a) 和 !!a 来进行显式强制类型转换为boolean型

  • Symbol 类型的隐式类型转换会产生错误

  • == 允许在相等比较中进行强制类型转换,而 === 不允许

  • typeof x == 'function' 是绝对安全的,因为不可能返回空值或boolean值

  • 七个大坑

    "0" == false;          // true -- 晕!
    false == 0;            // true -- 晕!
    false == "";           // true -- 晕!
    false == [];           // true -- 晕! //![]也是false,!![]是true
    "" == 0;               // true -- 晕!
    "" == [];              // true -- 晕!
    0 == [];               // true -- 晕!
    

语法

  • 利用赋值语句的副作用来简化代码

    function vowels(str) {
        var matches;
    
        // 提取所有元音字母,少写一个if
        if (str && (matches = str.match( /[aeiou]/g ))) {
            return matches;
        }
    }
    vowels( "Hello World" ); // ["e","o","o"]
    
  • JSON本身并不是合法的JavaScript语法,JSONP能将JSON转为合法的JavaScript语法。

  • JavaScript中并没有else if,只是因为else后只有一个单独的if语句,所以可以省略大括号

  • &&运算符优先于||,且优先于左右顺序(优先级)

  • ES6中定义了一个新概念TDZ(暂时性死区)

{
typeof a;   // undefined
typeof b;   // ReferenceError! (TDZ)
let b;
}

阻止鼠标连点多次触发事件(请求)

场景:这一问题在单页面中大量存在,点击按钮触发异步事件,在回调函数中更新界面。这时候,如果快速连点鼠标,就会多次触发该异步事件(请求),这就会出现几个问题:

  1. 请求返回的顺序不能保证
  2. 触发多次界面渲染,且可能导致渲染异常
  3. 会产生许多冗余的请求

解决思路

  1. 加锁。通过变量锁,或者UI上禁用掉相关按钮来实现。在请求完成后(成功或失败或返回异常)解锁。其问题在于需要增加额外的控制变量或者UI变化,且需要保证正确注册解锁事件(即complete),否则会导致功能不可用。

  2. 主动控制请求。

知乎上的这个回答很清晰地说明了这个问题。

取消不必要的请求

$(document).ready(
    var xhr;

    var fn = function(){
        if(xhr && xhr.readyState != 4){
            xhr.abort();
        }
        xhr = $.ajax({
            url: 'ajax/progress.ftl',
            success: function(data) {
                //do something
            }
        });
    };

    var interval = setInterval(fn, 500);
);

实际业务中,这一方案需要一个全局的控制,通过一个全局变量来挂载不同的请求XHR对象。

防抖节流

Debounce、Throttle可以控制一个函数在一定时间内的执行次数,可以间接控制DOM事件的触发频率,提高页面性能。

应用场景

Debounce: 调整窗口大小,自动补全功能
Throttle:滚动屏幕时不断检查距离底部的距离

_.debounce(func, [wait=0], [options={}])
//将多个调用合并成一次
//options.leading为true时,多次调用会在首次调用时触发

_.throttle(func, [wait=0], [options={}])
//事件会在一定时间内至少执行一次

// 错误
$(window).on('scroll', function() {
   _.debounce(doSomething, 300); 
});
// 正确
$(window).on('scroll', _.debounce(doSomething, 200));
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
	var data = options.data;
	var urlarr = options.url.split('/');
	var key = urlarr[urlarr.length - 1];
	// 判断是否需要阻止重复请求
	var abortOnRetry = true;
	if (originalOptions.data && originalOptions.data.abortOnRetry !== undefined) {
		abortOnRetry = false;
	}
	// 当请求不在缓存中时,则请求,否则终止
	if (abortOnRetry) {
		if (!requestXhr[key] || !requestXhr[key].data || requestXhr[key].data !== data) {
			requestXhr[key] = {
				xhr: jqXHR,
				data: data
			};
		} else {
			jqXHR.abort();
		}
	}

	var complete = options.complete;
	options.complete = function (jqXHR, textStatus) {
		//请求完成后,清除缓存请求的地址
		requestXhr[key] = null;

		if ($.isFunction(complete)) {
			complete.apply(this, arguments);
		}
	};
	
});

jquery的abort方法似乎不会从控制栏中看到(显示连接中断)。
最好预留参数处理那些不需要abort重复请求的请求。

设计模式(一)

设计模式

单例模式

定义

Javascript可以直接通过全局变量实现单例模式,但这会造成命名空间的污染,所以一般需要使用相应的命名空间或闭包来避免。

更加实用的是惰性单例,在需要的时候才去创建对象实例。

写法

let getSingle =(fn) => {let result; () => result || (result = fn.apply(this, arguments));}

应用场景

对于那些只需要执行一次的事件,都适合用单例模式。比如弹出模态框(登录注册之类的),事件绑定等。

策略模式

定义

定义一系列算法,一个个封装起来,并且他们具有相同的意图和目的。

写法

let strategies = {
   "s": (salary) => salary * 4,
   "a": (salary) => salary * 4,
   "b": (salary) => salary * 4
}
let calculateBonus = (level, salary) => strategies[level](salary);

应用场景

消除程序中的大片分支语句,制作一些动画效果(传入一些相同的参数,但是要对参数进行不同的计算),表单验证(不能为空,格式正确等)

代理模式

定义

不直接访问一个对象,而是通过中间代理去访问他,使代码满足单一制作原则,使代码更加健壮。

保护代理:控制不同权限的对象对目标对象的访问。
虚拟代理: 把一些开销很大的对象延迟到真正去要他的时候再创建。

写法

// 图片预加载
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.gif');
            img.src = src;
        }
    }
}

proxyImage.setSrc("image.jpg");

应用场景

图片的预加载(lazyloading),合并HTTP请求,缓存代理去缓存一些信息(复杂运算等),ajax请求缓存(分页)等。

迭代器模式

定义

提供一种方法按照优先级顺序访问聚合元素中的各个对象。

写法

let myObj = function(){
    for (let i = 0, fn; fn = arguments[i++];){
       let Obj = fn();
       if( Obj !== false){
            return Obj;
       }
    }
}

let Obj = myObj(firstfuc,secfuc,lastfuc);

应用场景

消除代码中大量的try,catch分支,按照优先级调用一些函数,实现一些功能的浏览器兼容性。

观察者模式

定义

也称作发布-订阅模式,定义了一种一对多的依赖关系,让多个订阅者对象同时监听一个发布者对象。当发布者对象状态发生变化时,会通知所有订阅者对象,使他们能够自动更新自己。

写法

\\ 自定义时间的绑定和监听
let event = {
    clientList: [],
    listen: (key, fn) => {
                 if (!this.clientList[key]){
                     this.clientList[key] = [];
                 }
                 this.clientList[key].push(fn);
    },
    trigger:() => { let key = Array.prototype.shift.call(arguments),
                                     fns = this.clientList[key];

                               if (!fns || fns.length === 0 ){
                                   return false;
                               }

                               for (let i=0,fn;fn = fns[i++]; ){
                                   fn.apply(this.arguments);
                              }
    }
}

应用

实现自定义事件的绑定和监听,对于一个事件的发生会对多处产生影响的场景很有作用(比如登录后,用户状态改变,页面很多地方都要发生变化)。

前端测试工具Jest——使用

Snapshot 测试

一般用于UI测试,配合React Component可以直观地比较UI的变化。

import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="http://www.facebook.com">Facebook</Link>)
    .toJSON();
  expect(tree).toMatchSnapshot();
});

第一次运行会生成一次snapshot,之后再运行,如果UI发生了变化,测试用例就会失败。这时,需要去检查是bug还是UI确实更新了,如果是bug则需要修复,如果是UI更新,可以通过命令jest --updateSnapshot来更新。该命令会更新所有的用例UI变化,所以应该在执行该命令前修复相应的bug,或者通过参数--testNamePattern来更新具体的用例。

最佳实践

  1. 视snapshots为代码
    利用相关工具进行代码美化:
  • eslint-plugin-jest with its no-large-snapshots option
  • snapshot-diff
  1. 测试应该是确定的(多次测试应该得到相同的结果)

  2. 使用描述性快照名称

  3. 推荐将最新的snapshot更新到版本管理工具中

Async Mock实例

// user.js
import request from './request';

export function getUserName(userID) {
  return request('/users/' + userID).then(user => user.name);
}

// request.js
const http = require('http');

export default function request(url) {
  return new Promise(resolve => {
    // This is an example of an http request, for example to fetch
    // user data from an API.
    // This module is being mocked in __mocks__/request.js
    http.get({path: url}, response => {
      let data = '';
      response.on('data', _data => (data += _data));
      response.on('end', () => resolve(data));
    });
  });
}

// __mocks__/request.js
const users = {
  4: {name: 'Mark'},
  5: {name: 'Paul'},
};

export default function request(url) {
  return new Promise((resolve, reject) => {
    const userID = parseInt(url.substr('/users/'.length), 10);
    process.nextTick(
      () =>
        users[userID]
          ? resolve(users[userID])
          : reject({
              error: 'User with ' + userID + ' not found.',
            }),
    );
  });
}

// __tests__/user-test.js
jest.mock('../request');

import * as user from '../user';

// The assertion for a promise must be returned.
it('works with promises', () => {
  expect.assertions(1);
  return user.getUserName(4).then(data => expect(data).toEqual('Mark'));
});

it('works with resolves', () => {
  expect.assertions(1);
  return expect(user.getUserName(5)).resolves.toEqual('Paul');
});

it('works with async/await', async () => {
  expect.assertions(1);
  const data = await user.getUserName(4);
  expect(data).toEqual('Mark');
});

// async/await can also be used with `.resolves`.
it('works with async/await and resolves', async () => {
  expect.assertions(1);
  await expect(user.getUserName(5)).resolves.toEqual('Paul');
});

Timer Mock实例

使用jest.useFakeTimers()mock setTimeout, setInterval, clearTimeout, clearInterval.

runAllTimers

// timerGame.js
'use strict';

function timerGame(callback) {
  console.log('Ready....go!');
  setTimeout(() => {
    console.log('Times up -- stop!');
    callback && callback();
  }, 1000);
}

module.exports = timerGame;

// __tests__/timerGame-test.js
'use strict';

jest.useFakeTimers();


// 验证callback被调用前等待了1s
test('waits 1 second before ending the game', () => {
  const timerGame = require('../timerGame');
  timerGame();

  expect(setTimeout).toHaveBeenCalledTimes(1);
  expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
});

// 验证callback在1s后被调用
test('calls the callback after 1 second', () => {
  const timerGame = require('../timerGame');
  const callback = jest.fn();

  timerGame(callback);

  // At this point in time, the callback should not have been called yet
  expect(callback).not.toBeCalled();

  // Fast-forward until all timers have been executed
  jest.runAllTimers();

  // Now our callback should have been called!
  expect(callback).toBeCalled();
  expect(callback).toHaveBeenCalledTimes(1);
});

runOnlyPendingTimers 处理timer嵌套的问题

// infiniteTimerGame.js
'use strict';

function infiniteTimerGame(callback) {
  console.log('Ready....go!');

  setTimeout(() => {
    console.log('Times up! 10 seconds before the next game starts...');
    callback && callback();

    // Schedule the next game in 10 seconds
    setTimeout(() => {
      infiniteTimerGame(callback);
    }, 10000);
  }, 1000);
}

module.exports = infiniteTimerGame;

// __tests__/infiniteTimerGame-test.js
'use strict';

jest.useFakeTimers();

describe('infiniteTimerGame', () => {
  test('schedules a 10-second timer after 1 second', () => {
    const infiniteTimerGame = require('../infiniteTimerGame');
    const callback = jest.fn();

    infiniteTimerGame(callback);

    // At this point in time, there should have been a single call to
    // setTimeout to schedule the end of the game in 1 second.
    expect(setTimeout).toHaveBeenCalledTimes(1);
    expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);

    // Fast forward and exhaust only currently pending timers
    // (but not any new timers that get created during that process)
    jest.runOnlyPendingTimers();

    // At this point, our 1-second timer should have fired it's callback
    expect(callback).toBeCalled();

    // And it should have created a new timer to start the game over in
    // 10 seconds
    expect(setTimeout).toHaveBeenCalledTimes(2);
    expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
  });
});

advanceTimersByTime(msToRun) 设置向前运行多少毫秒

it('calls the callback after 1 second via advanceTimersByTime', () => {
  const timerGame = require('../timerGame');
  const callback = jest.fn();

  timerGame(callback);

  // At this point in time, the callback should not have been called yet
  expect(callback).not.toBeCalled();

  // Fast-forward until all timers have been executed
  jest.advanceTimersByTime(1000);

  // Now our callback should have been called!
  expect(callback).toBeCalled();
  expect(callback).toHaveBeenCalledTimes(1);
});

前端路由方案总结

基本方案

  1. hash 路由
  2. history state

Hash 路由

兼容性更好,支持刷新,但会导致 url 中带有#

API

location.hash

读取和设置当前页面的hash url,当他发生改变时会触发hashchange事件。设置的方法有以下两种:

<a href='#/index'>Index</a>

location.herf = '#/index';

hashchange

可以监听location.hash的变化,注册相应的回调函数。基本用法:

window.addEventListener('hashchange',this.callback.bind(this),false);
currentHash = location.hash.slice(1);//字符串的 slice 方法,去除#号
// currentHash = location.hash.subString(1);

基本实现

function Router(){
  this.currentUrl='';
  this.routes={};
}
Router.prototype.route = function(path,callback){
  this.routes[path] = callback || function(){}
}
Router.prototype.refresh = function(){
  this.currentUrl = location.hash.slice(1) || '/';
  this.routes[this.currentUrl]();
}
Router.prototype.init = function(){
  window.addEventListener('load',this.refresh.bind(this),false);
  window.addEventListener('hashchange',this.refresh.bind(this),false);
}

var route = new Router();
// 初始化事件监听
route.init();
// 注册回调事件
route.route('/resume',showResumeFunc.bind(null,arg));

HTML5 History API 配合Ajax实现SPA地址栏变化

单页面应用通过Ajax拉取数据来更新页面,避免了页面的刷新,但同时也会导致不同页签的跳转不能被追踪,用户通过浏览器的"前进",”后退“功能不能实现正常的页面跳转。但是,利用HTML5新的API,我们就可以通过手动操作历史堆栈的内容,改变地址栏的URL,为用户提供正常的前进后退功能。

History API

History 基本用法

// 实现后退功能
window.history.back();

// 实现前进功能
window.history.forward();

// 载入到历史回话的某一个特定页面,当前页面的标志位为0
// 后退
window.history.go(-1);

// 查看历史堆栈中页面的数量
window.history.length;

HTML5中新引入的方法

通过 pushState 保存当前页面的信息,通过 popstate 事件触发时,可以传入之前存储的信息,触发相关操作。
可以不使用#,但是使用纯粹的相对路径时,服务端需要提供路由支持,否则页面刷新服务端直接解析改地址会导致404.

history.pushState(state Obj, title String, url String)

该方法会改变XMLHttpRequest时HTTP标头中referrer的值,该值为创建XMLHttpRequst对象的页面的URL。

state Obj 是一个与建立的浏览器记录有关的对象,可以被popstate事件的event对象获取,event.state会包含历史记录的状态对象的副本(不包含当前页面写入的state)。通常,我们在Ajax请求得到一个页面时,用stata Obj存储当前页面的相关信息,通过pushState将其写入历史记录。

title String 这里传入的字符串会作为页面的title显示在标签上,不过部分浏览器会忽略这一属性,因此传入一个空的字符串是一个更好的选择。

url String 这个URL只需要保证和目前的URL的Origin是相同的,他既可以是绝对地址也可以是相对地址(./newUrl/)

history.replaceState(state Obj, title String, url String)

该方法可以修改目前的历史记录

popstate 事件

popstate事件只会在浏览器点击前进后退,或者调用history的基本方法时触发。在页面加载时,Webkit内核的浏览器会触发该事件,IE和Firefox不会触发。

// 原生js绑定该事件

window.onpopstate = function(event) {
    var state = event.state; //这里state为上一页面的state,如果state没有记录,在当前页面直接调用往往为空
    if (state !== null){
        // do something
    }
}

// jquery实现
$(windwo).on('popstate', function(e) {
    // 这里的e是jQuery的event对象,而要获取DOM的事件对象,则需通过e.originalEvent
    var state = e.originalEvent.state;
    if (state !== null) {
        // do something
    }
})

路由基本思路(参考 backbone)

// 用法
this.route('search/:query/p:num', 'search', function(query, num) {
  ...
});

第一个参数支持路由带参数,第二个参数为路由名称是可选字段,第三个参数为注册的回调

也可以批量注册路由

var Workspace = Backbone.Router.extend({

  routes: {
    "help":                 "help",    // #help
    "search/:query":        "search",  // #search/kiwis
    "search/:query/p:page": "search"   // #search/kiwis/p7
  },

  help: function() {
    ...
  },

  search: function(query, page) {
    ...
  }

});

---

// 初始化时使用 routes 来存储所有的路由和其回调函数
if (options.routes) this.routes = options.routes;

// 绑定所有的回调
_bindRoutes: function() {
  if (!this.routes) return;
  this.routes = _.result(this, 'routes');
  var route, routes = _.keys(this.routes);
  while ((route = routes.pop()) != null) {
    this.route(route, this.routes[route]);
  }
},

// 将路由添加到 history 中并注册相关的回调
route: function(route, name, callback) {
  if (!_.isRegExp(route)) route = this._routeToRegExp(route);
  if (_.isFunction(name)) {
    callback = name;
    name = '';
  }
  if (!callback) callback = this[name];
  var router = this;
  Backbone.history.route(route, function(fragment) {
    var args = router._extractParameters(route, fragment);
    if (router.execute(callback, args, name) !== false) {
      router.trigger.apply(router, ['route:' + name].concat(args));
      router.trigger('route', name, args);
      Backbone.history.trigger('route', router, name, args);
    }
  });
  return this;
},

// 提取路由中参数的正则
// 将路由字符串转换为正则表达式
_routeToRegExp: function(route) {
  route = route.replace(escapeRegExp, '\\$&')
               .replace(optionalParam, '(?:$1)?')
               .replace(namedParam, function(match, optional) {
                 return optional ? match : '([^/?]+)';
               })
               .replace(splatParam, '([^?]*?)');
  return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
},

// 从正则表达式里提取出相应的参数
_extractParameters: function(route, fragment) {
  var params = route.exec(fragment).slice(1);
  return _.map(params, function(param, i) {
  if (i === params.length - 1) return param || null;
    return param ? decodeURIComponent(param) : null;
  });
}

---

// history 模块真正负责通过相关 api 实现路由
// backbone 中做了很多兼容性的判断,默认使用 hash

var addEventListener = window.addEventListener || function(eventName, listener) {
  return attachEvent('on' + eventName, listener);
};

if (this._usePushState) {
  addEventListener('popstate', this.checkUrl, false);
} else if (this._useHashChange && !this.iframe) {
  addEventListener('hashchange', this.checkUrl, false);
} else if (this._wantsHashChange) {
// 心跳监听
  this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
}

// 注册路由事件
route: function(route, callback) {
  this.handlers.unshift({route: route, callback: callback});
},

// 检查路由变化,触发回调事件
// Checks the current URL to see if it has changed, and if it has,
// calls `loadUrl`, normalizing across the hidden iframe.
checkUrl: function(e) {
  var current = this.getFragment();

  // If the user pressed the back button, the iframe's hash will have
  // changed and we should use that for comparison.
  if (current === this.fragment && this.iframe) {
    current = this.getHash(this.iframe.contentWindow);
  }

  if (current === this.fragment) return false;
  if (this.iframe) this.navigate(current);
  this.loadUrl();
},

// Attempt to load the current URL fragment. If a route succeeds with a
// match, returns `true`. If no defined routes matches the fragment,
// returns `false`.
loadUrl: function(fragment) {
  // If the root doesn't match, no routes can match either.
  if (!this.matchRoot()) return false;
  fragment = this.fragment = this.getFragment(fragment);
  return _.some(this.handlers, function(handler) {
    if (handler.route.test(fragment)) {
      handler.callback(fragment);
      return true;
    }
  });
}

Question:
路由跳转,携带参数,需要等跳转后的页面加载完成后,将该参数传入新的页面的相关函数(正常进入该页面可能不需要触发该函数),该函数甚至还是异步的请求,应该如何处理?是否应该将判断直接写在组件的生命周期里?

Vue 路由的钩子的作用? 是否可以解决这种场景?

TODO : 学习 React-router 和 Vue-router

Hello TypeScript

TypeScript——类

基于类的面向对象的编码风格。

基础

// Greeter类
class Greeter {
    // greeting属性
    greeting: string;
    // 构造函数
    constructor(message: string) {
        this.greeting = message;
    }
    // 方法
    greet() {
        return "Hello, " + this.greeting;
    }
}
// 创建了一个Greeter实例,并执行构造函数初始化他
let greeter = new Greeter("world");

继承

class Animal {
    name:string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

// 使用extends实现继承
class Snake extends Animal {
    //包含构造函数的派生类必须调用super(),他会执行基类的构造方法
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

class Horse extends Animal {
    constructor(name: string) { super(name); }
    //子类可以重写父类的放方法
    move(distanceInMeters = 45) {
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}

let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);

修饰符

TS中,成员默认为public,我们也可以手动将其标记为public

private

被标记为private的成员,不可以从类的外部访问。如果其中一个类型里包含一个private成员,那么只有当另外一个类型中也存在这样一个 private成员, 并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的。

protected

protected成员在派生类中仍然可以访问,构造函数被标记为protected意味着他不能再包含他的类外被实例化,但是能被继承。

class Person {
    protected name: string;
    protected constructor(theName: string) { this.name = theName; }
}

// Employee can extend Person
class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name);
        this.department = department;
    }

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // Error: The 'Person' constructor is protected

参数属性

在一个地方定义和初始化一个成员。

class Animal {
    // private name:string
    constructor(private name: string) { }
    move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

存取器

let passcode = "secret passcode";
// 只带有get不带有set的存取器会自动被判断为readonly
class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (passcode && passcode == "secret passcode") {
            this._fullName = newName;
        }
        else {
            console.log("Error: Unauthorized update of employee!");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    alert(employee.fullName);
}

静态属性

每个实例都要访问这个属性。

class Grid {
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        // 使用Grid.来访问静态属性
        let xDist = (point.x - Grid.origin.x);
        let yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

抽象类

作为其他派生类的基类使用,一般不直接被实例化。抽象类中的抽象方法不包含具体实现且必须在派生类中实现。

abstract class Department {

    constructor(public name: string) {
    }

    printName(): void {
        console.log('Department name: ' + this.name);
    }

    abstract printMeeting(): void; // 必须在派生类中实现
}

class AccountingDepartment extends Department {

    constructor() {
        super('Accounting and Auditing'); // constructors in derived classes must call super()
    }

    printMeeting(): void {
        console.log('The Accounting Department meets each Monday at 10am.');
    }

    generateReports(): void {
        console.log('Generating accounting reports...');
    }
}

let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: method doesn't exist on declared abstract type

高级技巧

构造函数

class Greeter {
    static standardGreeting = "Hello, there";
    greeting: string;
    greet() {
        if (this.greeting) {
            return "Hello, " + this.greeting;
        }
        else {
            return Greeter.standardGreeting;
        }
    }
}

let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());

// 取Greeter的类型
let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";

// 创建Greeter实例
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());

把类当做接口使用

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};

TypeScript——泛型

创建支持多种数据类型的可重用的组件。

基本

// T用于捕获用户传入的类型,并用它作为返回值的类型
function identity<T>(arg: T): T {
    return arg;
}

let output = identity<string>("myString");  // type of output will be 'string'
let output = identity("myString");  // type of output will be 'string'

泛型变量

泛型函数要求我们在函数体中正确使用这个传入的通用类型.

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

// 下面的写法是正确的
function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

泛型接口

interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

//指定泛型类型为number
let myIdentity: GenericIdentityFn<number> = identity;

泛型类

// 泛型类只可用于类的实例部分
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

泛型约束

我们不仅可以通过泛型变量中提到的传入正确的通用类型来约束泛型变量,也可以通过接口来描述约束条件。

interface Lengthwise {
    length: number;
}

// 接口约束了传入的值必须有number类型的length属性
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

loggingIdentity(3);  // Error, number doesn't have a .length property

loggingIdentity({length: 10, value: 3});
// 用属性名从对象中获取属性值,通过约束来确保属性存在于对象上
function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

TypeScript——模块

基础

模块在其自身的作用域里执行,不在全局作用域里。

导出语句

class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
export { ZipCodeValidator };
export { ZipCodeValidator as mainValidator };

导入

import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();

import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();

默认导出

const numberRegexp = /^[0-9]+$/;

export default function (s: string) {
    return s.length === 5 && numberRegexp.test(s);
}

// 可以自己指定默认导出的变量名
import validate from "./StaticZipCodeValidator";

let strings = ["Hello", "98052", "101"];

// Use function validate
strings.forEach(s => {
  console.log(`"${s}" ${validate(s) ? " matches" : " does not match"}`);
});

使用其他的JavaScript库

TypeScript——枚举,类型推论,类型兼容性,高级类型

枚举

定义一些有名字的数字常量,使用enum关键字。

enum Direction {
    Up = 1,
    Down, // 2
    Left, // 3
    Right, // 4
    Read = 1 << 1, //2
    G = "123".length //3
}

类型推论

//  被推断为联合数组类型,(Rhino | Elephant | Snake)[]
let zoo = [new Rhino(), new Elephant(), new Snake()];

// 被推断为Animal[]类型
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];

类型兼容性

如果x要兼容y,那么y至少具有与x相同的属性。

比较两个函数的类型兼容性

// 入参
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // OK
x = y; // Error
 
// 返回值
let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});

x = y; // OK
y = x; // Error because x() lacks a location property

高级类型

交叉类型

多在混入(mixins)时使用,将多个类型合并为一个类型。

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{};
    for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}

class Person {
    constructor(public name: string) { }
}
interface Loggable {
    log(): void;
}
class ConsoleLogger implements Loggable {
    log() {
        // ...
    }
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();

联合类型

一个值可以是几种类型之一。

/**
 * Takes a string and adds "padding" to the left.
 * If 'padding' is a string, then 'padding' is appended to the left side.
 * If 'padding' is a number, then that number of spaces is added to the left side.
 */
function padLeft(value: string, padding: number | string) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
}

padLeft("Hello world", 4); // returns "    Hello world"
let indentedString = padLeft("Hello world", true); // errors during compilation
interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
}

// 当一个值是联合类型时,我们只能访问所有类型的共有成员
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors

// 检查成员是否存在,需要增加类型断言
if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
}
else {
    (<Bird>pet).fly();
}

// 用户自定义类型保护
function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

typeof作为类型保护

function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

instanceof 类型保护

interface Padder {
    getPaddingString(): string
}

class SpaceRepeatingPadder implements Padder {
    constructor(private numSpaces: number) { }
    getPaddingString() {
        return Array(this.numSpaces + 1).join(" ");
    }
}

class StringPadder implements Padder {
    constructor(private value: string) { }
    getPaddingString() {
        return this.value;
    }
}

function getRandomPadder() {
    return Math.random() < 0.5 ?
        new SpaceRepeatingPadder(4) :
        new StringPadder("  ");
}

// 类型为SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();

if (padder instanceof SpaceRepeatingPadder) {
    padder; // 类型细化为'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
    padder; // 类型细化为'StringPadder'
}

字符串字面量类型

type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
    animate(dx: number, dy: number, easing: Easing) {
        if (easing === "ease-in") {
            // ...
        }
        else if (easing === "ease-out") {
        }
        else if (easing === "ease-in-out") {
        }
        else {
            // error! should not pass null or undefined.
        }
    }
}

let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here

TypeScript——接口

为对象类型命名,定义相关契约。

基本用法

// 接口声明
interface LabelledValue {
  label: string;
}

//只会检查接口设置的属性参数,不会检查其余属性
function printLabel(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

可选属性

一方面对可能存在的属性进行预定义,另一方面可以捕获引用了不存在的属性的错误。

// 可选属性用?表示
interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {
    let newSquare = {color: "white", area: 100};
    if (config.color) {
        newSquare.color = config.color;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

let mySquare = createSquare({color: "black"});

只读属性

// 变量用const,属性用readonly
interface Point {
    readonly x: number;
    readonly y: number;
}

额外的属性检查

如果使用可选属性,但却传入了可选属性外的属性,依然会报错(将对象向字面量赋值给另一个变量,可以绕过这一检查)。如果确认除了定义好的属性还可能传入任意数量的属性时,可以这样定义:

interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}

描述函数类型

// 入参类型和返回值类型
interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
// 参数只需按顺序类型匹配,不需要名称匹配
mySearch = function(src, sub) {
    let result = src.search(sub);
    return result > -1;
}

可索引的类型

class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}

// 表示用number去索引Okay时会得到Dog类型的返回值
// 可以使用readonly限制索引为只读
interface Okay {
    readonly [x: number]: Dog;
    [x: string]: Animal;
}
// 错误:使用'string'索引,有时会得到Animal!
// 索引支持number和string,但number的返回值类型必须是string返回值类型的子集
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Dog;
}

类类型——实现接口

// 强制一个类符合某种契约
interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

继承接口

更灵活地将接口分割到可重用的模块里。

interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

// 一个接口可以继承多个接口
interface Square extends Shape, PenStroke {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

混合类型

一个对象同时作为函数和对象使用。

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = <Counter>function (start: number) { };
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

TypeScript——Symbols

基本类型,通过Symbol构造函数创建。

// 不可变且唯一
let sym2 = Symbol("key");
let sym3 = Symbol("key");

sym2 === sym3; // false, symbols是唯一的

// 可以用作对象属性的键
let sym = Symbol();

let obj = {
    [sym]: "value"
};

console.log(obj[sym]); // "value"

可迭代性

实现了Symbol.iterator属性的对象时可迭代的,该函数负责返回供迭代的值。

for..of & for..in

for..of迭代对象的键值(value)并且可以用来迭代set,map,for..in迭代键(key)。

let pets = new Set(["Cat", "Dog", "Hamster"]);
pets["species"] = "mammals";

for (let pet in pets) {
    console.log(pet); // "species"
}

for (let pet of pets) {
    console.log(pet); // "Cat", "Dog", "Hamster"
}

CSS命名规范与实用代码段

命名规范

使用BEM规则,全部使用下划线来分隔,不使用驼峰式的命名方法。

  1. 如果class是大于等于两个单词(第二个之后的单词一般是修饰语Modifier),单词之间的分隔用一个下划线来实现。例如:
   .mobile_menu{
       display:block;
   }
  1. 如果class是该模块下的子元素,使用两个下划线来分隔。例如:
   .main__header{
       display:block;
   }

实用代码段

任意元素保持为窗口高度

html, 
body {
    height: 100%;
}
 
.item{
    height: 100%;
}

使用 IE 盒模型

html {
      box-sizing: border-box;
}
 
*, *:before, *:after {
box-sizing: inherit;
}

清浮动

.clearfix:after {
content:".";
display:block;
height:0;
visibility:hidden;
clear:both;
}


.clearfix:before, .container:after { content: ""; display: table; }.clearfix:after { clear: both; } /* IE 6/7 */.clearfix { zoom: 1; }

文字过长后用…代替

  p{
    white-space: nowrap;//不换行
    overflow: hidden;
    text-overflow: ellipsis;
      //用...代替过长的文字,如果是clip则为直接剪裁,只有firefox 9+支持以string作为其值
  }

// 注意该块或其父元素需要设置width或者(max-width)
 
// 对于现代浏览器已可以实现多行文字用...代替过长的文字

.box {
  display: -webkit-box;
  -webkit-line-clamp: 3;//设置行数
  -webkit-box-orient: vertical;
  overflow: hidden;
}

媒体查询

/* Smartphones (portrait and landscape) ----------- */
@media only screen 
and (min-device-width : 320px) and (max-device-width : 480px) {
  /* Styles */
}
 
/* Smartphones (landscape) ----------- */
@media only screen and (min-width : 321px) {
  /* Styles */
}
 
/* Smartphones (portrait) ----------- */
@media only screen and (max-width : 320px) {
  /* Styles */
}
 
/* iPads (portrait and landscape) ----------- */
@media only screen and (min-device-width : 768px) and (max-device-width : 1024px) {
  /* Styles */
}
 
/* iPads (landscape) ----------- */
@media only screen and (min-device-width : 768px) and (max-device-width : 1024px) and (orientation : landscape) {
  /* Styles */
}
 
/* iPads (portrait) ----------- */
@media only screen and (min-device-width : 768px) and (max-device-width : 1024px) and (orientation : portrait) {
  /* Styles */
}
 
/* Desktops and laptops ----------- */
@media only screen and (min-width : 1224px) {
  /* Styles */
}
 
/* Large screens ----------- */
@media only screen and (min-width : 1824px) {
  /* Styles */
}
 
/* iPhone 4 ----------- */
@media only screen and (-webkit-min-device-pixel-ratio:1.5), only screen and (min-device-pixel-ratio:1.5) {
  /* Styles */
}

视窗内水平垂直居中

.div{
   position: fixed;
   left:0;right:0;top:0;bottom:0;
   margin:auto;
}

font-face写法

@font-face {
    font-family: 'MyWebFont';
    src: url('webfont.eot'); /* IE9 Compat Modes */
    src: url('webfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
    url('webfont.woff') format('woff'), /* Modern Browsers */
    url('webfont.ttf')  format('truetype'), /* Safari, Android, iOS */
    url('webfont.svg#svgFontName') format('svg'); /* Legacy iOS */
}
 
body {
    font-family: 'MyWebFont', Arial, sans-serif;
}

可点击项目鼠标手形

a[href], input[type='submit'], input[type='image'], label[for], select, button, .pointer {    cursor: pointer;}

输入表单样式重置

input[type=text], textarea {
  -webkit-transition: all 0.30s ease-in-out;
  -moz-transition: all 0.30s ease-in-out;
  -ms-transition: all 0.30s ease-in-out;
  -o-transition: all 0.30s ease-in-out;
  outline: none;
  padding: 3px 0px 3px 3px;
  margin: 5px 1px 3px 0px;
  border: 1px solid #DDDDDD;
  resize: none;
}
 
input[type=text]:focus, textarea:focus {
  box-shadow: 0 0 5px rgba(81, 203, 238, 1);
  padding: 3px 0px 3px 3px;
  margin: 5px 1px 3px 0px;
  border: 1px solid rgba(81, 203, 238, 1);
}

GITHUB RIBBON USING CSS TRANSFORMS

.ribbon {
    background-color: #a00;
    overflow: hidden;
    /* top left corner */
    position: absolute;
    left: -3em;
    top: 2.5em;
    /* 45 deg ccw rotation */
    -moz-transform: rotate(-45deg);
    -webkit-transform: rotate(-45deg);
    /* shadow */
    -moz-box-shadow: 0 0 1em #888;
    -webkit-box-shadow: 0 0 1em #888;
}
.ribbon a {
    border: 1px solid #faa;
    color: #fff;
    display: block;
    font: bold 81.25% 'Helvetiva Neue', Helvetica, Arial, sans-serif;
    margin: 0.05em 0 0.075em 0;
    padding: 0.5em 3.5em;
    text-align: center;
    text-decoration: none;
    /* shadow */
    text-shadow: 0 0 0.5em #444;
}
 
// https://unindented.org/articles/github-ribbon-using-css-transforms/

css实现带阴影三角

.triangle-with-shadow {
  width: 100px;
  height: 100px;
  position: relative;
  overflow: hidden;
  box-shadow: 0 16px 10px -17px rgba(0, 0, 0, 0.5);
}
.triangle-with-shadow:after {
  content: "";
  position: absolute;
  width: 50px;
  height: 50px;
  background: #999;
  transform: rotate(45deg); /* Prefixes... */
  top: 75px;
  left: 25px;
  box-shadow: -1px -1px 10px -2px rgba(0, 0, 0, 0.5);
}

再看垂直居中

之前总结过水平垂直居中的方法,
以及CSS-tricks中也有清晰的总结。

最近发现项目中其他人写的样式中大量使用了display:table-cell去实现水平垂直居中.

.father{
  display: table-cell;
  width: 500px;
  height: 300px;
  background-color: #de3;
  vertical-align: middle;
  text-align: center;
}

.child{
  display: inline-block;
  max-width:400px;
  max-height:200px;
  background-color: #fd2;
}

优势在于不会有position:absolute的负面影响

以及实现多行元素垂直居中:

.father{
  display: table-cell;
  width: 100px;
  height: 200px;//高度设置无效
  background-color: #de3;
  vertical-align: middle;
  text-align: center;
  word-break: normal;
}

这一方法的问题在于:

  • 高度设置无效
  • overflow设置无效
  • margin不感知
  • 与 position/float等存在冲突

多行文字垂直居中:

.flex-center-vertically {
  display: flex;
  justify-content: center;
  flex-direction: column;
  height: 400px;
}

.ghost-center {
  position: relative;
}
.ghost-center::before {
  content: " ";
  display: inline-block;
  height: 100%;
  width: 1%;
  vertical-align: middle;
}
.ghost-center p {
  display: inline-block;
  vertical-align: middle;
}

你不知道的JavaScript(上)

作用域相关

  1. 变量的赋值操作:
    首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会查找该变量,如果能找到就对他赋值。
  2. 声明的提前
    编译器可以在代码生成的同时处理声明和值的定义。
  3. LHS & RHS
    变量出现在赋值操作的左侧时进行LHS,出现在非左侧,为了得到源值所进行的查询为RHS.
    不成功的RHS引用会导致抛出ReferenceError异常,不成功LHS引用会导致在非严格模式下自动隐式地创建一个全局变量。
  4. 异常类型
    ReferenceError同作用域判别失败相关。
    TypeError表示作用域判别成功,但对结果的操作是非法或不合理的。
  5. 遮蔽效应
    在多层嵌套的作用域中定义同名标识符,内部的会遮蔽外部的。作用域查找会在找到第一个匹配的标识符时停止。

  6. 通常会在全局作用域中声明一个名字足够独特的变量(对象),作为命名空间,其功能以对象的属性的方式暴露给外界。
  7. 模块管理
    通过依赖管理器的机制将库的标识符显式地导入到另外一个作用域中。

词法与块级作用域

  1. 词法作用域
    意味着作用域是由书写代码时函数声明的位置决定的,编译的词法分析阶段基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行过程中如何对他们进行查找。
    with和eval能够欺骗词法作用域,导致性能下降,所以不要使用他们。
  2. 块级作用域
  • catch,let,const会创建一个块级作用域.
  • let进行的声明,不会在块级作用域内进行提升。
  • const创建了具有块级作用域的常数。

函数相关

  1. 函数作用域
    属于函数的全部变量都可以在整个函数范围内使用及复用。
    软件设计中,应最小限度地暴露必要内容,将其他内容隐藏起来,通过作用域可以实现隐藏一些变量在作用域内部。
  2. 函数声明和函数表达式
    如果function是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。
  3. 匿名的函数表达式
    当函数需要引用自身时,只能使用arguments.callee(规范中已经不建议使用)。始终给函数表达式命名是一个最佳实践。
  4. IIFE
    立即执行函数表达式,使用具名的IIFE是一个值得推广的实践。

闭包

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域外执行。
理解:实际上我们是想在一个变量的作用域外访问该变量,这时候只需要在该作用域声明一个函数,用该函数访问想要访问的那个变量,由于作用域链的延伸,这是可以实现的。然后在该作用域内把这个函数作为返回值,这样就实现了从作用域外部访问作用域内的变量的目的。这个实现过程就叫做闭包。

以下两个代码块可以实现相同的功能,其实质解决的是javascrit本身的for循环不存在块级作用域的问题。setTimeout在for循环里声明了回调函数,在for循环结束后执行这些回调函数,而有了块级作用域后,每次回调都被关闭在了作用域内,也就能访问当前的i的值。

//使用let实现块级作用域
for(let i=1;i&lt;5;i++){
setTimeout(function timer(){
console.log(i);
},i*1000);
}
//使用IIFE实现块级作用域
for(var i=1;i&lt;5;i++){
(function(j){
setTimeout(function timer(){
console.log(j);
},j*1000);
})(i);
}

模块

两个必要条件:

  1. 必须有外部的封闭函数,该函数必须至少被调用一次(通过IIFE单次调用,或者通过var赋值多次调用),每次调用会创建一个新的模块实例。
  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或修改私有的状态。

this

this是在函数被调用时发生的绑定,他指向什么完全取决于 函数在哪里被调用。

当一个函数被调用时,会创建一个执行上下文,他会包含函数在哪里被调用(this)、函数的调用方法、传入的参数等信息,this就是记录的其中一个属性,会在函数执行过程中被找到。

this的绑定规则

1.默认绑定

直接使用不带任何修饰的函数引用进行调用,只能使用默认绑定,this指向全局对象。(只有在非严格模式下运行时才是这样)在严格模式下,this的默认绑定与函数的调用位置无关:

"use strict";
function foo(){
console.log(this.a);
}
var a=2;
(function(){
var a=4;
foo();//Uncaught TypeError 全局的严格模式,注意这里this的默认绑定为undefined
})();

function foo(){
console.log(this.a);
}
var a=2;
(function(){
"use strict";
var a=4;
foo();//2 局部的严格模式,这时this的绑定和其调用的位置无关(别这么用严格模式T T)
})();

2.隐式绑定

当函数引用有上下文对象时,this绑定到这个上下文对象。上下文引用链中只有最后一层会影响调用位置。

function foo(){
console.log(this.a);
}

var obj2={
a:42,
foo:foo
};
var obj1={
a:2,
obj2:obj2
};

obj1.obj2.foo();//42

要注意会出现隐式丢失的情况,尤其是在回调函数中经常会出现:

function foo() {
    console.log(this.a);
}
function doFoo(fn) {
    fn();
}
var obj = {
    a: 2,
    foo: foo
};
var a = "oops,global";
doFoo(obj.foo);// oops,global

显式绑定

通过call和apply方法的第一个参数指定this的绑定对象。

  1. 硬绑定
    硬绑定的一个典型应用场景就是创建一个包裹函数,传入所有的参数并返回接收到的所有的值:
function foo(something){
console.log(this.a,something);
return this.a+something;
}

var obj={
a:2
};

var bar = function(){
return foo.apply(obj,arguments);
};//硬绑定,等效于
var bar = foo.bind(obj);

var b = bar(3);//2 3
console.log(b);//5
  1. new绑定
    javascript中,构造函数只是使用new操作符时被调用的函数,他们不属于某个类,也不会实例化为一个类。
    使用new来发生构造函数调用会执行下面的操作:
  2. 创建一个全新的对象
  3. 新对象会被执行原型连接
  4. 新对象会绑定到函数调用的this
  5. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
function foo(a){
this.a = a;
}

var bar = new foo(2);
console.log(bar.a);

绑定优先级与例外

  1. 优先级

4-3-2-1

  1. 例外
  • 把null或undefined作为this绑定的对象传入call,apply,bind时,会引用默认绑定
function foo(a,b){
console.log("a:"+a+",b:"+b);
}

//空对象,可以更安全地使用this为空
var nope = Object.create(null);
//把数组展开成参数
foo.apply(nope,[2,3]);//a:2,b:3

//使用bind()进行柯里化
var bar = foo.bind(nope,2);
bar(3);//a:2,b:3
  • 间接引用
    最容易发生在赋值时,会应用默认绑定
function foo(){
console.log(this.a);
}

var a=2;
var o={a:3,foo:foo};
var p={a:4};

o.foo();//3
(p.foo=o.foo)();//2
  • 软绑定
    给默认绑定指定一个全局对象和undefined以外的值,且可以通过其他绑定方法修改this.
  • 箭头函数
    继承外层函数调用的this绑定。

编辑器配置

sublime 配置

  1. package control
    ctrl+ `
    then
import urllib.request,os,hashlib; h = '2915d1851351e5ee549c20394736b442' + '8bc59f460fa1548d1514676163dafc88'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.io/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by)
  1. 插件
  2. emmet
  3. jsformat
  4. csscomb
  5. sidebarEmhance
  6. tag
  7. terminal
  8. babel
  9. tag
  10. trailing space
  11. autoprefixer
  12. markdown
  13. omnimarkuppreviewer
  14. 配置
{
    "always_show_minimap_viewport": true,
    "bold_folder_labels": true,
    "caret_extra_bottom": 1,
    "caret_extra_top": 1,
    "caret_extra_width": 1,
    "caret_style": "blink",
    "color_scheme": "Packages/Material Theme/schemes/OLD/Material-Theme-Darker.tmTheme",
    "fade_fold_buttons": false,
    "font_face": "Menlo",
    "font_size": 13,
    "ignored_packages":
    [
        "Vintage"
    ],
    "indent_guide_options":
    [
        "draw_normal",
        "draw_active"
    ],
    "index_files": false,
    "line_padding_bottom": 2,
    "line_padding_top": 2,
    "overlay_scroll_bars": "enabled",
    "show_encoding": true,
    "show_line_endings": true,
    "theme": "Lanzhou.sublime-theme",
    "update_check": false
}

一个瀑布流相册

前两天看到一个纯CSS方案实现类似于500px的图片瀑布流,主要特点是每行近似等高,每幅图不等宽,可以根据窗口大小自动调整每一行的图片数目。

模仿给自己的博客做了个相册,结合了之前做的图片渐进式加载和懒加载,样式还没调整,最后的效果是这样的:

核心代码也就几行,主要依赖flex布局的flex-grow属性。这里我用React进行的模板渲染,初始设置每行图片的高度为300px左右(lineHeight),拿到图片的宽度和高度后计算得到每一个图片的宽度,然后依靠flex-grow属性使得图片自动拉伸填满整行。

核心代码如下:

divStyle: {
  width: width*this.state.lineHeight/height + 'px',
 flexGrow: width*this.state.lineHeight/height
},
iStyle: {
   paddingBottom: height/width * 100 + '%'
}

.img_box{
    display: flex;
    flex-wrap: wrap;
}

.img_box::after {
  content: '';
  flex-grow: 1e4;
  min-width: 20%;
}

.placeholder{
    margin: 2px;
    background-color: #ddd;
    position: relative;
    cursor: pointer;
}


.placeholder img{
    position: absolute;
    top: 0;
    width: 100%;
    vertical-align: bottom;
}
constructor(props){
		super(props);

		this.state = {
			divStyle:{},
			iStyle:{},
			lineHeight: 300
		}
	}

	componentDidMount(){
		let img = this.props.img;
		let id = this.props.id,
			  width = img.width,
				height = img.height;

		this.setState({
			divStyle: {
				width: width*this.state.lineHeight/height + 'px',
				flexGrow: width*this.state.lineHeight/height
			},
			iStyle: {
				paddingBottom: height/width * 100 + '%'
			}
		})
	}

    render(){
		const IMG_S = "?imageView2/2/w/30/h/20/interlace/0/q/100";
		return(
			<picture className="placeholder">
				<img src={this.props.src + IMG_S}
					   alt={this.props.alt}
					   id={this.props.id}
					   className="img_small" />
				<span>{this.props.alt}</span>
			</picture>
		)
	}

其中,padding-bottom还是起到之前的placeholder的作用,防止图片加载过程中布局不断地重排,闪瞎眼睛。

参考

  1. 使用纯 CSS 实现 Google Photos 照片列表布局
  2. 由flickr提供的js版本

学习 React 系列(四)

API

ReactDom

import ReactDOM from 'react-dom'

// render()将元素渲染到 DOM
ReactDOM.render(
  element,
  container,
  [callback]
)

// 将元素渲染到非父元素
ReactDOM.createPortal(child, container)

// container 是服务端渲染的节点
ReactDOM.hydrate(
  element,
  container,
  [callback]
)

// 从 DOM 节点中移除一个挂载的组件
ReactDOM.unmountComponentAtNode(container)

SyntheticEvent合成事件

处于性能考虑,无法以异步方式访问事件。在事件名后面添加Capture就可在事件捕获阶段注册事件。

function onClick(event) {
  console.log(event); // => nullified object.
  console.log(event.type); // => "click"
  const eventType = event.type; // => "click"

  setTimeout(function() {
    console.log(event.type); // => null
    console.log(eventType); // => "click"
  }, 0);

  // Won't work. this.state.clickEvent will only contain null values.
  this.setState({clickEvent: event});

  // You can still export event properties.
  this.setState({eventType: event.type});
}

事件

// 剪贴
onCopy onCut onPaste
// 间接输入(比如输入中文)
onCompositionEnd onCompositionStart onCompositionUpdate
// 键盘事件
onKeyDown onKeyPress onKeyUp
// 焦点事件
onFocus onBlur
// 表单事件
onChange onInput onSubmit
// 鼠标事件
onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit
onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave
onMouseMove onMouseOut onMouseOver onMouseUp
// 选择事件
onSelect
// 触摸事件
onTouchCancel onTouchEnd onTouchMove onTouchStart
// UI 事件,内容滚动
onScroll
// 滚轮事件,滚轮滚动
onWheel
// 媒体事件
onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted 
onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay 
onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend 
onTimeUpdate onVolumeChange onWaiting
// 图片事件
onLoad onError
// 动画事件
onAnimationStart onAnimationEnd onAnimationIteration
// CSS transition 事件
onTransitionEnd

TODO

服务端渲染
测试用例
浅层渲染

React 设计原则

  1. 组合

The key feature of React is composition of components.
In React, components describe any composable behavior, and this includes rendering, lifecycle, and state.

真正实现很好地复用,为组件添加功能不需要去改变组件内部的代码,组件包含了我们在 View 中需要的大部分内容。

  1. 公共抽象(业务功能)
    对于非常常用的业务场景,例如数据状态,生命周期。React 愿意在框架内部对这些功能进行很好地实现。

  2. 逃生舱
    对于一些虽然我们不认可但是在某些情况必须的操作,尽管提供不了完美的 API,但是仍然提供一个临时的降级的 API。

  3. 稳定性
    不需要完全的向前兼容,但能够提供平缓升级(自动升级)。

  4. 互通性
    不需要完全重写现有代码,就有可能将 React 的一些功能用于当前的项目。

  5. 调度

It is a key goal for React that the amount of the user code that executes before yielding back into React is minimal
异步的更新界面(setState), 统一的调度,更多的约束,但是实现更好的性能,更好地调试。

  1. 更好的开发和调试体验

  2. Beyond the DOM

  3. 实用

We try to provide elegant APIs where possible. We are much less concerned with the implementation being elegant.

  1. API 命名能很好表意

实例就是对组件类中this的引用。

函数组件根本就没有实例。类组件有实例,但永远不需要直接创建组件实例,React会完成这个实例的创建。

浮动与BFC小结

浮动的产生是为了实现 文字环绕效果,现在我们使用浮动实现很多布局。浮动的元素宽度会收缩为内容区域宽度,而不是再占据一整行。

为什么要清浮动

因为浮动元素会 脱离文档流,无法撑开父元素造成父元素的塌陷。

  • 浮动的其他影响
  1. 对于浮动元素自身,会变成块级对象,即display:block.
  2. 对于非浮动的块级兄弟元素,会占据浮动元素原始文档流中的位置,并且位于浮动元素z轴方向的下方,而且无法通过z-index调整到浮动元素上方。但是,其内部的文字会环绕浮动元素;对于内联的兄弟元素,会直接形成文字环绕效果。
  3. 对于浮动的兄弟元素,如果方向相同会更在上一个浮动元素的后方,只要那一行放的下。
  4. 对于其子元素。浮动元素的子元素会撑开浮动元素,使其高度适应于子元素的高度。

如何清除浮动

在包含浮动元素的父元素上插入.clearfix类。

方法一:底部插入clear:both

    .clearfix:after{
    content:'';
    display:table;
    clear:both;
    }

.container:before,
    .container:after {
    content:"";
    display:table;
    }
    .container:after {
    clear:both;
    }
    .container {
    zoom:1; /* For IE 6/7 (trigger hasLayout) */
    }

方法二:使父元素成为新的BFC

*zoom:1(IE6/7);
    float:left/right;
    position:absolute/fixed;
    overflow:hidden/scroll(IE7+);
    display:inline-block/table-cell;

为什么这些方法可以清除浮动 (的影响)

一、clear属性

规定了元素哪一侧 不允许出现浮动元素,一旦清除某一侧的浮动,会使元素的上外边框边界刚好在该侧浮动元素的下外边距边界之下。

二、新的BFC (IE中的haslayout)

BFC(Block formatting context)直译为"块级格式化上下文"。

BFC的布局规则

  • 内部的Box会在垂直方向,一个接一个地放置。

  • Box垂直方向的距离由margin决定。属于同一个BFC的两个毗邻的Box的margin会发生重叠.
    这里 毗邻的定义为:同级或者嵌套的盒元素,并且它们之间没有非空内容、Padding或Border分隔。

  • 每个元素的margin box的左边,与包含块border box的左边相接触(对于从左往右的格式化,否则相反).即使存在浮动也是如此.

  • BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素或受外面的元素影响.

  • 计算BFC的高度时,浮动元素也参与计算.

  • BFC区域不会与floatbox重叠.
    从BFC的布局规则中我们就可以看出为什么他实现了清楚浮动带来的父元素的塌陷的问题,具体产生新的BFC的方法:

  • 根元素html

  • 浮动元素,float 除 none 以外的值

  • 绝对定位元素,position(absolute,fixed)

  • display 为以下其中之一的值 inline-blocks,table-cells,table-captions

  • overflow 除了 visible(默认值) 以外的值(hidden,auto,scroll)

BFC规则的其他用途

  1. 解决外边距重叠问题

    一个常见的外边距的场景就是我希望一个div的子元素div能跟他的父元素上边距有一定的margin,如果直接对该子元素设置margin并不能达到预想的效果,而会将两者整体向下移动一定的距离,也就是和更高一级的父元素产生了margin.
    之所以会出现这样的情况是因为margin重叠的规则, 属于同一个BFC的两个毗邻的Box的margin会发生重叠.而通过增加border或者padding来分隔两个box显然不够理想.于是我们选择通过产生新的BFC来实现去除两个box见的毗邻状态,常见的方法是使用overflow:hidden.

  2. 去除图片的文字环绕效果
    对于浮动元素的非浮动块级兄弟元素,其文字依然会环绕浮动元素,这有时候并不是我们想要的效果,通过BFC规则的BFC区域不会与floatbox重叠.我们就可以给该兄弟元素设置overflow:hidden来实现去除文字环绕效果的功能.

encodeURI and encodeURIComponent

encodeURI和encodeURIComponent的区别在于前者被设计来用于对完整URL进行URL Encode,于是URL中的功能字符,比如&, ?, /, =等等这些并不会被转义;而后者被设计来对一个URL中的值进行转义,会把这些功能字符也进行转义。应用场景最常见的一个是手工拼URL的时候,对每对KV用encodeURIComponent进行转义。

作者:Jim Liu
链接:https://www.zhihu.com/question/21861899/answer/43469947
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

具体使用中,如果对整个URL进行编码就使用encodeURI,但更多场景中我们只需要对相关参数进行编码,即https:// + location + /&key=encodeURIComponent(value)

具体的编码:

Char  encUrI  encURIComp  escape
*     *       *           *
.     .       .           .
_     _       _           _
-     -       -           -
~     ~       ~           %7E
'     '       '           %27
!     !       !           %21
(     (       (           %28
)     )       )           %29
/     /       %2F         /
+     +       %2B         +
@     @       %40         @
?     ?       %3F         %3F
=     =       %3D         %3D
:     :       %3A         %3A
#     #       %23         %23
;     ;       %3B         %3B
,     ,       %2C         %2C
$     $       %24         %24
&     &       %26         %26
      %20     %20         %20
%     %25     %25         %25
^     %5E     %5E         %5E
[     %5B     %5B         %5B
]     %5D     %5D         %5D
{     %7B     %7B         %7B
}     %7D     %7D         %7D
<     %3C     %3C         %3C
>     %3E     %3E         %3E
"     %22     %22         %22
\     %5C     %5C         %5C
|     %7C     %7C         %7C
`     %60     %60         %60

more

xss防御中转义

var entityMap = {
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  '"': '&quot;',
  "'": '&#39;',
  '/': '&#x2F;',
  '`': '&#x60;',
  '=': '&#x3D;'
};

function escapeHtml (string) {
  return String(string).replace(/[&<>"'`=\/]/g, function (s) {
    return entityMap[s];
  });
}

Some Useful Gists

原生Js实现Jquery的on方法

var Event = (function(){
    var bindFuncList = {};

    //
    var add = function(funcName){
        var args = Array.prototype.slice.call(arguments,1);
        bindFuncList[funcName].func.apply(bindFuncList[funcName].scope,args);
        return this;
    }

    //
    var on = function(funcName, callback){
        if(typeof funcName === "string" && callback ==="function"){
            bindFuncList[funcName]={
                func:callback
            };
        }
        return this;
    }

    return {
        add: add,
        on: on
    }
})();

Event.add("clicked", "haha");
Event.on("clicked",function(info){
    alert(info+"Succeed!");
});

//===================

// 自定义事件的绑定和监听
// 发布-订阅者模式
let event = {
  eventList: [],
  on: (name, fn) => {
    if(!this.eventList[name]){
      this.eventList[name] = [];
    }
    this.eventList[name].push(fn);
  },
  emit: () => {
    let key = Array.prototype.shift.call(arguments),
        fns = this.eventList[name];

    if (!fns || fns.length === 0) {
      return false;
    }

    for (let i=0,fn;fn=fns[i++];) {
      fn.apply(this.arguments);
    }
    return true;
  }
}

产生不重复的随机数

var arr = [];
while(arr.length < 8){
  var randomnumber=Math.ceil(Math.random()*100)
  var found=false;
  for(var i=0;i<arr.length;i++){
	if(arr[i]==randomnumber){found=true;break}
  }
  if(!found)arr[arr.length]=randomnumber;
}
console.log(arr);

对cookie的最简单封装

/* get 获得cookie的值
   set 设置cookie的值
   unset 删除cookie的值 */
   
var CookieUtil = {
	get: (name) => {
   		const cookieName = encodeURIComponent(name) + "=",
        	  cookieStart = document.cookie.indexOf(cookieName),
              cookieValue = null;
        
        if(cookieStar > -1){
        	const cookieEnd = document.cookie.indexOf(";", cookieStart);
            if (cookieEnd == -1){
            	cookieEnd = document.cookie.length;
            }
            cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd));
        }
    },
    
    set: (name,value,expires,path,domain,secure) => {
    	const cookieText = encodeURIComponent(name) + "=" + encodeURIComponent(value);
        if (expires instanceof Date) {
        	cookieText += "; expires=" + expires.toGMTString();
        }
        if (path) {
        	cookieText += "; path=" + path;
        }
        if (domain) {
			cookieText += "; domain=" + domain;
		}
        if (secure) {
            cookieText += "; secure";
        }
        document.cookie = cookieText;
    },
    
    unset: (name,path,domain,secure) {
    	this.set(name, "", new Date(0), path, domain, secure);
    }
}

实现一个AMD风格的模块管理

let MyModules = (function Manager() {
    let modules={};
 
    function define(name, deps, impl) {
        for (let i=0; i < deps.length; i++) {
            deps[i] = modules[deps[i]]; //从modules中找到依赖的模块存入数组中
        }
        modules[name] = impl.apply(impl, deps);//核心,将依赖的模块以参数形式传入定义的模块
    }
 
    function get(name) {
        return modules[name];
    }
 
    return {
        define,
        get
    }
})();
//然后就可以以amd的形式管理模块
MyModules.define("bar", [], function(){
    function hello(who) {
        return "Let me introduce: " + who;
    }
    return {hello};
});
MyModules.define("foo", ["bar"], function(){
    let hungry = "hippo";
    function awesome(who) {
        console.log(bar.hello(hungry).toUpperCase());
    }
    return {awesome};
});
 
let bar = MyModules.get("bar");
let foo = MyModules.get("foo");
console.log(bar.hello("hippo");); //Let me introduce: hippo
foo.awesome();// LET ME INTRODUCE: HIPPO

Open a page in new tab the safe way

我们知道,想要在新的页面打开一个链接,只需要给该链接设置一个‘target=_blank’即可,但是这种做法是极不安全的(微信甚至禁用了这个属性),其原因在于通过‘target=_blank’打开的页面可以拿到其父页面的window对象,也就是说这个新的页面里的js可以随意控制之前的页面。

例如

if(window.opener){
    opener.location = "www.bingdu.com"//opener对象就拿到了当前页面的母页面
}

从代码中就可以看出,如果新窗口打开的网页被注入了恶意的js脚本,就有可能重定向之前的网页,这对于需要登录的场景尤其危险,因为页面可能被重定向到钓鱼网站。

opener似乎也有些用,比如在关闭弹出的窗口时自动刷新页面等,但从安全的角度考虑,实在不应使用这个API。

rel=noopener正是用来解决这个问题的,他会将window.opener设置为null。(Chrome 49+ and Opera 36+)对于firefox可以使用rel=noreferrer来达到相同的目的,所以一个兼容性比较好的方法是同时设置这两个值‘rel=noopener noreferrer’,如果还要兼容更老的浏览器(IE之类)的,则可以通过js手动将opener置为空。

var otherWindow = window.open();
otherWindow.opener = null;
otherWindow.location = url;

然而,safari上面的都不支持,所以,最好不要用_blank.

学习 React 系列(一)

什么是 JSX?

It is called JSX, and it is a syntax extension to JavaScript. We recommend using it with React to describe what the UI should look like.

JSX 看起来像是 HTML in JavaScript,实际上我理解他就是React.createElement()的语法糖,他让我们能够以一种更加习惯(直观)的方式描述 UI。他既不是简单的字符串模板(undersocre temple),也不是 HTML 模板引擎(jade),而是可以理解为一种 JavaScript 语法扩展(带语法糖的手写抽象语法树),他需要编译后才能在浏览器中运行。

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

// 编译后
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

因为 jsx 就是 JavaScript,我们一方面可以在其中使用各种 JavaScript 语法,同时也很好理解为什么我们不能在其中使用class,for等 HTML 属性内容,以为他们是 JavaScript 的保留字。

使用方法

  1. {}中可以写入任何的JavaScript 表达式,包括函数调用。
  2. 可以按照 HTML 的对齐来写,但是要注意最好加上(),防止错误地自动插入分号。
  3. jsx 可以用来书写自定义的 react 元素,不同于 HTML 元素,这些元素需要以大写字母开头。
  4. 如果你需要在一个模块中渲染多个 react 组件,你可以使用点符号
import React from 'react';

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Imagine a {props.color} datepicker here.</div>;
  }
}

function BlueDatePicker() {
  return <MyComponents.DatePicker color="blue" />;
}
  1. 标签间的内容可以通过props.children传递。

Class or Function

React Component 的定义方法大体上可以分为下面两种:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

通常,一个组件如果只是用来数据展示, 没有内部状态(state)的话,我们就应该使用函数的方法来书写。

无论用哪种写法,React 组件都应该表象地像纯函数一样,因为 props 都应该是只读的

纯函数:do not attempt to change their inputs, and always return the same result for the same inputs.

state & props

state 和 props 都应该是 immutable 的。

组件间通过 props 传递数据,props 应该是只读的,子组件不能改变来自父组件的 props,数据流是单向的。

组件内部的状态维护通过 state 实现,应该通过 setState 去更新他的值,而不能直接给他赋值。因为组件状态的更新可能是异步的,所以我们不应该依赖他们的值进行计算,如果可能,所有的状态更新都应该通过传入一个函数的方式来更新。

// Correct
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

多次调用 setState 函数产生的效果会被合并。

确定 state 应该在哪个组件中管理

  1. 找出每一个基于该状态进行渲染的组件
  2. 找到他们公共的父组件
  3. 状态应该由他们公共的父组件或者其他更高层级的组件来管理
  4. 如果找不到公共的父组件,应该创建一个,并将其引入到更高层级的组件中

组件的生命周期

常用的生命周期函数(除了基本的)

  • componentDidMount()
  • componentWillReceiveProps(nextProps)
  • componentDidUpdate(prevProps, prevState)
  • componentWillUnmount()

Mounting(挂载,初始化)

组件实例被创建并插入到 DOM 时调用。

constructor()

// 组件挂载前执行
constructor(props){
  super(props);// so that this.props should not be undefined
  this.state = {
    color: 'xx',
    time: props.time // 当这样写的时候,props 的更新是不会同步更新到 state 的,因此往往其真正需要的是 lift the state up,即让一个公共的父组件来处理这些状态
  };
  this.someEvent = this.someEvent.bind(this);
}

componentWillMount()

服务端渲染需要这个函数,其他时候我们用 contructor() 替代他。

render()

他会根据 state 和 props 渲染出对应的内容,他不应该修改组件的状态,他应当是一个纯函数。如果需要进行相关的交互,应该在 componentDidMount() 中完成。

他在挂载和更新阶段都会调用。

componentDidMount()

在组件挂载完成是调用,初始化网络请求,交互初始化(事件监听)都应当在这里完成。在其中调用 setState() 可能会导致额外的渲染,使用时要格外注意,我们在需要根据情况渲染模态框或提示语是还是可能会用到这种情景,使用时小心就好。

Updating(更新)

props 或 state 发生改变时会产生更新,一个组件重新渲染会涉及这些方法。

componentWillReceiveProps(nextProps)

父组件传入新的 props 时触发,你需要比较一下 this.props 和 nextProps(props 没有改变也可能会调用这个方法),然后再调用 setState()方法。

shouldComponentUpdate(nextProps, nextState)

当 props 或 state 发生改变时会调用他,当他返回 fales 时,componentWillUpdate(), render(), and componentDidUpdate()将不会被调用。在进行性能优化是才会想到调用这个方法,然而react 已经帮我们做了内部的优化,我们只需将我们的组件继承自React.PureComponent, 当然前提是我们需要保证我们的 props 和 state 都是 immutable 的对象。

componentWillUpdate(nextProps, nextState)

在重新渲染前被调用,可以做一些更新前的准备工作(?使用场景)。不应在其中进行 setState 或其他导致组件更新的操作。

componentDidUpdate(prevProps, prevState)

你可能在这里更加状态的改变进行一些 DOM 操作或者发起网络请求。

Unmounting(卸载)

componentWillUnmount()

在组件卸载前调用,一般用来清除一些定时器,网络请求,事件监听等。

Error Handling(错误捕捉)

捕捉子组件中的渲染,生命周期函数中,或者的构造函数中发生的错误。

componentDidCatch(error, info)

这个方法应当只用于错误捕捉,从意外的错误中恢复,而不应该进行流控制。你可以选择在一个较高层的组件中调用他一次即可。

setState(updater, [callback]) 方法

// 我们不应修改 prevState,而应该返回一个新的对象
this.setState((prevState, props) => {
  return {counter: prevState.counter + props.step};
});

应当使用componentDidUpdate()取代他的回调函数。

前端调试线上代码

问题背景

几个困惑:

  1. 使用webpack编译、打包、压缩后的代码上线后如何调试定位问题?
  2. 线上服务器不能替换文件,如何验证修改结果?
  3. 有许多服务器环境,如何利用本地代码直接调试不同环境?

然后了解的

  1. source map,有不同的设置可以分别应用与生产环境和开发环境。当.map文件部署到服务器上后,打开开发者工具后,就会下载对应的源码方便调试(IE也支持)。不过一般出于安全考虑,生产环境都不部署map文件,也只会启用不下载源码的模式。

  2. 还有另一种方案,使用本地的source map进行调试,ie好像是支持这个功能,Chrome上没找到要怎么操作

  3. chrome可以在工作区直接映射本地文件,workSpace

突然发现的

fiddler代理到本地文件

以前一直以为fiddler也就发发请求,改改响应之类的,用于验证接口是否正常。结果发现fiddler还有很多牛逼的功能,这里就说说将请求代理到本地文件。

  1. AutoResponder

将对应请求代理到本地文件,支持正则。

  1. Stave插件

提供批量映射的功能,可以直接映射到本地文件夹,也就可以直接利用本地文件来在服务器上调试开发了,不用在去服务器上换文件。

需要进一步了解的

  • 移动端调试:Fiddler配合weinre使用
  • 本地开发环境搭建,如何简单配置,在不同服务端快速切换

数组去重的三种方法

基本方法

indexOf进行判断如果在数组中不存在某元素则返回值为-1。

    // 对数组进行去重操作,只考虑数组中元素为数字或字符串,返回一个去重后的数组
    function uniqArray(arr) {
    var newarr=[];
    for (var i=0;i&lt;arr.length;i++){
    if(arr.indexOf(arr[i])===i) newarr.push(arr[i]);//和下面的原理相同,更喜欢这个方法
    //if (newarr.indexOf(arr[i]) == -1) newarr.push(arr[i]);
    }
    return newarr;
    }
    
    // 使用示例
    var a = [1, 3, 5, 7, 5, 3];
    var b = uniqArray(a);
    console.log(b);

哈希表法

将数组的各个项传入对象作为其KEY,然后通过判断对象的KEY是否存在,来去重。

function uniqArray(arr) {
    var newarr=[],hashObj={},len=arr.length;
    for (var i=0;i&lt;len;i++){
    if(!hashObj[a[i]]){
    hashObj[a[i]] = true;
    newarr.push(a[i]);
    }
    }
    return newarr;
    }

排序后去重

function uniqArray(arr) {
    arr.sort();
    var newarr = [arr[0]];
    for (var i=1;i&lt;arr.length;i++){
    if(arr[i]!=newarr[newarr.length-1]){
    newarr.push(arr[i]);
    }
    }
    return newarr;
    }

编写优雅的前端业务代码——Live 笔记

团队

  1. 第一个人的代码质量很重要,经验丰富的老司机很重要
  2. 在出新版本时尝试进行代码重构,新的模块尝试重构并兼容老的模块
  3. code review,团队分享——帮助团队提高代码质量(风格,错误,抽象)
  4. 拆分出业务模块,交互模块等
  5. 避免过度设计,可以后期总结积累
  6. 配置化一些静态属性
  7. 社区回答问题,帮别人解决问题,可以扩展自己的知识,让自己不受限与业务
  8. 代码重构从改函数名开始

common

  1. 全局广播统一维护
  2. API请求地址统一维护
  3. 多语言,国际化统一维护
  4. 辅助utils(function,array,object,string等)
  5. ajax封装
  6. 业务模块抽象,交互功能拆分

业务代码

  • 事件驱动(用户行为,浏览器行为)
  • 逻辑
  • 交互

设计模式

  • 装饰者
  • 发布订阅
  • 工厂模式
  • ES6 Class

问题

  1. 页面拆分组件?
    功能拆分(抽象),样式拆分,高阶组件。

  2. 选择器滥用

这样的写法更利于管理和代码压缩,字符串在代码压缩是不会被压缩。

// 集中存放选择器
  this.eles = {
          body: 'body',
          widgetDiv: ".left-widget div",
          inputResize: '.input-resize',
          pap: '.paper',
          centerY: '.center-y',
          centerBox: '.center-box',
          lineX: '.line-x',
          lineY: '.line-y',
          }

// 绑定选择器
initializeElements: function() {
  var eles = reportCustomApplication.Eles;
  for (var name in eles) {
    if (eles.hasOwnProperty(name)) {
      this[name] = $(eles[name]);
    }
  }
}
  1. 事件绑定滥用
    React 内部封装了事件代理。

模仿 backbone

// 统一绑定
// 尽量使用统一的没有样式意义的属性选择器作为钩子
this.eventsMap = {
  'dblclick .title': 'titledblclick',
  'dblclick .input input': 'inputdbclick',
  'click .input span': 'changeFontSize',
  'click .make-hr a': 'makehr',
  'click .change-paper a': 'changepapaer',
  'click #cusGo': 'cusGo',
  'click #toHtml': 'toHtml',
  'click #close': 'close'
};

_scanEventsMap: function(maps, isOn) {
  var delegateEventSplitter = /^(\S+)\s*(.*)$/;
  var bind = isOn ? this._delegate : this._undelegate;
  for (var keys in maps) {
      if (maps.hasOwnProperty(keys)) {
          var matchs = keys.match(delegateEventSplitter);
          bind(matchs[1], matchs[2], maps[keys].bind(this));
      }
  }
},
initializeOrdinaryEvents: function(maps) {
  this._scanEventsMap(maps, true);
},
uninitializeOrdinaryEvents: function(maps) {
  this._scanEventsMap(maps);
},
_delegate: function(name, selector, func) {
    doc.on(name, selector, func);
},
_undelegate: function(name, selector, func) {
    doc.off(name, selector, func);
},
  1. 业务生命周期
  • 数据获取
  • render 前后事件广播
  • update 前后事件广播
  • ummount 前事件广播
  1. 判断太多怎么办?
  • 提取公共部分为函数
  • 三元判断
  1. SPA 路由配置?
  • 前端 hash
  • 前端 router
  • 后端配置和前端相同的路由,输出相同的模板
  • 根据路由按需加载资源
  1. 好的编码习惯?
  • 花更多事件构思,再下手写。
  • 写的时候一气呵成,不应写一点调试一点
  • 先写伪代码,将业务逻辑拆分成很细颗粒的函数,再补充函数内容
  1. HTML 语义化的意义
  • 站点地图生成
  • css 加载失败网站依然会有基本的样式
  • 代码更清晰

npm 一个小坑记录

npm cache clean

其实之前实习时这个这个命令很常用,因为没有稳定的翻墙工具,npm install经常部分失败,cnpm install也经常会抽风漏掉一些东西,导致项目跑步起来,这时候一般会熟练操作:

  • rm -rf node_modules
  • npm cache clean

然而昨(今)天在坑里爬到2点才想起这个命令。

起因是 经过macOS 升级 high sierra,npm 升级,npm i失败,删除 node_modules,cnpm i,等一系列操作后。我开始用 Airbnb 的eslint 规则来初始化我本地项目的 eslint。

eslint --init

果然,卡住了,而且 ctrl+c不能结束命令,开始我直接关掉 iterm 没有在意,不过很快 cpu 的温度就飙到了90+,赶快开活动监视器发现 npm占用了100%的 cpu,马上强制关掉它。而且总是在处理这个包的时候loadExtraneous: sill resolveWithNewModule concat-map。第一反应是墙的锅,然后花了很长时间研究如何给我的 iterm 翻墙,这个后面再讲。折腾了半天我终于想到或许应该 clean 一下。执行上面两条命令后,使用翻墙后的 iterm,终于不会卡在那里了,然而还是特别慢,npm 真的是没救了。

这时候突然想到,eslint --init其实也就是一堆npm install,那么我依然可以走淘宝代理。于是执行下面的语句:

eslint --init --registry=https://registry.npm.taobao.org

成功初始化,但是提示很多 npm 依赖包没有安装,再执行cnpm i安装需要的包(配置文件里已配置),大功告成。

终端翻墙

尝试了很多方案,直接输 ss-ng 给的 export http_proxy命令,不行。proxifier 不行。polipo不行。

最后还是用到了之前记录的 linux 翻墙方法 #7 。配置好后使用proxychains4运行相关命令就在墙外了。

// 可以用这个命令来测试
sudo  proxychains4 curl ip.gs

动手制作自己的Chrome插件

终于自己动手制作了一次Chrome插件,完成了一个有基本功能的saveToGist的插件,最大收获在于第一次通过自己查阅各种资料,文档,API完成了一个可以用的小东西,也从中体会到了开发的乐趣.这篇文章依然是写给自己,作为总结,梳理一个完整的开发流程.

开始之前

网上可能有形形色色的教程,他们都能帮助你去完成一个简单的Demo,但是我依然认为阅读官方的文档时最好的途径.我自己也是一开始都通过开中文的教程去慢慢摸索,只是出于一个最简单的原因,对于英文的恐惧,虽然大部分单词都认识,但是放在一起的时候如果没有开发经验,很难正确理解意思,于是便会在做出东西前丧失兴趣.但是,当我回过头来再看文档时,很多重要的概念都在初次开发时被我忽略了,而文档中却写得很清晰.

所以,培养自己阅读官方文档的能力很重要.并不一定要从头到尾去读它,了解一个基本的概念,用到什么查什么,马上应用到实践中去是我目前发现的一个很好的学习方法.

基本概念

Manifest

manifest.json 是Chrome extension的配置文件,你可以在其中配置插件的相关信息,开放相关权限,以及设置一些关键文件的路径等,完整的内容可以看官方文档中的介绍. 也许你一看到那常常的JSON文件就头大了,其实你一开始并不需要一项项去了解他们,只需要有一个概念即可,需要什么再添加什么,甚至你不需要自己去写这个文件,后面你就会知道该如何偷懒.

browserAction

browser_action 中主要配置的是插件在浏览器工具栏的显示以及行为,其基本的配置如下:

"browser_action": {
 "default_icon": { // optional
 "16": "images/icon16.png", // optional 16x16px,下同
 "24": "images/icon24.png", // optional
 "32": "images/icon32.png" // optional
 },
 "default_title": "Google Mail", // optional; shown in tooltip
 "default_popup": "popup.html" // optional
}

这里,我们可以看到Chrome给我们规定了几个ICON的大小,你可以分别添加他们以提供更友好的显示,什么你问我你没有24px的icon,但有一个25px的怎么办?放心用吧,只要文件是完整的,并不需要正好使用官方推荐的大小,当然如果没有某个大小,你最好能够用稍大一点的图标去替换.如果你不会自己制作图片,你或许可以在这里找到许多精美的免费图标.

使用browser_action你的插件一直都是点亮的,如果你的插件直接特定的页面起作用,你可以去看看page_aciton,配置起来大同小异.

你也许已经注意到了在browser_action的配置中有一个default_popup,其值为一个页面的相对地址,这个页面就是点击你插件图标后会弹出的页面,下面我们就来看看popup是什么.

popup

popup.html就是你点击工具栏上的图片后弹出的界面,他和一个普通的web页面没有什么区别,最大的不同可能就是你需要自己根据需要在CSS中规定一下body的大小.是的,你可以像写正常的页面一样引入css文件和js文件,不过似乎不支持行间的js代码.

popup应该说是你的插件中最像一个正常的网页的部分了(还有background.html,但我们一般不在其中写内容),所以其中的js也和正常的网页一样,只有在页面打开的时候才会调用.所以就要特别注意,也许你通过pupop弹出了别的窗口,然后要从那些窗口向popup传回数据,这是就要特别小心,因为可能由于浏览器版本或系统版本的不同,你弹出页面的同时popup页面就自动关闭了,或者用户点击了正在浏览的页面也会关闭popup,所以如果有数据需要传递,就要传给拥有全局生命周期的background.js,再从其中发送数据给popup.

你不需要在popup中重复书写background中的一些代码,你可以直接获取到其中的方法

var backgroundPage = chrome.extension.getBackgroundPage();
backgroundPage.dosomething();//调用background中的方法

说了半天,background到底是什么?

background

background 可以分为 persistent background pages和event pages 故名思议,前者无论插件是否有代码需要运行都会常驻后台,这当然是不好的,所以目前官方推荐我们如果不是必须,则尽可能是evet pages.而这两者的区别其实只是一个配置项而已,你只需要

"background": {
 "scripts": ["background.js"],
 "persistent": false //配置这一项
}

这样配置就可以了.

理解EvetPage的生命周期至关重要,因为它是唯一一个会常驻后台,通过事件触发的程序.官网给出的生命周期为:

The app or extension is first installed or is updated to a new version (in order to register for events).
The event page was listening for an event, and the event is dispatched.
A content script or other extension sends a message.
Another view in the extension (for example, a popup) calls runtime.getBackgroundPage.

简单概括就是事件触发,无论是第一安装(install事件)还是别的页面发送信息过来(onMessage事件),都会触发他.所以一般我们会在background里做右键菜单的创建,全局事件的监听,消息在不同页面间的传递.

这里需要特别注意,只有在第一次安装或者升级时,background才会完整得执行,其他时候(比如重启浏览器后),他完全是事件驱动的,所以你要保证你的事件监听函数都在该页面的顶层作用域.

content_script

更多时候,我们的插件的功能都是去操作页面的DOM,而这时候就要提到content_script了,他就看起来像页面原本的js一样,在页面加载后被注入到页面中,其实他的实现更加巧妙,他只是共享了页面的DOM,所以你可以用它来操作页面而不需要考虑命名空间的问题.

其配置文件大致如下:

"content_scripts": [
 {
 "matches": ["http://www.google.com/*"],
 "css": ["mystyles.css"],
 "js": ["jquery.js", "myscript.js"]
 }
]

可以看出,你不仅可以在页面插入js,还可以插入css,甚至你可以针对不同的页面插入不同的文件.这里需要注意的时,如果你要引入第三方库的话,必须将其下载到本地,并不支持从cdn直接引入.

在content中支持的API也相对有限,只支持 extension,i18n,runtime,storage中的部分API.

消息传递

很多时候我们都需要在 popup,background,content之间传递消息.

在popup和background间或者从content向background传递消息,我们一般直接使用chrome.runtime.sendMessagechrome.runtime.onMessage.addListener这两个API即可.需要注意的是,我们如果想从background向content传递消息的话,就必须使用chrome.tab.sendMessage这个API了,因为runtime.sendMessage并不支持被多个页面监听,而通过tab的API我们可以指定发送到的对象的tabID.

你还可能会用到的API

  • chrome.runtime.connect,chrome.tabs.connect 建立长连接
  • chrome.tabs 读取当前tab的相关信息
  • chrome.storage.sync 存储
  • chrome.identity 进行OAuth2 的认证
  • chrome.contextMenus 右键菜单

使用脚手架

说了这么多,你也许想开始自己动手搭建一个插件了,但又想到长长的配置文件,和各种各样记不清的文件,以及最后打包发布,想想就麻烦.其实,你根本不需要从头开始自己搭建了,早有比你更懒的人看到了这里面的重复劳动,于是脚手架便诞生了.
generator-chrome-extension
按照他的说明文档操作,你就能很快搭起基本的环境了.(注意在初始化的最后会进行npm install,如果你那里和我一样没有反应,你可以手动停掉他,然后自己来cnpm install)

如果你还有开发其他东西的需要,你可以在yeoman.io里找到更多丰富的脚手架.

Ubuntu 14.04卡死排查与修复

之前偶尔会出现鼠标突然卡住不能动,键盘也没有反应的情况,不过过一会都会好过来,但今天开始频繁地卡死,只能强制关机来重启(心疼我的ssd),于是想看一下到底是哪个进程卡死的系统.

在终端中用top -i命令可以看到当前系统进程的资源占用情况,我这里就可以很明显看到kWorker占用CPU内存高达70%以上,知道了始作俑者也就很好解决了.去网上查一下发现这种情况并不少见,下面是对我有效的解决方案.

首先使用

grep . -r /sys/firmware/acpi/interrupts/

查看那一个gpe的数值特别大,我这里是13号.
然后使用下面把它关掉

~ cp /sys/firmware/acpi/interrupts/gpe13 /pathtobackup
~ crontab -e ##下面会弹出编辑器选择菜单,新手可以选择nano比较好操作
##将下面这行代码插入到文件的末尾
@reboot echo "disable" > /sys/firmware/acpi/interrupts/gpe13

保存之后退出,这样以后每次开机就会执行这条disable掉gpe13的语句了.

然后为了能让这句命令在每次从睡眠中唤醒时也能执行,我们需要执行下面的语句:

~ touch /etc/pm/sleep.d/30_disable_gpe13
~ chmod +x /etc/pm/sleep.d/30_disable_gpe13
~ nano /etc/pm/sleep.d/30_disable_gpe13

将下面的语句添加到文件中

#!/bin/bash
case "$1" in
    thaw|resume)
        echo disable > /sys/firmware/acpi/interrupts/gpe13 2>/dev/null
        ;;
    *)
        ;;
esac
exit $?

然后就大功告成了,CPU再也不会爆表了.

学习ES6系列——基础

基础快速梳理

新的基本语法

  1. 块级作用域
// ES6新增了块级作用域
// let,const声明的变量
// 函数可以声明在块级作用域内,但由于浏览器兼容性的问题,浏览器的实现可能会不同,应避免使用,可以用下面的方法替代

// 函数声明语句
{
  let a = 'secret';
  function f() {
    return a;
  }
}

// 函数表达式替代
{
  let a = 'secret';
  let f = function () {
    return a;
  };
}
  • const 注意

    // const实质上保证的是变量指向的内存地址不得改动,如果将对象赋予一个常量,其内容并不能保证为常量
    
    const foo = {};
    
    // 为 foo 添加一个属性,可以成功
    foo.prop = 123;
    foo.prop // 123
    
    // 将 foo 指向另一个对象,就会报错
    foo = {}; // TypeError: "foo" is read-only
    
    const a = [];
    a.push('Hello'); // 可执行
    a.length = 0;    // 可执行
    a = ['Dave'];    // 报错
    
    //应当使用 Object.freeze 方法将对象冻结
    const foo = Object.freeze({});
    
    //冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。
    //但是该方法不能保证对象完全不被改变,对象的属性值为对象的情况下依然可以改变
    
    const obj1 = {
      internal: {}
    };
    
    Object.freeze(obj1);
    obj1.internal.a = 'aValue';
    
    obj1.internal.a // 'aValue'
    
    // 要使对象不可变,需要递归地遍历对象的每个属性。但是该方法仍然有冻结不应冻结的对象的风险,例如 window 对象。
    
    // To do so, we use this function.
    function deepFreeze(obj) {
    
      // Retrieve the property names defined on obj
      var propNames = Object.getOwnPropertyNames(obj);
    
      // Freeze properties before freezing self
      propNames.forEach(function(name) {
       var prop = obj[name];
    
       // Freeze prop if it is an object
        if (typeof prop == 'object' && prop !== null)
          deepFreeze(prop);
      });
    
      // Freeze self (no-op if already frozen)
      return Object.freeze(obj);
    }
  • 顶层对象属性

    //let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性
    var a = 1;
    // 如果在Node的REPL环境,可以写成global.a
    // 或者采用通用方法,写成this.a
    window.a // 1
    
    let b = 1;
    window.b // undefined
  1. 新的赋值方法
// 解构赋值
// 数组
let [a, b, c] = [1, 2, 3];
let [head, ...tail] = [1, 2, 3, 4];
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

// 对象
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
// => baz = {foo:'aaa', bar:'bbb'}.foo
baz // "aaa"

// 字符串,会被转化成一个类数组对象
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length : len} = 'hello';
len // 5
  • 解构模式用途
// 1.变量交换
[x, y] = [y, x];

// 2. 从函数返回多个值,输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");

// 3. 提取JSON数据
let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

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

// 4. 函数参数的默认值
jQuery.ajax = function (url, {
  async = true,
  beforeSend = function () {},
  cache = true,
  complete = function () {},
  crossDomain = false,
  global = true,
  // ... more config
}) {
  // ... do stuff
};

// 5. 便利 map
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world

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

// 获取键值
for (let [,value] of map) {
  // ...
}
  1. 基本扩展
  • 字符串扩展方法
// Unicode编码相关,用到的时候再看
String.fromCharCode,codePointAt,normalize()

// for..of 遍历字符串
let text = String.fromCodePoint(0x20BB7);

for (let i = 0; i < text.length; i++) {
  console.log(text[i]);
}
// " "
// " "

for (let i of text) {
  console.log(i);
}
// "𠮷"

// 判断一个字符串是否包含在另一个字符串中
let s = 'Hello world!';

s.startsWith('world', 6) // true,从第 n 个位置到字符串结束
s.endsWith('Hello', 5) // true,针对前 n 个字符
s.includes('Hello', 6) // false

// repeat,将原字符串重复几次,返回一个新的字符串
'na'.repeat(2.9) // "nana"
  • 模板字符串

带来了新的 XSS Payload

// 使用`来定义,可以传入变量${},{}中甚至可以调用函数,所有的空格缩进都会被保留。
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// 标签模板——防止 xss,国际化处理
let message =
  SaferHTML`<p>${sender} has sent you a message.</p>`;

function SaferHTML(templateData) {
  let s = templateData[0];
  for (let i = 1; i < arguments.length; i++) {
    let arg = String(arguments[i]);

    // Escape special characters in the substitution.
    s += arg.replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;");

    // Don't escape special characters in the template.
    s += templateData[i];
  }
  return s;
}

let sender = '<script>alert("abc")</script>'; // 恶意代码
let message = SaferHTML`<p>${sender} has sent you a message.</p>`;

message
// <p>&lt;script&gt;alert("abc")&lt;/script&gt; has sent you a message.</p>

i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`
// "欢迎访问xxx,您是第xxxx位访问者!"

// String.raw()内置的模板字符串标签函数,返回一个斜杠被转义(如果已经转义,则不会做任何处理),变量被替换后的模板字符串
String.raw`Hi\n${2+3}!`;
// "Hi\\n5!"

String.raw`Hi\u000A!`;
// 'Hi\\u000A!'
  • 正则相关,用时查
var regex = new RegExp(/xyz/, 'i');
// 四个可以使用正则表达式的方法:match()、replace()、search()和split()
// 扩展运算符,将数组转为逗号分隔的参数序列。
---
// 1. 替代 apply 方法将数组转为参数序列,可供 Math.max等方法使用
// 2. 将数组 push 进一个数组中
console.log(...[1, 2, 3])
// 1 2 3

// 数组深度复制
const a1 = [1, 2];
// 写法一
const a2 = [...a1];

// 合并数组
[...arr1, ...arr2, ...arr3]

// 生成数组
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

// 字符串转数组
[...'hello']
// [ "h", "e", "l", "l", "o" ]

// 将实现了迭代器(Iterator)接口的对象转为数组
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]

const go = function*(){
  yield 1;
  yield 2;
  yield 3;
};

[...go()] // [1, 2, 3]

---

// Array.from()将类数组对象(所谓类似数组的对象,本质特征只有一点,即必须有length属性。)和可遍历对象转换为真正的数组
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

// 还可以有第二个参数,如果map函数里面用到了this关键字,还可以传入Array.from的第三个参数,用来绑定this。
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);

---

// Array.of(),用来替代Array()或new Array(),并且不存在由于参数不同而导致的重载。

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
// 对比
Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]

//实现
function ArrayOf(){
  return [].slice.call(arguments);
}

---

// copyWithin(),将制定位置的成员复制到其他位置,返回修改后的数组
Array.prototype.copyWithin(target, start = 0, end = this.length)
[1, 2, 3, 4, 5].copyWithin(0, 3) // [4, 5, 3, 4, 5]

---

// find(), findIndex()找到第一个符合条件的数组成员并返回该值或该值的位置
[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10

---

// fill()填充数组,数组中已有的元素会被抹去
[].fill(target, start, end)

---

// entries(),keys()和values()返回遍历器对象,配合 for..of 循环进行遍历,或.next()方法

// 遍历键名
for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

// 遍历键值
for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

// 遍历键值对
for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

---

// includes()表示某个数组是否包含给定的值,支持 NaN
[1, 2, NaN].includes(NaN) // true

---

// 空位规则处理不统一,应该避免出现
// 将数组的空位有的转换为 undefined
Array.from(['a',,'b'])
// [ "a", undefined, "b" ]
[...['a',,'b']]
// [ "a", undefined, "b" ]

// 有的没有
[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]
new Array(3).fill('a') // ["a","a","a"]
// for..of 会跳过空位,map 会遍历空位
  • 对象扩展
// 支持对象中直接写入变量和函数,属性名为变量名,属性值为变量值
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

// 等同于
const baz = {foo: foo};

// 于是有了更简洁的函数的写法
const o = {
  method() {
    return "Hello!";
  }
};

// 等同于

const o = {
  'method': function() {
    return "Hello!";
  }
};

// 输出模块更简洁
function clear () {
  ms = {};
}

module.exports = { clear };

---

// 允许使用对象字面量定义对象时使用表达式作为属性名
let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};

// 注意:属性名表达是为对象时会被转化
const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
  [keyA]: 'valueA',
  [keyB]: 'valueB'
};

myObject // Object {[object Object]: "valueB"}

---

// 对象方法和函数一样具有 name 属性,获得函数(方法)名
const person = {
  sayName() {
    console.log('hello!');
  },
};

person.sayName.name   // "sayName"

---

// Object.is()比较两个值是否相等,取代严格相等比较符,具有更加同一的行为
+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

// 实现方法
Object.defineProperty(Object, 'is', {
  value: function(x, y) {
    if (x === y) {
      // 针对+0 不等于 -0的情况
      return x !== 0 || 1 / x === 1 / y;
    }
    // 针对NaN的情况
    return x !== x && y !== y;
  },
  configurable: true,
  enumerable: false,
  writable: true
});

---

// Object.assign()用于对象合并,后面的同名属性会覆盖前面的,不拷贝继承属性,也不拷贝不可枚举属性
const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

// 注意:该方法实现的是浅拷贝,属性值为对象时,目标得到的是对象的引用
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

// 处理数组
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]

// 用途
// 1. 为对象添加属性
class Point {
  constructor(x, y) {
    Object.assign(this, {x, y});
  }
}

// 2. 为对象添加方法
Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});

// 3. 浅度克隆对象
// 深度克隆就方便的方法:JSON.parse(JSON.stringify(obj)),只是对 function 和 Date 处理有问题
let copy = Object.assign({}, obj);

// 保持继承链
function clone(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}

// 4. 合并多个对象
const merge =
  (...sources) => Object.assign({}, ...sources);

// 5. 制定参数属性默认值
const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}

---

//for...in循环:只遍历对象自身的和继承的可枚举的属性。大多数情况应考虑使用 Object.keys()代替

---

// __proto__属性,用来读取和设置对象的 prototype 属性,只部署在浏览器环境,应使用Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替。

---

// super 关键字:指向当前对象的原型对象,表示原型对象时只能用于方法中,且只能用于对象方法的简写法
// 报错
const obj = {
  foo: super.foo
}

// 报错
const obj = {
  foo: () => super.foo
}

// 报错
const obj = {
  foo: function () {
    return super.foo
  }
}

// OK
const obj = {
  foo() {
    return super.foo;
  }
};
  • 函数的扩展
// 为函数的参数设置默认值,通常设置默认值得参数应在参数列表的尾部,以实现可以省略他的目的
function log(x, y = 'World') {
  console.log(x, y);
}

function foo(x = 5) {
  // 不能在函数体中再次声明 x
  // 一个好的代码风格:不论是否函数的参数设置过默认值,在函数体内都不要声明和参数同名的变量
  let x = 1; // error
  const x = 2; // error
}

// 与解构值配合使用
function foo({x, y = 5} = {}) {
  console.log(x, y);
}
// 没有提供参数时会默认传入一个空对象
foo() // undefined 5

---

// 函数的 length 属性:表示该函数预期传入的参数个数

---

// 函数参数的初始化会形成单独的作用域
var x = 1;

function f(x, y = x) {
  console.log(y);
}

f(2) // 2

let x = 1;

// 单独形成作用域,作用域中没有找到 x 的值,所以找到原型链上的 x 而不是函数内部的 x
function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // 1

---

// 利用函数参数的默认值可以显示地制定某一个参数不可省略,省略就抛出错误
function throwIfMissing() {
  throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}

foo()
// Error: Missing parameter

// 声明为 undefined 表明参数可以省略
function foo(optional = undefined) { ··· }

---

// rest 参数,取代 arguments,所对应的变量是个数组
function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}

var a = [];
push(a, 1, 2, 3)

---
  • 箭头函数
// 基本用法
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

// 箭头函数的函数体如果多于一条语句,应用{}括起来,且使用 return 返回
var sum = (num1, num2) => { num1++; return num1 + num2; }

// 箭头函数返回对象时需要在对象外面加上括号
let getTempItem = id => ({ id: id, name: "Temp" });

// 和变量解耦配合使用
const full = ({ first, last }) => first + ' ' + last;

// 等同于
function full(person) {
  return person.first + ' ' + person.last;
}

---

// this, super, arguments, new.target在箭头函数中都不存在,他们都指向定义时外层函数中对应变量

// 因此,也无法通过 call, apply, bind 改变 this 的指向

---

// 尾调用优化:只保留内部函数的调用帧
function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同于
function f() {
  return g(3);
}
f();

// 等同于
g(3);

---

// 尾递归:只保留了一个调用记录,避免的堆栈溢出。只有在严格模式生效,正常模式失效
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};

  return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}

Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity

// 尾递归函数改写:确保函数最后一句只调用自身,需要将内部变量变成参数形式。但是这样会很不直观,可以通过下面两种方法规避

// 方法一:currying
function currying(fn, n) {
  return function (m) {
    return fn.call(this, m, n);
  };
}

function tailFactorial(n, total) {
  if (n === 1) return total;
  return tailFactorial(n - 1, n * total);
}

const factorial = currying(tailFactorial, 1);

factorial(5) // 120

// 方法二:函数默认值
function factorial(n, total = 1) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5) // 120

// 手动实现尾递归优化:减少调用栈 -> 使用‘循环’替换的‘递归’

function tco(f) {
  var value;
  var active = false;
  var accumulated = [];

  return function accumulator() {
    accumulated.push(arguments);
    if (!active) {
      active = true;
      while (accumulated.length) {
        value = f.apply(this, accumulated.shift());
      }
      active = false;
      return value;
    }
  };
}

var sum = tco(function(x, y) {
  if (y > 0) {
    return sum(x + 1, y - 1)
  }
  else {
    return x
  }
});

sum(1, 100000)

编码规范

// bad
const baz = [...foo].map(bar);

// good
const baz = Array.from(foo, bar); 

---

// bad
function processInput(input) {
  // then a miracle occurs
  return [left, right, top, bottom];
}

// the caller needs to think about the order of return data
const [left, __, top] = processInput(input);

// good
function processInput(input) {
  // then a miracle occurs
  return { left, right, top, bottom };
}

// the caller selects only the data they need
const { left, top } = processInput(input);

学习ES6系列——高级

Symbol

一种新的基本数据类型,从根本上防止属性名的冲突。

//symbol的参数主要为了调试时做区分
const obj = {
  toString() {
    return 'abc';
  }
};
const sym = Symbol(obj);
sym // Symbol(abc)

---

// 基本用法,不能用.运算符
let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

---

// 增强写法
let obj = {
  [s](arg) { ... }
};

//定义常量
log.levels = {
  DEBUG: Symbol('debug'),
  INFO: Symbol('info'),
  WARN: Symbol('warn')
};

---

// 属性名遍历
//Symbol 作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。

let obj = {
  [Symbol('my_key')]: 1,
  enum: 2,
  nonEnum: 3
};

// 该方法可以遍历所有属性名
Reflect.ownKeys(obj)
//  ["enum", "nonEnum", Symbol(my_key)]


## Map
键值对集合,可以用各种类型的值作为键的数据类型。

```js

// 基本用法
const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false

const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

// 只有对同一个对象的应用,才能视为同一个键
const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined

// 常用属性和方法
const map = new Map();
map.set('foo', true);
map.set('bar', false);

map.size // 2
map.get('foo'); //true
map.has('years')       // false
map.delete('foo')
map.clear()

---

// 按照插入顺序遍历
const map = new Map([
  ['F', 'no'],
  ['T',  'yes'],
]);

for (let key of map.keys()) {
  console.log(key);
}
// "F"
// "T"

for (let value of map.values()) {
  console.log(value);
}
// "no"
// "yes"

for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "F" "no"
// "T" "yes"

---

// Map 转数组
const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

[...map.keys()]
// [1, 2, 3]

[...map.values()]
// ['one', 'two', 'three']

[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]

[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

// forEach
const reporter = {
  report: function(key, value) {
    console.log("Key: %s, Value: %s", key, value);
  }
};

map.forEach(function(value, key, map) {
  this.report(key, value);
}, reporter);

Set

类似于没有重复值的数组。

// 数组去重
[...new Set(array)]

// 四个操作方法: add, delete, has, clear
let s = new Set([1,2]);
s.add(3);    // [1,2,3]
s.delete(2); // [1,3]
s.has(3);    // true
s.clear()

// 将set结构转换为数组
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);

---

// 四个遍历方法:keys(), values(), entries(), forEach()
// 遍历顺序为插入顺序
// set的键名和键值返回的值是一致的

let set = new Set(['red', 'green', 'blue']);
for (let item of set.entries()) {
  console.log(item);
}

// 可以省略values,直接遍历Set
for (let x of set) {
  console.log(x);
}

// forEach
set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value));

// 使用数组的map, filter 方法
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}

let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}

Promise基本概念

Promise是异步编程的一种解决方案,Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要改变了就回一只保持相应结果。

  • 优点:更清楚地表达异步操作流程,避免层层嵌套的callback
  • 缺点:
    • 一旦建立会立即执行,且无法中途取消Promise
    • 其内部发生的错误无法反映到外部
    • 在Pending状态时,无法获知进行到哪一阶段了

基本语法

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});//Promise是一个构造函数,内置resovle和reject参数,分别在异步操作成功和失败后执行

promise.then(function() {
  console.log('Resolved.');
}, function(error) {
     // failure
   });//.then方法可以分别指定两个回调

console.log('Hi!');

// Promise //promise声明后立即执行
// Hi! //然后代码继续执行
// Resolved //在异步操作成功后,执行resovle

推荐的实例方法的写法

promise
  .then(function(data) {
    // success
  })
  .catch(function(err) {
    // error
  });

其他实例方法

  1. Promise.all()
    将多个Promise实例,包装成一个新的Promise实例,参数为数组

    用法

    // 生成一个Promise对象的数组
    var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
      return getJSON("/post/" + id + ".json");
    });
    
    Promise.all(promises).then(function (posts) {
      // ...
    }).catch(function(reason){
      // ...
    });
    
  2. Promise.resolve()
    将一个现有的对象转为Promise对象,如果resolve的对象是thenable的,他会响应then的不同状态。
    p=Promise.resolve('fetch('dd.json)'); p.then(sucessfuc,falifuc);

  3. Promise.reject()
    返回一个状态为rejected的Promise实例

Generator

一个内部包含多个状态的函数,会返回一个遍历器对象。他可以暂停函数执行,返回表达式的值

// yield 返回一个表达式的值
// throw 返回一个 throw 语句
// return 返回 return语句

---

// 改写回调函数

// *为 Generator 函数的标志
function* main() {
// yield 表示函数在这里暂停,返回其后的表达式结果
  var result = yield request("http://some.url");
  var resp = JSON.parse(result);
    console.log(resp.value);
}

function request(url) {
  makeAjaxCall(url, function(response){
    it.next(response);
  });
}

// 返回一个遍历器对象
var it = main();
// 执行到第一次 yield
// next方法可以接受参数,参数替代了上一次yield语句的位置,被作为“返回值”
it.next();

---

// 控制流管理

Promise.resolve(step1)
  .then(step2)
  .then(step3)
  .then(step4)
  .then(function (value4) {
    // Do something with value4
  }, function (error) {
    // Handle any error from step1 through step4
  })
  .done();
  
function* longRunningTask(value1) {
  try {
    var value2 = yield step1(value1);
    var value3 = yield step2(value2);
    var value4 = yield step3(value3);
    var value5 = yield step4(value4);
    // Do something with value4
  } catch (e) {
    // Handle any error from step1 through step4
  }
}

// 所有操作应为同步
scheduler(longRunningTask(initialValue));

function scheduler(task) {
  var taskObj = task.next(task.value);
  // 如果Generator函数未结束,就继续调用
  if (!taskObj.done) {
    task.value = taskObj.value
    scheduler(task);
  }
}

// for..of 可以用来自动执行所有 yield 操作
for (var step of iterateJobs(jobs)){
  console.log(step.id);
}

---

// 给任意对象部署迭代器接口
function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
  }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
  console.log(key, value);
}

// foo 3
// bar 7

吐槽:看了 generator 的应用,thunk 函数什么的,感觉异步操作写成这样子太复杂了,并没有提供可读性。然后发现了..async。

async

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

// 基本用法
async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});

// 捕捉错误
const superagent = require('superagent');
const NUM_RETRIES = 3;

async function test() {
  let i;
  for (i = 0; i < NUM_RETRIES; ++i) {
    try {
      await superagent.get('http://google.com/this-throws-an-error');
      break;
    } catch(err) {}
  }
  console.log(i); // 3
}

test();

// 与 promise 比较
function chainAnimationsPromise(elem, animations) {

  // 变量ret用来保存上一个动画的返回值
  let ret = null;

  // 新建一个空的Promise
  let p = Promise.resolve();

  // 使用then方法,添加所有动画
  for(let anim of animations) {
    p = p.then(function(val) {
      ret = val;
      return anim(elem);
    });
  }

  // 返回一个部署了错误捕捉机制的Promise
  return p.catch(function(e) {
    /* 忽略错误,继续执行 */
  }).then(function() {
    return ret;
  });

}

async function chainAnimationsAsync(elem, animations) {
  let ret = null;
  try {
    for(let anim of animations) {
      ret = await anim(elem);
    }
  } catch(e) {
    /* 忽略错误,继续执行 */
  }
  return ret;
}

// 按顺序完成异步操作
async function logInOrder(urls) {
  // 并发读取远程URL
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // 按次序输出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}

Ubuntu下实现全局翻墙的配置过程

shadowsocks-qt5

安装

可以在github中找到。

sudo add-apt-repository ppa:hzwhuang/ss-qt5
sudo apt-get update
sudo apt-get install shadowsocks-qt5

安装完成后在搜索里可以找到shadowscoks-qt5.

配置

和windows配置相同,可以直接导入。
本地服务器类型选择SOCKS5,本地地址127.0.0.1,本地端口:1080

浏览器翻墙

安装完ss-qt5后,浏览器还并不能翻墙,需要配置浏览器的代理,因为chrome在linux里无法设置代理,所以需要安装插件。

下载SwitchyOmega

可以在github的xx-net项目中找到,需要点进该文件然后download。
把该文件拉入chrome的设置-扩展程序中来安装。(其他浏览器的方法也可在xxnet中找到)

配置SwitchyOmega

选项中设置prxoy里代理协议SOCKS5,代理服务器:127.0.0.1,端口1080(即浏览器连接本地)。
然后选中这一项(proxy)即可。

如果一切中常,现在浏览器就可以翻墙了。

全局翻墙

实际上linux中更多的操作是在终端中进行,很多程序和服务的下载都需要通过 npm,gem,nvm,git等命令进行,而在前内起下载速度十分感人,而且很容易快下载完了又直接失败,都要重新开始,通过全局翻墙可以改善这种情况。

安装配置proxychains

全局翻墙通过proxychains实现,即将任何程序和ss的proxy建立链接,原理和浏览器的代理相似。

下载

sudo apt-get install proxychains

配置

sudo nano /etc/proxychains.conf //nano比vim对新手更友好

在最后的ProxyList里加入Shawdowsocks的代理设置:

socks5    127.0.0.1    1080

使用
打开ss-qt5的前提现,在需要翻墙的命令前打上proxychains即可。

文本内容溢出显示为...

text-overflow: ellipsis; 这一属性用于在文字过长时将内容显示为...。在项目中我才发现他的应用可以说是无处不在,最好再组件设计的时候就应考虑到文字过长时的样式问题,否则就会到处去添加这一样式。

text-overflow属性

  1. 这一属性用于设置文字过长后文字的展现形式,目前支持广泛的为以下两种值:
  • clip //文字超长会截断
  • ellipsis //文字超长会转变为...
  1. 这一属性想要生效需要满足以下条件:
  • 只能在blockinline-block元素中生效,因为他要求元素本身具有宽度
  • 必须同时设置下面两种属性:
white-space: nowrap;
 overflow: hidden;
  • text-overflow:ellipsis;要想生效要求元素设置具体的宽度,百分比宽度不能生效

基本用法

.el {
  white-space: nowrap;
  max-width: 450px;
  overflow: hidden;
  text-overflow: ellipsis;
}

多行文字

.el {
overflow : hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

Progressive image loading

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>progressive image loading</title>
    <style>
        body{
            margin: 0 auto;
            padding: 0;
            background-color: #81C2D6;
        }
        h1 {
            font-family: monospace;
            font-size: 3rem;
            text-align: center;
            color: #fff;
        }
        figure {
            margin: 0 auto 100px;
            max-width: 75%;
            border: 1px solid #f6f6f6;
        }
        .placeholder {
            background-color: #E0CEB8;
            -webkit-background-size: cover;
            background-size: cover;
            background-repeat: no-repeat;
            position: relative;
            overflow: hidden;
            padding-bottom: 66.6%;
        }

        .placeholder img{
            position: absolute;
            opacity: 0;
            top: 0;
            left: 0;
            width: 100%;
            transition: opacity 1s linear;
        }

        .placeholder img.loaded{
            opacity: 1;
        }

        .img_small {
            filter: blur(50px);
            transform: scale(1);
        }

    </style>
</head>
<body>
    <h1>Progressive Image Loading Demo</h1>
    <figure>
        <div class="placeholder" data-large="http://7xr09v.com1.z0.glb.clouddn.com/lion-wild-africa-african.jpg">
            <img src="http://7xr09v.com1.z0.glb.clouddn.com/rsz_lion-wild-africa-african.jpg" alt="" class="img_small">
            <div class="holder"></div>
        </div>
    </figure>

    <script>

        window.onload = function() {

          var placeholder = document.querySelector('.placeholder'),
              small = placeholder.querySelector('.img_small')

          // 1: load small image and show it
          var img = new Image();
          img.src = small.src;
          img.onload = function () {
           small.classList.add('loaded');
          };

          // 2: load large image
          var imgLarge = new Image();
          imgLarge.src = placeholder.dataset.large;
          imgLarge.onload = function () {
            imgLarge.classList.add('loaded');
          };
          placeholder.appendChild(imgLarge);
        }

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

CSRF and XSS

web安全综述

  1. 输入验证
    • 尽可能使用白名单
    • 不能使用白名单时使用黑名单
    • 尽可能使用严格约定
    • 确保警示潜在的攻击
    • 避免直接的输入反馈
    • 尽可能地在不可信数据深入系统逻辑之前进行处理,或者直接使用你的框架的白名单机制
  2. 输出内容编码
    • 以合适的编码手段对所有从应用中吐出的数据进行编码
    • 尽可能地使用框架提供的输出编码功能
    • 尽量避免嵌入式渲染上下文
    • 以原始格式存放数据,在渲染时进行编码
    • 避免使用不安全的框架与规避了编码的JS调用
  3. 数据库
    • 避免直接从用户的输入中构建出SQL或者等价的NoSQL查询语句
    • 在查询语句与存储过程中都使用参数绑定
    • 尽可能使用框架提供好的原生的绑定方法而不是用你自己的编码方法
    • 不要觉得存储过程或者ORM框架可以帮到你,你还是需要手动调用存储过程
    • NoSQL 也存在着注入的危险
  4. 通信
    • 使用HTTPS
    • 采用HSTS强制使用HTTPS
    • 在Cookie中设置“secure”标志
    • 不要把敏感信息放在URL中
    • 确保HTTPS的配置是可用的

CSRF/XSS

CSRF(Cross-Site Request Forgery):跨站请求伪造

如何攻击

恶意代码可以在第三方网站上,不一定需要js,利用了本地cookie,get请求的不安全性。例如一些表单提交使用了get请求,黑客就可以在其网站上利用img的src指向该请求;

如何防范

可以给页面的cookie增加随机数(token)来防御,每次表单提交都要求提交该随机数,服务器端进行校验。通过referer进行源页面的判断,但是这个值是写在http请求头中的,也可能被篡改。

XSS(Cross-Site Scripting):跨站脚本攻击

如何攻击

必须源站接受恶意代码,需要js,如在用户输入中恶意注入js代码,利用img标签的onerror事件注入恶意代码,以及在url的get参数中加入恶意代码。

如何防范

要求站点对用户的输入进行校验,转译,并且能对输入输出进行编码,以及避免使用不安全的API(如_target)。如果要阻止通过js访问cookie,可以设置其为HttpOnly。

Material-UI-React

Reboot

使用<Reboot />组件进行样式的 normalize

withStyles

自定义组件样式withStyle(style)(component);且可以充值内部各个class,例如root,label等

MuiThemeProvider

组件主题定义

mouseup/mousedown 真是个大坑

记得之前看有人问过这个相关的,当时觉得根本没有应用场景就没在意,今天在坑里爬了一天。

 $('body').mouseup(() =>
      $('.textarea').focus()
 );

关键在于要能意识到这两个是分开的事件(废话),分清楚哪些事件是你想在mousedown触发的,哪些是想在mouseup触发的。尤其是在和点击,拖拽,鼠标移动在一起的时候,必须想清楚什么时刻触发哪个事件,产生什么结果。

IE图片无法加载,报“DOM7009 unable to decode image at url“警告

背景:IE 浏览器,单页面应用,单个 tab 页签,加载大量图片时出现图片无法显示的问题(显示为左上角为小叉),在新的 tab 页签直接访问图片的 url却能显示。

对于 DOM7009 网络上多为以下几种原因:

  1. 图片后缀名不正确,比如 png 的图片在 src 中写成了jpg 格式
  2. 图片颜色编码问题,IE浏览器不识别CMYK模式的图
  3. 配置文件中,<add name="X-Content-Type-Options" value="nosniff" />会阻止浏览器在请求的资源格式与预先设定的图片格式不匹配时自动调整。所以当图片请求的content-type设置为image/png时,当请求的图片时 jpg 时就无法加载了。
  4. 单张图片过大,会导致浏览器无法加载(推测与浏览器内存或缓存相关)

以上几种情况都未能解决我的问题,我面对的现象为对于较大的图片(10mb)左右,加载20张左右就会不能加载,对于较小的图片,每页100张,大概能加载不到3页的样子。考虑为 ie 内存机制的问题,通过百度图片(其为不断地懒加载)搜索测试,在加载20mb 图片(很多张小图)后也会出现这种现象。

推测 IE 的内存机制对单tab 页签的内存使用有限制,但考虑页面中的翻页不应导致内存不断增加,推测存在内存泄漏。

解决:排查后发现果然存在大量内存泄漏,一个 jquery 的 lazyload 小插件中存在内存泄漏。

仍旧存在的问题:当图片很大时,甚至一页图片(100张)也不能全部正常显示,所以需要在图片传递到前端之前做好压缩工作。

我们在制作弹框模块时往往有一个蒙版,来阻止用户在弹框时点击页面其他地方,一般会这样实现:

.model{
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 999;
}

但是 IE 的表现却出乎我们的意料,这种样式下,页面中的相关链接或者绑定了事件的按钮依然可以点击触发,我们需要给遮罩层加上颜色才能使得他的表现和 Chrome 统一

.model{
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 999;
/* for ie*/
background-color: rgba(0,0,0,0);
}

Make a React Component with Webpack

文档结构

assets\
  ---image
  ---stylesheets
  ---javascripts
          ---components
build\
views\
node_modules\
index.js
package.json
webpack.config.js

npm安装相关依赖包

  1. npm init 初始化项目
  2. npm install webpack --save-dev
    npm i webpack-dev-server --save-dev 安装webpack相关依赖
  3. npm install babel-loader css-loader style-loader sass-loader --save-dev 安装loaders
  4. cnpm i file-loader url-loader --save-dev 安装字体,图片的loader
  5. npm i babel-core babel-preset-es2015 --save-dev ES6语法支持
  6. npm i react react-addons-update react-dom babel-preset-react --save 安装React相关依赖

配置webpack

var webpack = require('webpack');
var path = require('path');

//定义一些地址常量,下面都是约定俗称的写法

const CURRENT_PATH = path.resolve(__dirname);//当前目录
const ROOT_PATH = path.join(__dirname, '../');//上一级目录
//指定任意目录
const BUILD_PATH = path.join(__dirname, 'build');
const MODULES_PATH = path.join(ROOT_PATH, './node_modules');

var config = {
    //上下文(相当设置当前目录)
    context: CURRENT_PATH,
    //配置入口
    entry: {
        index: './index.js',
    },
    //配置输出
    output: {
        path: BUILD_PATH,
        filename: 'bundle.js',//name会油entry中的键名替代
        publicPath: '/build/'
    },
    //解析模块路径的相关配置
    resolve: {
        root: MODULES_PATH,
        extensions: ['', '.js', '.json', '.scss'],//require模块可以省略不写这些后缀
        alias: {

        }//定义模块的别名,方便直接引用
    },
    //模块
    module: {
        loaders: [
        {test: /\.jsx?$/, loader: 'babel-loader', query: {presets: ['es2015', 'react']}},
        {test: /\.css$/, loader: 'style!css'},
        {test: /\.scss$/, loader: 'style!css!sass'},
        {test: /\.(jpe?g|png|gif|svg)\??.*$/, loader: 'url-loader?limit=8192&name=[name].[ext]'},
    ]//使用相关loader来加载,同时也可在文件中直接require(import)这些文件
    },

    devServer:{
        historyApiFallback: true,
        hot: true,
        inline: true,
        progress: true,
    },
    //插件
    plugins: {

    }
}

module.exports = config;

然后在package.json里加入

"scripts": {
    "dev": "webpack-dev-server --progress --profile --colors --hot",
    "build": "webpack --progress --profile --colors",
  }

使用npm run dev跑起项目,在浏览器打开localhost:8080

调用webpack编译后的文件

默认情况下,localhost:8080会去找根目录下的index.html,我们假设在其中写我们的组建代码,则需要通过script标签去调用build\bundle.js。(注意不是去找index.js

上线

npm run build然后上传build文件夹里的js后其他html即可。

学习 React 系列(二)

处理事件

// 1. 驼峰命名
// 2. 传入 Function 而非 string
// 3. 在constructor 中统一绑定 this
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }
  
  // 这样声明事件方法就不用绑定 this 了
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }

// 事件中传入参数的写法
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
// e 会自动传入
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

key 的作用

Keys help React identify which items have changed, are added, or are removed

key 可以帮助 react 更好的进行 diff 操作,以实现更高效率地 render。

function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />

      )}
    </ul>
  );
}

Forms元素

// controlled components
// 需要在其中进行参数校验或执行特定的输入格式是很实用
class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('Your favorite flavor is: ' + this.state.value);
    event.preventDefault();
  }

// 通过onChange实现form元素的值也通过state管理
// 所有form元素的值都可以放在value中,value中可设置默认值
// 通过name属性来区分不同的form元素
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite La Croix flavor:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">Grapefruit</option>
            <option value="lime">Lime</option>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
// uncontrolled components
// 用于比较简单的场景
// 通过ref属性更新值
// 通过defaultValue,defaultChecked设置默认值
<form onSubmit={this.handleSubmit}>
      <label>
        Name:
        <input
          defaultValue="Bob"
          type="text"
          ref={(input) => this.input = input} />
      </label>
      <input type="submit" value="Submit" />
    </form>

Some Important Design

  1. lifting the shared state up to their closest common ancestor
  2. 使用组合而不是继承来复用组件
  • 利用 props.children
  • React Element 也可以当做 props 来传递(他们本质上是对象);组件可以接受任意元素,包括基本数据类型、React 元素或函数。
  1. Top to down单向数据流
  2. 以单一职责原则细分组件
  3. 可以从一个静态版本开始,这样来解耦你的数据渲染和 UI 交互过程
  4. think of the minimal set of mutable state that your app needs,想清楚最小可变状态集。不要重复自身。
  5. 一个 state 不应该来自父级,不应该随着时间推移不会改变,不应该可以通过其他 state 或 props 直接计算得到。
  6. 通过回调函数和 onChange 监听实现反向数据流。

Progressive Image Loading

类似Medium网站中的图片是我见过的最优雅的图片加载方式,记得第一次见到这种用模糊图片做placeholder的图片加载方式是在一个国外大神的博客里,当时就被惊艳到了,这种看起来很有逼格的交互其实实现起来并不复杂,这里就记录一下我从零开始一步步实现图片渐进式加载和懒加载的过程。

Intrinsic Placeholders——百分比Padding的妙用

在开始实现Progressive Image Loading之前,我们需要解决我们在加载图片是经常会遇见的一个问题:

  • 图片闪烁出现,即在图片加载成功之前他的位置被文档流中的其他元素占据,加载成功后再突然出现。尤其是在移动端商城项目中,整个布局会被完全打乱。

一个优雅的解决这个问题的方案是Intrinsic Placeholders,其实现就是依靠将padding设置成百分比值。我们知道,当padding设置为百分比时,其值是相对于父元素的宽度的,假设我们的图片是一个宽高比为3:2的图片,我们希望他的的容器在图片加载出来之前能够正确地以3:2的比例占据那个位置,这时我们只需要设置一个子容器(可以用伪元素实现)的padding值为66.6%就可以了。

    .placeholder{
        display: block;
        margin: 0 auto 100px;
        width: 75%;
        border: 1px solid #f6f6f6;
        overflow: hidden;
            background-color: #E0CEB8;
            position: relative;
    }
    .placeholder:after {
           content: '';
           display: block;
           margin-bottom: 66.6%;
    }
    .placeholder img{
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
    }
要把图片放进来,我们只需要对容器进行相对定位,对图片绝对定位,设置图片宽度为100%就可以了。[See a Demo](http://codepen.io/anon/pen/PzVGrz)

实现模糊效果的的渐进加载

在实现了图片占位符后,我们就要去实现图片的渐进式加载,这里我们需要准备一大一小两张图片,然后对HTML代码做一点点小的改动。

<picture data-large="http://7xr09v.com1.z0.glb.clouddn.com/lion-wild-africa-african.jpg" class="placeholder">
    <img src="http://7xr09v.com1.z0.glb.clouddn.com/lion-wild-africa-african.jpg?imageView2/2/w/30/h/20/interlace/0/q/100" alt="" class="img_small">
</picture>

我们用img标签去直接加载小图片,并将大图片的地址存储在自定义属性data-large,方便以后js调用。实现的思路是先加载显示小图片,在大图完全加载完成后再将其添加进picture中覆盖小图,两个图片在加载过程中我们都让其完全透明,在加载完成后再逐渐显现出来,实现起来都很简单。唯一值得一提的是模糊效果的实现,有两个可选的方案,使用CSS3的blur属性或者使用canvas,第一种方法明显更加简单。下面是这一部分实现的主要代码:

    .placeholder img{
         position: absolute;
         opacity: 0;
         top: 0;
         left: 0;
         width: 100%;
         transition: opacity 1s linear; // 是模糊的渐变效果更平滑
    }
    
    .placeholder img.loaded{
        opacity: 1;
    }
    
    .img {
       -webkit-filter:blur(50px);
        filter: blur(50px); //设置50px的模糊
       transform: scale(1); //解决Safari 上的锯齿问题
    }
    
        const placeholder = document.querySelector('.placeholder'),
              small = placeholder.querySelector('.img_small');
    
        let ImgSmall = (function(){
    
            let smallImg = new Image();
            smallImg.src = small.src;
            //因为无法监听页面上的html元素的加载,需要将new一个smallImg
            smallImg.onload = function () {
             small.classList.add('loaded');
            };
            smallImg.addEventListener('load', () =&gt; smallImg.classList.add('loaded'));
        })();
    
        let imgLarge = new Image();
        imgLarge.src = placeholder.dataset.large;
        placeholder.appendChild(imgLarge);
        imgLarge.addEventListener('load', () =&gt; imgLarge.classList.add('loaded'));
    

filter属性可以支持Chrome18+,FireFox35+,Webkit6.0+所以,如果你想获得更好的兼容性,可以尝试用canvas实现,你可以在这里找到实现的方法。

Tips: 使用七牛云存储你可以直接获得不同大小的图片地址,例如我的小图就是来自于?imageView2/2/w/30/h/20/interlace/0/q/100这段带代码后缀,由于模糊效果的关系,我们所需要的小图比想象中小的多,我这里取了宽30px的图,质量未经压缩的情况下大小只有4kb

实现图片的Lazyloading 延迟加载

实际上,很多时候我们并不需要将页面中的图片全部加载出来,在打开网页时加载所有图片会使页面变得很卡。更好的办法是在用户想要看到那些图片时再去加载他们,这样即加快了页面的加载速度,又节省了流量,也能获得更好的用户体验,所以lazyloading是一项非常实用的技术。

要实现lazyloading的方法有很多,最原始的办法是监听scroll事件,判断`$(window).scrollTop() &gt;= $element.offset().top`(Jquery写法)来判断元素开始进入viewport,用原生Js写起来就更加麻烦了;或者你也可以用`getBoundingClientRect`方法来获取元素在viewport中的位置,在判断他进入viewport后设置img的src属性,这种方法虽然简单了不少,但判断还是很长而且性能并不好。这里我使用了Chrome51+开始支持的IntersectionObserver方法,来监视元素是否进入了了viewport,是的,他目前只支持Chrome51及以上的版本,但是其优雅便捷性实在是让人心动,而且你也可以用[polyfill](https://github.com/WICG/IntersectionObserver/tree/gh-pages/polyfill)去实现对其他浏览器的兼容,只是会损伤写性能。

`IntersectionObserver`的用法非常简单:
 const element = document.querySelector('.Observed');  // 拿到要被监视的元素
    
    let io = new IntersectionObserver((entries) =&gt; {
        //do something when the element come into/out the viewport  
    },option) // 实例化一个IntersectionObserver对象
    
    io.observe(element); //监听多个element的位置
    io.observe(element1); 
    io.observe(element1); 
  • callback中entries参数是一个数组,每一项为一个IntersectionObserverEntry对象,可以获得和元素位置相关的各种属性
[IntersectionObserverEntry]
      time: 3893.92
    rootBounds: ClientRect // view的信息
        bottom: 920
        height: 1024
        left: 0
        right: 1024
        top: 0
        width: 920
    boundingClientRect: ClientRect // 元素的位置信息
      // ...
    intersectionRect: ClientRect // 元素的进入view的信息
      // ...
      intersectionRatio: 0.54 // 进入了54%
    target: div#observee
      // ...
  • option中
{
    root: null, //设置viewport的元素,默认为浏览器
    rootMargin: // 设置提前多少出发callback(格式和margin相同)
    threshold: // 设置进入多少出发一次callback(为数组,可设置0-1直接的数)
  }

下面是使用IntersectionObserver实现的懒加载的写法,十分简洁:

let proxyImage = function(imgLargeSrc){
          let imgLarge = new Image();
          let observer =  new IntersectionObserver((changes) =&gt; {
            imgLarge.src = imgLargeSrc;
            placeholder.appendChild(imgLarge);
          }, {
              root: null,
              rootMargin: '100px'
          });
          observer.observe(placeholder);
      
          imgLarge.addEventListener('load', () =&gt; {
              imgLarge.classList.add('loaded');
              observer.unobserve(placeholder);// 加载完成后取消监听
          });
      };
      
      proxyImage(placeholder.dataset.large);

这里我设置了图片在进入视窗前100xp开始加载,以获得更好的体验。

参考资料

  1. How Medium does progressive image loading
  2. intrinsic-placeholders
  3. 使用 IntersectionObserver 和 registerElement 打造 Lazyload

The Basic of Redux

Redux中的三个概念

store(单一数据源原则)

用于存储应用中所有的数据(state以对象树的形式存在),一个应用中只能有一个,他是一个上层的接口,state实际在reducer中进行初始化和更新,可以拆分成不同的部分(每一部分的state可能是原来state的某几个键值,在合并时需写明其参数)但需要用combineReducer()把他们合并起来。
用法:

import { createStore } from 'redux';
import yourReducer from './reducers';

let store = createStore(yourReducer);

//for react
<Provider store={store}>
    <APP />
 </Provider>

reducer(state是只读的,使用纯函数执行修改)

用于更新state的值,初始化的state的值也是写在这里,可以为空但不能为undefined。state的更新需要从action中传入更新后的state的值(action.yourvalue)和产生更新的行为action.type

进行state的更新时需要注意,返回的state不应是对原对象的更改,而应是返回一个全新的对象。

用法:

export default const MyReducer = (state ={}, action) => {
    switch(action.type){
        case 'ONE_ACTION':
            return NewState;
        default:
            return state;
    }
}

action

是store的数据修改的来源,一般通过dispatch(storechangeFuc(newState))的方法将修改后的state的值(可以是state对象的某些属性值)传入reducer,然后在reducer中实现state的更新。

用法:

//actions/index.js

export const storechangeFuc = (newState) => {
    return {
        type: 'ONE_ACTION',
        stateKey: newState
    }
}

// components/oneComponent.js
// 声明一个dispatch的函数
/const boundAddTodo = (text) => dispatch(addTodo(text))
//在适当的位置调用他
boundAddTodo(text);

react+redux 基础流程总结

网上会有很多的框图来总结这个,但是我发现我对框图的阅读无力,所以用文字记录下,仅供自己阅读。

将redux是一个独立的数据流方案,他可以搭配任何框架使用,甚至原生js。这里,整理一下配合react使用是的总体思路。

引入react-redux将两者连起来由此得到Provider和connect,分别用于传入store和将state,dispatch与react连起来。
用Provider标签将应用包裹起来,并将store以props的方式传入,然后将整个Provider传入react的render中,渲染出来。

import { Provider } from 'react-redux';
render(
  <Provider store={store}>
    <MovieBox />
  </Provider>,
  document.getElementById('demo')
);

为了得到store,redux为我们封装好了createStore方法,所以在render之前我们需要拿到store:

import { createStore } from 'redux';
let store = createStore(MyReducer);

createStore是以你的reducer为参数的,所以我们需要完成我们的reducer函数。如果业务逻辑较多,你可以将他分为几个小的reducer然后用combineReducers将他们合并起来。

import { combineReducers } from 'redux';

const MyReducer = combineReducers({
    oneReducer,
    anotherReducer
});//如果这几个reducer你放在不同的文件中,你需要将他们import进来

在reducer之前你需要完成你的actions,actions中的函数名对应以业务逻辑中dispatch的相关函数,参数为修改的state,返回action.type和state合并后的对象传入reducer中。
你的react组件要对state进行更新需要去store中拿到相关数据,和相关dispatch函数。
通过 mapStateToProps(state)方法拿到你需要的state,并将其放在该组件的props中;
通过 mapDispatchToProps(dispatch)方法拿到actions中的dispatch方法,并将其放在该组件的props中。
以上要通过connect(mapStateToProps,mapDispatchToProps)实现
然后你就可以通过this.props.xx 来修改调用相关数据和函数。
在state更新之后,应用就会重新render得到新的视图。

Redux中更新state的正确姿势

state不应该被重写而是应该返回一个新的state,所以要时刻注意返回的state是一个全新的而不是在原来的地址上修改的。你可以参考文档中的immutable helpers 也可以在项目中引入Immutable.js。

修改数组

//添加项目(不要使用push)
return list.concat(newItems);

// 或者采用ES6的写法:
return [...list,0];

// 这里有一个小的tips,当你的state是一个对象,而数组是作为对象的一个键值的时候,如何更改这个数组。很容易误以为这个数组没有名字,实际上他的名字就是state.key。

let myObject={
  arr1:[1,2,3],
  others:'abc'
}
let updateObject = Object.assign({},myObject,{arr1:[...myObject.arr1,[4,5,6]]});

// 删除项目(不要使用splice)
return list.slice(0,index).concat(list.slice(index+1));
// 或者采用ES6的写法
return [...list.slice(0, index), ...list.slice(index+1)];
// 修改项目
// eg:将某一项加一
return list.slice(0,index).concat(list[index] + 1).concat(list.slice(index+1);
// ES6:
return [...list.slice(0,index),list[index]+1,...list.slice(index+1)];

修改对象

不能只是简单地修改对象的键值,要返回一个新的对象。
你可以新建一个新的对象然后将每组键值重写一遍。或者使用react-addons-update库中的update方法。

对于ES6中你可以使用Object.assign({},state,{key:newvalue});

注意{…state,{key:newvalue}}这样的写法在ES7中才可用。

action

描述发生了什么,描述了数据(state)的结构和内容,是store的唯一来源,通过dispatch来分发。

reducer

(prevState, action) => newAction
根据action来具体处理state的改变,发送给store。

import { combineReducers } from 'redux'
import * as reducers from './reducers'const todoApp = combineReducers(reducers)

store

存储所有的state,需要保持唯一的store。通过getState,dispatch,subscribe来获取state,分发action,注册监听。

import { createStore } from 'redux'
const store = createStore(reducer, initState)

react-redux

Provider

具有store属性,将state分发给所有被connect的组件。

connect

connect(mapStateToProps, mapDispatchToProps)(component),从store中获取state作为props传入指定组件,将分发actions作为props传入组件。

mapStateToProps(state, [ownProps])

返回一个对象,代表props

mapDispatchToProps(dispatch, [ownProps])

返回一个对象(一般是一些事件),作为props传给组件,在组件中调用
这里可以看出,view层(react)只会分发(dispatch)action,不会去触发reducer改变state,保证了单向数据流。改操作实现了setState的功能。

redux-thunk

帮助解决redux中异步问题的中间件,thunk表示辅助调用另一个子程序的子程序。

redux-thunk会检查action对象是不是函数,如果不是函数就放行,如果是就执行并将dispatch和getState作为参数传入该函数,在改函数中再去dispatch响应的action。

异步action的构造函数

export const sampleAsyncAction = () => {
    return (dispatch, getState) => {
        // 何时时机dispatch相应的action对象
    }
}

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.