-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 103 KB
/
content.json
1
{"meta":{"title":"Hexo","subtitle":null,"description":null,"author":"missop","url":"https://missop.github.io","root":"/"},"posts":[{"tags":[{"name":"ts","slug":"ts","permalink":"https://missop.github.io/tags/ts/"}],"title":"TS是最好的语言(一)","date":"2020/12/26","text":"前言都快 2021 年了,TS 早就不是什么新技术,而是前端需要掌握的硬核知识,所以今天就从一些库入手开始探究 TS 的应用以及一些小技巧 看本篇文章之前需要的一些基础知识: JS 基础知识 熟悉 react-router TS 官方文档至少看一遍,TS 的基本的类型了熟于心 如果没有看过 TS 官方文档的话,可以先去看官方文档,也可以试玩一下 TS 编译器,链接如下: https://www.tslang.cn/docs/home.html react-router从古老的用法开始: 以前使用 react-router 中的 history 或者获取路由参数必须先将组件用 withRouter 包装,但是如果我们不声明 App 参数类型那么我们只能从参数中取到路由参数: 1234567// 示例一import React from \"react\";import { withRouter } from \"react-router\";const App = withRouter(({ history, match, a }) => { // ... a取不到,会报错 // 类型“RouteComponentProps<any, StaticContext, unknown> & { children?: ReactNode; }”上不存在属性“a”。ts(2339)}); 这一堆报错我们很懵先不管他,我们直接声明 App 的参数类型: 12345678910111213// 示例二import React from \"react\";import { withRouter } from \"react-router\";interface IProps {}const App = withRouter(({}: IProps) => { // 类型“({}: IProps) => any”的参数不能赋给类型“ComponentClass<RouteComponentProps<any, // StaticContext, unknown>, any> | FunctionComponent<RouteComponentProps<any, StaticContext, // unknown>> | (FunctionComponent<...> & ComponentClass<...>) | (ComponentClass<...> & // FunctionComponent<...>)”的参数。 // 不能将类型“({}: IProps) => any”分配给类型“FunctionComponent<RouteComponentProps<any, StaticContext, unknown>>”。 // 参数“__0”和“props” 的类型不兼容。 // 类型 \"RouteComponentProps<any, StaticContext, unknown> & { children?: ReactNode; }\" 中缺少属性 \"a\",但类型 \"IProps\" 中需要该属性。}); 即使什么都不写也是一堆报错,😨,此时真的不知道怎么办,TS 不知道怎么办的时候就点进去看代码,发现 withRouter 是有两个泛型参数的: 12345678910// RouteComponentProps是从react-router中声明的router参数类型,而ComponentType就是我们熟知的class组件或者函数组件,返回值我们先不管export function withRouter< P extends RouteComponentProps<any>, C extends React.ComponentType<P>>( component: C & React.ComponentType<P>): React.ComponentClass< Omit<P, keyof RouteComponentProps<any>> & WithRouterProps<C>> & WithRouterStatics<C>; 根据这个函数的声明是否能正确地写出 withRouter 的泛型参数呢? 显然是要使用 RouteComponentProps,它需要传进去我们的路由参数 12345678910export interface RouteComponentProps< Params extends { [K in keyof Params]?: string } = {}, C extends StaticContext = StaticContext, S = H.LocationState> { history: H.History<S>; location: H.Location<S>; match: match<Params>; staticContext?: C;} 所以我们声明两个类型,一个是函数本身接受的参数,一个是路由参数 1234567891011interface IProps { name:string}interface IRouteProps = RouteComponentProps<{ appId:string}>const App = withRouter<IRouteProps, FC<IRouteProps & IProps>>(({ name, match }) => { // appId可以获得到代码提示 // ... // const { params: { appId } } = match} 此时感受到了一点:从来没有像写 TS 这样的享受,能够把逻辑写的更加明确,代码更加潇洒,而且还有提示 还有一类组件,它们是路由渲染的组件,本身带有RouteComponentProps,所以不需要使用withRouter包装123const App:FC<IRouteProps & IProps>> = ({history,match,name})=>{} 现在 react-router 有了 hooks,我们完全可以直接使用 hooks 来获取 history对象 123const App:FC<IProps> = () => { const history = useHistory();}; 下面我们着重来看 withRouter 和 RouteComponentProps 的类型声明中的小技巧 小技巧 in keyof Params extends { [K in keyof Params]?: string } = {} 这两个经常成对出现,它能够把类型中的 key 提取出来,in 则类似于遍历所有 key 设置到类型中去,上面这段代码将 Params 所有的 value 设置为可选的 string 类型 Omit Omit<P, keyof RouteComponentProps<any>> & WithRouterProps<C> Omit 是 TS 内置的类型,它的作用就是剔除掉某个属性与值,第二个参数是要剔除的属性值,详细的讲解可以参考这篇文章: https://mariusschulz.com/blog/the-omit-helper-type-in-typescript","permalink":"https://missop.github.io/2020/12/26/ts-day1/","photos":[]},{"tags":[{"name":"js基础","slug":"js基础","permalink":"https://missop.github.io/tags/js基础/"}],"title":"函数","date":"2020/10/25","text":"函数的行为当有默认值或者有剩余参数时实参个数将不起作用,非简单参数是指参数有默认值或者使用剩余参数 12345678910111213function f(...args) { console.log(arguments.length, f.length);}f(1, 2, 3);// 3 0function f(a = 1, b, c) { console.log(arguments.length, f.length);}f(1, 2, 3);// 3 0 当 f()开始调用,js 会在函数上下文的预处理之后将calleeContext作为可执行帧推到执行上下文栈的栈顶=》入栈 1234567891011121314151617181920function f() {}function prev() { f(x, y, z);}// f调用的形式代码function prev() { // 切换执行上下文 var callerContext = _pick_running_excution_context(); var celleeContext = _Prepare(); // 处理thisArg _bindThis(f, celleeContext, thisArg); // 调用f(x,y,z) var result = _EvaluateBody(f, args); // 从执行上下文栈溢出f()的callerContext并恢复到prev()} 由于 js 引擎总是从执行上下文栈顶端取可执行帧来执行代码,所以入栈也就相当于切换执行指针 不过 ECMAScript 规范已经移除了通过下面的手段来访问这个栈的大多数说明 arguments.callee aFunction.caller aFunction.arguments 隐式调用console.log123; 语法解析层面会将此代码解析为一个函数的调用,由于模板字面量所以参数在语法分析阶段就完成了它作为参数的预处理过程。 真正执行这段代码时,引擎从环境中取出了这个参数并执行一次,由于上下文不同而得到参数值的一个全新副本:对实际参数求值并绑定给形参 最后按照约定,引擎将这个副本作为一个称为、调用点数组(带有一个 raw 属性的 array-like 对象)、的参数传递给左侧函数已完成函数调用运算传入 foo() 类似隐式调用情况还包括 函数作为属性读取器,属性存取将隐式调用该函数 .bind() Proxy 创建原函数代理对象 new 操作符 将函数赋给对象的符号属性,Symbol.hasInstance,Symbol.iterator,在对象相应行为触发时调用该函数 callee:我是谁caller:谁调用我,调用栈 函数调用过程预处理绑定 this执行代码 闭包记录函数实例在运行期的可访问标识符的结构 一个函数实例的一次执行就会带来一个新的执行期作用域=>闭包 而在执行代码看来,它就是执行期的作用域链,因其外部引用指向它被调用时的作用域 函数实例 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748// foo的函数实例有两个,f1和f2function foo() { return function myFun() { // };}var f1 = foo();var f2 = foo();// 同一个函数中的所有子函数访问相同值的upvaluefunction MyFoo() { var data = 100; function func_1() { data = data * 5; } function func_n() { console.log(data); } func_1(); func_n();}MyFoo();// 多个闭包同时存在var checker;function myFunc() { if (checker) { checker(); } console.log(\"do MyFunc\" + str); var str = \"test.\"; if (!checker) { checker = function () { console.log(\"do MyFunc\" + str); }; } return arguments.callee;}myFunc()(); 函数被重写 12345function F(num) { F = num + 1;}F(10) // 11 函数表达式的特殊性12345678var msg = (function myFunc(num) { return (myFunc = typeof myFunc); })(10) + \", and upvalue's type is :\" + typeof myFunc; // \"function, and upvalue's type is :function\" js在为函数表达式构建闭包时使用了双层作用域,外层作用域称为函数环境,其中只有一个简单的标识符,之后才是该函数的作用域 const/let声明变量时,标识符时直接创建在词法作用域中的,与global对象无关使用var声明的变量会加入[[varNames]]列表,[[varNames]]用来表明那些声明过的变量名与函数名 对象闭包1234var obj = { a:1 }with(obj){ a++}","permalink":"https://missop.github.io/2020/10/25/函数/","photos":[]},{"tags":[],"title":"js语言的结构化","date":"2020/10/25","text":"语法元素语法元素的组织含义: 元素 物理形态 静态 动态 标识符 let/const/var函数声明类声明 非严格模式下的var非严格模式下的类声明 表达式 模板字符串 值箭头函数体 通过eval执行表达式语句实现 语句 .js文件 块与块级作用域 eval() 模块 .mjs文件 import/exportrequire import().then() 标识符(identifier)源代码文本 = 空白字符 + 词法记号(Tokens)序列 词法记号分为两类: 确定书写格式的:语言预设的标点符号和部分用作保留字的标识符名 由用户定义的标识符,字面量,模板等 表达式名字与值之间的计算 !表达式不能独立于语句而存在,即使是单个表达式也成为表达式语句 字面量 对象:{…} 数组:[…] 正则:/…/ 初始器字面量不包含运算过程而初始器包括 5种字面量语法在出现(编译期)的同时就是确知的:null、true/false、数值、字符串、正则表达式 而数组与对象则不行,反而必须是一个运算的结果 语句声明语句与非声明语句 模块","permalink":"https://missop.github.io/2020/10/25/js语言的结构化/","photos":[]},{"tags":[],"title":"micro-frontend(二) qiankun","date":"2020/08/09","text":"微前端要解决什么问题?子应用如何定义和使用?Single-spa官网[https://single-spa.js.org/docs/getting-started-overview/#simple-usage] Single-spa中实现了子应用的加载和卸载 Single-spa要求我们在主应用定义app,子应用导出boostrap,mount,unmount方法 主应用:12345678910111213import * as singleSpa from 'single-spa';const name = 'app1';/* The app can be a resolved application or a function that returns a promise that resolves with the JavaScript application module. * The purpose of it is to facilitate lazy loading -- single-spa will not download the code for a application until it needs to. * In this example, import() is supported in webpack and returns a Promise, but single-spa works with any loading function that returns a Promise. */const app = () => import('./app1/app1.js');/* single-spa does some top-level routing to determine which application is active for any url. You can implement this routing any way you'd like. * One useful convention might be to prefix the url with the name of the app that is active, to keep your top-level routing simple. */const activeWhen = '/app1';singleSpa.registerApplication({ name, app, activeWhen });singleSpa.start(); 子应用1234567891011121314151617181920212223242526let domEl;export function bootstrap(props) { return Promise .resolve() .then(() => { domEl = document.createElement('div'); domEl.id = 'app1'; document.body.appendChild(domEl); });}export function mount(props) { return Promise .resolve() .then(() => { // This is where you would normally use a framework to mount some ui to the dom. See https://single-spa.js.org/docs/ecosystem.html. domEl.textContent = 'App 1 is mounted!' });}export function unmount(props) { return Promise .resolve() .then(() => { // This is normally where you would tell the framework to unmount the ui from the dom. See https://single-spa.js.org/docs/ecosystem.html domEl.textContent = ''; })} 乾坤借鉴了single-spa注册子应用的方式,但是它支持传入html或者前端资源地址 如何动态加载?子应用导出umd,主应用按照配置去加载资源 如何隔离?执行 js 资源时通过 eval,会将 window 绑定到一个 Proxy 对象上,以防污染全局变量,并方便对脚本的 window 相关操作做劫持处理,达到子应用之间的脚本隔离。 而样式隔离则是通过在 unmount 时卸载样式表自然地做到","permalink":"https://missop.github.io/2020/08/09/micro-frontend2/","photos":[]},{"tags":[],"title":"查询参数的秘密","date":"2020/06/20","text":"前言最近做了一个项目,由于是Electron嵌入web应用,因此进入web应用时的地址栏参数是不可控的,web应用在路由跳转的时候顺带了一些查询参数,像这样: #/home?title=xxxx&id=12345, 通过withrouter之后的location.search拿到了 ?title=xxxx&id=12345 但是在Electron中它的地址是这样的: ?jkljkll#/home??title=xxxx&id=12345 于是便拿不到我们需要的值了,此时我们又需要修改很多代码,来适应这个地址,那么又没有一种最好的办法能够不修改代码,在之前的应用开发中就考虑到所有情况呢? 从location开始思考search在前hash在后,location.search能够直接拿到查询参数先随便输入一个地址,我们只需要关注hash和search即可https://www.jianshu.com/p/33deff6b8a63?a=1231313#123213 location 对象可以得到以下信息:hash: “#123213” pathname: “/p/33deff6b8a63” search: “?a=1231313” 当search在前,hash在后面时,此时 hash 和 search 是分开的,location.search能够直接拿到查询参数 当search在hash后面,search必须从location.hash中做处理后才能取到变化一下如果在地址后面又添加查询参数?a=1&b=2https://www.jianshu.com/p/33deff6b8a63?a=1231313#123213?a=1&b=2 hash: “#123213?a=1&b=2” https://www.jianshu.com/p/33deff6b8a63#123213?a=1&b=2 hash: “#123213?a=1&b=2” search: “” https://www.jianshu.com/p/33deff6b8a63#123213?a=1&b=2dsad##34e24 当hash出现,它就吞并了所有内容,连查询参数也无法显示出来,全部都在location.hash中 我们能把search放在前面吗在单页应用的世界里,如果每次跳转都将search放在前面那么就会导致页面刷新,这样的话就违背了单页的原则 如果是多页应用跳转的话则可以将search放在前面 VueRouter和ReactRouter对于地址栏参数的解析方式VueRouter能够快速解析出hash中的search,而react-router和原生的hash一样,必须自己去处理 但是对于路由参数两者都能快速解析出来 Vue Router能够将 location.search 从 location.hash 解析出来,但是它只能解析hash中的search https://5q3oq.csb.app/#/?a=123213 this.$route.query // {a: "123213"} Vue Router 里面的 query 类似于请求中的 get,而 params 类似于请求中的 post 能够不在地址栏显示就能够完成参数传递 React Routerconst reactHistory = useHistory() useHistory 创建的对象,它的 location 属性与原生基本没有差别 123456789location:hash: \"#1231?23213\"pathname: \"/\"search: \"?213123\"state: undefinedpush: ƒ push(path, state)replace: ƒ replace(path, state) const reactLocation = useLocation() location,使用 hooks 或者 withRouter,可以拿到,基本与 history.location 相同 1234hash: '#1231?23213'pathname: '/'search: '?213123'state: undefined const reactParams = useParams() 路由参数 12345678<Route path='/about/:id'> <About /></Route><Link to=\"/about/1\">About</Link>console.log(reactParams){id: \"1\"} 查询参数的最佳方案尽量少地添加查询参数,或者全部使用路由参数在Vue中可以使用类似与POST的方式传参,比较优雅,地址栏不会有乱七八糟的东西 对于React就使用多个路由参数,这样的缺点是必须按照顺序一一对应 /?:id/?:title/?:bb 天塌下来也从hash中取,保证hash中只有一个?最后希望和大家一起讨论更好的方案,说的不对的地方清大家指出来","permalink":"https://missop.github.io/2020/06/20/location/","photos":[]},{"tags":[],"title":"回溯算法","date":"2020/05/08","text":"一道算法题输入一个字符串,按字典序打印出该字符串中字符的所有排列,例: 输入abc,打印abc,acb,bac,bca,cab,cba","permalink":"https://missop.github.io/2020/05/08/回溯算法/","photos":[]},{"tags":[],"title":"Hexo Created Blog","date":"2020/05/07","text":"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. Quick Start开启Github Pages当你的仓库名为:用户名.github.io 之后默认开启Github Pages必须是这种格式,否则无法开启 安装Hexo1234567npm install hexo-cli -g hexo init #初始化网站 npm install hexo g #生成或 hexo generate hexo s #启动本地服务器 或者 hexo server,这一步之后就可以通过http://localhost:4000 查看了More info: [Server](https://hexo.io/docs/server.html) 创建页面和文章12hexo new "文章名" #新建文章hexo new page "页面名" #新建页面 常用简写1234hexo n == hexo newhexo g == hexo generatehexo s == hexo serverhexo d == hexo deploy 修改配置(_config.yml)注意,key与value之间需要空格,否则报错:can not read a block mapping entry; a multiline key may not be an implicit key…. 部署到线上1hexo d","permalink":"https://missop.github.io/2020/05/07/hello-world/","photos":[]},{"tags":[],"title":"nginx配置https","date":"2020/04/27","text":"视频中要调取麦克风,摄像头必须 https 协议,所以必须要将网页配成 https,配 https 网页有以下两个方法 nginx 转发首先需要生成公钥和私钥: 123openssl genrsa -out privkey.pem 1024openssl req -new -x509 -key privkey.pem -out server.pem -days 365 配置 https 12345678910111213141516server { listen 443 on; server_name localhost; ssl_certificate /usr/local/etc/nginx/server.pem; ssl_certificate_key /usr/local/etc/nginx/privkey.pem; ssl_session_timeout 5m; ssl_protocols SSLv2 SSLv3 TLSv1; ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP; ssl_prefer_server_ciphers on; charset koi8-r; location / { proxy_pass http://127.0.0.1:8090; } } webpack需要配置以下一个属性,但是没有起作用https:true","permalink":"https://missop.github.io/2020/04/27/nginx配置https/","photos":[]},{"tags":[],"title":"mac下的nginx安装","date":"2020/04/26","text":"mac 下的 nginx 安装有两条路: brew 集成安装 编译安装 下面分别介绍两种安装方式 brew 集成安装brew 类似于 centOS 的 yum 以及 Ubantu 的 apt-get,是 mac 的必备神器 然而 brew 的安装并不是一帆风顺的: 第一步:通过命令删除之前的 brew、创建一个新的 Homebrew 文件夹 123sudo rm -rf /usr/local/Homebrewsudo mkdir /usr/local/Homebrew 第二步:git 克隆(速度还是不好看文章尾部的扩展说明-1) 1sudo git clone https://mirrors.ustc.edu.cn/brew.git /usr/local/Homebrew 第三步:删除原有的 brew,创建一个新的 123sudo rm -f /usr/local/bin/brewsudo ln -s /usr/local/Homebrew/bin/brew /usr/local/bin/brew 第四步:创建 core 文件夹、克隆 123sudo mkdir -p /usr/local/Homebrew/Library/Taps/homebrew/homebrew-coresudo git clone https://mirrors.ustc.edu.cn/homebrew-core.git /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core 第五步:删除之前 brew 环境,重新创建: 12345sudo rm -rf /usr/local/var/homebrew/sudo mkdir -p /usr/local/var/homebrewsudo chown -R $(whoami) /usr/local/var/homebrew 最后一步:获取权限 运行更新(三句话分开运行) 12345sudo chown -R $(whoami) /usr/local/HomebrewHOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottlesbrew update 最后设置:设置环境变量,再运行下面两句后,重启终端: 123echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles' >> ~/.zshrcecho 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles' >> ~/.bash_profile 增加文件操作权限 1sudo chown -R $(shoami) /usr/local/var/homebrew 如果没有文件则手动创建文件,然后修改权限 123sudo mkdir /usr/local/var/homebrewsudo chown -R $(shoami) /usr/local/var/homebrew 镜像源三选一: https://mirrors.ustc.edu.cn/ https://mirrors.aliyun.com/homebrew/ https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/ 最后:brew install nginx brew的nginx命令: brew services start nginx brew services stop nginx brew覆盖编译安装由于之前编译安装失败,故采用此方法安装,还是有一些坑要踩 brew update报成功并不代表成功 brew doctor发现失败,马上删除之前的再执行一下就可以了 brew link openssl –force 覆盖之前不成功的openssl安装 编译安装–巨坑需要下载四个文件,当然核心是 nginx,其他自选 nginx pcre openssl(https必备) zlib 基本都是下载完成之后./configure或者./config编译 然后make&make install安装 这样安装之后会出现如下问题 nginx找不到ssl报如下错误,网上的方法几乎一样,都无法解决问题 unknown directive “ssl” nginx,openssl的快捷命令需要自己设置 创建快捷命令sudo ln -s /usr/local/nginx/sbin/nginx /usr/local/bin/nginx","permalink":"https://missop.github.io/2020/04/26/mac下的nginx安装/","photos":[]},{"tags":[],"title":"SFF是个什么鬼","date":"2020/04/12","text":"前端发展历程说到 SFF,就不得不回到一二十年前,那时候一片混沌,前端后端揉一块儿,后来前端横空出世,他们的武器就是前端三剑客:html+css+js,主要任务就是切图,做一些简单页面 后来业务越来越复杂,不得不使用框架帮助开发者来操作 DOM,提高性能 再后来你猜怎么着?开始出现了 BFF 架构,BFF 即 Backend For Frontend,用 Nodejs 作为基础,前端开始涉足后端了(其实也不算,只是分担了他们的工作),BFF 主要解决了 1. 多端应用 API 复用,可直接通过 Node 服务器转发,不需要后端写多套代码 2.服务聚合 微服务,能够提供配置项 3. SSR BFF 层可以做: 权限控制、应用缓存(离用户近)、第三方入口 但是 BFF 会增加开发成本 可不可以有不增加开发成本的方式呢?于是出现 serverless,对于一些小程序类应用尤其适合,而且按需计费,这就是 SFF,Server For Frontend,把运维的事情都帮我们处理了,我们无需关心部署等等复杂的问题 serverless云函数–为前端页面提供接口(Nodejs 也可以其他语言),下面以腾讯云函数为例: 函数配置 运行环境: Nodejs8.9 或者以上,可以在创建函数时选择 超时时间: 180s,我设置为三分钟,默认为 3s,一般查询数据库可能会超时,需要注意 所属网络: Default,需要和 MySql 数据库属于一个内网环境才可以访问 所属子网: 同上 触发方式,可以添加定时任务,我们也可以添加网关触发器,来测试后端接口是否可以使用","permalink":"https://missop.github.io/2020/04/12/SFF是个什么鬼/","photos":[]},{"tags":[],"title":"eslint爱恨情仇","date":"2020/03/21","text":"前言eslint 的配置网上很多,就不啰嗦,本文主要讲一个 eslint 使用过程中遇到的问题 先了解一下 eslint 在哪里配置: 在 package.json 中eslintConfig字段配置 .eslintrc.*中配置 官网第一个例子这是官网第一个例子,就是末尾分号与单引号的校验,然而这里面水很深 123456{ \"rules\": { \"semi\": [\"error\", \"always\"], \"quotes\": [\"error\", \"double\"] }} 一般我们的配置都是单引号和不要末尾分号,然鹅在我们格式化的时候(Vue): 网上的解决方案: 12345678910111213141516{ \"prettier.tabWidth\": 2, // #去掉代码结尾的分号 \"prettier.semi\": false, // #使用带引号替代双引号 \"prettier.singleQuote\": true, \"prettier.jsxSingleQuote\": true, \"vetur.format.defaultFormatter.js\": \"vscode-typescript\", // vetur默认使用ts格式化 \"vetur.format.defaultFormatter.html\": \"js-beautify-html\", \"vetur.format.options.tabSize\": 2, \"vetur.format.defaultFormatterOptions\": { \"prettier\": { \"semi\": false, // 格式化不加分号 \"singleQuote\": true // 格式化以单引号为主 }} 我一股脑全部设置之后依然没有什么用,然后我打开 vscode 控制台发现每次格式化的时候并没有读取这个配置: 1234567[\"INFO\" - 11:21:05 AM] Prettier Options:{ \"filepath\": \"/Users/zhuxiansheng/work/彩云h5项目/h5-agg-bagui/src/reverse-order/App.vue\", \"parser\": \"vue\", \"useTabs\": false, \"tabWidth\": 2} 这是哪里的配置呢? .editorconfig 中的配置,但是它里面不能设置分号和引号这些,所以只能创建一个 .prettierrc 123456{ \"trailingComma\": \"es5\", \"tabWidth\": 4, \"semi\": false, \"singleQuote\": true} 好了,现在就可以与 eslint 一样地格式化了 另外,我猜测是.editorconfig 影响了 prettier 格式化,然后在配置文件中将 vue 删掉也能够完美解决这个问题 总结: vue 格式化时需要配置如下 json,并且在.editorconfig 删掉 vue 或者添加.prettierrc 文件单独配置当前文件的格式 12345678910111213141516{ \"prettier.tabWidth\": 2, // #去掉代码结尾的分号 \"prettier.semi\": false, // #使用带引号替代双引号 \"prettier.singleQuote\": true, \"prettier.jsxSingleQuote\": true, \"vetur.format.defaultFormatter.js\": \"vscode-typescript\", // vetur默认使用ts格式化 \"vetur.format.defaultFormatter.html\": \"js-beautify-html\", \"vetur.format.options.tabSize\": 2, \"vetur.format.defaultFormatterOptions\": { \"prettier\": { \"semi\": false, // 格式化不加分号 \"singleQuote\": true // 格式化以单引号为主 }}","permalink":"https://missop.github.io/2020/03/21/eslint爱恨情仇/","photos":[]},{"tags":[],"title":"每周一个手写代码:数组扁平化","date":"2020/03/15","text":"题目 const arr = [[1, 2, 2],[3, 4, 5, 5],[6, 7, 8, 9, [11, 12, [12, 13, [14]]]],10]编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组 网友的答案 Array.prototype.flatten = function () { return Array.from(new Set(this.flat(Infinity))).sort((a, b) => a - b)} 关键方法: Array.form、Array.prototype.flat、Array.prototype.sort 关键技能:去重、排序、扁平化 Array.fromArray.from(arrayLike [, mapFn [, thisArg]]) 作用:伪数组=》数组 或者 模拟map Array.prototype.flat语法 arr.flat([depth]); 需要无限展开的话 arr.flat(Infinity) 模拟实现一个数组扁平化方法 思路一:reduce+concat 例如:先实现一个简单的展开:[1, 2, [3, 4]] 可以这样 A:arr.reduce((acc, val) => acc.concat(val), []) 也可以这样 B:[].concat(...arr) 必须 val 是一维数组或者简单类型,无法判断是否是一维,所以只能判断 val 为简单类型则使用 concat 1234567891011function flatDeep(arr, d = 1) { return d > 0 ? arr.reduce( (acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), [] ) : arr.slice()}console.log(flatDeep(arr, Infinity)) 思路二:使用数组的栈方法 123456789101112131415161718// 不能控制展开次数// 数组末尾的操作效率比前面高,因此不使用shift/unshiftfunction flatten(input) { const stack = [...input] const res = [] while (stack.length) { // 从末尾截取数据 const next = stack.pop() if (Array.isArray(next)) { // 拷贝 stack.push(...next) } else { res.push(next) } } // 反向 return res.reverse()} 思路三:使用生成器函数 123456789101112131415function* flatten(array, depth) { if (depth === undefined) { depth = 1 } for (const item of array) { if (Array.isArray(item) && depth > 0) { yield* flatten(item, depth - 1) } else { yield item } }}const arr = [1, 2, [3, 4, [5, 6]]]const flattened = [...flatten(arr, Infinity)] Array.prototype.sort参考快速排序:https://missop.github.io/2019/11/14/quickSort/ 参考插入排序:https://missop.github.io/2019/11/15/insertSort/","permalink":"https://missop.github.io/2020/03/15/每周一个手写代码-数组扁平化/","photos":[]},{"tags":[],"title":"Grid布局","date":"2020/02/19","text":"Grid布局入门基本概念网格容器,网格轨道(grid-template-columns,grid-template-rows),fr单位,repeat方法 轨道大小和minmaxgrid-auto-rows: minmax(100px, auto)如下图:第二个元素被撑开,大于100px,这是行自适应,那么列呢?列的自适应后面再说,与行不同 跨轨道放置元素–相当于合并单元格,规定从哪一个单元格开始哪一个结束 grid-column-start, grid-column-end, grid-row-start , grid-row-end grid-column:1/4 => grid-column-start:1;grid-column-end:4 网格间距grid-column-gap,grid-row-gap grid-gap:10px 20px; => grid-column-gap:10px;grid-row-gap:20px grid vs flex如果只需要横向或者纵向布局则使用flex,如果都需要则使用grid flex布局时设置flex-wrap: wrap;可以使得元素自动换行此时元素并不能对齐,这时就需要grid了 使用auto-fill和minmax()函数实现同样的功能1234.wrapper { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));} fr与flex-basic:布局基本单位 绝对定位如果网格容器设置了相对定位,网格容器子元素的绝对定位根据它所占的网格进行定位 display: contentsdisplay: contents在flex布局和grid布局中都起作用 它可以让子元素参与布局12345678910111213<!-- grid/flex布局容器 --><div> <div style=\"display:content;\"> <!-- 子元素可以参与网格布局 --> <div></div> <div></div> <div></div> <!-- --> </div> <div></div> <div></div> <div></div></div>","permalink":"https://missop.github.io/2020/02/19/Grid布局/","photos":[]},{"tags":[],"title":"uform之布局篇","date":"2020/02/18","text":"默认布局首先看一下默认的布局: 贴上这段代码直接在codesandbox中运行 参考地址:https://codesandbox.io/s/hopeful-snowflake-fiirt参考效果:123456789101112131415161718192021222324import React from \"react\";import ReactDOM from \"react-dom\";import { SchemaForm, SchemaMarkupField as Field, FormButtonGroup, Submit, Reset} from \"@uform/next\";import Printer from \"@uform/printer\";import \"@alifd/next/dist/next.css\";const App = () => ( <Printer> <SchemaForm> <Field name=\"aaa\" type=\"string\" title=\"字段1\" /> <Field name=\"bbb\" type=\"number\" title=\"字段2\" /> <Field name=\"ccc\" type=\"date\" title=\"字段3\" /> <FormButtonGroup offset={8}> <Submit>提交</Submit> <Reset>重置</Reset> </FormButtonGroup> </SchemaForm> </Printer>);ReactDOM.render(<App />, document.getElementById(\"root\")); 查看SchemaForm源码: 12345<div> <div> <form class=\"ant-form ant-form-horizontal\"></form> </div></div> schemaForm渲染为以下结构,为什么不支持添加className??? 只能使用外层的子代选择器修改样式 schemaForm上面没有布局样式 查看<Field name="aaa" type="string" title="字段1" />源码:基本上就是antd的form表单样式123456789101112<div class=\"ant-row ant-form-item\"> <div class=\"ant-col ant-form-item-label\"> <label class=\"\" title=\"字段1\">字段1</label> </div> <div class=\"ant-col ant-form-item-control-wrapper\"> <div class=\"ant-form-item-control\"> <span class=\"ant-form-item-children\" ><input type=\"text\" class=\"ant-input\" value=\"\" /></span> </div> </div></div> 可以看见有部分样式名是唯一的,我们可以根据他们去修改样式:12345678910.ant-form.ant-form-horizontal{ > .ant-form-item{ > .ant-form-item-label{ // label样式 } > .ant-form-item-control-wrapper{ // 表单项样式 } }} 内联布局<SchemaForm inline>–display:inline-block12345.ant-form-inline .ant-form-item { display: inline-block; margin-right: 16px; margin-bottom: 0;} 可以自动换行 可以使用wrapperCol={16} labelCol={8}但是有时候样式较为怪异 layout布局组件与默认布局一样,不同的是他可以进行局部布局控制 设置标题与表单的宽度:wrapperCol={8} labelCol={6}–float布局 1234/* 宽度是用的百分比 */.ant-form-item-label,.ant-form-item-control-wrapper{ float:left;} 其内部是浮动的,容易导致容器塌陷,解决方法就是在父元素上加一个overflow:hidden Sticky-按钮吸底<FormButtonGroup sticky></FormButtonGroup> nested-嵌套布局 1.FormLayout 局部控制 labelCol/wrapperCol/size/labelAlign/labelTextAlign 2.FormCard 卡片式分离表单模块 3.FormBlock 在卡片内部的区块化分割 4.FormItemGrid 表单字段的局部网格布局能力 5.FormTextBox 表单文本化串联 只要使用到了以上的组件,那么就不适合使用schema对象直接生成表单了,因为要添加很多无意义的结构 FormCard+FormLayout: 首先普及一个condole.log中的输出类型: %s for a String value 字符类型 %d or %i for a Integer value 整型 %f for a Floating point number 浮点类型number %o for an Object hyperlink 对象类型超链接 看下面的嵌套表单: 要横排多个就用FormItemGrid1234<FormItemGrid gutter={10} cols={[6, 11]}> <Field name=\"ddd1\" default={123} type=\"number\" /> <Field name=\"[startDate,endDate]\" type=\"daterange\" /></FormItemGrid> 当列表项目个数不确定时实现分组的方案:123456789101112131415161718192021222324252627282930313233343536373839404142434445const keys = [ { name: \"name\", type: \"string\", title: \"姓名\" }, { name: \"password\", type: \"string\", title: \"密码\" }, { name: \"email\", type: \"string\", title: \"邮箱\" }, { name: \"[start,end]\", type: \"daterange\", title: \"查询时间\" }, { name: \"money\", type: \"number\", title: \"金额\" } ];{keys.reduce((prev, current, index) => { const i = window.parseInt(index / 3); if (index % 3 === 0) { prev.push([]); } prev[i].push(current); return prev;}, []).map((item, index) => { return ( <FormItemGrid gutter={10} cols={[6, 6, 6]} key={index}> {item.map((innerItem, index) => { return <Field {...innerItem} key={index} />; })} </FormItemGrid> );})} 发现会生成这样的表单: 也就是说:只有input默认是设置的占满宽度,其他类型需要自己去设置 还有一种方式就是直接在默认的布局上面改为Grid布局,也能实现相似的效果,详情见Grid布局篇 要显示一条横线区分开来就用FormBlock 把表单与文本串联:FormTextBox12345<FormTextBox title=\"文本串联\" text=\"订%s元/票 退%s元/票 改%s元/票\"> <Field type=\"number\" default={10} required name=\"aa1\" /> <Field type=\"number\" default={20} required name=\"aa2\" /> <Field type=\"number\" default={30} required name=\"aa3\" /></FormTextBox>","permalink":"https://missop.github.io/2020/02/18/uform之布局篇/","photos":[]},{"tags":[],"title":"疫情项目若干思考","date":"2020/02/14","text":"2020nocv项目背景:由于疫情的扩散,不得不启动线上的信息收集和企业复工申请 项目安排:由于白天客户要看成果提要求,所以一般到下午才能提出需求,导致开发时间紧促,经常晚上加班 这样就对开发人员提出了较高的要求,要在睡眠不足的前提下保质保量地完成任务,确实很艰难,如今项目已经大体完成,想把项目中的若干思考以文章形式呈现出来 版本问题由于测试和线上均为主项目版本,也就是说版本是固定的,因此如果本地包版本与其不同的话,很有可能本地可以使用而线上不行 例如antd的upload组件的disabled属性,我们可以查看changelog: 3.19.0修复 Upload 列表在 disabled 时仍然可以移除的问题 关于uform查看uform思考一栏 upload组件封装upload组件中不要加其他内容,否则点击之后也会触发文件选择 导入模板,直接使用通用上传接口上传之后下载fileName需不需要decode 导出问题导出需要考虑服务器成本,例如导出500个人的记录,且都需要打包为压缩包,此时服务器压力就很大了 分页与筛选条件方式一:默认参数不传pageIndex,则请求第一页,请求方法拿到所有参数请求之后再修改state ps:hooks用这个方式比较简单1234567891011// classfetchList=(params)=>{ params.pageIndex = params.pageIndex||1 sevive.getList(params) .then(res=>{ this.setState({ ...params, dataSource:res }) })} 方式二:每次先设置这个state,请求参数统一从state获取 ps:如果筛选条件比较多,建议这个方式,否则所有的参数判断逻辑容易放到fetchList中造成混乱12345678910// classthis.setState({ pageIndex:1},this.fetchList)// hooks中没有回调怎么办,用useEffectsetPageIndex(1)useEffect(()=>{ this.fetchList()},[pageIndex]) 存在的问题:后端对于默认值的判断需要提前约定,比如时间传-1则为全部,字符串则传空或者undefined 我们遇到这样一个问题,筛选条件很多,但是后端对于空字符串和undefined都无法判断,那么这样的话必须前端对每一个字段判断,然后添加或者删除掉不需要的参数 通用方法删除对象空值1234567891011 const filters = [null, undefined,NaN, \"\"]; const cleanObj = params => Object.keys(params).forEach(key => { if (filters.indexOf(params[key]) !== -1) { _.unset(params, key); } });// 用indexOf不能去掉NaN// 需要改为includesfilters.includes(params[key]) 疫情还没结束,我们一起在努力!!!!!","permalink":"https://missop.github.io/2020/02/14/疫情项目若干思考/","photos":[]},{"tags":[],"title":"jest入门","date":"2020/02/04","text":"jest语法匹配器普通匹配器 toBe–精准匹配简单类型数据 toBe的检测使用的是Object.is(),无法检测对象 toEqual–匹配对象 1234567891011// toBetest('one plus one equals two', () => { expect(1+1)toBe(2)})// toEqualtest('object set', () => { const data = { success: true, total: 5 } data['total'] = 6 expect(data).toEqual({ success: true, total: 6 })}) toStrictEqual 匹配边界情况 toBeNull–>null toBeUndefined–>undefined toBeTruthy–>true toBeFalse–>false 数字匹配器 toBeGreaterThan toBeGreaterThanOrEqual toBeLessThan toBeLessThanOrEqual toBeCloseTo(判断两个浮点数是否相等) 123456789101112131415test('a is greater than 5', () => { const a = 6 expect(a).toBeGreaterThan(5)})// failedtest('a is 6.2', () => { const a = 6.2 expect(a).toBeEqual(6.2)})// successtest('a is 6.2', () => { const a = 6.2 expect(a).toBeCloseTo(6.2)}) 字符串toMatch(使用正则测试字符串)1234// not是取反test('there is no I in team', () => { expect('team').not.toMatch(/I/)}) 数组包含某项toContain1234test('shopping list contains apple', () => { const shoppingList = ['apple'] expect(shoppingList).toContain('apple')}) 测试异步代码测试用例一定要在测试对象结束之后才能够结束 异步中调用回调函数 使用单个参数调用done,而不是将测试放在一个空参数的函数中,Jest会等done回调函数执行结束后,结束测试。12345678910111213function fetchData (call) { setTimeout(() => { call('apple') }, 20)}test('received data apple', (done) => { function callback (data) { expect(data).toBe('apple') done() } fetchData(callback)}) Promise 测试必须返回一个Promise 确保添加了expect.assertions来验证一定数量的断言被调用1234567891011121314151617function fetchData () { return new Promise((resolve, reject) => { setTimeout(() => { resolve('apple') }, 20) })}test('received data apple', () => { expect.assertions(1) return fetchData().then(data => { expect(data).toBe('apple') }) // 直接使用resolves/rejects return expect(fetchData()).resolves.toBe('apple')}) error捕捉123456789101112131415function fetchData () { return new Promise((resolve, reject) => { setTimeout(() => { reject('error') }, 20) })}test('fetch failed with an error', () => { expect.assertions(1) return fetchData().catch(e => expect(e).toMatch('error')) // 直接使用resolves/rejects return expect(fetchData()).rejects.toMatch('error')}) Async/Await 没有什么特别之处,与js函数调用相同1234567891011121314test('the data is peanut butter', async () => { expect.assertions(1); const data = await fetchData(); expect(data).toBe('peanut butter');});test('the fetch fails with an error', async () => { expect.assertions(1); try { await fetchData(); } catch (e) { expect(e).toMatch('error'); }}); 测试准备工作 为多次测试重复设置 beforeEach/afterEach 例如我们需要在每次测试之前调用init,测试后调用destroy 如下每个测试都会输出1和destroy123456789101112function init () { console.log(1)}function destroy () { console.log('destroy')}beforeEach(() => { init()})afterEach(() => { destroy()}) 作用域 describe可将测试分组,组内的beforeEach/afterEach比外面的后执行","permalink":"https://missop.github.io/2020/02/04/jest入门/","photos":[]},{"tags":[],"title":"每周一个手写代码(hack setTimeout)","date":"2020/01/25","text":"第一周 0202大年初一使用requestAnimationFrame模拟setTimeout 首先需要计算时间确定requestAnimation的callback的执行时机,123456789101112131415const hackSetTimeout = (callback, interval) => { const timeStart = Date.now(); console.log(\"hackSetTimeout invoked\"); requestAnimationFrame(function call() { const timeEnd = Date.now(); if (timeEnd - timeStart < interval) { requestAnimationFrame(call); } else { callback(); } });};// testhackSetTimeout(() => console.log(1), 2000); 其次就是clearTimeout12345678910111213141516171819const hackSetTimeout = (callback, interval) => { const timeStart = Date.now(); console.log(\"hackSetTimeout invoked\"); let id = requestAnimationFrame(function call() { const timeEnd = Date.now(); if (timeEnd - timeStart < interval) { id = requestAnimationFrame(call); } else { callback(); } }); return id;};const hackClearTimeout = id => { cancelAnimationFrame(id);};const id = hackSetTimeout(() => console.log(1), 2000);hackClearTimeout(id); 如果不使用cancelAnimationFrame的话就比较复杂了: 首先只能在caller函数中判断某个条件然后退出执行 这个条件就是一个id,在创建requestionFrame的时候生成,也就是需要一个caller的数组,以它的索引为id,然后创建一个map存储是否执行的数据,格式为{id:boolean} 12345678910111213141516171819202122232425262728let id = 0;let idMap = {};let calls = [];const hackSetTimeout = (callback, interval) => { const timeStart = Date.now(); idMap[id] = true; calls[id++] = callback; requestAnimationFrame(function call() { const currentId = calls.indexOf(callback); if (!idMap[currentId]) { return; } const timeEnd = Date.now(); if (timeEnd - timeStart < interval) { requestAnimationFrame(call); } else { callback(); } }); return id - 1;};const hackClearTimeout = id => { idMap[id] = false;};const tId = hackSetTimeout(() => console.log(1), 2000);hackClearTimeout(tId);","permalink":"https://missop.github.io/2020/01/25/每周一个手写代码-setTimeout/","photos":[]},{"tags":[],"title":"webpack-proxy","date":"2020/01/21","text":"前言开发环境经常需要设置代理,这样才能在本地跨域调接口 但是webpack的代理配置有许多,如果不搞清楚就需要慢慢的去查找每个配置,很费时间,于是我打算总结一下webpack的代理配置 proxy基本配置12345678devServer: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } }} 上面的配置之后:axios.get(‘/api’)==>请求http://localhost:3000 ————后面在上面的配置下分条论述————- 路径修改,前面匹配的是axios请求地址,后面是实际路由 pathRewrite123456// rewrite pathpathRewrite: {'^/old/api' : '/new/api'}// axios.get('/old/api/list') ==> http://localhost:3000/new/api/list// custom rewritingpathRewrite: function (path, req) { return path.replace('/api', '/base/api') } ————proxy的详细配置则在http-proxy-middleware中———— 参考地址:https://github.com/chimurai/http-proxy-middleware onError错误处理123456789onError(err, req, res) { // 出现错误之后返回500 res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end( 'Something went wrong. And we are reporting a custom error message.' );} onProxyRes响应头1234onProxyRes(proxyRes, req, res) { proxyRes.headers['x-added'] = 'foobar'; // add new header to response delete proxyRes.headers['x-removed']; // remove header from response} onProxyReq请求头123456// 有时候我们需要设置登陆信息头才能请求接口onProxyReq(proxyReq, req, res) { // add custom header to request proxyReq.setHeader('x-added', 'foobar'); // or log the req} socket相关配置123456789101112131415// socket设置onProxyReqWs(proxyReq, req, socket, options, head) { // add custom header proxyReq.setHeader('X-Special-Proxy-Header', 'foobar');}onOpen(proxySocket) { // listen for messages coming FROM the target here proxySocket.on('data', hybiParseAndLogMessage);}onClose(res, socket, head) { // view disconnected websocket connections console.log('Client disconnected');}","permalink":"https://missop.github.io/2020/01/21/webpack-proxy/","photos":[]},{"tags":[],"title":"[email protected]","date":"2020/01/13","text":"uform定位总结一句话:解决复杂的表单问题,例如表单联动,表单显示隐藏 但是,uform也能显示类似于表单的文本,格式为”标题:文字” JSON Schema | JSchemauform提供了两种表达表单的方式,一种是JSON schema:12345678{ type:'object', properties:{ aa:{ type:'string' } }} 一种是JSchema标签:1234567891011121314151617181920<SchemaForm onSubmit={v => console.log(v)} actions={actions} labelCol={7} wrapperCol={12} effects={($, { setFieldState }) => { $('onFormMount').subscribe(() => { setFieldState('radio', state => { state.required = true }) }) }}> <Field type="radio" enum={['1', '2', '3', '4']} title="Radio" name="radio" /> </SchemaForm> 两者的使用情况总结: 一般情况都使用JSON Schema 涉及到布局组件的时候,由于它的属性在结构中不表含实际意义,所以使用JSX列出所有布局组件:FormLayout,FormCard,FormBlock,FormItemGrid1234567891011121314151617181920212223242526272829// schema\"UFORM_NO_NAME_FIELD_$0\": { \"type\": \"object\", \"x-props\": { \"title\": \"基本信息\" }, \"x-component\": \"card\", \"properties\": { \"aaa\": { \"type\": \"string\", \"title\": \"字段1\" }, \"bbb\": { \"type\": \"number\", \"title\": \"字段2\" }, \"ccc\": { \"type\": \"date\", \"title\": \"字段3\" } }}// JSX<FormCard title=\"基本信息\"> <Field name=\"aaa\" type=\"string\" title=\"字段1\" /> <Field name=\"bbb\" type=\"number\" title=\"字段2\" /> <Field name=\"ccc\" type=\"date\" title=\"字段3\" /></FormCard> uform1.0特性Antd扩展库 表单布局 一行一个字段的形式: 123456789101112// 设置label占比多少,表单元素占比多少,和为24// 全局设置<SchemaForm labelCol={7}wrapperCol={12}/>// 局部设置<FromLayoutlabelCol={7}wrapperCol={12}/> 一行多个字段:需要使用表单布局元素:FormItemGrid 123456789// 全局<SchemaForm inline/>// cols:内部网格宽度占比 Array<number | {span:number,offset:number}>// gutter:列间距<FormItemGridcols={[9,15]}gutter={10}/> 提交与重置按钮12345678910// submit需要在SchemaForm onSubmit中拿数据<Submit/>// 自定义提交按钮<Button onClick={async ()=>{ const res = await actions.submit()// 可以拿到数据}}><Reset/> Schema渲染库(@uform/react-schema-renderer) 自定义组件注册 1234567891011121314151617181920registerFormField('name', connect()(({dataSource,value,onChange,onBlur})=>{ return <div></div> }))registerFormFields({ 'name':connect()(({dataSource,value,onChange,onBlur})=>{ return <div></div> })})// 非单例模式,命名相同不会受到污染const useFields = () => useMemo(() => ({ name: connect()(({dataSource,value,onChange,onBlur})=>{ return <div></div> }) }))<SchemaForm fields={useFields()}/> 如何实现超复杂自定义组件?(InternalField) React核心库(@uform/react) 数组类型,对象数组类型增删改查 @uform/react的Field组件能够接受一个函数式子组件来渲染子元素,返回了状态以及操作方法1234567891011121314151617181920212223242526272829303132333435363738394041const InputField = props => ( <Field {...props}> {({ state, mutators }) => { const loading = state.props.loading return ( <> <input disabled={!state.editable} value={state.value || ''} onChange={mutators.change} onBlur={mutators.blur} onFocus={mutators.focus} /> </> ) }} </Field>)<Field>{ ({state,mutators})=>{ return ( <> { state.value.map((item,index)=>{ return ( <div key={index}> <InputField name={`idList[${index}]`} /> <button onClick={() => mutators.moveUp(index)}>Add Item</button> <button onClick={() => mutators.moveDown(index)}>Add Item</button> <button onClick={() => mutators.remove(index)}>Add Item</button> </div> ) }) } <button onClick={() => mutators.push()}>Add Item</button> </> ) }}</Field> 从类型1中思考怎么嵌套数组呢? 使用受控组件 请求过来的初始值往往通过initialValues不能传递到子组件中去,就直接使用value onFieldValueChange设置副作用改变表单值(onFieldChange会有很多问题,例如失去焦点会触发一次) 异步联动(也是使用Field) 联动校验 复用effects 核心:@uform/core 时间旅行Observable Graph,可以记录任意时刻的全量状态 高效更新,精确渲染,无需整树渲染 校验能力 根据路径取值","permalink":"https://missop.github.io/2020/01/13/uform-1.0版本/","photos":[]},{"tags":[],"title":"react-dnd","date":"2020/01/10","text":"前言react的拖拽库很多,例如react-beautiful-dnd(以下简称rbd),react-draggable,react-dnd 但是它们都有各自的特点: rbd交互做的很完美,符合物理学的特征 局限性:根元素必须是可放置元素,也就是说我们无法单独设置一个只可往外拖拽的元素,太多的东西已经帮我们做好了,所以扩展性差 react-draggable功能比较局限 react-dnd只提供底层API,我们还是需要去处理复杂数据交换,扩展性高 react-dnd当无法选择的时候,react-dnd就是最好的选择了 react-dnd现在有hooks,写起来非常方便 组成部分:react-dnd-html5-backend、react-dnd123<DndProvider backend={Backend}> <Example /></DndProvider> useDrag,useDrop中的数据传递useDrag(item)=>useDrag(begin)=>useDrop(hover) hover没有返回值,这条链断裂 useDrag(item)=>useDrag(begin)=>useDrop(drop)=>useDrag(end) 因此一般在useDrag(end)中进行数据交换的操作 阻止冒泡官方的处理方式:useDrop({isOverCurrent: monitor.isOver({ shallow: true })}) 但是其实这样做还不够,因为hover的执行是不受影响的,非常影响性能,如果使用了redux就更是如此了,整个组件树每秒渲染几次,谁受的了? 所以需要在hover中我们要自己去diff前后状态(与redux中的状态比较)是否有改变,再决定是否执行后面的操作 DropTargetMonitor 判断是否放下元素 monitor.didDrop() 鼠标位置 monitor.getClientOffset() 返回{ x,y } 这个位置可以用来判断放在元素上面还是下面,我们还需要一个方法 const ref = useRef<any>(null); const boundRect:{ top,bottom,left,right,width,height } = ref.current.getBoundingRect() 判断元素位置函数 1234567function getPosition (mouseY,min,max){ return mouseY>=min && mouseY<max}const offset = monitor.getClientOffset()const isTop = getPosition(offset.y,boundRect.top,boundRect.top+boundRect.height/2)const isBottom = getPosition(offset.y,boundRect.bottom-boundRect.height/2,boundRect.bottom) getBoundingRect,offset,scroll getBoundingRect–相对于视口的位置,也就是相对于document的位置,滚动之后会发生变化,不能使用缓存函数缓存值 monitor.getClientOffset()–也是相对于视口的位置,同上实时获取 offset–相对于父级定位元素的尺寸,无法计算嵌套元素的位置 scroll–滚动的高度","permalink":"https://missop.github.io/2020/01/10/react-dnd/","photos":[]},{"tags":[],"title":"micro frontend(一)","date":"2019/12/09","text":"micro-frontend将渲染方法挂在window上,通过路由判断调用哪个方法1234567891011121314151617181920<!-- 这些脚本不会马上渲染应用 --> <!-- 而是分别暴露全局变量 --> <script src=\"https://browse.example.com/bundle.js\"></script> <script src=\"https://order.example.com/bundle.js\"></script> <script src=\"https://profile.example.com/bundle.js\"></script> <div id=\"micro-frontend-root\"></div> <script type=\"text/javascript\"> // 这些全局函数是上面脚本暴露的 const microFrontendsByRoute = { '/': window.renderBrowseRestaurants, '/order-food': window.renderOrderFood, '/user-profile': window.renderUserProfile, }; const renderFunction = microFrontendsByRoute[window.location.pathname]; // 渲染第一个微应用 renderFunction('micro-frontend-root'); </script> webComponents变体主要区别在于使用 Web Component 代替全局变量123456789101112131415161718192021<!-- 而是分别提供自定义标签 --> <script src=\"https://browse.example.com/bundle.js\"></script> <script src=\"https://order.example.com/bundle.js\"></script> <script src=\"https://profile.example.com/bundle.js\"></script> <div id=\"micro-frontend-root\"></div> <script type=\"text/javascript\"> // 这些标签名是上面代码定义的 const webComponentsByRoute = { '/': 'micro-frontend-browse-restaurants', '/order-food': 'micro-frontend-order-food', '/user-profile': 'micro-frontend-user-profile', }; const webComponentType = webComponentsByRoute[window.location.pathname]; // 渲染第一个微应用(自定义标签) const root = document.getElementById('micro-frontend-root'); const webComponent = document.createElement(webComponentType); root.appendChild(webComponent); </script> 公用库管理全部写在主项目中,每加一个公用组件需要写导出文件,并且还需要发布主项目(缺陷),主项目以包的形式引入子项目 使用微前端不能解决样式干扰问题,需要其他方案 BEM css in js css module 后端通讯(Backends For Frontends:BFF)即每一个微应用对应一个后端应用,但是如果只有一些稳定的api那么完全不必要单独构建后端","permalink":"https://missop.github.io/2019/12/09/micro-frontend1/","photos":[]},{"tags":[],"title":"babel哪些事","date":"2019/12/01","text":"babel主要配置presets:[]–预设,从后向前解析 @babel/env 完成了一部分polyfill,语法转换只是将高版本的语法转换成低版本的,注意内置函数,实例方法无法转化配置”corejs”: 3–相当于@babel/polyfill @babel/react,@babel/typescript–没有什么可说的 plugins:[]–插件,在presets之前从前向后解析 @babel/plugin-transform-runtime–避免帮助函数污染全局,减少重复的polyfill函数inject 小技巧 如果插件名称为 @babel/plugin-XXX,可以使用短名称@babel/XXX,同样适用于presets 文件格式,一共有四种可配置的格式 babel.config.js 1234567891011module.exports = function(api){ api.cache(true); const presets = [...]; const plugins = [...]; return { presets, plugins };} babelrc/package.json类似JSON格式 12345{ "presets": [], "plugins": []}"babel":{"presets":[]} .babelrc.js–以js方式写 1//可以在其中调用 Node.js 的APIconst presets = [];const plugins = [];module.exports = { presets, plugins }; .babelrc配置react只要配置好babel预处理插件就可以了1234567891011121314151617181920212223242526272829303132333435363738简单版:{ ["@babel/env","@babel/react","@babel/typescript"]}复杂版:{ "presets": [ ["@babel/[preset-]env", { "modules": false, // 模块使用 es modules ,不使用 commonJS 规范 "useBuiltIns": "usage", // usage-按需引入 entry-入口引入(整体引入) false-不引入polyfill "corejs": 3 // 就是以前的babel-polyfill }], "@babel/[preset-]react" ], "plugins": [ ["@babel/plugin-proposal-decorators", { // 解析类的装饰器 "legacy": true }], ["@babel/plugin-proposal-class-properties", { // 解析class语法 "loose": true }], // https://babeljs.io/docs/en/babel-plugin-transform-runtime#docsNavbabeljs.io/docs/en/babel-... // 去除重复代码 // A plugin that enables the re-use of Babel's injected helper code to save on codesize [ "@babel/plugin-transform-runtime", { // 上面presets useBuiltIns写了,这里就不用写了 // "corejs": 3, // you can directly import "core-js" or use @babel/preset-env's useBuiltIns option. "helpers": true, // 默认 "regenerator": false, // 通过 preset-env 已经使用了全局的 regeneratorRuntime, 不再需要 transform-runtime 提供的 不污染全局的 regeneratorRuntime "useESModules": true // 使用 es modules helpers, 减少 commonJS 语法代码 } ], // https://babeljs.io/docs/en/next/babel-plugin-syntax-dynamic-import.htmlbabeljs.io/docs/en/next/b... "@babel/plugin-syntax-dynamic-import" // 懒加载 ] }","permalink":"https://missop.github.io/2019/12/01/babel哪些事/","photos":[]},{"tags":[],"title":"insertSort","date":"2019/11/15","text":"思路: 插入排序的工作方式像许多人排序一手扑克牌: 左手为空,桌子上牌面向下每次从桌子上拿走一张牌插入左手中正确的位置为了找到正确位置,从右到左将它与已经在手中的每张牌进行比较,然后插入重复步骤2~3 123456789101112131415161718function insertSort(target) { var result = [target[0]], len = target.length - 1; for (let index = 1; index < len - 1; index++) { const outerEle = target[index]; // 查看当前项在result数组中的位置然后插入 for (let j = 0; j < result.length - 1; j++) { if (outerEle <= result[j]) { result.splice(j, 0, outerEle); // 找到位置退出循环 break; } else if (j === result.length - 1) { // 如果比最后一个还大 result.push(outerEle); } } }}","permalink":"https://missop.github.io/2019/11/15/insertSort/","photos":[]},{"tags":[],"title":"quickSort","date":"2019/11/14","text":"快速排序有两个版本:存储值版本,空间复杂度高,原址排序:有n个变量在数组之外1234567891011121314151617181920212223function quickSort(iArr) { var n = iArr.length; // 若只有一个,则返回 if (n <=1) { return iArr; } // 若有多个,则选择基准进行分组,递归处理 else{ var p = parseInt(n-1); var pivot = iArr[p]; var leftArr = [], rightArr = [], arrVal; for(var i = 0; i < n-1; i++){ arrVal = iArr[i]; if(arrVal <= pivot){ // 小于基准放置左侧 leftArr.push(arrVal); }else{ // 大于基准放置右侧 rightArr.push(arrVal); } } // 递归计算左边、右边子集,将数组合并返回 return quickSort(leftArr).concat([pivot].concat(quickSort(rightArr))); }} 交换值版本,空间复杂度低一点12345678910111213141516171819202122232425262728293031323334353637383940414243// 流程:flag:基准值 left(default:0) right(default:最后一个index)function quickSort(target,left=0,right=target.length-1){ // 若左右指针相遇,待排序数组长度小宇1,即递归的终点,return(注意不能写成left==right,这里left是有可能大于right的)。 if(left>=right) return // 定义可移动的左右指针 i,j,定义flag为基数下标。不能直接使用left,right var i=left,j=right,flag=left // 在i<j时不断循环,i一旦与j碰头,则跳出循环。 while(i<j){ // j项的值大于等于目标值,并且还没有到达flag索引值则右侧指针不断左移 while(target[j]>=target[flag]&& j>flag) j-- // 由于j可能已被改变,需再次判断i与j是否碰头。 if(i>=j){ break; } // i不断右移,找到且比基数小的数,且i不能与j碰头。(由于两次交换已合并,此处不需要使得i在flag左侧) while(target[i]<=target[flag]&& i<j) i++ // num[flag] num[j] num[i]三者换位,可用ES6语法糖 [target[flag],target[j],target[i]] = [target[j],target[i],target[flag]] } quickSort(target,left,flag-1) quickSort(target,flag+1,right)}// 测试用例quickSort([9,17,0,6,10,5])// 当数据量很大的时候,递归快排会造成栈溢出,为解决此问题,我们可使用js数组来模拟栈,将待排序数组的[left,right]保存到数组中,循环取出进行快排,代码如下。function quickSort(target,left=0,right=target.length-1){ // 将[left,right]存入数组中,类似于递归入栈 var list=[[left,right]] // 若list不为空,循环弹出list最后一个数组进行快排 while(list.length>0){ var now=list.pop() // 若左右指针相遇,待排序数组长度小宇1,则无需进行快排(注意不能写成now[0]==now[1],这里now[0]是有可能大于now[1]的 if(now[0]>=now[1]){ continue; } var i = now[0], j = now[1], flag = now[0]; // 后面与上面相似 }}","permalink":"https://missop.github.io/2019/11/14/quickSort/","photos":[]},{"tags":[],"title":"css揭秘","date":"2019/11/09","text":"第一题:想给一个容器设置一层白色背景和一道半透明白色边框,但是body的背景会从它的半透明边框透上来。解析:默认情况下,背景会延伸到边框所在的区域下层。所以只能调整背景的显示区域,需要用到background-clip:padding-box 实验代码:1234567#app { width: 100px; height: 100px; border: 10px solid rgba(204, 204, 204, 0.692); background: red; /* background-clip: padding-box; */} 第二题:如何设置多层边框解析:border属性只能设置一层边框,而要设置两层及以上边框则需要其他属性帮忙了: box-shadow:不占空间,不影响文档流,支持点语法叠加多层,能够继承border-radius,缺陷:只能模拟实线边框1234567#app { width: 100px; height: 100px; border: 10px solid rgba(204, 204, 204, 0.692); background: yellowgreen; box-shadow: 0 0 0 10px #655, 0 0 0 20px green, 0 0 0 30px red;} outline:可以设置虚线,缺陷:不能继承border-radius,也就是不能有圆角 第三题:让背景图片跟右边缘保持 20px 的偏移量,同时跟底边保持 10px 的偏移量background-position 的扩展语法方案1background-position:right 20px bottom 10px; background-origin 方案 background-position是根据padding-box来定位的,那么如果需要改变其定位参考则需要设置background-origin,设置为content-box calc方案 background-position:calc(100% - 20px) calc(100% - 10px);","permalink":"https://missop.github.io/2019/11/09/css揭秘/","photos":[]},{"tags":[],"title":"svg-loading小效果实现","date":"2019/10/27","text":"svg基础知识–圆标签:circle 基本属性: cx:圆x坐标 cy:圆y坐标 r:半径 样式: css属性 说明 值 fill 填充颜色 none stroke 描边颜色 #00B51D stroke-width 描边宽度 5 stroke-linecap 开放路径两端的形状 round stroke-dasharray 创建虚线 44, 44 transform-origin 变换中心 center problem: 1.开放路径两端的形状是什么意思呢? 被虚线分割之后有两个端点,设置其形状 2.创建虚线–难点 一个参数:虚线长度和每段虚线之间的间距两个参数:虚线长度 | 虚线间距解释:虚线是指空的不显示的区块 实战:豆瓣loading图分析动画过程–重要嘴巴嘴巴是一个半圆的描边样式,那么虚线长度就是圆周长的1/2,所以为44,间距与其相等为44stroke-dasharray: 44, 44; 嘴巴动画动画为两圈,第一圈嘴巴变大为3/4,就是间距变小为22,最后有一个停留的时间12345678910@keyframes mouthAni {40% { stroke-dasharray: 44, 22;}80%,100% { stroke-dasharray: 44, 44; transform: rotate(720deg);}} 眼睛当虚线为0时,可以得到无数个点状的圆,间距为1/4,即22stroke-dasharray: 0, 66; 眼睛动画–复杂睛部分同样是旋转了两圈,并且在旋转第一圈的时候,两边的间距增大为7/8,在第二圈的时候,间距恢复为3/4,且最后有一段的停留时间。 产出代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657<!DOCTYPE html><html> <head> <title>Parcel Sandbox</title> <meta charset=\"UTF-8\" /> <style> .mouth { fill: none; stroke: #00b51d; stroke-width: 5; stroke-linecap: round; stroke-dasharray: 44, 44; transform-origin: center; animation: mouthAni 2.3s ease-out infinite; } .eye { fill: none; stroke: #00b51d; stroke-width: 5; stroke-linecap: round; stroke-dasharray: 0, 66; transform-origin: center; transform: rotate(-45deg); animation: eyeAni 2.3s ease-in-out infinite; } @keyframes mouthAni { 40% { stroke-dasharray: 44, 22; } 80%, 100% { stroke-dasharray: 44, 44; transform: rotate(720deg); } } @keyframes eyeAni { 40% { stroke-dasharray: 0, 77; /* 间距改为7/8 */ } 80%, 100% { transform: rotate(675deg); /* 间距恢复为3/4 */ stroke-dasharray: 0, 66; } } </style> </head> <body> <svg width=\"100\" height=\"100\"> <circle class=\"mouth\" cx=\"50\" cy=\"50\" r=\"14\"></circle> <circle class=\"eye\" cx=\"50\" cy=\"50\" r=\"14\"></circle> </svg> <script src=\"src/index.js\"></script> </body></html>","permalink":"https://missop.github.io/2019/10/27/svg-loading小效果实现/","photos":[]},{"tags":[],"title":"styled-components原理","date":"2019/10/09","text":"神奇的模板字符串1234567891011121314// 先创建一个打印参数的函数,它将参数进行了二次分解// 函数+模板字符串获得的是一个二维数组const logArgs = (...args) => console.log(...args)// 普通的字符串logArgs`213123` // [\"213123\", raw: Array(1)]// 带有变量logArgs`123 ${a}` //[\"123 \", \"\", raw: Array(2)] 123// 带有函数logArgs`123 ()=>{}` //[\"123 ()=>{}\", raw: Array(1)]// 函数在字符串里面,无法执行// 变量函数,这样拿到的函数是可以直接执行的logArgs`123 ${()=>{}}` // [\"123 \", \"\", raw: Array(2)] ()=>{}// 增加变量个数logArgs`123 ${a} 456 ${props=>props.a}` // [\"123 \", \" 456 \", \"\", raw: Array(3)] 123 props=>props.a 规律:字符串被变量分割开了,变量成为了后面的所有参数 按照这个规律,我们可以把函数与非函数对应起来1234567891011121314151617181920// 例如const Wrap = styled.div`font-size:14px;line-height:${props=>props.lh?1:2};color:${props=>props.isWhite?'#fff':'black'};`const logFullArg = (...args) => argsconst rel = logFullArg`font-size:14px;line-height:${props=>props.lh?1:2};color:${props=>props.isWhite?'#fff':'black'};`const styArg = rel[0]const func = rel.slice(1)styArg.forEach((item,index)=>{ if(/(.|\\n)+:/.test(item)){ // 对应到具体函数了 console.log(item,func[index]) }})","permalink":"https://missop.github.io/2019/10/09/styled-component原理/","photos":[]},{"tags":[],"title":"JS正则速记:正则五道题","date":"2019/09/29","text":"元字符. 换行符之外\\d 数字\\s 空白符^$\\w 字母,数字,下划线,汉字\\b 单词开始或结尾 限定符+:一次或多次?:0或1次*:0或多{n}:n次{n,}:n或更多次{n,m}:n-m次 反义代码\\W\\S\\D\\B[^x] 分组语法?=exp 匹配字符后面必须是exp?<=exp 匹配字符前面必须是exp?<!exp 前面不是exp 懒惰限定符*? 重复任意次,但尽量少+? 1或多,尽量少?? 0或1,尽量少{n,m}?{n,}? 练习: 连续3个或3个以上 相同的 字符/(\\d)\\1{2,}/g 相同字符匹配:(\\d)\\1 匹配标签匹配标签头部:<[a-z].*?>匹配整个标签:<[a-z].*?>.*?<\\/[a-z].*?> 提取连接地址 123var str = 'IT面试题博客中包含很多<a href=\"http://hi.baidu.com/mianshiti/blog/category/微软面试题\">微软面试题</a>';var reg = /(?<=\")(.+)(?=\")/reg.exec(str) 如何获取一个字符串中的数字字符,并按数组形式输出, 12var str = 'dgfhfgh254bhku289fgdhdy675gfh';console.log(str.match(/\\d+/g)); 敏感词过滤 12345// 比如:“我草你妈哈哈背景天胡景涛哪肉涯剪短发欲望”// 过滤:‘草肉欲胡景涛’var str = '我草你妈哈哈背景天胡景涛哪肉涯剪短发欲望'var reg = /[草|肉|欲|胡|景|涛]/gstr = str.replace(reg,'*') 判断是否符合 USD 格式: 12// $开头,满三,,有小数则为两位var pattern= /^\\$\\d{1,3}(,\\d{3})*(\\.\\d{2})$/ 给定字符串 str,检查其是否以元音字母结尾。 123456function endsWithVowel(str) { return (/[a,e,i,o,u]$/i).test(str);}console.log(endsWithVowel('gorilla')); //trueconsole.log(endsWithVowel('gorillE')); //trueconsole.log(endsWithVowel('gorillx')); //false 驼峰式字符串borderLeftColor和 连字符式字符串border-left-color相互转换 1234567var str = 'borderLeftColor';var str2 = 'border-left-color';///把str换成 连字符式console.log(str.replace(/[A-Z]/g, (item) => '-' + item.toLowerCase())); //border-left-color//把str换成 驼峰式console.log(str2.replace(/-([a-z])/g, (item, $1) => $1.toUpperCase())); //borderLeftColor 对人口数字的格式化处理,三位数字用一个’,’(逗号)隔开","permalink":"https://missop.github.io/2019/09/29/JS正则速记/","photos":[]},{"tags":[],"title":"bizcharts","date":"2019/09/28","text":"图表库各有各的优势 g2-bizcharts的祖先g2-antv的一个组成部分-是一个阿里开发的图标库从最简单的折线图看一看g2的代码:123456789101112131415161718192021222324252627var data = [{ year: '1991', value: 3 },...]var chart = new G2.Chart({ container: 'mountNode', forceFit: true, height: window.innerHeight });chart.source(data); chart.scale('value', { min: 0 }); chart.scale('year', { range: [0, 1] }); chart.tooltip({ crosshairs: { type: 'line' } }); chart.line().position('year*value'); chart.point().position('year*value').size(4).shape('circle').style({ stroke: '#fff', lineWidth: 1 }); chart.render(); G2的特点:每一块初始化配置都比较分散,有利于组件化,数据和配置是分离开的 echarts 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 // 基于准备好的dom,初始化echarts实例 var myChart = echarts.init(document.getElementById('main')); // 指定图表的配置项和数据 var option = { title: { text: 'ECharts 入门示例' }, tooltip: {}, legend: { data:['销量'] }, xAxis: { data: [\"衬衫\",\"羊毛衫\",\"雪纺衫\",\"裤子\",\"高跟鞋\",\"袜子\"] }, yAxis: {}, series: [{ name: '销量', type: 'bar', data: [5, 20, 36, 10, 10, 20] }] }; // 使用刚指定的配置项和数据显示图表。 myChart.setOption(option); ``` echarts主要内容都集中在配置项中,位置比较集中* bizcharts组件化### bizcharts四大组件- `<Chart />`---所有组件之父,可配置项: - height(指 canvas 高度) ~= 整个 charts 组件高度-5 - padding(图表内边距) - \"auto\" --上下左右都贴边显示 - 具体数值 * forceFit:true --自适应宽度,不能适应高度 - data(数据源,数组格式) - 横轴 key:value - 纵轴 key:value - scale(数据比例尺)--data 中的 key 的配置 - alias(别名)--影响到 toolTips 上的字段名的显示 - range(数据范围)--[0,1](从x轴最左侧开始,若不配置则会有很大空隙) - placeholder--图表 source 为空时显示的内容 谨慎使用,会影响图表动画,若自定义的话有时会有报错* `<Axis name=\"sold\" />`---坐标轴组件--name 对应 data 中的 key 值* `<Gemo />`--几何标记和图表类型 - type | 参数 | 说明 | :---- | :---- | point | 点图 | path | 路径 | line | 线(折线图) | area | 区域图 | interval | 柱形或饼图 | polygon | 多边形 * position --确定 x 轴和 y 轴的数据字段 time\\*key * size--点的粗细,点的半径等 * color(颜色) String | Array 语法:['field', callback)] - 颜色渐变--`<Geom color={['price', '#ff0000-#00ff00']}/> //l表示线性渐变,(0)是从左至右,(90)是从上至下,后面的0和1表示渐变的位置 color=\"l (90) 0:rgba(26,92,209,0.8) 1:rgba(56,149,234,0)\"` * 自定义: ```javascript //代码示例 <Geom color={[ \"cut\", cut => { //some code if (cut < 1000) return \"#00ff00\"; else return \"#ff0000\"; } ]} /> tooltip 将数据值映射到 Tooltip 上。 Boolean | String | Array 若该 Gemo 组件不需要显示 tooltip 则设置为 false若需要自定义则可按如下方式: 123456789 <Geom tooltip={false} /><Geomtooltip={['sales*city', (sales, city)=>{return { name:'xxx', value:city + ':' + sales}}]}/> active 鼠标 hover 时的效果 Boolean | Array 12345678<Geomactive={[true, {highlight: false, // true 是否开启 highlight 效果,开启时没有激活的变灰 style: { fill: 'red'} // 选中后 shape 的样式}]}/> <Tooltip /> –提示信息(tooltip)组件,hover 时展示具体数据 itemTpl–这个属性可以格式化 tooltip 的显示内容 12345itemTpl = \"<li data-index={index}>\" + '<span style=\"background-color:{color};width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:8px;\"></span>' + \"{name}: {value}\" + \"</li>\"; g2-tooltip –设置 tooltip 容器的 CSS 样式。 图表切换动画 dataset 12345678import DataSet from \"@antv/data-set\";class Element extends Component { dv = new DataSet().createView(); render() { this.dv.source(formatData); return <Chart data={this.dv} />; }}","permalink":"https://missop.github.io/2019/09/28/bizcharts/","photos":[]},{"tags":[],"title":"函数表达式与闭包","date":"2019/09/22","text":"函数表达式与函数声明函数声明提升1234functionName(); //可以调用,由于函数提升function functionName(arg0, arg1, arg2) { //函数体} 函数表达式123// 函数表达式是将一个匿名函数的值给到变量,因此只会提升变量不会提升函数// 能够有效地防止函数提升带来的一系列问题var functionName = function () {}; 递归1234567891011121314151617181920212223function factorial(num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); }}// 这个阶乘看起来没什么问题但是如果这样做:var copyFactorial = factorial;factorial = null;copyFactorial(10); // 报错// 这是由于函数内部引用的是本身的函数名,而函数名只是一个函数指针而已// 指针一旦修改那么函数也将被改变// 这里可以使用arguments.callee引用函数体// 但是严格模式下arguments.callee不允许使用,所以我们使用函数表达式来解决这个问题var factorial = function f(num) { if (num <= 1) { return 1; } else { return num * f(num - 1); }};// 此时函数名f只在这个函数内部有效,外部无法引用或者修改f,这样就能够愉快地使用递归了 彻底理解闭包 1234567891011function compare(a, b) { return function () { if (a > b) { return 1; } };}// 当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链。// 然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象(activation object)。但在作用域// 链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作// 为作用域链终点的全局执行环境。 箭头函数绑定 this 不可变1234567891011121314151617class MyObj { constructor(id) { this.id = id; } get foo() { return () => { console.log(this.id); }; }}var f = new MyObj(\"o1\").foo;f();var obj = { id: \"obj\", foo: f };obj.foo(); // 'o1' 栈的可见与可写根据下面的方法可以直接访问栈,修改栈,但是并不成功 12345678910function func_3() { arguments.callee.caller.arguments[0] = \"name\";}function func_4(name) { func_3(); console.log(arguments[0], name);}func_4(\"orginName\"); 打印一下 arguments 的可写性,发现它已经是不可写的属性了,这样就防止了函数内修改父函数的形参,导致调用混乱“{“value”:{},”writable”:false,”enumerable”:false,”configurable”:false}” 尝试修改arguments的长度也失败了123456789function func_5() { Array.prototype.push.call(arguments.callee.caller.arguments, 100);}function func_6(name) { func_5(); console.log(arguments.length);}func_6(\"MyName\");","permalink":"https://missop.github.io/2019/09/22/函数表达式与闭包/","photos":[]},{"tags":[],"title":"从loader到ast树","date":"2019/05/26","text":"我的第一个loaderloader/index.js12345678910111213const loaderUtils = require(\"loader-utils\");module.exports = function (content, map, meta) { //this是我们运行时数据调用方法的补充载体,loader函数的执行上下文 console.log('前置钩子', this.data.value); const options = loaderUtils.getOptions(this); console.log('配置文件', options); // tofo content进行遍历的过程 return content+'console.log(1)';}//pitch的前置钩子module.exports.pitch = function (r, prerequest, data) { data.value = \"京城一灯\"} webpack.config.js1234567891011121314151617const path = require('path');module.exports = { module: { rules: [ { test: /\\.m?js$/, exclude: /(node_modules|bower_components)/, use: { loader: path.resolve('./loader/index.js'), options: { data:\"自定义的配置\" } } } ] }} loader的原理-ast树分析用acorn这个库来实现代码的转化的12345678const acorn = require('acorn');const walk = require('acorn-walk');const result = acorn.parse(\"const a=20\");walk.simple(result, { Literal(node) { console.log(`Found a literal:${node.value}`); }}); 用esprima+estraverse+escodegen实现1234567891011121314151617181920212223242526272829303132333435const esprima = require('esprima');const estraverse = require('estraverse');const escodegen = require('escodegen');const code = `const view = { a: 3, init: () => { console.log(this.a); }, render: () => { a = 4; }}`;const ast = esprima.parse(code);// console.log(ast);/*Script { type: 'Program', body: [ VariableDeclaration { type: 'VariableDeclaration', declarations: [Array], kind: 'const' } ], sourceType: 'script' }*/estraverse.traverse(ast, { enter: function (node) { // console.log(node); if (node.type == 'VariableDeclaration') { node.kind = 'var'; } }});const reg_code = escodegen.generate(ast);console.log(JSON.stringify(reg_code, null, 4)); tapable的执行123456789101112131415161718192021222324const { Tapable, SyncHook, SyncBailHook, AsyncParallelHook, AsyncSeriesHook} = require(\"tapable\");let queue = new SyncHook([\"name\"]);//订阅queue.tap(\"1\", function (name, name2) { console.log(\"1\", name, name2); return 1;});queue.tap(\"2\", function (name) { console.log(\"1\", name); return 2;});queue.tap(\"3\", function (name) { console.log(\"1\", name); return 3;});//执行用callqueue.call(\"webpack\", \"webpack-cli\");","permalink":"https://missop.github.io/2019/05/26/webpack-loader/","photos":[]},{"tags":[{"name":"参考 https://blog.csdn.net/yu452148611/article/details/48141089","slug":"参考-https-blog-csdn-net-yu452148611-article-details-48141089","permalink":"https://missop.github.io/tags/参考-https-blog-csdn-net-yu452148611-article-details-48141089/"}],"title":"cocos2d事件绑定与动画机制","date":"2019/05/11","text":"cocos2d事件绑定 事件监听器(cc.EventListener) 封装用户的事件处理逻辑 触摸事件监听器 (cc.EventListenerTouch)类似于移动的touch事件单点触摸:cc.EventListener.TOUCH_ONE_BY_ONE多点触摸:cc.EventListener.TOUCH_ALL_AT_ONCEonTouchBegan,onTouchMoved,onTouchEnded 键盘事件监听器 (cc.EventListenerKeyboard)cc.EventListener.KEYBOARDonKeyPressed,onKeyReleased 加速计事件监听器 (cc.EventListenerAcceleration)cc.EventListener.ACCELERATION 鼠标事件监听器 (cc.EventListenerMouse)cc.EventListener.MOUSEonMouseMove,onMouseUp,onMouseDown,onMouseScroll针对鼠标操作而设计的游戏,需要判断用户按下什么键,响应滚轮等,这就需要开发者编写鼠标事件监听器了,其他的情况下触摸事件监听器就够用了 自定义事件监听器 (cc.EventListenerCustom)cc.EventListener.CUSTOM 事件管理器(cc.eventManager) 管理用户注册的事件监听器,根据触发的事件类型分发给相应的事件监听器 事件对象(cc.Event) 包含事件相关信息的对象 创建事件的流程 1) 创建一个按钮精灵 var btn1 = new cc.Sprite(res.monkey)2) 创建事件 var lis1=cc.EventListener.create({ //事件类型 event:cc.EventListener.TOUCH_ONE_BY_ONE, onTouchBegan:function (touch, event) {}, onTouchMoved:function (touch, event) {}, onTouchEnded:function (touch, event) {} })3) 把事件添加到管理器中 // cc.eventManager.addListener(lis1, btn1); 自定义事件—实现观察者模式 先绑定事件var _listener1 = cc.EventListener.create({ event: cc.EventListener.CUSTOM, eventName: "game_custom_event1", callback: function(event){ // 可以通过getUserData来设置需要传输的用户自定义数据 statusLabel.setString("Custom event 1 received, " + event.getUserData() + " times"); } }); cc.eventManager.addListener(this._listener1, 1);然后定义事件触发器++this._item1Count; var event = new cc.EventCustom("game_custom_event1"); event.setUserData(this._item1Count.toString()); cc.eventManager.dispatchEvent(event); 动画动画基本步骤//定义动画对象 var sprite = new cc.Sprite(“图片地址..”); // 定义动画动作 var action = cc. scaleBy(0.5, 0.5); // 执行动画 sprite.runAction(action); // 动画执行一次 一次性动画var action = cc. scaleBy(0.5, 0.5); 无限执行动画action. repeatForever(); 翻转动画var action2 = action. reverse(); 动画顺序执行var seq = cc.sequence(action, action2); 多个动画同时执行var spawn = cc.spawn(action1, action2); 缓动动画var jump = cc.jumpTo(1秒, x, y, 幅度, 次数); jump.easing(// 缓动函数); A、cc.easeIn(); // 从0开始加速 B、cc.easeOut(); // 减速到0 C、cc.easeInOut(); // 先加速后减速 动画回调var cb = cc.callFunc(回调处理函数, 函数的this对象); var fade = cc.fadeOut(2); // 2秒后fadeOut var action = cc.sequence(fade, cb); sprite.runAction(action); // fade执行完后,执行 cb","permalink":"https://missop.github.io/2019/05/11/coocos2d-basic1/","photos":[]},{"tags":[],"title":"提升webstorm性能","date":"2019/05/07","text":"webstorm是一个集成度很高的IDE所以它非常需要优化 exclude项目中不用的文件进入Settings->Project->Directories,把用不到的文件目录都Excluded(选中该文件目录,右击鼠标,点Excluded)进入Settings->Editor->File Types,在最下面的Ignore files and folders中加入要ignore的文件或文件夹在project窗口的文件夹上,右击鼠标,点击Mark Directory As->Excluded 优化代码检查进入Settings->Editor->Inspections,把用不到的检查都关掉进入Settings->Editor->General,把最下面的Error highlighting->Autoreparse delay(ms) 改成比较大的值(如20000)去掉平时用不到的插件进入Settings->Plugins,去掉平常用不到的插件,在一定程度上会提高软件打开时的加载速度。 优化typescript编译进入Settings->Languages & Frameworks->Typescript,取消Track changes 清除缓存项目久了之后可以清除缓存:点击File -> Invalidate Caches / Restart","permalink":"https://missop.github.io/2019/05/07/webstorm-improve/","photos":[]},{"tags":[],"title":"页面优化之懒加载与预加载","date":"2019/05/01","text":"1.前言PC端网速较快,现在普通都是百兆宽带,那么折算一下就是10M/s的下载速度,基本上资源如果在10M以内的话加载还是比较快的。 但是移动端就不行了。移动端需要消耗巨额的流量,所以尽量减少移动端流量消耗成为了移动端Web页面的一个重要需求。 一般前端页面的加载方式就分为两种:懒加载和预加载,懒加载也叫lazyload,两者容易混淆, 2.预加载2.1预加载运用场景 在制作图片墙,相册这样的单页图片资源比较大的页面时,为了能让用户有流畅的使用体验,需要提前将图片缓存到本地,这就是图片预加载的用途。 2.2预加载的使用 优先加载图片,加载图片时显示loading图或者动画,然后再显示页面,这样用户就无需等待页面的空白而残忍地关闭页面了123456789101112131415161718192021222324var imgs = [] function imgPreload(url, len, cb) { var img = new Image(), flag = 0 img.onload = function () { flag++ if (flag === len) { cb(); } }; img.onerror = function () { }; img.src = url;} for (var i = 0, len = imgs.length; i < len; i++) { imgPreload( imgs[i], len, function () { // 显示页面 } )} 2.3预加载的缺陷 预加载一般是由于本身资源文件巨大,所以提前加载,这样的话加载时间也是比较长的。如果页面上图片很多,但是可以做按需加载那么就尽量不要使用预加载了。 注意:预加载只是把图片加载提前,并没有改变页面加载图片的数量。 3.懒加载3.1使用场景 一般情况下资源比较多而且不需要一次性显示所有的图片 3.2实现 与预加载原理类似只是执行的时机不同,另外需要写一个图片地址列表以便加载图片 4.预加载和懒加载的妙用页面上有多个按钮,并且需要点击之后展示不同的3d轮播图,此时资源图太大需要优化,例如用swiper实现 使用懒加载:点击弹窗之后加载本弹窗所需的图片,之后再初始化swiper,只需要一个swiper(替换图片就可以了) 使用预加载:所有图片预先加载,有一段等待时间,加载完之后swiper初始化(多个swiper列举了所有弹层) 然而这两种方法最后都有后遗症: 懒加载偶尔有时候swiper错乱,但是滑动之后又正常…………………………………..(略去一万字) 预加载时间又太长……………………….. 直到有大佬提醒了我我才搞定,那么是什么方法呢? 还是使用预加载:当滚动过了第一屏的时候,把所有的图片元素放到一个隐藏的div中去,让它去默默加载。直到点击弹层的时候,这时候必定已经加载完毕,把对应的图片添加到swiper中去然后初始化就可以了","permalink":"https://missop.github.io/2019/05/01/loadpage/","photos":[]},{"tags":[],"title":"cocos2d-firstday","date":"2019/05/01","text":"cocos2d需要的环境JDK(java 环境)选择此版本的JDK环境: Java SE 8u211 / Java SE 8u212https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html下载速度比较慢,此时我们可以去下载其他的内容 python环境https://www.python.org/downloads/windows/一共有三种版本,我选择了这个版本:Download Windows x86 executable installer直接下载install然后点击安装就可以了,和nodejs的安装一模一样 NDKAndroid NDK 是一套允许您使用 C 和 C++ 等语言,以原生代码实现部分应用的工具集。在开发某些类型的应用时,这有助于您重复使用以这些语言编写的代码库。native运行环境,直接下载https://developer.android.google.cn/ndk/downloads ANTApache Ant,是一个将软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于Java环境中的软件开发。由Apache软件基金会所提供。直接下载即可https://ant.apache.org/bindownload.cgi 环境配置安装完下载完所有的文件后,已经似乎过去一天了,接下来进行环境的配置","permalink":"https://missop.github.io/2019/05/01/cocos2d-firstday/","photos":[]},{"tags":[],"title":"排序算法解析","date":"2019/04/29","text":"冒泡排序最慢的排序算法—–两层循环相邻比较最佳情况:T(n) = O(n) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(n2) 选择排序从数组的开头开始,将第一个元素和其他元素比较,最小的元素放到第一个位置再从第二个元素开始最佳情况:T(n) = O(n2) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(n2) 插入排序外循环:选中的元素,逐渐向右移动,第一个为index为1的元素内循环:将选中元素与前面index-1个元素比较插入到合适的位置比较相邻元素1234567891011var data = [], temp, inner;for (let outer = 1; outer < data.length; outer++) {/*选中的元素 */temp = data[outer];inner = outer; while (inner > 0 && (data[inner - 1]) >= temp) { data[inner] = data[inner - 1]; inner--; } data[inner] = temp;} 最佳情况:T(n) = O(n) 最坏情况:T(n) = O(n2) 平均情况:T(n) = O(n2) 希尔排序改善的插入排序:比较较远的元素按照一定的增量进行分组,组内排序,然后组间排序,如果增量为1则是插入排序1234567891011121314151617function shellSort(arr) { for (let gap = Math.floor(arr.length / 2); gap > 0; gap = Math.floor(gap / 2)) { // 内层循环与插入排序的写法基本一致,只是每次移动的步长变为 gap for (let i = gap; i < arr.length; i++) { let j = i; let temp = arr[j]; for (; j > 0; j -= gap) { if (temp >= arr[j - gap]) { break; } arr[j] = arr[j - gap]; } arr[j] = temp; } } return arr;} 最佳情况:T(n) = O(nlog2 n) 最坏情况:T(n) = O(nlog2 n) 平均情况:T(n) =O(nlog2n) 归并排序1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253function mergeSort(arr) { if (arr.length < 2) { return; } var step = 1, left, right; /* 不能划出数组长度 */ while (step < arr.length) { left = 0; right = step; while (right + step <= arr.length) { mergeArrays(arr, left, left + step, right, right + step); left = right + step; right = left + step; } if (right < arr.length) { mergeArrays(arr, left, left + step, right, arr.length); } step *= 2; } return arr;}/* 归并排序就是分左数组和右数组的过程,先从两个数组开始慢慢合并 */function mergeArrays(arr, startLeft, stopLeft, startRight, stopRight) { /* 先创建哨兵值 */ var rightArr = new Array(stopRight - startRight + 1), leftArr = new Array(stopLeft - startLeft + 1), temp = startRight; for (let index = 0; index < rightArr.length - 1; index++) { rightArr[index] = arr[temp]; temp++; } temp = startLeft; for (let j = 0; j < leftArr.length - 1; j++) { leftArr[j] = arr[temp]; temp++; } rightArr[rightArr.length - 1] = Infinity; leftArr[leftArr.length - 1] = Infinity; /* 对左右数组排序 */ /* 左右数组的起始位置 */ var m = 0, n = 0; for (let a = startLeft; a < stopRight; a++) { if (leftArr[m] <= rightArr[n]) { arr[a] = leftArr[m]; m++; } else { arr[a] = rightArr[n]; n++; } }}mergeSort([123, 123, 34, 32, 65, 2]); 最佳情况:T(n) = O(n) 最差情况:T(n) = O(nlogn) 平均情况:T(n) = O(nlogn) 快速排序1234567891011121314151617function quickSort(arr) { if (arr.length == 0) { return []; } /* 设置基准值和比他大的数组和小的数组 */ var pivot = arr[0], lesser = [], greater = []; for (let index = 1; index < arr.length; index++) { /* 比基准值小的放入小数组,反之放入大数组 */ if (arr[index] < pivot) { lesser.push(arr[index]); } else { greater.push(arr[index]); } } return quickSort(lesser).concat(pivot, quickSort(greater));}quickSort([123, 123, 34, 32, 65, 2]); 最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(nlogn)","permalink":"https://missop.github.io/2019/04/29/basicsort-way/","photos":[]},{"tags":[],"title":"面试小题","date":"2019/03/22","text":"看到你的简历上使用了iframe,为什么要使用它?iframe怎么实现父级与子级的通信?iframe如何跨域? ajax的同步和异步有什么区别 浏览器怎么实现跨域?服务器正式地址上怎么解决跨域?nginx用过没有? 用到了Vue哪些特性?Vue-router实现原理?Vuex的优缺点? React与Vue的不同之处? 数组常用方法,数组去重? 字符串中重复次数最多的字符? 性能优化是否有了解过? 浏览器缓存有哪些?","permalink":"https://missop.github.io/2019/03/22/job-interview1/","photos":[]},{"tags":[],"title":"console的用法","date":"2019/03/17","text":"前言console.log基本上是我们每天都要重复的操作,但是我门可能大部分都不知道console还有其他很多好用的方法 console.dir()有时候我们看到一个新的对象,想看看它有什么方法于是我们console.log(obj)得到的答案是这样的:ƒ Promise() { [native code] }但是我们想看下它怎么使用,那么就需要用到console.dir()–它会以树状显示一个对象一直到最后一个原型对象的所有属性和方法 console.dirxml()–显示网页的某个节点(node)所包含的html/xml代码)示例代码:123var node = document.createElement("div");node.innerHTML += "<p>追加的元素显示吗</p>";console.dirxml(node); console.assert()判断变量是否是真console.count()统计代码被执行的次数console.table表格显示方法copy通过此命令可以将在控制台获取到的内容复制到剪贴板","permalink":"https://missop.github.io/2019/03/17/consoleAPI/","photos":[]},{"tags":[],"title":"calculatejs","date":"2019/03/17","text":"JS基本运算加法(不同类型之间的加法) 字符串与其他类型的加法 第一类:相当于字符串拼接 12345672+'2'true+'2'false+'2'undefined+'2'NAN+'2'Symbol+'2'null+'2' 第二类:特殊 12[]+'2' //'2'{}+'2' //2","permalink":"https://missop.github.io/2019/03/17/calculatejs/","photos":[]},{"tags":[],"title":"jenkins配置","date":"2019/03/16","text":"1 配置JAVA环境 JAVA简介JAVA环境分为两种:JDK/JRE,JDK为开发环境更加全面所以安装这种JAVA是Oracle维护的,所以先去官网:https://www.oracle.comJAVA SE是稳定版 下载地址:https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html注意:Linux上有两种格式的安装文件,一个是rpm,一个tar,rpm是CentOS用的,如果ubantu需要使用的话,必须要先用alien把rpm转换成deb我是ubantu的,所以直接使用tar格式的 CentOS系统rpm -ivh jdk-8u201-linux-x64.rpm Ubantu系统 压缩文件: tar -xzvf jdk-8u65-linux-x64.gz 配置环境变量: /etc/profile/ 最后面以及~/.bashrc里面的exapmle后面加入以下代码:1234export JAVA_HOME=/opt/java/jdk1.8.0_201 export JRE_HOME=$JAVA_HOME/jre export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib export PATH=$JAVA_HOME/bin:$PATH 然后java -version可以运行了然而!!!仍然无法启动jenkins的话,就是因为默认启动路径/usr/bin/java下没有配置 直接进行软链接:ln -s /opt/java/jdk1.8.0_201/bin/java(安装jenkins的路径) /usr/bin/java此时JAVA环境安装完毕 2 安装jenkins CentOS系统(三步)https比较慢的话就使用httpsudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.reposudo rpm –import https://pkg.jenkins.io/redhat-stable/jenkins.io.keysudo yum install jenkins Ubantu wget -q -O - https://pkg.jenkins.io/debian/jenkins-ci.org.key | sudo apt-key add -这一步如果报错:gpg: no valid OpenPGP data found.那么就分成两步来运行 第二步是sudo apt-key add 文件名 sudo sh -c ‘echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list’ sudo apt-get update sudo apt-get install jenkinsUnable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another process using it? sudo rm /var/lib/dpkg/lock-frontend 3 配置jenkins 输入密码/var/lib/jenkins/secrets/initialAdminPassword 中找到密码 修改默认端口号 一开始想从网上找怎么改,按照网上根本找不到配置文件,然后直接查看/etc/init.d/jenkins配置文件 我们发现这么一个片段(这里有提到端口号,是一个变量$3,然而找不到,我们再继续往下看): 123456check_tcp_port() { local service=$1 local assigned=$2 local default=$3 local assigned_address=$4 local default_address=$5 这里有一个端口号(注意这里修改了无效): 1check_tcp_port "http" "$HTTP_PORT" "8080" "$HTTP_HOST" "0.0.0.0" || return 2 后来发现了一个路径:/etc/default/jenkins 查看这个文件发现端口号在里面,直接改为8001就可以了","permalink":"https://missop.github.io/2019/03/16/jenkins-config/","photos":[]},{"tags":[],"title":"经典CSS2布局","date":"2019/03/12","text":"双飞翼可能一开始设置一个固定高度,然后通过浮动布局实现。123456789101112131415161718192021222324252627<style> * { margin: 0; padding: 0; } div { height: 150px; } .left { background: orange; float: left; width: 300px; } .right { width: 200px; background: blue; float: right; } .middle { background: yellow; } </style><div class="left">左</div><div class="right">右</div><div class="middle">中</div> 由于页面是从上到下解析的,这样的布局不利于页面主题内容渲染。我们发现一旦把中放到最上面的话,中就骑上去了,上和下都掉下来了然后我们修改代码让所有盒子左浮动,然后中的宽度为100%,这样左和右骑在中上面了;最后想到加一个父盒子,然后相对父盒子定位:名为圣杯布局123456789101112131415161718192021.container{padding: 0 200px 0 300px; } .right { //增加定位 position: relative; right: -200px; margin-left: -200px; width: 200px; background: blue; float: left; } .left { //增加定位 position: relative; left: -300px; background: orange; float: left; width: 300px; margin-left: -100%; } 后来淘宝改造了一下,增加了一个inner,然后把定位都删掉了:名为双飞翼布局1234<div class="middle"><div class="inner">中</div></div> .inner{ margin-left: 300px; } 等高布局假等高布局法:在双飞翼的基础之上增加无限高度,然后父盒子溢出隐藏12345678.left,.middle,.right{ padding-bottom: 9999px; margin-bottom: -9999px;}.container{ height: 200px; overflow: hidden;}","permalink":"https://missop.github.io/2019/03/12/css-calssical-layout/","photos":[]}],"categories":[],"tags":[{"name":"ts","slug":"ts","permalink":"https://missop.github.io/tags/ts/"},{"name":"js基础","slug":"js基础","permalink":"https://missop.github.io/tags/js基础/"},{"name":"参考 https://blog.csdn.net/yu452148611/article/details/48141089","slug":"参考-https-blog-csdn-net-yu452148611-article-details-48141089","permalink":"https://missop.github.io/tags/参考-https-blog-csdn-net-yu452148611-article-details-48141089/"}]}