Giter Club home page Giter Club logo

blog's Introduction

blog's People

Contributors

qppq54s avatar

Watchers

James Cloos avatar  avatar  avatar

blog's Issues

理解React生命周期componentWillReceiveProps

componentWillReceiveProps 以下简称receiveProps
最近在工作中遇到了一行代码上的问题,在receiveProps方法内部调用this.a的时候出现数据丢失问题,具体代码如下:

constructor(props) {
    super(props);
    this.key = null;
} 
componentWillReceiveProps(nextProps) {
    if (nextProps.status !== this.props.status) {
      this.doSomething(nextProps.status);
    }
  }
eventListener(key) { // 某个事件触发
    this.key =key;
    this.props.updateStatus(2); // 调用redux方法,修改props内容
  };

eventLister触发--》receiveProps--》在doSomething方法里调用 this.key。
奇怪的是,第一次调用doSomething可以访问到this.key 第二次调用却会返回null;

ok为了解决问题,我们先看下官方文档上对receiveProps方法的解释

UNSAFE_componentWillReceiveProps()
UNSAFE_componentWillReceiveProps(nextProps)
Note

This lifecycle was previously named componentWillReceiveProps. That name will continue to work until version 17. Use the rename-unsafe-lifecycles codemod to automatically update your components.

Note:

Using this lifecycle method often leads to bugs and inconsistencies

If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.
If you used componentWillReceiveProps for re-computing some data only when a prop changes, use a memoization helper instead.
If you used componentWillReceiveProps to “reset” some state when a prop changes, consider either making a component fully controlled or fully uncontrolled with a key instead.
For other use cases, follow the recommendations in this blog post about derived state.

UNSAFE_componentWillReceiveProps() is invoked before a mounted component receives new props. If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.

Note that if a parent component causes your component to re-render, this method will be called even if props have not changed. Make sure to compare the current and next values if you only want to handle changes.

React doesn’t call UNSAFE_componentWillReceiveProps() with initial props during mounting. It only calls this method if some of component’s props may update. Calling this.setState() generally doesn’t trigger UNSAFE_componentWillReceiveProps().
Google翻译一下。。
注意:

使用此生命周期方法通常会导致错误和不一致

如果您需要执行副作用(例如,数据提取或动画)以响应props中的更改,请改用componentDidUpdate生命周期。
如果您仅在prop更改时使用componentWillReceiveProps重新计算某些数据,请使用memoization helper。
如果您在prop更改时使用componentWillReceiveProps“重置”某些状态,请考虑使用组件完全控制组件或完全不受控制。
对于其他用例,请遵循此博客文章中有关派生状态的建议。

在安装的组件接收新的props之前调用UNSAFE_componentWillReceiveProps()。如果您需要更新状态以响应prop更改(例如,重置它),您可以比较this.props和nextProps并使用此方法中的this.setState()执行状态转换。

请注意,如果父组件导致组件重新渲染,即使props没有更改,也会调用此方法。如果您只想处理更改,请务必比较当前值和下一个值。

在安装过程中,React不会使用初始道具调用UNSAFE_componentWillReceiveProps()。如果某些组件的道具可能会更新,它只会调用此方法。调用this.setState()通常不会触发UNSAFE_componentWillReceiveProps()。

文档上说,使用此生命周期方法通常会导致错误和不一致,那到底是什么原因导致的错误和不一致呢?

后续测试

在电脑端写了个测试程序,程序中的this.key并没有丢失,初步判断是硬件问题导致,具体原因还要继续看。

JavaScript中深拷贝和浅拷贝(新增浅比较)

为什么会有浅拷贝和深拷贝

在js里,将对象赋值给一个新的变量:

var a = {a: 1, b: 2};
var b = a;
a===b; // true

因为a和b指向同一个内存地址,所以它们完全相同,而且改变其中一个,另一个也会改变。

在一些情境下,我们需要拷贝一个全新的对象,防止对原对象产生影响。

浅拷贝

常见的浅拷贝方法

$.extend({}, obj);
Array.prototype.slice();
Object.assign(); // let b = {...a}也一样
function shallowCopy(src) {
  var dst = {};
  for(var prop in src) {
    if (src.hasOwnProperty(prop)) {
      dst[prop] = src[prop];
    }
  }
  return dst;
}

可以看出来,浅拷贝就是对象的第一层key-value的复制,对于value值为简单数据类型的可以做到与原对象互不影响。
如果是复杂的对象,浅拷贝还是不能解决之前提到的问题,这时候就需要深拷贝

深拷贝

常见的深拷贝方法

function deepClone(source){
    // 先判断是否为对象
    if(!source || typeof source !== 'object'){
      throw new Error('请传入参数对象');
    }
    // 创建空的对象和数组
    let targetObj = Array.isArray(source) ? [] : {};
    // 如果value是简单数据类型,把对象的key-value拷贝到新的对象
    // 如果value是对象, 递归调用方法,把对象最终解析到基本数据类型
    for (let keys of Object.keys(source)) {
        if(source[keys] && typeof source[keys] === 'object'){
            targetObj[keys] = deepClone(source[keys]);
          }else{
            targetObj[keys] = source[keys];
          }
    }
    return targetObj;
 }

function deepClone(source){
  return JSON.parse(JSON.stringify(source));
}

相对的,既然有深/浅拷贝,就会有深/浅比较

严格/浅比较 (strictEqual / shallowEqual)

严格比较

严格比较由JavaScript语言本身提供 ===

function strictEqual(a, b) {
  return a === b
}

浅比较

这里我们引用react-redux里的源码

const hasOwn = Object.prototype.hasOwnProperty

// is方法可以判断基本数据类型是否相等
function is(x, y) {
  if (x === y) {
    return x !== 0 || y !== 0 || 1 / x === 1 / y    // 排除 -0 === 0 返回 true的情况
  } else {
    return x !== x && y !== y    // 排除NaN === NaN 返回 false的情况
  }
}

// 浅比较(看上去一样)
export default function shallowEqual(objA, objB) {
  // 首先对基本数据类型做比较
  if (is(objA, objB)) return true

  // 基本类型比较完成后,判断参数是否为对象,如果不是直接返回false,因为typeof null是object但null
     的情况在is方法里可以判断,所以也直接返回false
  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false
  }

  const keysA = Object.keys(objA)
  const keysB = Object.keys(objB)

  // 判断两个对象是否相同,可以先判断key的多少,不一样的肯定不同
  if (keysA.length !== keysB.length) return false

  // 递归判断每一层的key-value,此处与深拷贝类似
  for (let i = 0; i < keysA.length; i++) {
    if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
      return false
    }
  }

  return true
}

ReactDOM render方法原理解析,react是怎么把元素渲染到DOM节点上的

先断点调试找下调用链条

  1. render
  2. legacyRenderSubtreeIntoContainer
  3. updateContainer
  4. scheduleWork(scheduleUpdateOnFiber)
  5. performSyncWorkOnRoot
  6. finishSyncRender
  7. commitRoot
  8. runWithPriority
  9. commitRootImpl
  10. invokeGuardedCallback
  11. commitMutationEffects
  12. commitPlacement
  13. insertOrAppendPlacementNodeIntoContainer
  14. appendChildToContainer
  15. appendChildToContainer
  16. parentNode.appendChild(child)

我们按顺序来看下每个方法做了什么事情

function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function,
) {
  let root: RootType = (container._reactRootContainer: any);
  let fiberRoot;
  // 这里根据root判断是否初次挂载(initial mount)
  if (!root) {
    // 根据container创建一个firberRoot,并挂到container上,名称为_reactRootContainer
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    fiberRoot = root._internalRoot;
   
    unbatchedUpdates(() => {
      // 执行到这里
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  }
  return getPublicRootInstance(fiberRoot);
}

从代码上看,legacyRenderSubtreeIntoContainer方法主要是创建了一个"firberRoot",然后执行updateContainer方法。
legacyRenderSubtreeIntoContainer这个方法最终调用的是一个叫createFiberRoot的方法,我们线看下这个方法

export function createFiberRoot(
  containerInfo: any,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);

  const uninitializedFiber = createHostRootFiber(tag);
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  initializeUpdateQueue(uninitializedFiber);
  return root;
}
export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): ExpirationTime {
  const current = container.current;
  const currentTime = requestCurrentTimeForUpdate();
  const suspenseConfig = requestCurrentSuspenseConfig();
  const expirationTime = computeExpirationForFiber(
    currentTime,
    current,
    suspenseConfig,
  );
  // 取createFiberRoot方法时添加的current,并执行scheduleWork方法
  scheduleWork(current, expirationTime);
  return expirationTime;
}

Centering in CSS: A Complete Guide(css居中方案完全指南)

翻译自centering-css-complete-guide

居中是人们对css抱怨的最多的问题之一。它为什么这么难?要吃人。我认为问题并不在居中很难,而是在于在各种情况下要采取不同的方案去实现,合适的使用才是关键。

所以,让我们画一个决定树去尝试让居中变得容易一点。

我需要在这种情况下实现居中...

水平居中

是否为行内元素(inline 或者 inline-*)?

在块级父元素中对行内元素居中,可以使用下面的方法

.center-children {
  text-align: center;
}

这种方法对于inline,inline-block,inline-table,inline-flex等等都适用。

是否为块级元素(inline 或者 inline-*)?

要让块级元素居中,可以设置margin-left和margin-right为auto(而且必须设置width,不然这个元素就是全宽的,不需要居中)。看下面的代码:

.center-me {
  width: 10px;
  margin: 0 auto;
}

这样无论在什么宽度的宽级元素或者父元素中都可以实现居中。
注意:你不能使用float让元素居中。但是有一个特殊方法

是否有多个块级元素?

如果存在多个块级元素在同一行居中,更好的选择可能是使用另外一种display类型。下面是将块级元素设置为inline-block和使用flexbox居中的案例:

.inline-block-center {
  text-align: center;
}
.inline-block-center div {
  display: inline-block;
  text-align: left;
}

.flex-center {
  display: flex;
  justify-content: center;
}

垂直居中

垂直居中在css中有点棘手。

是否为行内元素

是否为单行的行内元素

有些情况下行内元素/文字能够垂直居中是因为它们的上面和下面填充了一样的padding

.link {
  padding-top: 30px;
  padding-bottom: 30px;
}

如果在某些情况下不能使用padding,那么对于不换行的元素,有个技巧就是设定line-height和height一样,内容就会居中。

.center-text-trick {
  height: 100px;
  line-height: 100px;
  white-space: nowrap;
}

是否为多行的行内元素

给元素的顶部和底部赋予相同的padding也能让多行文字居中,但是如果这样行不通的话,可能是文字所在的元素是table内,或者css设置的display:table内。在这种情况下,就要用到vertical-align属性了,这种方法和通常的垂直居中方式不太一样。

<table>
  <tr>
    <td>
      I'm vertically centered multiple lines of text in a real table cell.
    </td>
  </tr>
</table>

<div class="center-table">
  <p>I'm vertically centered multiple lines of text in a CSS-created table layout.</p>
</div>


table {
  background: white;
  width: 240px;
  border-collapse: separate;
  margin: 20px;
  height: 250px;
}

table td {
  background: black;
  color: white;
  padding: 20px;
  border: 10px solid white;
  /* default is vertical-align: middle; */
}

.center-table {
  display: table;
  height: 250px;
  background: white;
  width: 240px;
  margin: 20px;
}
.center-table p {
  display: table-cell;
  margin: 0;
  background: black;
  color: white;
  padding: 20px;
  border: 10px solid white;
  vertical-align: middle;
}

如果用不了table这样子的方式,或许可以试试用flexbox?flex内部的子元素可以轻松的居中。

.flex-center-vertically {
  display: flex;
  justify-content: center;
  flex-direction: colum;
  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;
}

是否为块级元素

元素的高度是否确定

在web页面布局中,不知道元素的高度的情况是很常见的,有很多原因:如果宽度发生改变,文本流可能会改变高度。文本样式的变化也会改变高度。文本数量的变化也可以改变高度。具有固定宽高比的元素,比如图像,在调整大小时会改变高度。等等情况。

但是如果你可以确定高度,你可以用如下方式实现垂直居中:

.parent {
  position: relative;
}
.child {
  position: absolute;
  top: 50%;
  height: 100px;
  margin-top: -50px; /* 需要计算padding和border如果不是box-sizing: boder-box */
}

不知道元素的高度

仍然可以把元素放到50%高度然后上升自身的50%以居中:

.parent {
  position: relative;
}
.child {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
}

能不能使用flexbox

没啥大惊小怪,在flexbox里面很容易就可以实现。

.parent {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

水平垂直居中

你可以以任意方式结合上面的技巧来实现完美居中的元素。但是我发现大致可以分为以下几种情况:

元素的宽高是否确定

在绝对定位和设置50%/50%后设置宽高的一半的负margin可以获得兼容性极好的居中:

.parent {
  position: relative;
}

.child {
  width: 300px;
  height: 100px;
  padding: 20px;

  position: absolute;
  top: 50%;
  left: 50%;

  margin: -70px 0 0 -170px;
}

元素的宽高无法确定

如果不确定元素的宽高,可以使用transform属性和50%的负translate来设置居中:

.parent {
  position: relative;
}
.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

flexbox

flexbox里要实现居中,只用同时使用两个居中属性

.parent {
  display: flex;
  justify-content: center;
  align-items: center;
}

grid

这是一个小技巧对于单个元素比较有效:

body, html {
  height: 100%;
  display: grid;
}
span { /* 居中的元素 */
  margin: auto;
}

结论

你现在可以在任何情况下实现元素居中了。

JavaScript数组方法合集

ES3

pop 移除数组末尾的元素

let a = [5,6];
a.pop(); // 6
a // [5];

push 向数组末尾添加元素

let a = [5,6];
a.push(7); // 7
a // [5, 6, 7];

reverse 反转数组

let a = [5,6];
a.reverse(); // [6,5]
a // [6,5];

shift 移除数组的第一个元素

let a = [5,6];
a.shift(); // 5
a // [6];

unshift 把元素插入数组的开始

let a = [5,6];
a.unshift(4); // 3 数组的长度
a // [4,5,6];

slice 对数组的一段做浅复制

let a = [5,6];
a.slice(0); // [5,6]
a.slice(0, 1); // [5]
a // [5,6];

sort 对数组进行排序(不能用于数字,把所有元素视为字符串,但是可以自己定义比较函数)

let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
a.sort(); // [1, 10, 2, 3, 4, 5, 6, 7, 8, 9]
a //  [1, 10, 2, 3, 4, 5, 6, 7, 8, 9]
a.sort((a,b) => a-b);  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

splice 移除数组中的0个或多个元素,并插入0个或多个元素

let a = [5,6];
a.splice(0, 1); // [5] 返回的是被删除的**数组**
a // [6]
a.splice(0, 0, 4, 5); // []
a // [4,5,6]

ES5

forEach 遍历数组

let a = [5,6];
a.forEach((value, index, array) => {
  value // 5,6
  index // 0,1
  array // [5, 6]
}) // 返回undefined 

map 遍历数组后创建一个新数组

let a = [5,6];
a.map((value, index, array) => {
  return value + 1;
}) // 返回[6,7] 

filter 返回过滤后的新数组

let a = [5,6];
a.filter((value, index, array) =>  value > 5;) // 返回[6] 
a // [5,6];

some 返回是否有元素满足条件

let a = [5,6];
a.some((value, index, array) =>  value > 5;) // 返回true
a // [5,6];

every 返回是否所有元素满足条件

let a = [5,6];
a.some((value, index, array) =>  value > 5;) // 返回false
a // [5,6];

indexOf 查找元素位置

let a = [5,6];
a.indexOf(5) // 返回0,未查到返回-1
a // [5,6];

lastIndexOf 从末尾开始查找元素位置

let a = [5,6];
a.lastIndexOf(5) // 返回0,未查到返回-1
a // [5,6];

reduce 递归

let a = [0, 1, 2, 3, 4];
a.reduce((accumulator, currentValue, currentIndex, array) => { return accumulator + currentValue; }, 0 ); // 返回10,0 + 0 = 0; 0 + 1 = 1; 1 + 2 = 3; 3 + 3 = 6; 6 + 4 = 10; 
a.reduce((accumulator, currentValue, currentIndex, array) => { return accumulator + currentValue; }, 10 ); // 返回20,10 + 0 = 10; 10 + 1 = 11; 11 + 2 = 13; 13 + 3 = 16; 16 + 4 = 20; 
a //  [0, 1, 2, 3, 4]

reduceRight 从末尾开始递归

let a = [0, 1, 2, 3, 4];
[0, 1, 2, 3, 4].reduceRight(function(previousValue, currentValue, index, array) {
    return previousValue + currentValue;
}, 10); // 返回20,10 + 4 = 14; 14 + 3 = 17; 17 + 2 = 19; 19 + 1 = 20; 20 + 0 = 20; 
a //  [0, 1, 2, 3, 4]

ES6

... 扩展运算符

...[1,2] // 1 2 主要用于函数调用
Math.max.apply(null, [14, 3, 7]) => Max.max(...[14, 3, 7]);
运用
1. 复制数组
es5: 
var a1 = [1,2];
var a2 = a1.concat();
es6:
const a1 = [1,2];
const a2 = [...a1]; // 或者 const [...a2] = a1;
2. 合并数组
[...a1, ...a2, ...a3]

Array.from() 将类似数组的对象(array-like object)和可遍历(iterable)的对象转为真正的数组

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

Array.of() 将一组值转为数组

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]

Array.copyWithin(target, start = 0, end = this.length) 将start到end-1的内容拷贝到target

let a = [5,6,7,8,9];
a.copyWithin(0, 2); // [7, 8, 9, 8, 9]
a //  [7,8,9,8,9];

Array.find() 和Array.findIndex() // 查找符合条件的值,用于代替indexOf

let a = [5,6,7,8,9];
a.find((value, index, arr) => {
  return value > 7;
}); // 8
a.findIndex((value, index, arr) => {
  return value > 7;
}); // 3
可以接受第二个参数,用于绑定回调函数的this对象
function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // 26

Array.fill() 空数组的初始化,当填充类型为对象时,只做浅拷贝(内存地址一样)
entries() keys() values()
includes()
flat/flatMap(flat + map)

[1,[2,3],4].flat(1) // [1,2,3,4] 参数为层数

rax开发(编译时)踩坑记录

  1. 嵌套使用(例如if else里面有if else)await方法时,每一层内使用都要加上try-catch方法,不然内层的error不会被最外层的try-catch到,会导致js代码报错
  2. 使用components时,子节点使用function渲染时,内部元素state发生变化,子节点不会同步更新
  3. render方法里渲染节点,无法使用嵌套判断,例如,在map(item => ( ))的View组件里判断if(item.success) renturn ... 或者 item.success &&
  4. rax-input受控组件需要使用onInput进行输入事件监听。

公司内部npm服务搭建实录(cnpmcore)

使用cnpmcore搭建企业级的npm私有仓库

cnpmcore包获取和初始化

连接服务器,执行

git clone https://github.com/cnpm/cnpmcore.git
cd cnpmcore
npm install
// 因为项目是使用TS编写的,先转为js
npm run tsc

配置

编辑 config/config.default.js

可上传域管理

只接受类似 @cnpm/my-components 类似的以配置的作用域开头的包上传
allowScopes: [ '@cnpm','@cnpmcore','@example'],

管理员账号配置

更改为管理员的账号

admins: {
    // name: email
    cnpmcore_admin: '[email protected]',
},

数据库配置

更改为实际数据库内容

config.orm = {
  client: 'mysql',
  database: process.env.MYSQL_DATABASE || 'cnpmcore',
  host: process.env.MYSQL_HOST || 'localhost',
  port: process.env.MYSQL_PORT || 3306,
  user: process.env.MYSQL_USER || 'root',
  password: process.env.MYSQL_PASSWORD,
  charset: 'utf8mb4',
};

redis 配置

config.redis = {
        client: {
            port: 6379,
            host: '172.0.0.1',
            password: '',
            db: 0,
        },
    };

nfs 配置

这里我使用的是 https://github.com/cnpm/fs-cnpm

config.nfs = {
    client: new FSClient({ dir: '/data/nfs' }),
    path: '/data/nfs'
}

其他配置

// 添加
config.keys = 'sdjkhakjdhkj_sjkhdjkshd_28373837893' // 随机串

启动服务

npm start

验证

添加npm源后执行npm发布和安装成功

问题

相对于cnpmjs.org无web端ui界面,待探索

JavaScript理解call、apply

网上搜索call和apply的使用方法,大多数时候总是把这两个方法搞混,所以我尝试用一些取巧的方式去记忆。

call和apply的共同点:

  1. 第一个参数传入的是执行上下文的对象,也就是改变方法的this指向到这个参数。
  2. 都会执行函数

以上都不理解?没事,看代码

function getName() {
  console.log(this.name)
}
getName(); // undefined
getName.call({name: 'qp'}); // qp
getName.apply({name: 'qp'}); // qp

call和apply的不同点:

  1. call方法后面的参数是一个列表,而apply方法第二个参数是array(数组)
    如果容易记混的话,就看apply和array长得特别像,就不会搞错了。

看代码

function getName(...param) {
  console.log(param)
}
getName(); // undefined
getName.call({}, 'qp', 'qp'); // ['qp', 'qp']
getName.apply({}, ['qp', 'qp']); // ['qp', 'qp']

前端开发兼容性问题和其他问题记录

react-transtion-group导致魔点科技双面屏设备闪白块

解决方案,移除react-transtion-group相关代码,原理正在研究

双面屏点击按钮,按钮绑定onTouchStart或者onTouchTap事件打开modal,modal自动关闭

在onTouchTap事件中加上e.preventDefault后不再出现
目前猜想:因为onTouchTap的触发比点击事件早,在触发展示modal后,onClick事件被触发,导致点击蒙层,关闭modal。

mobile-ant-design在初次渲染无法打开modal

初步判断是低版本ios的问题,在android和ios10以上系统无法复现

快速点击时,容易触发多次点击事件

方案1:

let a = false;  
function onClick = () => {  
  if (a) return;  
  // do something  
  a = true;  
  setTimeout(() =>{  
      a = false;  
  }, 500);  
}  

然而这种方案每次使用的时候都要创建变量,使用上不能即插即用。
方案2: debounce函数

// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
  var timeout;
  return function() {
    var context = this, args = arguments;
    var later = function() {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };
    var callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func.apply(context, args);
  };
};

移动端弹层滚动穿透问题

关于滚动穿透,一开始我的解法是采用了网络上的“最优解”:

body.modal-open {
    position: fixed;
    width: 100%;
}

/**
  * ModalHelper helpers resolve the modal scrolling issue on mobile devices
  * https://github.com/twbs/bootstrap/issues/15852
  * requires document.scrollingElement polyfill https://uedsky.com/demo/src/polyfills/document.scrollingElement.js
  */
var ModalHelper = (function(bodyCls) {
  var scrollTop;
  return {
    afterOpen: function() {
      scrollTop = document.scrollingElement.scrollTop;
      document.body.classList.add(bodyCls);
      document.body.style.top = -scrollTop + 'px';
    },
    beforeClose: function() {
      document.body.classList.remove(bodyCls);
      // scrollTop lost after set position:fixed, restore it back.
      document.scrollingElement.scrollTop = scrollTop;
    }
  };
})('modal-open');

然后在react生命周期中调用ModalHelper对应的方法。然而这种方法需要修改项目样式文件,在作为组件时不易使用。后面我们还是采用了监听touchmove事件的方法。

// 在modal打开的时候添加事件监听
this.ref.addEventListener('touchmove', this.preventDefault, {passive: false});
this.inner.addEventListener('touchmove', this.preventDefault, {passive: false});

<div ref={ref => this.ref = ref}>
  <div ref={ref => this.inner = ref}}>
  </div>
</div>

preventDefault = (e) => {
  e.preventDefault();
}

ios页面在键盘收起时没有及时响应导致页面卡死

在最近的一次native发布后,我们的页面在键盘回落时没有复原,导致页面底部的按钮无法点击。
解决方案是通过给输入框添加onblur监听并且使用window.scrollTo(0,0)触发滚动。具体原理还是不知道

实现一个好用又漂亮的验证码/证件号输入框组件

需求内容

功能:实现自定义的输入框组件,限制可以输入英文或者数字,长度限制为4位。
美观:4个输入框单独展示【】【】【】【】

实现

方案1

四个单独的input框,限制只能输入英文或数字。
问题: 切换输入框时,ios键盘会重制,导致ios手机使用系统默认键盘时,如果输入4个数字,需要多次切换输入法,体验不好。

方案2

自己写四个假的输入框,同时写一个input组件,并通过opacity:0设置为透明
问题:在ios手机上,使用中文键盘输入时,待选英文之间存在空格,假的输入框如果和input内容一直,容易令人疑惑。

方案3

此方案基于方案二优化,因中文键盘原因,最好能限制ios系统键盘唤出时,只支持输入英文或数字。当input组件的type设置为password时,ios系统键盘会唤起密码输入特殊键盘,只包括英文数字和特殊字符,只需过滤特殊字符即可。

cordova-template-framework7-vue-webpack开发实战

在开始项目前,请安装Nodejs和cordova相关环境变量

创建项目

Template GitHub地址

执行以下代码

cordova create testApp com.qppq54s.testApp TestApp --template cordova-template-framework7-vue-webpack

完成后,你已经创建成功了一个基础App

Web端预览和开发

项目创建成功后,执行以下代码

cd testApp // 进入文件夹
npm install // 安装相关依赖
cordova platform add browser // 添加浏览器平台
cordova platfrom run browser -- --lr // Web端实时自动更新调试开发

创建登录页面

首先我们在 src\assets\vue\pages\ 下创建一个登录页面 login.vue,然后在routes.js添加路由控制代码

{
    path: '/login/',
    component: require('./assets/vue/pages/login.vue')
}

接下来我们想在用户打开App的时候做登录状态判断和页面跳转,将main.vue里组件内的url="/"更改为:url="url",并且在下方的script标签内加入

export default {
        data() {
            return {
                url: "/"
            }
        },
        created() {
            var currentUser = localStorage.currentUser; // 通过localStorage存储用户信息
            if (currentUser) {
                // 跳转到首页
                this.url = "/";
            } else {
                // currentUser为空时,可打开登录
                this.url = "/login/";
            }
        },
    }

这样一来,就可以实现登陆状态的判断和跳转了。

Android下的返回键控制 backbutton

通常我们通过 document.addEventListener("backbutton", onBackKeyDown, false);控制Android手机上的返回键,但是让人比较疑惑的是,在这个项目里,我们把这段代码加到哪里比较好

我的做法是在main.vue的script标签内加入以下代码

mounted() {
  var self = this;
  var exitSure = false;
  document.addEventListener('backbutton', function () {
      var url = self.$f7.views.main.router.url;
      if (exitSure) {
          navigator.app.exitApp(); // 退出应用
      } else {
          if (url === '/' || url === '/login/') {
              exitSure = true;
              console.log('再按一次退出应用');
              setTimeout(function () {
                  exitSure = false;
              }, 2000)
          } else {
              self.$f7.views.main.router.back(); // 通过这段代码控制返回上一页
          }
      }
  }, false)
},

项目包大小优化记录 bn.js ellptic.js

webpack打包完成后,发现index.js有将近600k(压缩后),其中,有bn.js和ellptic.js有将近400k大小,分析node_modules依赖后,发现,postcss-cssnext包里有这个依赖,是我们的postcss-loader在线上环境也开启了source-map: true导致的。
把source-map: true 移除后,发现这两个包还在。
一顿google后,发现如果在项目中引用了crypto,也对导致webpack打入这两个依赖
之后,在我们的请求库里,发现我们使用了[email protected],而crypto-js的core.js文件中有这么一段代码
// Native crypto import via require (NodeJS) if (!crypto && typeof require === 'function') { try { crypto = require('crypto'); } catch (err) {} }
导致webpack打包的时候还是依赖了crypto。然而,之前的版本[email protected]并没有加入这段代码,所以暂时把crypto-js的版本锁定为3.3.0。

这两个改动之后,index.js包里终于没有bn.js和ellptic.js,包大小也减少了400k左右。

Cordova 相关框架评估

Ionic2

cordova + angular2 + ionic2

评价

开发上手还是比较简单的
主要问题在于angular2的学习和使用,开发过程中发现,组件的本地定制(例如弹出的日期组件在android本地语言是中文的情况下展示为英文,需要配置)较为复杂

Framework7

cordova + (framework7 + avalon.js | framework7-vue | framework7-react)

评价

  1. avalonjs 调试不太方便,代码耦合度高
  2. f7-vue 没有实时预览,只能通过先看Framework7文档再找对应的vue组件,结合kitchen-sink里的案例使用

OnsenUI

cordova + onsenui + (主流前端js框架)

评价

  1. 相对于ionic2只支持angular2,onsenui选择了包容万象,而针对每一个js框架,例如 vuejs,都有对应的文档、cli和案例,上手较为简单
  2. 主要问题是UI的定制化比较难,例如要更改一些样式需要修改全局的css

React-Native

评价

  1. 不支持web端调试
  2. 如果使用expo等框架,打包项目比较复杂,且不能真机调试框架内不包含的插件

Ant design mobile + dva + cordova

评价

  1. 之前我们的项目都是用的dva + ant.design 比较熟悉了
  2. 文档比较完善,ui很给力
  3. 降低跨项目的难度
  4. 跟原生的差距较大
  5. 页面跳转没有ui效果等

最终,我选择了Framework7+vue,使用了这个Template,具体使用方法和过程中遇到的问题在这里#1

JavaScript判断变量是数组或者对象,以及其原理

js中我们常用的判断类型方法typeof在对象和数组上都返回'object',所以要使用其他方案来判断

第一种方法,判断对象的长度

var a = [];
var b = {};
a.length; // 0
b.length; // undefined

然而,因为对象的属性可以被修改,所以这种方法并不可靠;但是因为let等会计作用域的保证,所以这个方法是开发中比较简单常用的方法;

第二种方法,instanceof

var a = [];
var b = {};
a instanceof Array; // true
b instanceof Array; // false

instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object的原型链上。
换句话来说,就是检测参数a的原型链上是否存在Array的的原型;
理解这个方法可以了解下mdn上关于Object.getProtoTypeOf的解释

JavaScript中的 Object 是构造函数(创建对象的包装器)。
一般用法是:
var obj = new Object();

所以:
Object.getPrototypeOf( Object );               // ƒ () { [native code] }
Object.getPrototypeOf( Function );             // ƒ () { [native code] }

Object.getPrototypeOf( Object ) === Function.prototype;        // true

Object.getPrototypeOf( Object )是把Object这一构造函数看作对象,
返回的当然是函数对象的原型,也就是 Function.prototype。

正确的方法是,Object.prototype是构造出来的对象的原型。
var obj = new Object();
Object.prototype === Object.getPrototypeOf( obj );              // true

Object.prototype === Object.getPrototypeOf( {} );               // true

第三种方法,Object.prototype.toString.call()

var a = [];
var b = {};
b.toString(); // "[object Object]"
Object.prototype.toString.call(a); // "[object Array]"
Object.prototype.toString.call(b); // "[object Object]"

那么问题来了,为什么要使用Object.prototype.toString来帮助判断,不直接用toString?其实是为了防止自定义的toString方法导致返回的结果被修改。
每个对象都有一个toString()方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中type是对象的类型。

第四种方法,Array.isArray()

var a = [];
var b = {};
Array.isArray(a); // true
Array.isArray(b); // false

Array是ES6推出的方法,在可以使用ES6的环境下,提供了简单好用的array判断方法!

严格判定JavaScript对象是否为数组

window10 安装hadoop和环境变量配置

1、安装JAVA环境

下载JDK8

因为JDK安装路径里有空格会导致报错,所以我把安装后的文件夹重命名为jdk后放到了C:\Java\ 下面

2、下载hadoop

下载hadoop

解压到C:\hadoop,路径可以随意,但不要有中文或空格。

下载winutils,前往https://github.com/cdarlint/winutils 找到 3.0.1,下载bin目录的文件,放入/替换 C:\hadoop\bin 文件夹内的文件,并复制一份hadoop.dll放到C:\Windows\System32下。

配置hadoop环境变量,把C:\hadoop添加到HADOOP_HOME,并在Path添加 %HADOOP_HOME%\bin 和 %HADOOP_HOME%\sbin 。

打开CMD,输入hadoop version 测试是否正常显示版本信息。

3、配置hadoop

切换至目录:C:\hadoop\hadoop-3.0.1\etc\hadoop 下,

修改core-site.xml:

需在目录C:/hadoop/hadoop-3.0.1/下创建data文件夹,配置文件中路径前需加“/”。HDFS可使用localhost,如果在hosts文件已经配置了主机映射,也可以直接填主机名。

hadoop.tmp.dir /C:/hadoop/hadoop-3.0.1/data fs.defaultFS hdfs://localhost:9000

修改hadoop-env.cmd

set JAVA_HOME=C:\Java\jdk

修改hdfs-site.xml

单节点填1即可,如果是多节点,根据节点数量填写。

dfs.replication 1 dfs.namenode.name.dir /C:/hadoop/hadoop-3.0.1/data/namenode dfs.datanode.data.dir /C:/hadoop/hadoop-3.0.1/data/datanode

修改mapred-site.xml

mapreduce.framework.name yarn #### 修改yarn-site.xml yarn.nodemanager.aux-services mapreduce_shuffle yarn.nodemanager.aux-services.mapreduce.shuffle.class org.apache.hahoop.mapred.ShuffleHandler 修改完毕。

4、节点格式化

打开cmd执行:

hdfs namenode -format
如正常,会显示namenode has been successfully formatted。如果出错,可能原因有如:环境变量配置错误如路径出现空格,或者winutils版本不对hadoop版本过高等,或hadoop的etc下文件配置有误。

5、启动Hadoop

切换到C:\hadoop\sbin目录下(如果有正常配置环境变量是不需要切换目录的),执行命令:

start-all.cmd
启动Hadoop,此时会弹出4个CMD窗口,分别是NameNode、ResourceManager、NodeManager、DataNode。检查4个窗口有没有报错。在CMD执行jps看到这4个进程。

我在这边报了个错,sbin下找不到packag.json文件。
处理了下yarn.cmd文件,修改其yarn运行代码为:

@Rem start resourceManager
start "Apache Hadoop Distribution" C:\hadoop\hadoop-3.0.1\bin\yarn resourcemanager
@Rem start nodeManager
start "Apache Hadoop Distribution" C:\hadoop\hadoop-3.0.1\bin\yarn nodemanager
@Rem start proxyserver
@Rem start "Apache Hadoop Distribution" yarn proxyserver

6、登录WEB验证

在浏览器输入 localhost:8088 访问集群节点。

在浏览器输入localhost:9870 访问HDFS。

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.