var obj = { '2': 3, '3': 4, length: 2, splice: Array.prototype.splice, push: Array.prototype.push } obj.push(1) obj.push(2) console.log(obj)
数组对象
JavaScript 数组的有一些特性是其他对象所没有的:
当有新的元素添加到列表中的时候,自动更新
length
属性设置
length
为一个较小值的时候将会截断数组从
Array.prototype
中继承一些有用的方法其类属性为
Array
这些特性让 JavaScript
数组和常规对象有明显的区别。但是它们不是定义数组的本质特性。
类数组对象
把拥有一个数值 length
属性和对应非负整数属性的对象看作一种类型的数组
我们定义的 function
函数中 Arguments
对象就是一个类数组对象,同样在客户端 JavaScript 中,一些 DOM 的方法比如 document.getElementsByTagName()
也是返回的类数组对象
// 判断是否是类数组对象 const isArrayLike = obj => obj && // 非 null undefined typeof obj === 'object' && // 是对象 isFinite(obj.length) && // 是有穷数 obj.length >= 0 && // 为非负数 obj.length === Math.floor(obj.length) && // 整数 obj.length < Math.pow(2, 32) // < 4294967296
同时 JavaScript 数组方法是特意定义为通用的,因此它们不仅应用在真正的数组而且在类数组对象上都能正确的工作。类数组对象没有继承自 Array.prototype
,不能直接直接调用数组的方法,但是也是可以通过 Function.call
方法调用。
那么我们改变一下题目
var obj2 = { '2': 3, '3': 4, length: 2 } Array.prototype.push.call(obj2, 1) Array.prototype.push.call(obj2, 2) // 这样的到的有效结果是一样的 {2: 1, 3: 2, length: 4} // 通过数组的方法 // 1 2 Array.prototype.forEach.call(obj2, item => console.log(item)) // 其本质还是一个类数组
结果
这是题目在 Chrome 浏览器控制台输出结果
我们改变的题目输出结果
我们可以看到有效结果是一样的,那么为什么结果会是如此呢?
push()
方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。push
方法根据length
属性来决定从哪里开始插入给定的值。如果length
不能被转成一个数值,则插入的元素索引为 0,包括length
不存在时。当length
不存在时,将会创建它。MDN
so ~
// obj.push(1) // 等同于 obj['2'] = 1 obj.length++ // length = 3 // obj.push(2) // 等同于 obj['3'] = 2 obj.length++ // length = 4 // 那么之后的结果就是我们看到的了
但是还是有不一样的地方比如
[empty × 2, 1, 2, splice: ƒ, push: ƒ]
和 {2: 1, 3: 2, length: 4}
那么我们接下来继续看
只有当对象 splice
属性是一个 Function
的时候输出才为 [empty × 2, 1, 2, splice: ƒ, push: ƒ]
那么为此我又去 Firefox 控制台下面试了一下,结果如下图:
跟 Chrome 没有定义 splice 为 Function 是一致的
所以说可能是 Chrome 对其做的优化吧。
关于 {} + {} 浏览器不同的输出,我刚好研究了下
原文在这
大概在chrome版本49之前,Chrome控制台上面的输出结果基本和Firefox一致,之后在chrome上有人提出bug,Issue 499864,大概意思就是说我在控制台输入{a: 4, b: 5}你给我报个错干嘛,我就是想要一个对象而已。Chrome没过多久就修复了,修复的方式也特别666,就是凡是语句以{开头,以}结尾,我解析的时候就包裹一层括号在外面。git记录,里面的关键代码如下:
+ if (/^s*{/.test(text) && /}s*$/.test(text))
+ text = '(' + text + ')';
故在chrome下{} + {} 即是({} + {})
故这里的花括号正确地被解析成了对象
API 探索
Redux API
1. createStore(reducer, [preloadedState], enhancer)
创建一个 Redux store 来以存放应用中所有的 state。详情可见 Redux API,这里主要强调两点:
preloadedState
:初始时的 state。在同构中会用到,比如从一个session恢复数据。
当 store 创建后,Redux 会 dispatch action({ type: ActionTypes.INIT })
) 到 reducer 上,得到初始的 state 来填充 store。所以你的初始 state 是 preloadedState
在 reducers 处理 ActionTypes.INIT
action 后的结果。 https://github.com/reactjs/redux/blob/v3.6.0/src/createStore.js
enhancer
:如果有enhancer
,那么会首先得到增强的createStore
,然后再createStore(reducer, [preloadedState])
。
https://github.com/reactjs/redux/blob/v3.6.0/src/createStore.js 可结合下面 applyMiddleware
一起看。
2. middleware 与 applyMiddleware(...middlewares)
我们可以用 middleware 来扩展 Redux。Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。
middleware 的函数签名是 ({ getState, dispatch }) => next => action
。
如下是两个 middleware:
// logger middleware function logger({ getState }) { return (next) => (action) => { console.log('will dispatch', action) // 调用 middleware 链中下一个 middleware 的 dispatch。 let returnValue = next(action) console.log('state after dispatch', getState()) // 一般会是 action 本身,除非 // 后面的 middleware 修改了它。 return returnValue } } // thunk middleware function thunk({ dispatch, getState }) { return (next) => (action) => { if (typeof action === 'function') { return action(dispatch, getState); } return next(action); } }
applyMiddleware
返回一个应用了 middleware 后的 store enhancer。这个 store enhancer 的签名是 createStore => createStore
,但是最简单的使用方法就是直接作为最后一个 enhancer
参数传递给 createStore()
函数。
再来看下 applyMiddleware
:
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } // chain 是 [(next) => (action) => action, ...] chain = middlewares.map(middleware => middleware(middlewareAPI)) // compose(...chain) 返回这样一个函数: // 对 chain 进行 reduce,从右向左执行,每次的结果作为下次执行的输入 dispatch = compose(...chain)(store.dispatch) // 最终的 dispatch 是这样的:(action) => action return { ...store, dispatch } } }
可以看到(假设 enhancerTest = applyMiddleware(A, B, C)
):
- middleware 其实只是劫持/包装了
dispatch
。 dispatch
本质上是同步的,但我们可以通过 thunk 等延迟执行dispatch
。chain[index](dispatch) --> (action) => action
,即我们得到的dispatch
是一个层层嵌套的(action) => action
函数。- 除了最右侧的 C 得到的
next
是原本的dispatch
,剩下的都是被层层嵌套的(action) => action
函数,并且越右侧越嵌套在里面,所以当dispatch(action)
调用时,将会以下面顺序执行:A -> B -> C -> B -> A
。C
之前A/B
都只执行了next
之前的逻辑,之后各自完全执行。
3. combineReducers(reducers)
把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数。
真的很简单,从逻辑上来讲,就是:
combineReducers({ keyA: reducerA, keyB: reducerB }) // ---> function recuderAll (prevState, action) { return { keyA: reducerA(prevState.keyA, action), keyB: reducerB(prevState.keyB, action) } }
核心就是干了上面的事,只是多了一些判断和检查。
4. bindActionCreators(actionCreators, dispatch)
把 action creators 转成拥有同名 keys 的对象,但使用 dispatch 把每个 action creator 包围起来,这样可以直接调用它们。
const actionCreators = { updateOrAddFilter(filter) { type: UPDATE_OR_ADD_FILTER, filter }, removeFilter(type) { type: REMOVE_FILTER, filterType: type } } bindActionCreators(actionCreators, dispatch) // --> { updateOrAddFilter: (...args) => dispatch(original_updateOrAddFilter(...args)), removeFilter: (...args) => dispatch(original_removeFilter(...args)), }
核心就是自动 dispatch
,这样我们可以在 react 组件里直接调用,Redux store 就能收到 action。
React Redux API
1. Provider
用法:
ReactDOM.render( <Provider store={store}> <MyRootComponent /> </Provider>, rootEl )
源码:
// storeKey 默认是 'store' class Provider extends Component { getChildContext() { return { [storeKey]: this[storeKey], [subscriptionKey]: null } } constructor(props, context) { super(props, context) this[storeKey] = props.store; } render() { return Children.only(this.props.children) } } Provider.propTypes = { store: storeShape.isRequired, children: PropTypes.element.isRequired, } Provider.childContextTypes = { [storeKey]: storeShape.isRequired, [subscriptionKey]: subscriptionShape, }
注意到, Provider
应用了 React Context,子组件都可以去访问 store
。
2. connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
connect
的函数签名是 ([mapStateToProps], [mapDispatchToProps], [mergeProps], [options]) => (WrappedComponent) => ConnectComponent
,最后返回的 onnectComponent
可以通过 context
去访问 store
。
connect
API 比较复杂,这里主要讲下前两个参数。
[mapStateToProps(state, [ownProps]): stateProps] (Function)
: 如果定义该参数,组件将会监听 Redux store 的变化。任何时候,只要 Redux store 发生改变,mapStateToProps 函数就会被调用。该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。如果你省略了这个参数,你的组件将不会监听 Redux store。[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function)
: 如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,而且这个对象会与 Redux store 绑定在一起,其中所定义的方法名将作为属性名,合并到组件的 props 中。
- 共 1 页
- 1
这个 SameSite=strict 就可以对付了。
第 28 题:cookie 和 token 都存放在 header 中,为什么不会劫持 token?