Giter Site home page Giter Site logo

blog's People

Contributors

shuch avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar

blog's Issues

how class & decorator work

es6 新增class关键字

class Cat {
    say() {
        console.log("meow ~")
    }
}

利用es5 function 实现:

function Cat() {}

Object.defineProperty(Cat.prototype, 'say', {
    value: function() { console.log("meow ~"); },
    enumerable: false,
    configurable: true,
    writable: true
})

实际上当我们给一个类添加一个属性的时候,会调用到 Object.defineProperty 这个方法,它会接受三个参数:target 、name 和 descriptor ,上面两种写法等价

es7 新增装饰者decorator

function isAnimal(target) {
    target.isAnimal = true
    return target
}

// 装饰器
@isAnimal
class Cat {
    // ...
}
console.log(Cat.isAnimal)    // true

// 上面装饰器代码基本等同于
Cat = isAnimal(function Cat() { ... })

总结

  • decorator优势
    • 将嵌套函数的写法变成平级,代码更加可读
    • 代码没有侵入性,降低修改代码成本

参考:
Decorator 浅析与实践

Taro 微信小程序接入echart 折线图柱状图踩坑记录

经历过H5接入echart 的需求后,同时小程序端使用taro开发,本以为小程序接入图表不是很难的事,结果硬生生躺了一次坑,上线之后决定总结一次,记录开发期间遇到的各种问题以及解决办法,方便自己后续迭代以及后来者避开风险,只有在面临bug和上线的一番对抗和内心挣扎之后,才会理解经验是多么宝贵。

本地下载 ec-canvas 文件过大(超过500k),微信开发者工具无法编译上传的问题

按照官网的指示,下载 ec-canvas 包引入工程目录,github版本的 echart.js 文件大小为 700k,虽然本地可以成功引入并绘制,但最后一步上传却让人大失所望,超过微信单个文件大小限制500k。

此时官网按照指示,在线定制 echart.js 内容

定制好后,成功将文件大小控制在363k,导入工程目录替换原文件。然而经过taro编译压缩过后,原来的图表变成一片空白,内心崩溃。

经过几番折腾过后,终于找到了解决办法:
在官网定制时不能勾选代码压缩,下载源文件后去专业的压缩网站压缩代码,再导入工程目录

此时上传问题迎刃而解。出现这个问题的原因在于,taro在编译 echart.js 时会将注释的部分内容进行编译导致代码报错,报错截图为证:

A2BCA4F2-7DAA-46f9-8C20-CE43403C7135

2020.4.11更新
echart官网在线定制源文件,目前已经移除license注释信息

微信原生 canvas 层级最高问题,遮挡底部悬浮菜单和弹窗

在经过一番挣扎过后,终于可以画出满意的折线图了,微信开发者工具中一切风平浪静,上传到体验版后,傻眼了,弹窗和底部悬浮button都被图标盖住了,这下改动就大了。于是傻乎乎的把底部button和弹窗改成了 cover-view,再到真机上,渲染一个列表就卡的不行,更不谈样式的各种不兼容。看来cover-view 虽然强大可也不能滥用啊!

经过一番思索,决定在渲染完成后,启用 echart 的保存图片功能,将绘制完成的 canvas 保存成图片。

const ecComponent = this.$scope.selectComponent('#deal-chart-dom-area');
if (!ecComponent) {
  return;
}

ecComponent.canvasToTempFilePath({
  success: res => {
    this.setState({ img: res.tempFilePath });
  },
  fail: res => console.error('canvasToTempFilePath', res)
});

可问题是,图片虽然可以解决层级问题,但失去了图表的可交互性,无法两全其美,如果能在初始状态echart渲染图表,渲染完成后将canvas转换成图片保存下来,在有弹框时将 canvas 切换成图片,就可以解决这个问题。实际效果看上去还可以接受,剩下的问题就是控制图片和图表的切换,工作量应该会大幅减少。

安卓真机上无法保存图片的问题

生产环境中,在部分安卓手机上保存图片会失败,查找原因,将下部分代码

ctx.draw(true, () => {
    wx.canvasToTempFilePath(opt, this);
});

加100ms延时,改成

ctx.draw(true, setTimeout(() => {
   wx.canvasToTempFilePath(opt, this);
}, 100));

重新编译,图片就能正常保存啦!

总结来看,微信小程序做图表不是一个很好的方式,从长期看,建议使用h5承载图表业务,代码的健壮性和扩展性能更加可控。

以上是开发过程的不完全记录,也是个人的解决办法,可能有不妥之处,如果有更好的微信图表方案,欢迎留言探讨!

Node.js错误处理

同步代码异常

try {
  throw new Error('throw error');
} catch (e) {
  console.log(e);
}

异步代码异常

try {
  setTimeout(() => {
     throw new Error('throw error');
  }, 1000);
} catch (e) {
  console.log(e);
}

异步代码异常用try/catch捕获不了,因为event loop,异步函数在事件队列中,执行是在调用栈顶部,脱离了定义的上下文,如果抛出异常,无法追溯到异步任务的创建者。

callback处理异常

回调函数第一个参数是error对象,如果没有错误就是null

fs.readFile('noexist', (err, data) => {
  if (err) throw err;
  console.log(data);
})

event捕获异常

const EventEmitter = require('event')
const myEvent = new EventEmitter();

myEvent.on('error', function (e) {
  console.log('error event fired', e);
})

myEvent.emit('error', new Event('error from event'))

promise捕获异常

new Promise((resolve) => {
  throw new Error('123');
}).catch(e => {
  console.log(e);
})

process捕获异常

process.on('uncaughtException', (e) => {
  console.log('catch exception',e );
})

setTimeout(() => {
  throw new Error('124')
}, 1000);

domain 捕获异常

const domain = require('domain');

const d = domain.create();

d.on('error', (e) => {
  console.log('domain error', e);
})

d.run(() => {
  setTimeout(() => {
    throw new Error('124');
  }, 1000);
});

d.run(() => {
  throw new Error('123');
})

总结

  • 异常分为同步和异步,同步使用try/catch
  • 异步可以用callbackpromiseevent
  • event包含原生EventEmitter、全局processdomain方式

参考

移动端适配

rem方案

动态计算font-size,计算公式:

375 / 16 = deviceWidth / fontSize

document.documentElement.style.fontSize = (document.body.clientWidth / 375) * 16 + 'px';

按750设计稿,基础字体大小 16px计算

viewport 单位替换px

375/100=3.75=1vw

JIT编译器和AOT编译器理解

编译器特点

jit是Just-in-Time 及时编译的缩写,特点是程序执行时进行编译。
aot是 Ahead-of-Time 提前编译的缩写,特点是程序执行前进行编译。

JavaScript 的 map 与 flatMap

JavaScript 的 map 与 flatMap

map

let arr = ['hello','world'];
arr.map(s => s.split(''))

map 得到的数组深度加一

default

flatMap

arr.flatMap(s => s.split(''))

flatMap 得到的 depth 数组深度是1,被压平一层

2018-11-27 9 23 40

flat

const arr = [1, [2], 3, [4, [5]]];
arr.flat()

得到的数组 depth 深度是2

2018-11-28 8 44 39

chrome dev tools 使用记录

profromance 使用

  • 蓝色:HTML解析和网络通信
  • 黄色:JavaScript语句执行占用时间
  • 紫色:重排(页面布局)占用时间
  • 绿色:重绘(视觉属性)占用时间

前端异常监控

错误处理

  • 同步代码使用 try/catch,在catch块中上报错误信息
  • 异步代码使用回调的方式,或promise.catch捕获错误
  • 全局捕获 window.onerror

全局捕获

window.onerror = function(message, source, lineno, colno, error) {
  // 错误信息
  console.log('message', message);
  // 发生错误脚本URL
  console.log('source', source);
  // 错误行号
  console.log('lineno', lineno);
  // 错误列号
  console.log('colno', colno);
  // 错误堆栈信息
  console.log('error', error);
  // 阻止错误打印
  return true;
}

throw new Error(123);

异步异常捕获

promise代码抛出的错误,无法在window.errortry/catch中捕获,可以添加promise全局异常捕获:

window.addEventListener('unhandledrejection', function(e) {
  // 错误信息
  console.log('reason', e.reason);
  e.preventDefault();
});

错误上报

function report(message, source, lineno, colno, error) {
  var url = 'https://report.net/error';
  url += '?msg='+message+'&source='+source+'&row='+lineno+'&col='+colno+'&err='+error;
  var img = new Image();
  img.url = url;
}

定位错误

线上代码一般都有压缩,map文件存贮着源文件和压缩文件的映射关系,可以定位到源文件的出错位置。

  • 开启source-map
  • bundle.js.map文件发送到监控系统保存
  • 记录线上错误,上报错误信息
  • 通过source-map查出源文件的报错位置

nodejs 版本升级

nodejs 版本升级

1.安装 n 模块
npm install -g n

2.安装稳定版本
n stable

3.安装最新版本
n latest

4.安装具体版本
n v12.4.0

延迟加载清单

图片延迟加载

  • IntersectionObserver
const observer = new IntersectionObserver((entries) => {
  const image = entries[0];
  if (image.isIntersecting) {
      setLoading(false);
      observer.unobserve(ref.current);
  }
});

observer.observe(ref.current);
  • getBoundingClientRect
function elementInViewport(el) {
  var rect = el.getBoundingClientRect()
  return (
     rect.top >= 0
     && rect.left >= 0
     && rect.top <= (window.innerHeight || document.documentElement.clientHeight)
 )
}

JavaScript延迟加载

  • react中的suspense
  • ES6动态 import()
const ProfilePage = React.lazy(() => import('./ProfilePage')); // 懒加载

// 在 ProfilePage 组件处于加载阶段时显示一个 spinner
<Suspense fallback={<Spinner />}>
  <ProfilePage />
</Suspense>

字体延迟加载

font-display

@font-face {
  font-family: 'MyWebFont'; /* Define the custom font name */
  src:  url('myfont.woff2') format('woff2'),
        url('myfont.woff') format('woff'); /* Define where the font can be downloaded */
  font-display: fallback; /* Define how the browser behaves during download */
}

参考

webpack: hot module replacement 实践

hot module replacement

在开发模式下,使用代码热替换可以提高开发效率,使用 HMR 过程的总结如下:

webpack 提供两种模式的使用方式:

webpack-dev-server 模式

  1. 新建客户端文件src/index.js
import './style.css';
import img from './cat.jpg';

const cat = new Image();
cat.src = '../' + img;

document.body.appendChild(cat);

底部添加 HMR客户端

if (module.hot) {
  module.hot.accept();
}

src/index.html引入打包好的文件

<script src="bundle.js"></script>

因为webpack-dev-server将文件打包到内存,引入时需要访问项目根路径/

  1. 服务端修改 webpack.config.js
devServer: {
  contentBase: './src',// 访问http://localhost:3000/到src/index.html
  port: 3000,
}
plugins: [
  new webpack.HotModuleReplacementPlugin(),
],
  • 启动 npx webpack-dev-server,访问http://localhost:3000/
  • 改动index.js文件,控制台即可看到效果
[HMR] Waiting for update signal from WDS...
[WDS] Hot Module Replacement enabled.
[WDS] Live Reloading enabled.

webpack-hot-middleware 模式

配合express 插件模式,开启HMR

  1. 配置 webpack.config.js
entry: {
  app: ['./src/index.js', 'webpack-hot-middleware/client'],
},
plugins: [
  new webpack.HotModuleReplacementPlugin(),
],
  1. 服务端添加 webpack-hot-middleware
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');

const app = express();
const config = require('../webpack.dev.config');
const compiler = webpack(config);

app.use(webpackDevMiddleware(compiler));
app.use(webpackHotMiddleware(compiler));
  1. 客户端index.js 添加 hot module replacement 浏览器端入口,跟webpack-dev-server 模式相同。
if (module.hot) {
  module.hot.accept();
}

配置完成后,启动nodejs服务,改动客户端index.js代码,即可发现浏览器更新了

[HMR] connected
[HMR] bundle rebuilding
[HMR] bundle rebuilt in 109ms
[HMR] Checking for updates on the server...
[HMR] Updated modules:
[HMR]  - ./src/print.js
[HMR] App is up to date.

比较两种模式的异同

  • webpack-dev-server 中使用 websocket 在客户端和浏览器端建立长链接,实现代码同步,自身充当web服务器角色。
  • webpack-hot-middleware 使用 server-sent events 事件流的方式拉取服务服务端最新的代码,通过在node服务器中引入中间件,实现HMR

参考

JavaScript 模块化演进

模块化的出现是为了代码复用,隔离作用域

IIFE

Immediately Invoked Function Expression立即执行函数表达式

(function() {
  var x = 1;
  var y = 2;
  consoe.log(x + y);
})()

在自执行函数中编写代码,隔离外部环境

AMD

Asynchronous Module Definition异步模块定义

// 定义 math.js
define(function() {
  var add = function(x, y) {
     return x + y;
  }
  return {
     add,
  };
})
// 引入
require(['math'], funtion(math) {
  console.log(math.add(1, 2));
})

推崇依赖前置,实现有require.jscurl.js

CMD

Common Module Definition通用模块定义

define(function(require, exports) {

  // 获取模块 math 的接口
  var a = require('./math');

  // 调用模块 math 的方法
  a.add(1, 2);

});

推崇依赖后置,在使用的地方requresea.js实现

commonjs

nodejs服务端模块规范

var math = require('math');
math.add(1, 2);

模块运行时同步加载,引入对象的拷贝,有缓存。

UMD

Universal Module Definition通用模块定义,兼容以上amd,cmd,commonjs三种规范

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        //Node, CommonJS之类的
        module.exports = factory(require('jquery'));
    } else {
        //浏览器全局变量(root 即 window)
        root.returnExports = factory(root.jQuery);
    }
}(this, function ($) {
    //方法
    function myFunc(){};
    //暴露公共方法
    return myFunc;
}));

webpack require ensure

webpack 2.x中的代码分割方法,通过jsonp的形式异步加载代码。目前被es module动态import()取代。

ES Module

ES2015引入的模块化规范。

import math from './math.js';
math.add(1, 2);

浏览器支持script标签运行es module

<script type="module"></script>

属于编译时静态引入,输出的是模块的引用地址,且引用是只读的。

参考

介绍模块化发展历程

浏览器渲染原理浅析

浏览器多线程

浏览器是多线程的

  • GUI 渲染线程
  • JavaScript 引擎线程
  • 定时器线程
  • 事件线程
  • 异步请求线程

为了防止渲染出现不可预期的结果(js可能修改domcss),GUI 渲染线程和JavaScript引擎线程互斥。

share

渲染线程

  • 解析 HTML 构建DOMDom Tree
  • 解析 CSS 构建 CSS 规则树 CSS Rule Tree
  • CSS规则树附加到 DOM树上,删除不可见元素,形成渲染树 Render Tree
  • 回流(ReflowLayout):计算每个元素盒子位置和大小
  • 重绘(Repaint):根据元素的位置和大小,计算元素的绝对像素
  • 渲染(Display):将绝对像素发给GPU,将多个合成层合并,形成一个层。

68747470733a2f2f692e696d6775722e636f6d2f64384b785a53772e706e67

元素新增和删除以及位置或大小改变会触发节点的回流(Reflow),之所以叫回流是因为浏览器默认的布局方式是流式布局。参考回流和重绘

优化方式

  • 尽量避免js操作dom
  • style 样式改变通过class而不是手动修改
  • 开启GPU加速渲染will-changetranslateZ
  • documentFragment离线操作 dom
  • 耗时操作放在web worker中完成

Render-Process-Skipping

script defer和async

  • async 属性会异步下载script,下载后立即执行,下载完成前不影响html解析。
  • defer 属性会异步下载script,下载后延迟执行,等到html解析完成,执行完成后触发DOMContentLoaded 事件
  • async是无序的,而 defer有序

参考

你真的了解回流和重绘吗

JavaScript Event Loop

JavaScript中的事件循环

浏览器中的 event loop

先看一个图:

v2-64476c110e4efcd85df76dc49b510abb_r

JavaScript引擎,简称为引擎,执行过程包含两种:同步过程和异步过程

同步过程:

  • 引擎执行一个方法,会把这个方法的执行环境推入到执行栈(stack)中,然后进入执行环境,执行其中的代码。
  • 当执行环境环境的代码执行完毕,返回结果后,引擎会退出当前执行环境并销毁,回到上一个方法的执行环境。直到执行栈中的代码全部执行完毕。

所谓JavaScript单线程语言,指的是只有一个执行栈,采用FILO先进后出的顺序执行。

异步过程:

  • 引擎遇到一个异步事件后,会将这个事件挂起,并在 event table 中注册回调函数,继续执行执行栈中的其他任务(WebAPIs)。
  • 当一个异步事件返回结果后,引擎会将回调函数加入到事件队列event queue中,也叫做callback queue
  • 等执行栈stack中当前宏任务执行完毕,主线程会查找event queue中的事件,并将其回调依次加入到执行栈,异步任务执行。

从第一个同步任务开始执行到第一个异步任务执行完毕的过程,称为一次事件循环 event loop

任务分类

  • 任务task包含任务队列task queue和微任务队列microtask queue参考

  • 在一个事件循环中,异步任务返回的结果会被放入到一个任务队列中。根据异步事件的类型,这个事件会被推到对应的任务队列或者微任务队列中。在当前执行栈为空的时候,主线程会查看微任务队列是否有事件存在。

  • 如果存在,则会依次执行微任务队列中的事件对应的回调,直到微任务队列为空。然后去Task Queue中取出一个事件,把对应的回调推入执行站。

  • 如果不存在,再去Task Queue中取出一个事件,并把它对应的回调推入到当前执行栈。

task queue:setInterval, setTimeout, setImmediate
microtask queue:promise.then(), process.nextTick(), new MutaionOberserver()

结论:
只有执行栈(stack)执行完毕,才去处理微任务队列中的事件,然后再去task queue中取出一个事件。同一次事件循环,microtask queuetask queue之前执行。

node 中的event loop

ma(i)crotask-in-node

node 中的 event looplibuv 实现(源码),不同的任务会分配给不同的线程。总共分为6个阶段,不同阶段对应执行不同的task queue,每个阶段task queue执行完后再去执行微任务队列中的任务。

  • timer 阶段:执行setTimeoutsetInterval 回调函数
  • i/o callbacks 阶段:执行fssocket 回调函数
  • check 阶段:执行setImmedate 回调函数

process.nextTick是独立于任务队列之外的任务,优先于微任务执行.参考

例子1

setTimeout(function () {
    console.log(1);
});

new Promise(function(resolve,reject){
    console.log(2)
    resolve(3)
}).then(function(val){
    console.log(val);
})

打印结果:2 3 1

例子2

process.nextTick(() => {
  console.log('nextTick')
})
Promise.resolve()
  .then(() => {
    console.log('then')
  })
setImmediate(() => {
  console.log('setImmediate')
})
console.log('end')

打印结果

end
nextTick
then
setImmediate

总结

With the new changes in Node v11, nextTick callbacks and microtasks will run between each individual setTimeout and setImmediate callbacks, even if the timers queue or the immediates queue is not empty

  • 浏览器中执行完一个宏任务就去执行微任务队列
  • node 11以前,每个阶段都有一个宏任务队列,执行完宏任务就去执行微任务。
  • node 11之后,为了跟浏览器保持一致,变成了执行宏任务后就去执行微任务队列。

参考

浅谈Web应用的身份验证

Web应用的身份验证

1. 基于 cookiesession 的身份验证

20181218143040425

过程:

  • 客户端发起登录请求,服务端生成该用户的 session,并放在 Set-Cookie 响应头中。
  • 客户端设置 cookie,在下一次请求头中带上 cookie
  • 服务端收到 cookie 后验证用户的合法性和有效性。

基于 cookie session 认证的特点是服务端维护用户信息,如果一个应用存在多个服务,根据跨域原则,实现单点登录就需要在多个服务器之间共享用户信息。另外一种是基于 token 的身份验证方式。

2. 基于 token 的身份验证方式

2018121814305772

过程:

  • 客户端发起登录请求,服务端生成对应用户信息,并在响应中返回 token
  • 客户端将响应体中的token存储起来,在下一次请求头 Authorization 中带上 token
  • 服务端收到请求,验证 token 是否有效。

这种做法好处在于服务端不需要存储当前用户的信息,减轻服务端同步数据的压力,易于实现单点登录。缺点是在有效期之前用户退出,服务端无法判断是否有效,客户端需要销毁失效的 token,并在下一次请求时重新登录。

JWT

基于 token 的验证方式比较流行的是 JSON Web Token(JWT),下图是它的登录原理:

163a569e24bffb93

另外两种方式是 SAML2.0 OAuth 2.0 ,后面继续更新。

git push 执行 Too many open files

当执行git push时,git控制台有报错信息:

> git fetch
error: cannot create pipe for ssh: Too many open files
fatal: unable to fork

原因是git文件占用内存超出限制,手动执行垃圾回收,清理git缓存

git gc

JavaScript运行时

概念

runtime广义理解包括三个范畴

  • 程序生命周期的一个阶段,如RustC 更容易将错误发现在编译时而非运行时。
  • 原生语言的标准款库,如C语言stdio.h
  • 某门语言的宿主环境,如JavaScriptnodejs或浏览器

javacript运行时

一般指js运行的宿主环境

react-native 原理解析

js引擎

  • 原生native内置JavaScriptCore负责js代码的解析和执行
  • 2009年创建的执行引擎Hermesjs代码进行预编译(字节码),减少加载代码体积,缩短了用户交互时间。

jsnative 通信

  • js 和 native 是通过bridge来通信的
  • native 启动时将原生模块和方法注册到全局,共rn调用。
  • native 通过JavaScriptCore直接执行js代码。

Flutter 学习笔记

环境搭建

1.下载 flutter repo

2.编辑 .zshrc
mac catalina 系统,编辑 $HOME/.zshrc 文件,加入
export PATH="$PATH:/Users/shuchen/Code/flutter/bin"
运行 source .zshrc 生效

3.检查 PATH
echo $PATH
which flutter

dart 语言与JavaScript对比

  • build是flutter widget 更新时调用的方法,用于重新渲染,类似于react中的render.
  • final 声明常量,类似于js中的 const
  • future 表示一个异步返回值,相当于js中的promise,同样适用 async/await

浏览器跨域

何为跨域

浏览器同源安全策略,包括协议域名端口,只要一个不同,即为跨域。
如果没有同源策略,别人的网站可以获取自己已登录网站的cookie,数据泄漏。
要知道,浏览器可以正确做出请求,服务器也能正确做出响应,但浏览器会拒绝接受跨域服务器的响应

跨域方式

  • 跨域资源共享(CORS)
  • 同域代理
  • jsonp
  • iframe
  • img src

跨域资源共享(CORS)

Cross-origin Resource sharing,需要客户端和服务端支持,分为简单请求(GET,POST,HEAD)和非简单请求(PUT,DELETE)

简单请求
客户端请求头带 origin 字段,标识请求源

Origin: http://shuch.com

服务器根据origin字段判断是否同意请求。同意请求的响应头

Access-Control-Allow-Origin: http://shuch.im

如果允许任意来源访问,可用 * 标识

非简单请求
浏览器会先发送一个预检请求(Preflight Request),询问服务器是否支持该请求

OPTION: api.com/delete/1
Origin: http://shuch.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Content-Type

若服务器支持,则返回响应体

Access-Control-Allow-Origin: http://shuch.com
Access-Control-Allow-Methods: GET,POST,DELETE
Access-Control-Allow-Headers: Content-Type

浏览器检测响应头中包含origin,则正常发出请求

携带身份认证信息
XMLHttpRequest请求默认不发送cookie, 可以设置 withCredentials: true来允许浏览器发送cookie信息,同时服务器响应头中应包函

Access-Controll-Allow-Credentials: true

若服务器没有返回该字段,则浏览器不会把响应体给发送端。
·
该响应头和Access-Controll-Allow-Origin:*互斥,若服务器设置通配符,客户端请求失败。

参考 HTTP访问控制(CORS)

同域代理

使用ajax向服务端发出请求,带上请求路径以及参数,代理服务端接收到请求后,直接对路径和参数转发请求,由于服务端不受跨域请求限制,等接收到响应后再返回到前端,实现跨域。

JSONP跨域

相比于CORS跨域,JSONP仅支持GET请求,但浏览器兼容性强。

利用script标签的跨域特点,定义一个回调函数callback,在服务器返回json数据时,包装一层callback(json),在客户端执行。

为避免callback冲突,客户端可用时间戳设置函数名。

function jsonp() {
  return new Promise((resove, reject) => {
     var script = document.createElement('script');
     var calbackName = 'callback' + Date.now();
     script.src = req.url + '?callback=' + callbackName;
     window[calbackName] = function(json)  {
        resovel(json);
     }
     script.onerror = function() {
       reject(new Error('error' + script.src));
     }
  });
}

jsonp({
  url: 'https://api.com/get',
}).then(res => {
  console.log(res);
});

iframe跨域

  1. 将两个文档document.domain设置为相同实现跨域

  2. postMessage 通信

// 发送消息
function sendMessage() {
  const iframe = document.getElementById('iframe');
  iframe.contentWindow.postMessage({a: 1}, 'http://localhost:3001');
}

// 接受消息
window.addEventListener('message', (e) => {
  if (e.origin === "http://localhost:3000") {
    console.log('receive message', e.data);
    e.source.postMessage({
      a: 'hi 3000',
      arr: [1,2,3],
      date: new Date(),
    }, e.origin);
  }
})
  1. window.name跨域
    原理是同一个tab下设置的window.name可以共享

  2. window.location.hash跨域

// 改变`hash`
function changeHash() {
  const iframe = document.getElementById('iframe');
  iframe.src = 'http://localhost:3001' + '#' + JSON.stringify({ a: 1});
}

// 监听`hash change`
window.addEventListener('hashchange' , (e)  => {
  const hash = window.location.hash;
  const data = decodeURIComponent(hash.split('#')[1]);
  console.log('hash from parent: ', JSON.parse(data));
  window.parent.location.href = "http://localhost:3000#" + data;
})

图片跨域

利用图片的src属性可以跨域的特点,发送请求,一般用于数据埋点,性能监控
优势

  • 天然支持跨域
  • 不需要处理请求结果
  • 体积小,不阻塞页面

参考

[译]JavaScript原型继承原理

众所周知,JavaScript有原型继承性。但通常情况下,原型继承是通过new操作符实现的,大部分的解释都让人困惑,这篇文章旨在澄清什么是原型继承以及如何使用。

原型继承定义

JavaScript原型通常是这样定义的:

When accessing the properties of an object, JavaScript will traverse the prototype chain upwards until it finds a property with the requested name. Javascript Garden

JavaScript通过__proto__属性来代表原型链中下一个对象。跟着这片文章,让我们看看__proto__prototype的区别。

说明:__proto__是JavaScript非标准属性,最好不要在你的代码中用到。当然,这篇文章是用来解释JavaScript原型继承的原理。

下面的一段代码展示了JavaScript引擎是如何找一个对象的属性:

function getProperty(obj, prop) {
  if (obj.hasOwnProperty(prop))
    return obj[prop]
 
  else if (obj.__proto__ !== null)
    return getProperty(obj.__proto__, prop)
 
  else
    return undefined
}

根据原型继承的定义,我们创建一个对象 Point 它包含两个属性x,y坐标和一个print方法。
为了创建一个新的point对象,我们只需要创建一个对象,并将新的对象的__proto__属性指向Point

var Point = {
  x: 0,
  y: 0,
  print: function () { console.log(this.x, this.y); }
};
 
var p = {x: 10, y: 20, __proto__: Point};
p.print(); // 10 20

JavaScript 怪异的原型继承

奇怪的是,大家都知道原型继承的定义,但并不会这么写。反而更喜欢下面的写法:

function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype = {
  print: function () { console.log(this.x, this.y); }
};
 
var p = new Point(10, 20);
p.print(); // 10 20

这跟上面写的代码完全不同,这里的Point是一个函数,他使用了一个prototype的属性,还有new操作符,这是个什么鬼?

new 到底做了什么

Brendan Eich 在创造JavaScript 时,想让它看起来像传统的面向对象语言,类似于Java、C++。在传统的面向对象语言中,使用了 new 操作符来创建一个类的实例。所以,他也写了一个JavaScriptnew操作符:

  • C++有构造函数的概念,用来初始化对象实例的属性,因此 new 操作符的对象应该是一个函数。
  • 我们需要把对象的方法放在某个地方,由于我们正在使用原型语言,因此将其放在函数的 prototype 属性中。

new 操作符接收一个函数F和参数arguments: new F(arguments...),总共做了三件事:

  1. 创建类的实例。它是一个空对象,其__proto__属性设置为F.prototype
  2. 实例初始化。带上参数,调用这个方法F,并把this绑定到实例上。
  3. 返回实例。
     function New (f) {
/*1*/  var n = { '__proto__': f.prototype };
       return function () {
/*2*/    f.apply(n, arguments);
/*3*/    return n;
       };
     }

写一个测试用例来检验一下:

function Point(x, y) {
  this.x = x;
  this.y = y;
}
Point.prototype = {
  print: function () { console.log(this.x, this.y); }
};
 
var p1 = new Point(10, 20);
p1.print(); // 10 20
console.log(p1 instanceof Point); // true
 
var p2 = New (Point)(10, 20);
p2.print(); // 10 20
console.log(p2 instanceof Point); // true

JavaScript中真正的原型继承

JavaScript规范只给我们提供了 new 操作符,然而,Douglas Crockford 找到一种方法,利用new操作符实现了真正的原型继承。
他创建了 Object.create 方法:

Object.create = function (parent) {
  function F() {}
  F.prototype = parent;
  return new F();
};

代码看起来很奇怪,但做的事情确很简单,它创建了一个新的对象并把它的prototype属性设置为任意一个你要创建的对象。如果允许使用__proto__的话,还可以这么写:

Object.create = function (parent) {
  return { '__proto__': parent };
};

使用真正的原型继承,第一个Point例子可以改写成:

var Point = {
  x: 0,
  y: 0,
  print: function () { console.log(this.x, this.y); }
};
 
var p = Object.create(Point);
p.x = 10;
p.y = 20;
p.print(); // 10 20

结论

我们已经了解了什么是原型继承,以及实现原型继承的几种方式。
但是,使用__proto__Object.create()有一些缺点:

  • 非标准化__proto__是非标准化的属性,甚至会被废弃。原生的Object.create()方法实现方式和 Douglas Crockford 实现方式并不完全相同。
  • 性能不高:原生的或自定义的Object.create()方法跟new 不同,还没有被优化过。速度会慢上10倍。

引申阅读:

彩蛋

如果你能看懂下面这张图(来自 ECMAScript 标准),你将会有意想不到的收获!

Object/Prototype Relationships

小程序强制更新

强制更新

var updateManager  = wx.getUpdateManager();

updateManager.onCheckForUpdate(function (res) {
  // 请求完新版本信息的回调
  console.log(res.hasUpdate)
})

updateManager.onUpdateReady(function () {
  wx.showModal({
    title: '更新提示',
    content: '新版本已经准备好,是否重启应用?',
    success: function (res) {
      if (res.confirm) {
        // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
        updateManager.applyUpdate()
      }
    }
  })
})

参考

UpdateManager | 微信开放文档

单页应用上传文件

client 使用 XMLHttpRequest 和 Formdata 上传文件

upload.html

<input type="file" name="myfile" id="file">

<script type="text/javascript">
	document.getElementById('file').onchange = function (event) {
		const xhr = new XMLHttpRequest();
		var formData = new FormData();
		const file = event.target.files[0];
		formData.append("myfile", file);
		xhr.open("POST", "/profle", true);
		xhr.send(formData);
	}
</script>

server 端通过 express 提供服务,使用 multer 处理文件

server.js

const express = require('express')
const multer = require('multer')
const path = require('path')

const app = express();
const upload = multer({ dest: 'upload/' })

app.post('/profle', upload.single('myfile'), function (req, res, next) {
	// console.log(req.body);
	res.send({ret_code: '0'});
})

app.get('/upload', function(req, res, next) {
	const html = path.join(__dirname, 'upload.html');
	res.sendFile(html);
})

app.listen(3000);

运行服务,上传文件

node server.js

正常的话,upload目录目下会多出一个文件。

问题

MulterError: Unexpected field
    at wrappedFileFilter (D:\code\study\html\node_modules\multer\index.js:40:19)
    at Busboy.<anonymous> (D:\code\study\html\node_modules\multer\lib\make-middleware.js:114:7)
    at emitMany (events.js:147:13)
    at Busboy.emit (events.js:224:7)
    at Busboy.emit (D:\code\study\html\node_modules\busboy\lib\main.js:38:33)
    at PartStream.<anonymous> (D:\code\study\html\node_modules\busboy\lib\types\multipart.js:213:13)
    at emitOne (events.js:116:13)
    at PartStream.emit (events.js:211:7)
    at HeaderParser.<anonymous> (D:\code\study\html\node_modules\dicer\lib\Dicer.js:51:16)
    at emitOne (events.js:116:13)

如果遇到上传失败,检查 upload.single('myfile') 文件名是否与 formData.append("myfile", file) 文件名对应

浏览器缓存

“计算机科学只存在两个难题:缓存失效和命名。” ——Phil KarIton

缓存分类

按存储位置来分,缓存分为以下几种:

  • Service Worker
  • Memory Cache
  • Disk Cache

优先级由上到下,如果没有命中,就走HTTP Network

Service Worker

Service Worker提供一种灵活的缓存方案,允许开发者中手动存储缓存文件,以及配置缓存匹配策略。

  • install安装实例,添加缓存
  • activate激活实例,删除旧缓存
  • fetch配置缓存匹配规则

Memory Cache

浏览器缓存策略。几乎所有的网络请求,都会被加入内存缓存。主要分为preloaderpreload

  • css@import
  • <video poster />
  • <link rel="preload" />

效果是相同一个请求img srclink href不会请求两次。在页面被关闭时,缓存失效。

Disk Cache

绝大部分缓存都来自磁盘缓存,并遵循HTTP Header规则,包括强缓存和协商缓存

  • 强缓存

    • Expires(http 1.0)
    • Cache-Control(http 1.1)
  • 协商缓存

    • Last-ModifiedIf-Modified-Since
    • EtagIf-None-Match

Cache-Control常规字段

  • public 公共资源缓存,服务器和客户端都缓存
  • private私有资源,只有客户端可缓存
  • max-age客户端最长缓存时间,相对值
  • no-cache 客户端不使用强制缓存,而走协商缓存
  • no-store 客户端不缓存,每次请求服务器

优先级:

  • 先匹配强缓存,没有命中,匹配协商缓存。
  • Cache-Control优先于Expires
  • Etag优先于Last-Modified
  • Cache-Control: no-cacheCache-Control: max-age: 0等效,目的是不取强缓存,请求服务端验证资源新鲜度(有效性)。
  • s-maxagemax-age作用一样,前者只在代理服务器(类似nginxcdn)生效,优先级高于max-ageexpire

更新缓存

当请求返回时,有三种情况

  • 将重复资源放入memory cache
  • 根据cache-control设置disk cache
  • 根据service workerfetch规则决定是否缓存文件

参考

具体实现

JavaScript 发布订阅模式

发布订阅

function PubSub() {
  this.store = {};
  
  this.on = function(event, fn) {
    var events = this.store[event];
    if (!events) {
      this.store[event] = [];
    }
    events.push(fn);
    return fn;
  }

  this.emit = function(event) {
    var events = this.store[event];
    if (!events) {
      return;
    }

    for (var i=0;i<events.length;i++) {
      var event = events[i];
      event();
    }
  }
}

取消订阅

function PubSub() {
  this.store = {};
  
  this.off  = function (event, fn) {
    var events = this.store[event];
    if (!events) {
      return;
    }
    var index = events.indexOf(fn);
    console.log('index',index);
    if (index !== -1) {
      events.splice(index , 1);
    }
  }
}

触发一次

function PubSub() {
  this.store = {};
  
  this.once  = function (event, fn) {
    var events = this.store[event];
    if (!events) {
      return;
    }
    var index = events.indexOf(fn);
    console.log('index',index);
    if (index !== -1) {
      events.splice(index , 1);
    }
  }
}

JavaScript 字面量 vs 构造器(new Array vs [] and new Object vs {})

译JavaScript 字面量 vs 构造器(new Array vs [] and new Object vs {})

JavaScript 中声明一个对象或数组有两种主要的方式。一种是用字面量语法创建,另一种是使用冗长的构造器方式。

var obj = {};
var arr = [];

或者

var obj = new Object;
var arr = new Array;

多年来,由于以下三个原因,使用字面量语法已经成为习惯方式,正因为如此,我们也应该使用这种方式。

  1. ObjectArray 对象可以被覆盖,而字面量声明的却不会
Array = Object = function () {
    alert('Foo');
};

var arr = new Array; // alerts "Foo"
var obj = new Object; // alerts "Foo"

你可能会想,我怎么可能覆盖 ObjectArray ,我不担心。不过要记住你永远无法阻止第三方库会这么做,非常可能一不小心,你的程序就跪了。

  1. 对象字面量允许你内联 定义属性,构造器方式不行
var obj = {
    prop: 1,
    add: function (val) {
        return this.prop + val;
    }
};
  1. 数组构造器有个可怕的接口,它会根据使用场景改变而改变
// What do you think ar and ar2 equal?
var ar = new Array(1);
var ar2 = new Array(1,2,3,4);

// Probably this?
alert(ar.join(",") === "1");
alert(ar2.join(",") === "1,2,3,4");

事实上,当 new Array() 接收一个参数,它会创建长度为这个参数的数组,每个数组元素的值是 undefined。当数组构造器接收多个参数时,它会创建一个数组,每个数组元素就是传入的值。

这里有个疑问,如果遇到ObjectArray 对象被覆盖,有方法可以还原吗?希望有答案的同学通知我。

原文链接

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.