什么是函数柯里化?实现 sum(1)(2)(3) 返回结果是1,2,3之和
函数柯里化:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数
1 | function sum(a) { |
引申:实现一个curry函数,将普通函数进行柯里化
1 | function curry(fn, args = []) { |
一个能力有限的前端
函数柯里化:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数
1 | function sum(a) { |
1 | function curry(fn, args = []) { |
1 | var addHandler = function(e, type, handler) { |
1 | target = event.target || event.srcElement; |
1 | event.preventDefault ? event.preventDefault() : event.returnValue = false |
1 | event.stopPropagation ? event.stopPropagation() : event.cancelBubble = false |
constructor是一种用于创建和初始化class创建的对象的特殊方法。
描述
如果不指定构造方法,则使用默认构造函数。对于基类,默认构造函数是:
1 | constructor() {} |
对于派生类,默认构造函数是:
1 | constructor(...args) { |
extends关键字用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。
描述
extends关键字用来创建一个普通类或者内建对象的子类
继承的.prototype必须是一个Object或者null。
类(class)通过static关键字定义静态方法。不能在类的实例上调用静态方法,而应该通过类本身调用。这些通常是实用程序方法,例如创建或克隆对象的功能
1 | class ClassWithStaticMethod { |
1 | class StaticMethodCall { |
1 | class StaticMethodCall { |
1 | class Car { |
在上面的事例中,我们依靠Car的实例调用getMilesDriven方法来获取到它的私有属性_milesDriven。但是,因为没有什么能使_milesDriven成为私有的,
所以任何实例都可以访问它。
1 | const tesla = new Cal(); |
目前Class Fields有个提案,我们可以通过#创建私有字段。
1 | class Car { |
call、apply的功能相同,区别在于传参的方式不一样:
1 | Function.prototype.call = function(context) { |
apply的实现和call很类似,但是需要注意他们的参数不一样,apply的第二个参数是数组或类数组。
1 | Function.prototype.apply = function(context, rest) { |
bind 和 call/apply 有一个很重要的区别,一个函数被 call/apply 的时候,会直接调用,但是 bind会创建一个新函数。当这个新函数被调用时,bind()的第一个参数将
作为它运行时的this,之后的一系列参数将会在传递的实参前传入作为它的参数。
1 | Function.prototype.bind = function(context) { |
FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
首先创建一个FileReader实例:
1 | var reader = new FileReader(); |
方法 | 描述 |
---|---|
abort() | 中止读取操作。在返回时,readyState属性为DONE。 |
readAsArrayBuffer() | 开始读取指定的 Blob中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象. |
readAsBinaryString() | 开始读取指定的Blob中的内容。一旦完成,result属性中将包含所读取文件的原始二进制数据。 |
readAsDataURL() | 开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个data: URL格式的字符串以表示所读取文件的内容 |
readAsText() | 开始读取指定的Blob中的内容。一旦完成,result属性中将包含一个字符串以表示所读取的文件内容。 |
input file实现本地图片预览
1 | var inputBox = document.getElementById("inputBox"); |
通过之前我们已经知道了react-router-dom的
var createBrowserHistory = function createBrowserHistory() { // arguments[0], 由上一节可知它是一个非必传的object var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; (0, _invariant2.default)(_DOMUtils.canUseDOM, 'Browser history needs a DOM'); // 之前将history赋值给变量globalHistory var globalHistory = window.history; // _DOMUtils.js 中处理一些浏览器兼容性 和 浏览器内核、机型判断的方法 // 判断是否非主流机型以及浏览器 var canUseHistory = (0, _DOMUtils.supportsHistory)(); // 是否为IE浏览器 var needsHashChangeListener = !(0, _DOMUtils.supportsPopStateOnHashChange)(); // forceRefresh为true,代表将在页面导航时使用完整页面刷新。其他参数请自行参考官网文档 var _props$forceRefresh = props.forceRefresh, forceRefresh = _props$forceRefresh === undefined ? false : _props$forceRefresh, _props$getUserConfirm = props.getUserConfirmation, getUserConfirmation = _props$getUserConfirm === undefined ? _DOMUtils.getConfirmation : _props$getUserConfirm, _props$keyLength = props.keyLength, keyLength = _props$keyLength === undefined ? 6 : _props$keyLength; var basename = props.basename ? (0, _PathUtils.stripTrailingSlash)((0, _PathUtils.addLeadingSlash)(props.basename)) : ''; var getDOMLocation = function getDOMLocation(historyState) { var _ref = historyState || {}, key = _ref.key, state = _ref.state; var _window$location = window.location, pathname = _window$location.pathname, search = _window$location.search, hash = _window$location.hash; var path = pathname + search + hash; (0, _warning2.default)(!basename || (0, _PathUtils.hasBasename)(path, basename), 'You are attempting to use a basename on a page whose URL path does not begin ' + 'with the basename. Expected path "' + path + '" to begin with "' + basename + '".'); if (basename) path = (0, _PathUtils.stripBasename)(path, basename); return (0, _LocationUtils.createLocation)(path, state, key); }; var createKey = function createKey() { return Math.random().toString(36).substr(2, keyLength); }; var transitionManager = (0, _createTransitionManager2.default)(); var setState = function setState(nextState) { _extends(history, nextState); history.length = globalHistory.length; transitionManager.notifyListeners(history.location, history.action); }; var handlePopState = function handlePopState(event) { if ((0, _DOMUtils.isExtraneousPopstateEvent)(event)) return; handlePop(getDOMLocation(event.state)); }; var handleHashChange = function handleHashChange() { handlePop(getDOMLocation(getHistoryState())); }; var forceNextPop = false; var handlePop = function handlePop(location) { if (forceNextPop) { forceNextPop = false; setState(); } else { var action = 'POP'; transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) { if (ok) { setState({ action: action, location: location }); } else { revertPop(location); } }); } }; var revertPop = function revertPop(fromLocation) { var toLocation = history.location; // TODO: We could probably make this more reliable by // keeping a list of keys we've seen in sessionStorage. // Instead, we just default to 0 for keys we don't know. var toIndex = allKeys.indexOf(toLocation.key); if (toIndex === -1) toIndex = 0; var fromIndex = allKeys.indexOf(fromLocation.key); if (fromIndex === -1) fromIndex = 0; var delta = toIndex - fromIndex; if (delta) { forceNextPop = true; go(delta); } }; var initialLocation = getDOMLocation(getHistoryState()); var allKeys = [initialLocation.key]; var createHref = function createHref(location) { return basename + (0, _PathUtils.createPath)(location); }; var push = function push(path, state) { (0, _warning2.default)(!((typeof path === 'undefined' ? 'undefined' : _typeof(path)) === 'object' && path.state !== undefined && state !== undefined), 'You should avoid providing a 2nd state argument to push when the 1st ' + 'argument is a location-like object that already has state; it is ignored'); var action = 'PUSH'; // 重写location对象 var location = (0, _LocationUtils.createLocation)(path, state, createKey(), history.location); transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) { if (!ok) return; var href = createHref(location); var key = location.key, state = location.state; if (canUseHistory) { globalHistory.pushState({ key: key, state: state }, null, href); if (forceRefresh) { window.location.href = href; } else { var prevIndex = allKeys.indexOf(history.location.key); var nextKeys = allKeys.slice(0, prevIndex === -1 ? 0 : prevIndex + 1); nextKeys.push(location.key); allKeys = nextKeys; setState({ action: action, location: location }); } } else { (0, _warning2.default)(state === undefined, 'Browser history cannot push state in browsers that do not support HTML5 history'); window.location.href = href; } }); }; var replace = function replace(path, state) { (0, _warning2.default)(!((typeof path === 'undefined' ? 'undefined' : _typeof(path)) === 'object' && path.state !== undefined && state !== undefined), 'You should avoid providing a 2nd state argument to replace when the 1st ' + 'argument is a location-like object that already has state; it is ignored'); var action = 'REPLACE'; var location = (0, _LocationUtils.createLocation)(path, state, createKey(), history.location); transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) { if (!ok) return; var href = createHref(location); var key = location.key, state = location.state; if (canUseHistory) { globalHistory.replaceState({ key: key, state: state }, null, href); if (forceRefresh) { window.location.replace(href); } else { var prevIndex = allKeys.indexOf(history.location.key); if (prevIndex !== -1) allKeys[prevIndex] = location.key; setState({ action: action, location: location }); } } else { (0, _warning2.default)(state === undefined, 'Browser history cannot replace state in browsers that do not support HTML5 history'); window.location.replace(href); } }); }; var go = function go(n) { globalHistory.go(n); }; var goBack = function goBack() { return go(-1); }; var goForward = function goForward() { return go(1); }; var listenerCount = 0; var checkDOMListeners = function checkDOMListeners(delta) { listenerCount += delta; if (listenerCount === 1) { (0, _DOMUtils.addEventListener)(window, PopStateEvent, handlePopState); if (needsHashChangeListener) (0, _DOMUtils.addEventListener)(window, HashChangeEvent, handleHashChange); } else if (listenerCount === 0) { (0, _DOMUtils.removeEventListener)(window, PopStateEvent, handlePopState); if (needsHashChangeListener) (0, _DOMUtils.removeEventListener)(window, HashChangeEvent, handleHashChange); } }; var isBlocked = false; var block = function block() { var prompt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var unblock = transitionManager.setPrompt(prompt); if (!isBlocked) { checkDOMListeners(1); isBlocked = true; } return function () { if (isBlocked) { isBlocked = false; checkDOMListeners(-1); } return unblock(); }; }; var listen = function listen(listener) { var unlisten = transitionManager.appendListener(listener); checkDOMListeners(1); return function () { checkDOMListeners(-1); unlisten(); }; }; var history = { length: globalHistory.length, action: 'POP', location: initialLocation, createHref: createHref, push: push, replace: replace, go: go, goBack: goBack, goForward: goForward, block: block, listen: listen }; return history; };
既然知道了这个原理,我们来看下 react-router 的实现,我们打开 react-router 项目地址,把项目克隆下来,或则直接在 github 上预览,在 React 开发的项目里,我们通过 router.push(‘home’) 来实现页面的跳转,所以我们检索下,push 方法的实现
我们检索到了 46个 js 文件,😂,一般到这个时候,我们会放弃源码阅读,那么我们今天的文章就到这结束,谢谢大家!
开个玩笑,源码阅读不能这么粗糙,react-router 4.x用法,我们只需要安装 react-router-dom。所以我们找到 package 下的 react-router-dom/modules 目录,点开 index.js
文件。
export BrowserRouter from "./BrowserRouter"; export HashRouter from "./HashRouter"; export Link from "./Link"; export MemoryRouter from "./MemoryRouter"; export NavLink from "./NavLink"; export Prompt from "./Prompt"; export Redirect from "./Redirect"; export Route from "./Route"; export Router from "./Router"; export StaticRouter from "./StaticRouter"; export Switch from "./Switch"; export generatePath from "./generatePath"; export matchPath from "./matchPath"; export withRouter from "./withRouter";
看到 history 对象的实例与配置的 mode 有关,react-router-dom 通过3种方式实现了路由切换。我们今天讲的内容相匹配的是 createBrowserHistory
的实现方案 (另外两个分别是:createHashHistory和createMemoryHistory)。 这里 react-router-dom 将 BrowserRouter、HashRouter、Link 拆分为单独组件实现,也是与 react-router 3 之间的区别之一。
详细文档
我们来看 react-router-dom 中的 BrowserRouter 源码:
import warning from "warning"; import React from "react"; import PropTypes from "prop-types"; import { createBrowserHistory as createHistory } from "history"; import Router from "./Router"; /** * The public API for athat uses HTML5 history. */ class BrowserRouter extends React.Component { static propTypes = { basename: PropTypes.string, forceRefresh: PropTypes.bool, getUserConfirmation: PropTypes.func, keyLength: PropTypes.number, children: PropTypes.node }; history = createHistory(this.props); componentWillMount() { warning( !this.props.history, " ignores the history prop. To use a custom history, " + "use `import { Router }` instead of `import { BrowserRouter as Router }`." ); } render() { return ; } } export default BrowserRouter;
由上可知我们在项目中使用的
通过上面的学习,我们知道了,单页面应用路由的实现原理,我们也尝试去实现一个。在做管理系统的时候,我们通常会在页面的左侧放置一个固定的导航 sidebar,页面的右侧放与之匹配的内容 main 。点击导航时,我们只希望内容进行更新,如果刷新了整个页面,到时导航和通用的头部底部也进行重绘重排的话,十分浪费资源,体验也会不好。这个时候,我们就能用到我们今天学习到的内容,通过使用 HTML5 的 pushState 方法和 replaceState 方法来实现,
思路:首先绑定 click 事件。当用户点击一个链接时,通过 preventDefault 函数防止默认的行为(页面跳转),同时读取链接的地址(如果有 jQuery,可以写成$(this).attr(‘href’)),把这个地址通过pushState塞入浏览器历史记录中,再利用 AJAX 技术拉取(如果有 jQuery,可以使用$.get方法)这个地址中真正的内容,同时替换当前网页的内容。
为了处理用户前进、后退,我们监听 popstate 事件。当用户点击前进或后退按钮时,浏览器地址自动被转换成相应的地址,同时popstate事件发生。在事件处理函数中,我们根据当前的地址抓取相应的内容,然后利用 AJAX 拉取这个地址的真正内容,呈现,即可。
最后,整个过程是不会改变页面标题的,可以通过直接对 document.title 赋值来更改页面标题。
好了,我们今天通过多个方面来讲了 pushState 方法和 replaceState 的应用,你应该对这个两个方法能有一个比较深刻的印象,如果想要了解更多,你可以参考以下链接
前端路由是通过改变URL,在不重新请求页面的情况下,更新页面视图。
目前在浏览器环境中这一功能的实现主要有2种:
history 提供了两个方法,能够无刷新的修改用户的浏览记录,pushSate,和 replaceState,区别的 pushState 在用户访问页面后面添加一个访问记录, replaceState 则是直接替换了当前访问记录
history 对象的详细信息已经有很多很好很详细的介绍文献,这里不再做总结history对象
history.pushState方法接受三个参数,依次为:
state:一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null。
title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。
url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
假定当前网址是example.com/1.html,我们使用pushState方法在浏览记录(history对象)中添加一个新记录。
var stateObj = { foo: 'bar' }; history.pushState(stateObj, 'page 2', '2.html');
添加上面这个新记录后,浏览器地址栏立刻显示 example.com/2.html,但并不会跳转到 2.html,甚至也不会检查2.html 是否存在,它只是成为浏览历史中的最新记录。这时,你在地址栏输入一个新的地址(比如访问 google.com ),然后点击了倒退按钮,页面的 URL 将显示 2.html;你再点击一次倒退按钮,URL 将显示 1.html。
总之,pushState 方法不会触发页面刷新,只是导致 history 对象发生变化,地址栏会有反应。
如果 pushState 的 url参数,设置了一个新的锚点值(即hash),并不会触发 hashchange 事件。如果设置了一个跨域网址,则会报错。
// 报错 history.pushState(null, null, 'https://twitter.com/hello'); 上面代码中,pushState想要插入一个跨域的网址,导致报错。这样设计的目的是,防止恶意代码让用户以为他们是在另一个网站上。
history.replaceState 方法的参数与 pushState 方法一模一样,区别是它修改浏览历史中当前纪录,假定当前网页是 example.com/example.html。
history.pushState({page: 1}, 'title 1', '?page=1'); history.pushState({page: 2}, 'title 2', '?page=2'); history.replaceState({page: 3}, 'title 3', '?page=3'); history.back() // url显示为http://example.com/example.html?page=1 history.back() // url显示为http://example.com/example.html history.go(2) // url显示为http://example.com/example.html?page=3
Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.
1 | $ hexo new "My New Post" |
More info: Writing
1 | $ hexo server |
More info: Server
1 | $ hexo generate |
More info: Generating
1 | $ hexo deploy |
More info: Deployment