Live and learn


  • 首页

  • 标签

  • 归档

关于React中的super(props)

发表于 2019-06-13 | 分类于 React

读书笔记
原文链接


super

在 JavaScript 中,super 指的是父类(即超类)的构造函数。在下面的例子中,它指向了 React.Component 的实现。super只能用在子类的构造函数之中。

class Checkbox extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOn: true };
  }
  // ...
}

值得注意的是,在调用父类的构造函数之前,你是不能在 constructor 中使用 this 关键字的。JavaScript 不允许这个行为。这是一种成功陷阱机制。

class Checkbox extends React.Component {
  constructor(props) {
    // 🔴  还不能使用 `this`
    super(props);
    // ✅  现在可以了
    this.state = { isOn: true };
  }
  // ...
}

为什么要传入props👇

为了让 React.Component 构造函数能够初始化 this.props,将 props 传入 super 是必须的。

看 React 源码 :

/**
 * Base class helpers for the updating state of a component.
 */
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.isReactComponent = {};

为什么不传props,依然能访问到 this.props👇

这当然不是javascript内置的默认逻辑,而是 React 内部做的手脚,在调用构造函数后也立即将 props 赋值到了实例上:

const instance = new YourComponent(props);
instance.props = props;

super() 能代替 super(props) ? 👇

最好不要,虽然 React 会在构造函数执行完毕之后给 this.props 赋值。但在构造函数中 this.props 一直是 undefined。

// React 內部
class Component {
  constructor(props) {
    this.props = props;
    // ...
  }
}

// 你的程式碼內部
class Button extends React.Component {
  constructor(props) {
    super(); // 😬 我们忘了传入 props
    console.log(props);      // ✅ {}
    console.log(this.props); // 😬 未定义
  }
  // ...
}

如果在构造函数中调用了其他的内部方法,方法链中用到了this.props,就麻烦了。这是 React 作者建议开发者一定执行 super(props) 的原因。 所以,我们还是乖乖的秉承规范:

class Button extends React.Component {
  constructor(props) {
    super(props); // ✅ 传入 props
    console.log(props);      // ✅ {}
    console.log(this.props); // ✅ {}
  }
  // ...
}

H5拍照上传处理

发表于 2019-05-28 | 分类于 web

前言


很多公司业务中,会存在上传资料图片,以供审核的需求。那我们用H5如何实现,并达到好的效果呢

涉及技术点:

  • html media capture
  • FileReader || ULR.createObjectURL()
  • canvas
  • EXIF & orientation

调用相机

html media capture

<input type='file' accept='image/*' capture="camera"/>

获取图片信息


以下两种方式皆可:

  • new FileReader().readAsDataURL(file) 异步/data:base64
  • ULR.createObjectURL(file) 同步/内存url

压缩


  • 限制最大宽度,对宽高做处理
  • 等比例压缩
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = Math.min(image.naturalWidth, option.maxWidth)
  // 高度通过宽度压缩比处理,保证等比例压缩
  const ratio = canvas.width / image.naturalWidth
  canvas.height = image.naturalHeight * ratio
  ...
  // 二次压缩(比如90%)
  canvas.toDataURL(fileType, 0.9); // fileType: image/jpeg

旋转处理


手机相机(基本上都是Iphone)拍照,你会发现有时候照片会自动旋转,有时候并不是我们期望的显示效果。
不过手机拍照获取的图片,有EXIF(Exchangeable image file format),会默认设置一个orientation tag(jpeg格式才有)。orientation标示了图片旋转信息。

首先,我们可以用Exifjs获取orientation。Exif.js提供了js读取图像的原始数据的功能扩展(拍照方向、相机设备型号、拍摄时间、ISO感光度、GPS地理位置等数据).

import EXIF from 'exif-js';
...

EXIF.getData(file, function () {
  const orientation = EXIF.getTag(this, 'Orientation');
  console.log(`orientation: ${orientation}`);
});

然后,我们用canvas对图片做一些旋转处理即可( 亲测只存在1,3,6,8四种情况):

  // 判断图片方向,重置 canvas 大小,确定旋转角度,iphone 默认的是 home 键在右方的横屏拍摄方式
  switch (orientation) {
      // 1 不需要旋转
      case 1: {
          ctx.drawImage(img, 0, 0, width, height);
          ctx.clearRect(0, 0, width, height);
          ctx.drawImage(img, 0, 0, width, height);
          break;
      }
      // iphone 横屏拍摄,此时 home 键在左侧 旋转180度
      case 3: {
          ctx.clearRect(0, 0, width, height);
          ctx.translate(0, 0);
          ctx.rotate(Math.PI);
          ctx.drawImage(img, -width, -height, width, height);
          break;
      }
      // iphone 竖屏拍摄,此时 home 键在下方(正常拿手机的方向) 旋转90度
      case 6: {
          ctx.clearRect(0, 0, width, height);
          ctx.translate(0, 0);
          ctx.rotate(90 * Math.PI / 180);
          ctx.drawImage(img, 0, -height, width, height);
          break;
      }
      // iphone 竖屏拍摄,此时 home 键在上方 旋转270度
      case 8: {
          ctx.clearRect(0, 0, width, height);
          ctx.translate(0, 0);
          ctx.rotate(-90 * Math.PI / 180);
          ctx.drawImage(img, -width, 0, width, height);
          break;
      }
      default: {
          ctx.clearRect(0, 0, width, height);
          ctx.drawImage(img, 0, 0, width, height);
          break;
      }
  }

最后,我们上传处理完的图片。


参考资料

React - Render Props

发表于 2019-04-23 | 分类于 React

可组合组件

我们先回顾一下可组合组件。在 props 对象中有一个常用的 children 属性。通过它你可以将元素从上层传递到你的组件中,这些元素对你的组件来说是未知的,但是却为组件相互组合提供了可能性。children 非常灵活,可以是字符串,组件,甚至他们的集合,就像写html一样:

const A= ({ children }) => (
  <div>
    <p> A logic</p>
    { children }
  </div>
)
const App = () => (
  <>
    <p> children 示例:</p>
    <A>
      组件B:<B />
      组件C:<C />
    </A>
  </>
);

上面的组件A就是一个可组合组件。

可共享的可组合组件

上面示例中 children 是最基本的使用方式,只是在组合层面,还没到达共享层面。如果 B C 组件要使用 A 组件的属性,即 A 组件要共享自己的属性给B C 使用。通过 通过callBack 实现 A > APP > B&C 这种传递显然太low。这里就有一个技巧:把 children 函数化 这样便可以通过传递实现数据共享。
我们来看一个实例:一个检测浏览器在线/离线状态的共享组件,很多组件都需要通过这个组件获取浏览器是否在线:

import React from "react";

// 共享组件
class BrowserIfOnline extends React.Component {
  state = {
    isOnline: window.navigator.onLine
  };
  onOffline = () => {
    this.setState({ isOnline: false });
  };
  onOnline = () => {
    this.setState({ isOnline: true });
  };
  componentDidMount() {
    // 事件监听
    window.addEventListener("offline", this.onOffline);
    window.addEventListener("online", this.onOnline);
  }
  componentWillUnmount() {
    window.removeEventListener("offline", this.onOffline);
    window.removeEventListener("online", this.onOnline);
  }

  render() {
    return (
      <>
        {this.state.isOnline ? (
          <div>网断已连接 ...</div>
        ) : (
          <div>网络已断开 ...</div>
        )}
        {this.props.children(this.state.isOnline)}
      </>
    );
  }
}
// 子组件
const B = ({ isOnline }) => <p>组件B:{isOnline ? "在线" : "离线"}</p>;
const C = ({ isOnline }) => <p>组件C:{isOnline ? "在线" : "离线"}</p>;

// 通过 函数化的 children 使用共享组件
class A extends React.Component {
  render() {
    return (
      <BrowserIfOnline>
        {isOnline => (
          <>
            <B isOnline={isOnline} />
            <C isOnline={isOnline} />
          </>
        )}
      </BrowserIfOnline>
    );
  }
}
export default A

Render props 技巧

render props 是 React 的 一个高级特性。指一种在 React 组件之间使用一个值为函数的 prop 共享代码/逻辑的简单技术。是一个用于告知组件需要渲染什么内容的函数。

上述可共享的可组合组件的实现,使用的便是 children + render props 技巧。在官方文档中介绍的 render props 使用的是另一种方式:把一个函数作为render 属性传递,而非 children:

// 共享组件 
// 通过props.render 渲染其他组件,并共享数据
class BrowserIfOnline extends React.Component {

  // ......
  // 这里省略其他逻辑 同上
  // ......

  render() {
    return (
      <>
        {this.state.isOnline ? (
          <div>网断已连接 ...</div>
        ) : (
          <div>网络已断开 ...</div>
        )}
        {this.props.render(this.state.isOnline)}
      </>
    );
  }
}
//通过`render props` 的 属性(render)方式使用共享组件
class A extends React.Component {
  render() {
    return (
      <BrowserIfOnline
        render={isOnline => (
          <>
            <B isOnline={isOnline} />
            <C isOnline={isOnline} />
          </>
        )}
      />
    );
  }
}

render 属性是国际惯例,你也可以自定义,但最好不要。复杂场景下,你可能需要多个 render 函数。这个时候必须要重命名了,但依然建议加render前缀,比如renderOne renderTwo。

render props 优势

很多时候,React Hoc 可替代 render props 来实现逻辑共享。比如上面的功能用React Hoc 实现如下:

import React from "react";
// 把BrowserIfOnline组件封装成一个高阶组件 withBrowserIfOnline
const withBrowserIfOnline = comps =>
  class BrowserIfOnline extends React.Component {
    state = {
      isOnline: window.navigator.onLine
    };
    onOffline = () => {
      this.setState({ isOnline: false });
    };
    onOnline = () => {
      this.setState({ isOnline: true });
    };
    componentDidMount() {
      // 事件监听
      window.addEventListener("offline", this.onOffline);
      window.addEventListener("online", this.onOnline);
    }
    componentWillUnmount() {
      window.removeEventListener("offline", this.onOffline);
      window.removeEventListener("online", this.onOnline);
    }

    render() {
      return (
        <>
          {this.state.isOnline ? (
            <div>网断已连接 ...</div>
          ) : (
            <div>网络已断开 ...</div>
          )}
          {comps.map((Comp) => <Comp isOnline={this.state.isOnline}/>)}
        </>
      );
    }
  }
const B = ({ isOnline }) => <p>组件B:{isOnline ? "在线" : "离线"}</p>;
const C = ({ isOnline }) => <p>组件C:{isOnline ? "在线" : "离线"}</p>;

const D = withBrowserIfOnline([B, C]);
export default class A extends React.Component {
  render() {
    return (
      <D/>
    );
  }
}

我们使用 render props 的优点主要体现在它的灵活性。比如上述示例,我们是可以在组件 B C 前后增加一些零碎的代码逻辑,而React Hoc就没那么便利。

class A extends React.Component {
  render() {
    return (
      <BrowserIfOnline
        render={isOnline => (
          <>
            <p>组件B:</p>
            <B isOnline={isOnline} />
            <p>组件C:</p>
            <C isOnline={isOnline} />
            <p>其他信息</p>
          </>
        )}
      />
    );
  }
}

参考资料

官网 - Render Props
React Render Props

React 单元测试

发表于 2019-04-09 | 分类于 React

Jest 快照测试

  • Jest 是一个在 Facebook 使用的测试框架。在 React 社区,它被用来做 React 的组件测试。
  • Jest 赋予你写快照测试的能力。
import React from 'react';
import ReactDOM from 'react-dom';
// 写快照之前,需要额外安装一个工具库:
import renderer from 'react-test-renderer';
import App, { Table } from './App';

...
// “describe”块中来定义一个测试套件。可包含一系列关于特定组件的“it”块。
describe('Search', () => {
  // mock 一些测试数据 ,非常重要
  const props={ 
    list:[
      { title: '1', author: '1', num_comments: 1, points: 2, objectID: 'y' },
      { title: '2', author: '2', num_comments: 1, points: 2, objectID: 'z' },
    ] 
  }
  // “it”块描述了一个测试用例。
  it('renders without crashing', () => {
    const div = document.createElement('div');
    ReactDOM.render(<Table { ...props } />, div);
  });
  // “test”块来实现一个快照测试
  test('has a valid snapshot', () => {
    const component = renderer.create(
      <Table { ...props } />
    );
    let tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });
});

Enzyme 单元测试

Enzyme 可以用来断言、操作、遍历 React 组件。你可以用它来管理单元测试,在 React 测试中与快照测试互补。

安装(还需要安装一个扩展库):

npm install --save-dev enzyme react-addons-test-utils enzyme-adapter-react-16

适配器初始化:

import React from 'react';
import ReactDOM from 'react-dom';
import renderer from 'react-test-renderer';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import App, { Search, Button, Table } from './App';
Enzyme.configure({ adapter: new Adapter() });

写一个单元测试:使用 shallow() 方 法渲染你的Table组件,并且断言 Table 有两个子项,因为你传入了两个列表项。断言仅仅检查这个元素两个带有类名叫 table-row 的元素:

import React from 'react';
import ReactDOM from 'react-dom';
import renderer from 'react-test-renderer';
import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import App, { Search, Button, Table } from './App';
...
describe('Table', () => {
  const props = {
    list: [
      { title: '1', author: '1', num_comments: 1, points: 2, objectID: 'y' },
      { title: '2', author: '2', num_comments: 1, points: 2, objectID: 'z' },
    ],
  };
...
  it('shows two items in list', () => {
      const element = shallow(<Table { ...props } />
    );
    expect(element.find('.table-row').length).toBe(2);
  });
});

Enzyme API 中总共有三种渲染机制。 浅渲染 shallow() 外,这里还有 mount() 和render() 方法。这两种方式都会初始化父组件和所有的子组件。此外 mount() 还给予你调用组件生命周期的方法。但是什时候该使用哪种渲染机制呢?这里有一些建议:

  • 不论怎样都优先尝试使用浅渲染(shallow()),不会渲染子组件。
  • 如果需要测试 componentDidMount() 或 componentDidUpdate(),使用 mount()。
  • 如果你想测试组件的生命周期和子组件的行为,使用 mount()。
  • 如果你想测试一个组件的子组件的渲染,并且不关心生命周期方法和减少些渲染的花销的话,使用 render()。

React 测试入门教程-阮一峰

前端同构渲染

发表于 2019-03-12 | 分类于 web

常见渲染方式

  • CSR (Client Side Rendering):SPA单页应用常用,客户端 异步获取数据渲染页面的方式。
  • SSR (Server Side Rendering):在服务端获取数据生成html返回客户端的方式,html也可包含js文件,加载后获取数据二次渲染。
  • SSG (Static Site Generation):静态网站生成类似于服务器端渲染,不同之处在于您在构建时而不是在请求时渲染页面。
  • CSR with Pre-rendering:预渲染原理是:一般在构建阶段的最后,在本地启动一个 Puppeteer 服务,访问配置了预渲染的路由,然后将 Puppeteer 中渲染的页面输出到 HTML 文件中打包,并建立路由对应的目录。

以此, 达到预渲染的目的。

什么是同构渲染

同构渲染是为了解决当下客户端渲染为主的组件式卡法模式下性能,白屏问题而提出的一种解决方案。强调服务端跟客户端公用一套代码。服务端负责渲染,客户端来负责交互。当然服务端渲染挂了,客户端也是可以渲染的。

客户端渲染 还是 同构渲染

可阅读文章: 精读前后端渲染之争

作者通过精读Here’s Why Client-side Rendering Won这篇文章,并收集近10位同仁的意见,对于同构渲染进行了总结并发表了自己的看法。

主要阐述了同构渲染在优化体验的同时也会带来一系列问题,并没有想象中的美好。作者认为还是应该选择客户端渲染的方案为主流,可以通过其他方式优化,或部分同构的方式来解决客户端渲染的性能问题。

同构渲染架构如何实现?

可阅读文章:一文吃透React SSR服务端渲染和同构原理。

作者为我们详细解答了:

  • 为什么需要同构渲染?
  • 实现同构渲染的核心原理是什么?
  • 实现同构渲染有哪些技术难题?如何设计对应的解决方案和具体实现( 在 React 提供的 SSR 能力的几个API的基础上)。

可行性和是否成熟先不谈,作者解决各种问题的思路值得学习。

我的看法

同构渲染固然是一种非常好的解决问题的思路,但是需要解决的技术难题非常多。推崇同构渲染的同仁可能会说,有问题解决问题便可,比如上述文章基本上给出了各种技术问题的解决方案。我个人也相对保守,崇尚kiss原则,我觉得在形成一套成熟的同构渲染技术方案(非常困难)的情况下,不宜推广全栈同构,可以部分同构。

我认为的成熟的同构方案至少满足以下两点:

  • 解决同构渲染的各种技术难题,稳定且不存在很大的性能,兼容性等问题。
  • 不能使得普通开发者的开发成本和维护成本大大增加。性能虽然重要,开发质量,可维护性也是相当重要的。

软件开发中遇到的所有问题,都可以通过增加一层抽象而得以解决。

React技术体系

发表于 2019-03-05 | 分类于 React

基础概念

  • Virtual DOM(Diff算法)
  • Jsx 语法
  • Flux Data Flow(单向数据流)
  • 类组件&生命周期
  • 函数组件-无状态组件FSC
  • 高阶特性 Hoc Render Props Context 等

生命周期

在挂载过程中有四个生命周期方法,它们的调用顺序是这样的:

  • constructor() 组件初始化时被调用,用于初始化状态
  • componentWillMount() 在 render() 之前被调用。可用于设置一些组件本地状态,不过还是推荐在 constructor() 中去初始化状态。
  • render() 它返回作为组件输出的元素。这个方法应该是一个纯函数,因此不应该在这个方法中修改组件的状态。
  • componentDidMount() 它仅在组件挂载后执行一次。一般用于处理Ajax请求等副作用。

共有5个生命周期方法用于组件更新周期,调用顺序如下:

  • componentWillReceiveProps(nextProps) 新的属性会作为它的输入。因此你可以利用 this.props 来对比之后的属性和之前的属性,基于对比的结果去实现不同的行为。此外,你可以基于新的属性来设置组件的状态。
  • shouldComponentUpdate((nextProps, nextState) 每次组件因为状态或者属性更改而更新时,它都会被调用。组件及其子组件将根据该方法返回的布尔值来决定是否重新渲染,从而避免不必要的渲染。用于性能优化。
  • componentWillUpdate((nextProps, nextState) - 这个方法是 render() 执行之前的最后一个方法。你已经拥有下一个属性和状态,它们可以在这个方法中任由你处置。你可以利用这个方法在渲染之前进行最后的准备。注意在这个生命周期方法中你不能再触发 setState()。如果你想基于新的属性计算状态,你必须利用componentWillReceiveProps()。
  • render() 同上。
  • componentDidUpdate() 这个方法在 render() 之后立即调用。你可以用它当成操作 DOM 或者执行更多异步请求的机会。

组件卸载也有生命周期。只有一个:

  • componentWillUnmount()。它会在组件销毁之前被调用。你可以利用这个生命周期方法去执行任何清理任务。

componentDidCatch() 。它在 React 16 中引入,用来捕获组件的错误。

setState() 异步?

  • setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
  • setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

React Hooks

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

  • 基础 Hook : useState useEffect useContext
  • 其他 Hook : useReducer useCallback useMemo useRef useImperativeHandle useLayoutEffect useDebugValue

React SSR

  • CSR 客户端渲染
  • SSR 服务端渲染
  • SPA 单页应用

SSR其实属于古老的技术了,早期的前端页面都是SSR渲染的,node + ejs(模版引擎),java + (velocity模版引擎),php,jsp等都可以实现。但是只实现SSR其实没啥意义,技术上没有任何发展和进步,否则 SPA 技术就不会出现。
但是单纯的 SPA又不够完美,所以最好的方案就是这两种体验和技术的结合,第一次访问页面是服务端渲染,基于第一次访问后续的交互就是 SPA 的效果和体验,还不影响SEO 效果,这就有点完美了。关于React SSR,主要有下面几个框架:

Nextjs

React轻量级后端渲染框架,同构渲染利器。

Umijs

Umi,中文可发音为乌米,是可扩展的企业级前端应用框架,参考 next.js 做的。要说有哪些地方不如Umi,我觉得可能是不够贴近业务,不够接地气。比如 antd、dva 的深度整合,比如国际化、权限、数据流、配置式路由、补丁方案、自动化 external 方面等等一线开发者才会遇到的问题。

Bigfish(阿里内部,非开源)

  • Umi 和 Bigfish,前者是从无线业务中长出来的,后者是从中台业务中长出来的。
  • 后来统一技术栈,Bigfish 后来改造成 umi + umi 插件集的一个架构。这样一个对外开源,一个对内服务。类似的还有 eggjs 和 chair。
    既然是阿里内部框架,这里为啥提及:这是一种很好的方式,开源和业务两不误

Eggjs

企业级Node.js框架, 基于 Koa 开发,性能优异,高度可扩展的插件机制,内置多进程管理。
有成熟的配合React实现SSR技术解决方案


Redux

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。

Redux中文文档

redux-saga

异步解决方案,相似仓库还有:

  • redux-thunk
  • redux-promise

dav

基于 redux 和 redux-saga 的数据流方案,额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。


好文

React SSR详解
Egg + React + SSR 服务端渲染
蚂蚁金服前端框架和工程化实践
React + Redux 最佳实践
支付宝前端应用架构的发展和选择
你真的理解setState吗?

JS 基础知识碎片

发表于 2018-08-21

8 种基本数据类型


  • Number: 数值类型。范围是±(253-1);特殊值有Infinity(正无穷,其实是大过最大值), -Infinity(负无穷,其实是小过最小值)和 NaN(非数值)。
  • String: 字符串类型。
  • Object: 对象类型。
  • Boolean: 布尔类型 true or false
  • null: 特殊的对象,表示空,已定义未赋值。
  • undefined: 未定义
  • BigInt: 表示任意长度的整数。通过在数值末尾加 n来定义,比如 const bigInt = 1234567890123456789012345678901234567890n;
  • Symbols: 生成唯一标示符。

typeof 用于检测变量的数据类型:

  • 两种使用方式: typeof x or typeof(x)。
  • 返回类型的字符串, 比如 "string"。
  • typeof null 返回 "object" null 被认为是一个 特殊 的对象( 空对象 )。

BigInt 注意兼容性: Firefox/Chrome/Edge/Safari都已支持, IE尚不支持。

作用域(scope)之声明提前(hoisting)

  • js 只有函数作用域,没有块级作用域。
  • js 的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的。
  • 这意味这变量在函数体内声明之前就已经可用,这个特性被非正式的称为声明提前(hoisting)。
var scope = "global"
function f() {
  console.log(scope);  // 输出 undefined 而非 global
  var scope = "local";
  console.log(scope);  // 输出 local
}

上述函数相当于


function f() {
  var scope;  // 自动的 声明提前
  console.log(scope);
  scope = "local";
  console.log(scope);
}

toString() 和 valueOf()

  • 所有的对象继承了两个转换方法:toString() 和 valueOf()。
  • toString() 不同的对象类型表现不同,比如数组返回逗号拼接的字符串,日期类型返回一个可读的日期事件字符串等。
  • valueOf() 如果对象存在原始值,就返回原始值。对象是复合值,大多数时候无法真正表示为一个原始值,就返回其本身。
[1,2,3].toString();  // "1,2,3"
new Date(2010,0,1).toString();  // "Fri Jan 01 2010 00:00:00 GMT+0800 (中国标准时间)"
new Date(2010,0,1).valueOf();  // 1262275200000

in 运算符

var obj = { x: 1, y: 2 }
"x" in obj; // => true
"z" in obj; // => false
"toString" in obj; // => true: 所有对象继承了toString()方法

instanceof 运算符

  • A instanceof B : 左操作数A为一个对象,右操作数B标示对象的类型。
  • 如果A是B的实例就返回true,否则返回false。
var d = new Date();
d instanceof Date;     // => true
d instanceof Object;   // => true : 所有对象都是 Object 的实例
d instanceof Number;   // => false
var a = [1, 2, 3];
a instanceof Array;    // => true
a instanceof Object;   // => true
function f() { };
f instanceOf Function;  // => true

创建对象

  • 对象直接量 :常用方式。
var empty = {};       // 空对象
var point = {x: 0, y: 0};
var book = {
  "main title": "Javascropt", // 属性名有空格必须使用字符串表示
  "sub-title": "The Definitive Guide",  // 属性名有连字符必须使用字符串表示
  for: "all audiences"
}
  • new:new运算符创建并初始化一个新对象,new后面跟随一个函数调用,这个函数其实是一个构造函数(constructor)。
var empty = new Object();       // 同 {}
var point = new Object({x: 0, y: 0});
var a = new Array(); // 同 []
var b = new Date();
  • Object.create(proto, propertiesObject):ES6定义的方法,它创建一个对象.其中proto(必须参数)是这个对象的原型。
    propertiesObject是可选参数,作用就是给新对象添加新属性以及描述器。具体可参考 Object.defineProperties() - mdn 的第二个参数。新添加的属性是新对象自身具有的属性也就是通过hasOwnProperty() 方法可以获取到的属性,
// 创建一个没有原型的对象
var o1 = Object.create(null);  
// 同 {} 和 new Object()
var o2 = Object.create(Object.prototype); 
o2.a = 1;
// o3._propo_ = o2 继承了o2, 也继承了o2的属性a;增加了属性 b=1;
var o3 = Object.create(o2, {b: { vaue: 1, writable: true}}); 
o3; // print {b:1} :自🈶️属性
o3.a; // print 1: 获取对象的属性,如果自有属性没有,会继续在对象继承的原型链中(o3>o2>Object.prototype)查找。

自己实现一个 Object.create():

Object.myCreate = function (obj, properties)  {
  var F = function ()  {}
  F.prototype = obj
  if (properties) {
     Object.defineProperties(F, properties)
  }
  return new F()
}

Object.myCreate({}, {a: {value: 1}})     // {a: 1}

对象动态属性

// 设置
var selfProp = 'other';
var index = 1;
var book={
  "main title": "", // 属性名有空格必须使用字符串表示
  "sub-title": "",  // 属性名有连字符必须使用字符串表示
  auther: "",
  [selfProp + index]: ""   // 动态属性通过 [] 设置。 
}
// 读取 
var auther = book.auther;
var title = book["main title"]; // 属性名有空格,连字符的属性通过 [] 获取。
var other = book[selfProp + index]; // 动态属性通过 [] 获取。

删除属性

  • delete只能删除自由属性,无法删除继承属性。
  • delete无法删除通过var定义的全局变量。
delete book.auther;              // true
delete book["main title"];       // true
var obj = Object.create({x: 1}); 
delete obj.x;                    // true 但是并没有删除继承属性x。
var global1 = 1;                 // 定义全局变量,用var
this.global2 = 2;                // 定义全局变量
delete global1;                  // 无法删除
delete global2;                  // 可删除

检测属性的几种方式

  • in 运算符
  • .property !== undefined的形式
  • hasOwnProperty() ( 只能检测自有属性 )

枚举对象属性

  • for/in 运算符: 可枚举自有属性和继承属性
  • Object.keys(obj):只能枚举自有属性

Object对象常用方法

  • Object.assign();
  • Object.create();
  • Object.keys();
  • Object.values();
  • Object.defineProperty();
  • Object.entries();
  • 其他-MDN

对象原型判断

  • isPrototypeOf方法(推荐)
  • _proto_属性
var obj = {};
var newObj = Object.create(obj);
obj.isPrototypeOf(newObj);  // true
newObj._proto_ === obj; // true

获取对象类属性(class attribute)

  • typeof 操作符:
typeof null;         // => "object"
typeof undefined;    // => "undefined"
typeof true;         // => "boolean"
typeof 100;          // => "number"  NaN也是
typeof "abc";        // => "string"
typeof function(){}; // => "function"
typeof 任意内置对象;  // => ""object
  • 利用Object.prototype.toString() 返回 [object class]的特性,实现一下方法:
function classof(0) {
  if (o === null) return 'null';
  if(o === undefined) return 'undefined';
  return Object.prototype.toString.call(o).slice(8,-1);
}

classof(1); // => 'Number'
classof(""); // => 'String'
classof(false); // => 'Boolean'
classof({}); // => 'Object'
classof([]); // => 'Array'
classof(/./); // => 'Regexp'
classof(new Date()); // => 'Date'
classof(Window); // => 'Window'
function f() {};
classof(new f()); // => 'Object'

数组须知

  • 数组索引必须为非负整数(非负整数的字符串也可以)。使用其他类型来索引数组,只能作为数组的属性。
var a = new Array(10) // => length = 10 
a["1000"] = 1; // => length = 1001  ( [0,1,...1000] )
a[-1] = -1; // => length 不变,数组多一个“-1”属性
  • 数组也是对象,可以使用对象的各种方法和操作。
//eg
var a = new Array(10)
Object.defineproperty(a,'length',{writable: false}) // 让数组length不可变
  • delete 不会改变数组的length,只是让某个 索引位 没有值。

Get/Post 请求须知

  • 请求数据大小的限制并不是HTTP协议限制,而是浏览器的限制。
  • get适合查找数据,post适合添加/修改数据。所以在缓存策略上,get请求可以被缓存,post请求不会被缓存。

为什么频繁调用 element.getboundingclientrect() 可能引发重绘/回流?

浏览器都会优化重绘和回流的操作。浏览器会把所有会引起回流、重绘的Dom操作放入一个队列中,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。

另外,当我们取一些属性值时(offsetWidth、clientWidth、width)等,只为了取到正确的值,浏览器可能提前执行 flush 队列,即便是队列里的操作不影响所取的值。

ES6 定义对象方法时,还可以省去 function 关键字。

{
  reducers: {
    add() {}  // 等同于 add: function() {}
  },
  effects: {
    *addRemote() {}  // 等同于 addRemote: function*() {}
  },
}

Set集合 & Map字典

Set 操作方法:

  • add(value):新增,相当于 array里的push。
  • delete(value):存在即删除集合中value。
  • has(value):判断集合中是否存在 value。
  • clear():清空集合。

Map 操作方法:

  • set(key, value):向字典中添加新元素。

  • get(key):通过键查找特定的数值并返回。

  • has(key):判断字典中是否存在键key。

  • delete(key):通过键 key 从字典中移除对应的数据。

  • clear():将这个字典中的所有元素删除。

  • 共同点:集合、字典 可以储存不重复的值。

  • 不同点:集合 是以 [value, value]的形式储存元素,字典 是以 [key, value] 的形式储存

页面加载多个JS的处理过程

step 1. 读入第一个代码块。

step 2. 做语法分析,有错则报语法错误(比如括号不匹配等),并跳转到step5。

step 3. 对var变量和function定义做“预编译处理”(永远不会报错的,因为只解析正确的声明)。

step 4. 执行代码段,有错则报错(比如变量未定义)。

step 5. 如果还有下一个代码段,则读入下一个代码段,重复step2。

step6. 结束。

HTTPS加密为啥需要CA认证

方案一:对称加密

加密解密使用同一个密钥。
存在密钥协商的问题(客户端和服务器同步密钥的时候很容易被截获)。

方案二:非对称加密

公钥/私钥互为锁和钥匙。如果 公钥 在服务器发送给客户端时被窃取。那么服务器私钥加密的内容会被坏人解密。不过,客户端发送的公钥加密过的数据,坏人没有私钥没法解密。另外,非对称加密性能是很差的,因为支持双向加解密,复杂度和密钥长度都需要很大,否则容易被暴力破解。

方案三:改进方案

  • 方案:客户端获取公钥后生成对称密钥,然后用公钥加密后给服务器,服务器用私钥解密得到对称密钥,后续就进行对称加密即可。
  • 问题:如果坏人一开始就窃听了公钥,然后发送假的公钥(当然坏人有对应的私钥)给客户端。那么客户端发给服务器的对称密钥会被第三方解密,然后第三方用真的公钥加密给服务器。这样,后续的通信中,第三方就能获取所有的信息,毕竟得到了对称密钥。

为了做到绝对的安全,CA认证(数字证书+数字签名)诞生。具体流程如下:

  1. 服务器需要提交服务器站点的信息如域名、公司名称、公钥等给CA机构去申请和购买数字证书。CA机构就是数字证书颁发的权威机构,负责颁发证书以及验证证书的合法性。

  2. CA机构在给服务器颁发证书的时候,除了数字证书,还会根据 数字证书 + hash算法(MD5)计算出摘要,CA机构自己的私钥对摘要进行加密形成数字签名一并发给服务器。

  3. 服务器在与客户端通信的时候,就会将数字证书和数字签名出示给客户端。客户端也能拿到浏览器内置的CA机构的公钥。

  4. 客户端使用CA公钥解密数字签名得到摘要,再通过 数字证书 + hash算法计算出摘要。如果两个摘要一致,那就证书有效,否则证书被篡改。因为CA机构的私钥坏人得不到。

  5. 客户端生成对称密钥,用从证书拿到服务器的 公钥 加密后发给服务器,服务器用 私钥 解密拿到密钥。因为客户端拿到的肯定是服务器正确的公钥,所以没有方案三存在的问题。

  6. 密钥协商完成,对称加密传输数据。

数组解构赋值交换元素

等号的左右两边模式相同,就会将右边的值赋给左边的变量。

[array[index1],array[index2]] = [array[index2],array[index1]];

JSBridge 原理

  • JavaScript 调用 Native 推荐使用 注入 API 的方式。通过 WebView 提供的接口,Native 向 JavaScript 的 Context(window)中注入对象或者方法,让 JavaScript 调用时,直接执行相应的 Native 代码逻辑,达到 JavaScript 调用 Native 的目的。
  • Native 调用 JavaScript 则直接执行拼接好的 JavaScript 代码即可。
//js具体实现:
(function () {
    var id = 0,
        callbacks = {},
        registerFuncs = {};

    window.JSBridge = {
        // 调用 Native
        invoke: function(bridgeName, callback, data) {
            // 判断环境,获取不同的 nativeBridge
            var thisId = id ++; // 获取唯一 id
            callbacks[thisId] = callback; // 存储 Callback
            nativeBridge.postMessage({
                bridgeName: bridgeName,
                data: data || {},
                callbackId: thisId // 传到 Native 端
            });
        },
        receiveMessage: function(msg) {
            var bridgeName = msg.bridgeName,
                data = msg.data || {},
                callbackId = msg.callbackId, // Native 将 callbackId 原封不动传回
                responstId = msg.responstId;
            // 具体逻辑
            // bridgeName 和 callbackId 不会同时存在
            if (callbackId) {
                if (callbacks[callbackId]) { // 找到相应句柄
                    callbacks[callbackId](msg.data); // 执行调用
                }
            } elseif (bridgeName) {
                if (registerFuncs[bridgeName]) { // 通过 bridgeName 找到句柄
                    var ret = {},
                        flag = false;
                    registerFuncs[bridgeName].forEach(function(callback) => {
                        callback(data, function(r) {
                            flag = true;
                            ret = Object.assign(ret, r);
                        });
                    });
                    if (flag) {
                        nativeBridge.postMessage({ // 回调 Native
                            responstId: responstId,
                            ret: ret
                        });
                    }
                }
            }
        },
        register: function(bridgeName, callback) {
            if (!registerFuncs[bridgeName])  {
                registerFuncs[bridgeName] = [];
            }
            registerFuncs[bridgeName].push(callback); // 存储回调
        }
    };
})();
//作者:嫖桑 https://juejin.cn/post/6844903585268891662

语音播报

function  voiceAnnouncements(str){
//百度
    var  url = "http://tts.baidu.com/text2audio?lan=zh&ie=UTF-8&text=" + encodeURI(str); // baidu
    var  n = new  Audio(url);
    n.src = url;
    n.play();
}
voiceAnnouncements('你好,今天吃的什么?')

静态作用域 和 闭包

  • 因为JavaScript是静态作用域的,所以它内部环境中需要的变量在编译时就确定了,运行时不会改变;
  • 又因为JavaScript中,函数是一等公民,可以被调用,可以作为参数传递,可以赋值给变量,也可以作为函数返回值,所以它的运行时环境很容易变化;
  • 当函数作为另一个函数(外层函数)的返回值返回时,其外层函数中的变量已经从调用栈弹出,但是我们必须让内部函数可以访问到它需要的变量,因此运行时的环境和定义时的作用域之间就产生矛盾;
  • 所以我们把内部环境中需要的变量,打包交给内层函数(闭包函数),它就可以随时访问这些变量了,就形成了闭包。

跨域问题

发表于 2018-07-25 | 分类于 web

什么是跨域

作为前端开发,尤其当下前后端分离越来越普遍,跨域成为我们工作中经常会遇到的问题。

跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。所谓的同源是指 域名(包括二级域名)、协议、端口均相同。

localhost 调用 127.0.0.1 也属于跨域。

同源策略限制了以下行为:

  • 无法读取 Cookie、LocalStorage 、SessionStorage 和 IndexDB
  • 无法获取 DOM 和 JS 对象
  • 无法发送 Ajax 请求

常见跨域场景主有二种:

  • 场景一:嵌套第三方页面,并互相通信。
  • 场景二:跨域Ajax请求。

场景一主要涉及的前端技术有:

  • Iframe…
  • PostMessage
  • 代理服务(Nginx)

场景二主要涉及的前端技术有:

  • JSONP
  • 跨域资源共享(Cors)
  • 代理服务(Nginx)

接下来,我们分别探讨一下。

Iframe

Iframe 的应用一直非常广泛,包括当下。用于在你的页面开辟一个子窗体嵌套展示其他页面。主要解决页面级别的复用问题。很多时候,你不仅需要展示子页面,还需要和子页面沟通。由于Iframe同样受同源策略限制,跨域问题就随之而来。

曾经我们主要通过以下2种方式解决iframe跨域问题:

  • 设置 domain 解决主域名相同,二级域名不同导致的跨域阻碍。
  • 通过一个同域的中间页。

当下,我们主要通过接下来要介绍的 PostMessage来解决跨域窗口通信问题,以上方式就不具体赘述。

PostMessage

window.postMessage是一个安全的、基于事件的消息API。它允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文档、多窗口、跨域消息传递。IE8+,chrome,firefox等都已经支持。同源窗口通信也可以使用PostMessage,逻辑比较清晰。

PostMessage实现通信的方式也很简单,分为两步:

  • 发送消息
  • 接受消息

发送消息

在需要发送消息的源窗口调用targetWindow.postMessage(message,targetOrigin)方法即可发送消息:

targetWindow

targetWindow是对目标窗体的引用。获得该引用的方法包括:

  • Window.open JS打开的窗体
  • Window.opener 打开当前窗体的窗体
  • HTMLIFrameElement.contentWindow iframe窗体
  • Window.parent 当前窗体的父窗体
  • Window.frames[index] 当前窗体的 iframe

message 参数

  • 作用:要传递的数据。
  • 类型:可以是JS的任意基本类型或可复制的对象。然而部分浏览器只能处理字符串参数,保险起见,推荐使用JSON.stringify()方法对数据序列化。

targetOrigin 参数:

  • 类型:string
  • 作用:为了安全考虑,指明目标窗口的源,协议+域名+端口号[+path],path会被忽略,所以可以不写。当然如果愿意也可以设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话可设置为"/"。

只有当目标窗口的源与postMessage函数中传入的源参数值匹配时,才能接收到消息。

举个栗子:

// http://www.domainA.com
// 发送消息
var iframe = document.getElementById('iframe')
iframe.contentWindow.postMessage('hi', 'http://www.domainB.com')

接收消息

目标窗体通过监听window的message事件就可以接收任何窗口传递来的消息了。

message事件的event对象有三个属性,分别是:

  • event.data 表示接收到的消息;
  • event.origin 表示postMessage的发送来源,包括协议,域名和端口;
  • event.source 表示发送消息的窗口对象的引用。我们可以用这个引用来建立两个不同来源的窗口之间的双向通信。

举个栗子:

// http://www.domainB.com
// 接受消息
function receiveMsg(event) {
  // 打印消息
  console.log(event.data)
  // 双向通信
  event.source.postMessage('hello', 'http://www.domainA.com')
  // 如果是父窗口 也可以这么沟通
  window.parent.postMessage('hello', 'http://www.domainA.com')
}
// 监听message事件
if (window.addEventListener) {
  window.addEventListener('message', receiveMsg, false);
}else {
  window.attachEvent('message', receiveMsg);
}

JSONP

曾经主流的跨域Ajax请求的主要方式,虽然当下已经有点过时,但还是值得了解以下的。它的原理比较有趣,算是一种“投机取巧”的设计模式吧。

  • 原理:利用html页面允许通过相应的标签从不同域名下加载静态资源文件是被浏览器允许的特点,动态的创建script标签,再去请求一个带参(包含一个回调方法作为参数)url来实现跨域通信。
  • 缺点:只能够实现 get 请求。
//原生实现方式
let script = document.createElement('script');

script.src = 'http://www.nealyang.cn/login?username=Nealyang&callback=callback';

document.body.appendChild(script);

function callback(res) {
  console.log(res);
}

跨域资源共享(Cors)


CORS 是目前主流的跨域 Ajax 请求解决方案。

  • 是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。
  • 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
  • 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
  • 整个CORS通信过程,都是浏览器自动完成,不需要用户参与。
  • 实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多一次附加的请求,但用户不会有感觉。

要理解Cors 需要搞清楚以下几个概念:

简单请求

简单请求需要满足以下几点:

  • 请求方式为 HEAD、POST 或者 GET
  • http头信息不超出以下原始字段:Accept、Accept-Language 、 Content-Language、 Last-Event-ID、 Content-Type。
  • Content-Type限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain

非简单请求

简单请求之外的其他请求。非简单请求在正式通信之前,浏览器会先发送OPTION请求,进行预检,这一次的请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。

withCredentials 属性

CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。另一方面,开发者必须在AJAX请求中打开withCredentials属性。

代理服务(如 Nginx)

  • 特殊,个别,紧急情况下,可以考虑通过代理服务(如Nginx)配置url地址映射解决跨域等问题。
  • 一般由运维/后端人员负责配置。
  • 不用发布代码,高效,非常有用。

参考资料

详解跨域
阮一峰-Cors详解
Cors常见问题

CSS碎片

发表于 2018-06-28

单行/多行文本溢出省略号


/* 单行 */
.oneLine {
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap
}

/* 👇 仅适用于WebKit浏览器或移动端的页面 */

.twoLine {
  /* 必须结合的属性 ,将对象作为弹性伸缩盒子模型显示 */
  display: -webkit-box; 
  /* 必须结合的属性 ,设置或检索伸缩盒对象的子元素的排列方式 */
  -webkit-box-orient: vertical; 
  /* 行数*/
  -webkit-line-clamp: 2; 
  overflow: hidden;
}

毛玻璃模糊遮罩效果


/* 
filter: CSS属性将模糊或颜色偏移等图形效果应用于元素。
blur(radius):函数将高斯模糊应用于输入图像。
  radius 定义了高斯函数的标准偏差值,或者屏幕上有多少像素相互融合.
  因此,较大的值将产生更多的模糊。若没有设置值,默认为0。
  该参数可以指定为 CSS 长度,但不接受百分比值。
*/
.Mask {
  filter: blue(2px);
}

position:sticky 实现粘性布局的坑


设定了 position:sticky 的元素表现为 relative 还是 fixed 是根据元素是否达到设定了的阈值(top, left,...)决定的。

/* 下面元素当滚动到viewPort顶部以后吸顶*/
.Box {
  top:0;
  position: sticky;
}
  • 问题:粘性布局突然时效。
  • 原因:其他组件CSS 给页面顶层元素增加 overflow:hidden; 导致。设定为 position:sticky 元素的任意父节点的 overflow 属性必须是 visible,否则 position:sticky 不会生效。
  • 参考文档

position:sticky 的生效的其他限制:

  • 须指定 top, right, bottom 或 left 四个阈值其中之一,才可使粘性定位生效。否则其行为与相对定位相同。
  • 并且 top 和 bottom 同时设置时,top 生效的优先级高,left 和 right 同时设置时,left 的优先级高。

隐藏滚动元素的滚动条


  • 背景:显示原生滚动条太丑,自定义滚动条交互样式。
  • 方法:滚动元素包装一个父元素,设置其高度为 滚动元素高度 - 滚动条高度(以X轴为例)。
/* 滚动元素 */
.scrollDiv {
  height: 5rem;
  overflow-x: auto;
}
/* 滚动元素父元素*/
.scrollDivContainer {
  overflow: hidden;
  height: 4.6rem;
}

方法2: 增加一个同级 Div 或使用父元素的 伪类 元素;通过 绝对定位 + 同色背景 遮住滚动条。

css 新特性 contain

CSS contain 属性允许开发者声明当前元素和它的内容尽可能的独立于 DOM 树的其他部分。这使得浏览器在重新计算布局、样式、绘图、大小或这四项的组合时,只影响到有限的 DOM 区域,而不是整个页面,可以有效改善性能。

使用参考: CSS新特性contain,控制页面的重绘与重排

参考文档


Moz-filter滤镜

web碎片

发表于 2018-06-25 | 分类于 web

本地种cookie

document.cookie = "name=value;path=path;domain=domain"


微信URl自带参数

原来微信会在原来的网页网址后面加上一个参数,朋友圈、好友分享和微信群的参数各不同:
朋友圈 from=timeline&isappinstalled=0
微信群 from=groupmessage&isappinstalled=0
好友分享 from=singlemessage&isappinstalled=0


滚动区域css背景设置

滚动区域设置background-color,不可见区域没有生效。
办法是加float: left


object[key]

eval(“myValue = myObject.” + myKey + “;”);
可以直接写成
myValue = myObject[myKey];


纯函数

一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数。


深拷贝

  • Object.assign({},obj) 只深拷贝第一层!!!!
  • JSON.parse(JSON.stringfy(obj)) 完全深拷贝

因为在序列化JavaScript对象时,所有函数和原型成员会被有意忽略。用于拷贝的对象中只能是Number, String, Boolean, Array, 扁平对象,即那些能够被 JSON 直接表示的数据结构。JSON.parse(JSON.stringfy(obj)) 缺陷:

  • 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式,而不是对象的形式。
  • 如果obj里有RegExp(正则表达式的缩写)、Error对象,则序列化的结果将只得到空对象;
  • 如果obj里有函数,undefined,file数据流,则序列化的结果会把函数或 undefined丢失;
  • 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null;
  • 如果对象中存在循环引用的情况也无法正确实现深拷贝;

参考文档JSON.stringify深拷贝的缺点


获取元素位置的快速方法

使用getBoundingClientRect()方法。它返回一个对象,其中包含了left、right、top、bottom四个属性,分别对应了该元素的左上角和右下角相对于浏览器窗口(viewport)左上角的距离。
所以,网页元素的相对位置就是:

  var X= this.getBoundingClientRect().left;
  var Y =this.getBoundingClientRect().top;

再加上滚动距离,就可以得到绝对位置

  var X= this.getBoundingClientRect().left + document.documentElement.scrollLeft;
  var Y =this.getBoundingClientRect().top + document.documentElement.scrollTop;

IOS Universal Links

Universal Links是iOS9推出的一项功能,使你的应用可以通过传统的HTTPS链接来启动APP(如果iOS设备上已经安装了你的app,不管在微信里还是在哪里), 或者打开网页(iOS设备上没有安装你的app)。官网配置如下:
Adding support for universal links is easy. There are three steps you need to take:

  • Create an apple-app-site-association file that contains JSON data about the URLs that your app can handle.

  • Upload the apple-app-site-association file to your HTTPS web server. You can place the file at the root of your server or in the .well-known subdirectory.

  • Prepare your app to handle universal links.

const 定义的数据也可变

对象这种引用类型可以:

// allowed
const helloWorld = {
text: 'Welcome to the Road to learn React'
};
helloWorld.text = 'Bye Bye React';

箭头函数 this

一个普通的函数表达式总会定义它自己的 this 对象。this指向函数被调用时候的作用域。
但是箭头函数表达式仍然会使用包含它的语境下的 this 对象。this指向函数被定义时候的作用域。

ES6 自动属性名

// ES5
var user = {
name: 'Robin',
};
// ES6
const key = 'name';
const user = {
[key]: 'Robin',
};

import export

导入全部变量:

//file1
const firstname = 'robin';
const lastname = 'wieruch';
export { firstname, lastname };
// file2
import * as person from './file1.js';

在导入 default 输出时省略花括号。default 语句,可以被用在一些使用情况下:

  • 为了导出和导入单一功能
  • 为了强调一个模块输出 API 中的主要功能
  • 这样可以向后兼容 ES5只有一个导出物的功能
//file1
const firstname = 'robin';
const lastname = 'wieruch';
const person = {
firstname,
lastname,
};
export {
firstname,
lastname,
};
export default person;
// file2
import developer, { firstname as fname, lastname } from './file1.js';

chrome 浏览器截取页面

  • 首先按下 ⌘Command + ⌥Option + I(Windows 为 F12)快捷键,召唤出调试界面。
  • 按下 ⌘Command + ⇧Shift + P(Windows 为 Ctrl + Shift + P),输入命令 Capture full size screenshot(只输前几个字母就能找到),敲下回车,Chrome 就会自动截取整个网页内容并保存至本地。

axios fetch 区别

Axios是对XMLHttpRequest的封装,而Fetch是一种新的获取资源的接口方式,并不是对XMLHttpRequest的封装。
Fetch唯一碾压Axios的一点就是现代浏览器的原生支持,而Axios需要引入Axios库。他们都基于promise。

为什么 Element.getBoundingClientRect() 可能引发重绘/回流?

浏览器都会优化重绘和回流的操作。浏览器会把所有会引起回流、重绘的操作放入1个队列中,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。另外,当我们取一些属性值时,类似offsetWidth、clientWidth、width等,只为了取到正确的值,浏览器可能提前执行 flush 队列,即便是队列里的操作不影响所取的值。

hexo + prism 代码高亮

发表于 2018-05-06

为什么选择 prism

我使用 hexo + next 搭建的个人博客, NexT 使用 Tomorrow Theme 作为代码高亮, 效果实在差强人意,并存在明显的错误。尤其对jsx的支持很差。在网上去找其他替代方案的时候发现了prism。效果提升明显。

参考文档

prism官网
prism配置介绍

注意,项目重新npm install的话,高亮会丢失,你需要重新配置上述文档中的最后一步。

React碎片

发表于 2018-04-07 | 分类于 React

react 受控组件

表单元素比如 ,