永不消失的彩虹

永不消失的彩虹 查看完整档案

成都编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

永不消失的彩虹 赞了回答 · 8月29日

解决webpack怎么配置才能把内联js改为src引用呢?

const shouldInlineRuntimeChunk = false;

关注 3 回答 1

永不消失的彩虹 提出了问题 · 8月14日

请问Ant Design Pro的classname通过“-”自动处理嵌套是怎么实现的?

请问antd处理classname的是什么插件?或者在webpack额外配置了什么?

less样式(举例):
image

引用方式是:

<div className={styles.modal}>
   <div className={styles.root}>
      <div className={styles.mask}>

渲染后:
image

关注 1 回答 0

永不消失的彩虹 提出了问题 · 6月11日

解决js怎么获取含有calc的width呢?

元素的css是width: calc(100% - 260px)offsetWidthclientWidth都获取不到真实宽度。
如果用window.getComputedStyle(XXX).width获取到的值还是calc(100% - 260px)
请问该怎么直接获取最终渲染的px尺寸,或者怎么计算该元素的px宽度最合适呢?

el.getBoundingClientRect()返回的所有值都是0
QQ截图20200611180136.png

关注 3 回答 3

永不消失的彩虹 提出了问题 · 5月19日

解决webpack怎么配置才能把内联js改为src引用呢?

我在用react开发chrome插件
webpack打包后会生成如下代码

<!doctype html>
<html>

<head></head>

<body>
    <div id="popup"></div>
    <script>!function (e) { function t(t) { for (var n, l, p = t[0], f = t[1], i = t[2], c = 0, s = []; c < p.length; c++)l = p[c], Object.prototype.hasOwnProperty.call(o, l) && o[l] && s.push(o[l][0]), o[l] = 0; for (n in f) Object.prototype.hasOwnProperty.call(f, n) && (e[n] = f[n]); for (a && a(t); s.length;)s.shift()(); return u.push.apply(u, i || []), r() } function r() { for (var e, t = 0; t < u.length; t++) { for (var r = u[t], n = !0, p = 1; p < r.length; p++) { var f = r[p]; 0 !== o[f] && (n = !1) } n && (u.splice(t--, 1), e = l(l.s = r[0])) } return e } var n = {}, o = { 4: 0 }, u = []; function l(t) { if (n[t]) return n[t].exports; var r = n[t] = { i: t, l: !1, exports: {} }; return e[t].call(r.exports, r, r.exports, l), r.l = !0, r.exports } l.m = e, l.c = n, l.d = function (e, t, r) { l.o(e, t) || Object.defineProperty(e, t, { enumerable: !0, get: r }) }, l.r = function (e) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(e, "__esModule", { value: !0 }) }, l.t = function (e, t) { if (1 & t && (e = l(e)), 8 & t) return e; if (4 & t && "object" == typeof e && e && e.__esModule) return e; var r = Object.create(null); if (l.r(r), Object.defineProperty(r, "default", { enumerable: !0, value: e }), 2 & t && "string" != typeof e) for (var n in e) l.d(r, n, function (t) { return e[t] }.bind(null, n)); return r }, l.n = function (e) { var t = e && e.__esModule ? function () { return e.default } : function () { return e }; return l.d(t, "a", t), t }, l.o = function (e, t) { return Object.prototype.hasOwnProperty.call(e, t) }, l.p = "./"; var p = this["webpackJsonpphoto-mode"] = this["webpackJsonpphoto-mode"] || [], f = p.push.bind(p); p.push = t, p = p.slice(); for (var i = 0; i < p.length; i++)t(p[i]); var a = f; r() }([])</script>
    <script data-original="./static/js/0.e0a8cfc2.chunk.js"></script>
    <script data-original="./static/js/popup.cda31bdb.chunk.js"></script>
</body>

</html>

由于第一个script标签中有!function立即执行函数,会导致浏览器加载打包的项目时报错:
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' blob: filesystem: chrome-extension-resource:". Either the 'unsafe-inline' keyword, a hash ('sha256-3zf5Fx0iIHyDynX6oqOyWFGNS138fNsneA+6JFUX8oI='), or a nonce ('nonce-...') is required to enable inline execution.

请问webpack中怎么配置能不生成内联JS,改为src引用呢?

追加webpack插件配置:

plugins: [
      new HtmlWebpackPlugin(
        Object.assign(
          {},
          {
            filename:"index.html",
            inject: true,
            template: paths.appHtml,
            chunks: ['main'],  // 按需引入对应名字的js文件
          },
          isEnvProduction
            ? {
                minify: {
                  removeComments: true,
                  collapseWhitespace: true,
                  removeRedundantAttributes: true,
                  useShortDoctype: true,
                  removeEmptyAttributes: true,
                  removeStyleLinkTypeAttributes: true,
                  keepClosingSlash: true,
                  minifyJS: true,
                  minifyCSS: true,
                  minifyURLs: true,
                },
              }
            : undefined
        )
      ),
      new HtmlWebpackPlugin(
        Object.assign(
          {},
          {
            filename:"popup.html",
            inject: true,
            template: paths.appPopup,
            chunks: ['popup'],  // 按需引入对应名字的js文件
          },
          isEnvProduction
            ? {
                minify: {
                  removeComments: true,
                  collapseWhitespace: true,
                  removeRedundantAttributes: true,
                  useShortDoctype: true,
                  removeEmptyAttributes: true,
                  removeStyleLinkTypeAttributes: true,
                  keepClosingSlash: true,
                  minifyJS: true,
                  minifyCSS: true,
                  minifyURLs: true,
                },
              }
            : undefined
        )
      ),
      isEnvProduction &&
        shouldInlineRuntimeChunk &&
        new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
        new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
        new ModuleNotFoundPlugin(paths.appPath),
        new webpack.DefinePlugin(env.stringified),
        isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
        isEnvDevelopment &&
        new WatchMissingNodeModulesPlugin(paths.appNodeModules),
      isEnvProduction &&
        new MiniCssExtractPlugin({
          filename: 'static/css/[name].[contenthash:8].css',
          chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
        }),
        new ManifestPlugin({
        fileName: 'asset-manifest.json',
        publicPath: paths.publicUrlOrPath,
        generate: (seed, files, entrypoints) => {
          const manifestFiles = files.reduce((manifest, file) => {
            manifest[file.name] = file.path;
            return manifest;
          }, seed);
          const entrypointFiles = entrypoints.main.filter(
            fileName => !fileName.endsWith('.map')
          );

          return {
            files: manifestFiles,
            entrypoints: entrypointFiles,
          };
        },
      }),
      new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
      isEnvProduction &&
        new WorkboxWebpackPlugin.GenerateSW({
          clientsClaim: true,
          exclude: [/\.map$/, /asset-manifest\.json$/],
          importWorkboxFrom: 'cdn',
          navigateFallback: paths.publicUrlOrPath + 'index.html',
          navigateFallbackBlacklist: [
            // Exclude URLs starting with /_, as they're likely an API call
            new RegExp('^/_'),
            // Exclude any URLs whose last part seems to be a file extension
            // as they're likely a resource and not a SPA route.
            // URLs containing a "?" character won't be blacklisted as they're likely
            // a route with query params (e.g. auth callbacks).
            new RegExp('/[^/?]+\\.[^/]+$'),
          ],
        }),
      // TypeScript type checking
      useTypeScript &&
        new ForkTsCheckerWebpackPlugin({
          typescript: resolve.sync('typescript', {
            basedir: paths.appNodeModules,
          }),
          async: isEnvDevelopment,
          useTypescriptIncrementalApi: true,
          checkSyntacticErrors: true,
          resolveModuleNameModule: process.versions.pnp
            ? `${__dirname}/pnpTs.js`
            : undefined,
          resolveTypeReferenceDirectiveModule: process.versions.pnp
            ? `${__dirname}/pnpTs.js`
            : undefined,
          tsconfig: paths.appTsConfig,
          reportFiles: [
            '**',
            '!**/__tests__/**',
            '!**/?(*.)(spec|test).*',
            '!**/src/setupProxy.*',
            '!**/src/setupTests.*',
          ],
          silent: true,
          // The formatter is invoked directly in WebpackDevServerUtils during development
          formatter: isEnvProduction ? typescriptFormatter : undefined,
        }),
    ].filter(Boolean),

关注 3 回答 1

永不消失的彩虹 关注了专栏 · 2019-12-20

freeCodeCamp成都社区

用通俗易懂的语言,专注IT干货

关注 7

永不消失的彩虹 收藏了文章 · 2019-10-10

作为前端,你不得不知道的SEO

  研发的同学,其实很多人并没有深入了解SEO这个概念。在技术博客里,提及这一块的也寥寥无几。我今天就拿自己的经验,简单给大家扫个盲,有什么遗漏的地方,欢迎大家补充。

  文字内容有点多,但是干货满满,请耐心阅读!

什么是SEO:

  以下内容摘自百度百科:

SEO(Search Engine Optimization):汉译为搜索引擎优化。搜索引擎优化是一种利用搜索引擎的搜索规则来提高目前网站在有关搜索引擎内的自然排名的方式。SEO是指为了从搜索引擎中获得更多的免费流量,从网站结构、内容建设方案、用户互动传播、页面等角度进行合理规划,使网站更适合搜索引擎的索引原则的行为。

SEO原理

  其实搜索引擎做的工作是相当复杂的,我们这里简单说一下大致的过程。后续针对SEO如何优化,也会根据这几个点展开描述。

  • 页面抓取: 蜘蛛向服务器请求页面,获取页面内容
  • 分析入库:对获取到的内容进行分析,对优质页面进行收录
  • 检索排序:当用户检索关键词时,从收录的页面中按照一定的规则进行排序,并返回给用户结果

SEO优化

  既然是优化,那我们就得遵循SEO的原理来做,可谓知己知彼,百战不殆。针对上面提出的三点,我们分别展开叙述。我们这里主要以百度蜘蛛举例。

页面抓取

  如何才能吸引蜘蛛光顾我们的网站,如何才能让蜘蛛经常光顾我们的网站。这里提出以下几个优化点:

  1. 提交页面。提交页面又分为几种不同的方式

    1. sitemap提交。sitemap,顾名思义,就是网站地图,当蜘蛛来到我们的网站时,告诉它我们有多少页面,不同页面是按什么分类的,每个页面的地址是什么。顺着我们的指引,蜘蛛会很轻松的爬遍所有内容。另外,如果你的页面分类比较多,而且数量大,建议添加sitemap索引文件。如果站点经常更新添加新页面,建议及时更新sitemap文件;
    2. 主动提交。就是把你的页面直接丢给百度的接口,亲口告诉百度你有哪些页面,这是效率最高也是收录最快的方式了。但是需要注意,百度对每天提交的数量是有限制的,而且反复提交重复的页面,会被降低每日限额,所以已被收录的页面不建议反复提交。收录有个时间过程,请先耐心等待;
    3. 实时提交。在页面中安装百度给的提交代码,当这个页面被用户打开我,便自动把这个页面提交给百度。这里不需要考虑重复提交的问题。

以上几种提交方式可以同时使用,互不冲突。

  1. 保证我们的页面是蜘蛛可读的。

      早在ajax还没流行的的时候,其实SEO对于前端的要求并没有很多,或者说,那个时候还没有前端这个职业。页面全部在服务器端由渲染好,不管是用户还是蜘蛛过来,都能很友好的返回html。ajax似乎原本是为了避免有数据交互导致必须重刷页面设计的,但是被大规模滥用,一些开发者不管三七二十一,所有数据都用ajax请求,使得蜘蛛不能顺利的获取页面内容。庆幸的是这反倒促进了前端的飞速发展。

      到了后来,各种SPA单页应用框架的出现,使得前端开发者不再需要关心页面的DOM结构,只需专注业务逻辑,数据全部由Javascript发ajax请求获取数据,然后在客户端进行渲染。这也就导致了老生常谈的SEO问题。百度在国内搜索引擎的占有率最高,但是很不幸,它并不支持ajax数据的爬取。于是,开发者开始想别的解决方案,比如检测到是爬虫过来,单独把它转发到一个专门的路由去渲染,比如基于Node.js的Jade引擎(现在改名叫Pug了),就能很好地解决这个问题。React和Vue,包括一个比较小众的框架Marko也出了对应的服务端渲染解决方案。详细内容查看对应文档,我就不多说了。

  2. URL与301

  URL设置要合理规范,层次分明。如果网站到了后期发现URL不合理需要重新替换时,会导致之前收录的页面失效,就是我们所说的死链(这种情况属于死链的一种,404等也属于死链)。所以一定要在网站建设初期就做好长远的规划。一旦出现这种情况也不要过于担心,我们可以采取向搜索引擎投诉或者设置301跳转的方式解决。

  URL层级嵌套不要太深,建议不超过四层。增加面包屑导航可以使页面层次分明,也有利于为蜘蛛营造顺利的爬取路径。

  除此之外,将指向首页的域名全部设置301跳转到同一URL,可以避免分散权重。

分析入库

  当蜘蛛把页面抓取回去之后,就需要对页面内容进行分析,并择优收录入库。为什么说是择优呢?下面我给你慢慢分析。

  搜索引擎的目的是给用户提供高质量的、精准的搜索结果。如果整个页面充斥着满满的广告和各种不良信息,这无疑会很大程度上影响用户体验。

  除此之外,你肯定不希望自己辛辛苦苦创作的文章被别人轻而易举的抄走,所以搜索引擎在一定程度上帮助你避免这种情况的发生。对于已经收录的内容,搜索引擎会降低其权重,甚至直接不收录这个页面。即便是这样,为了保证页面的新鲜度,很多网站都会爬取或者转载其他网站的内容。这就是我们经常听到的伪原创。所以,想要让你的页面能够以较高的权重被收录,就需要坚持更新网站内容,并且是高质量的原创内容。
  

检索排序

  这块我的理解是,页面被收录后,会给每个页面打一些tag。这些tag将作为搜索结果排序的重要依据。比如用户搜索“北京旅游”,搜索引擎会检索收录页面中被打了“北京旅游”tag的页面,并根据一系列规则进行排序。所以,如何提升这些tag在搜索引擎里面的权重是至关重要的。

  1. TDK优化
TDK是个缩写,seo页面中的页面描述与关键词设置。

其中"T"代表页头中的title元素,这里可能还要利用到分词技术,当标题(Title)写好后,我们就尽可> 能不要再去修改了,尽量简洁,没意义的词尽量不要加入到标题中,避免干扰到搜索引擎识别网站主题。

其中"D"代表页头中的description元素,要知道描述是对网页的一个概述,也是对title的补充,因为title中只能书写有限的字数,所以在description中就要稍微详细的补充起来,一般用一句两句话概括文章的内容。

其中"K"代表页头中的keywords元素,提取页面中的主要关键词,数量控制在三到六个内。想方设法让主关键字都出现。

  以上内容摘自百度百科,这里需要补充几点。

  TDK是搜索引擎判断页面主题内容的关键,所以要在title里面言简意赅的体现出页面的主要内容,如果主体比较多,可以用一些符号把不同的主题词隔开,但是关键词不要太多,最多不要超过五个。

  keywords里面把每个关键词用英文逗号隔开,三到五个最佳。尽量覆盖每个关键词。

  description就是用自然语言描述页面的主要内容,这里注意一点就是把每个关键词至少覆盖一遍才能达到最佳效果。

  1. 提升页面关键词密度

  首先说个概念,叫关键词密度。简单理解就是关键词在所有文字内容中出现的比例。提升关键词的密度,有利于提升搜索引擎针对对应关键词的搜索排名。但并不是我们整个页面密密麻麻堆砌关键次就好,我们来分析一个案例。

  我们在百度搜索“北京旅游”,排在第一的是百度旅游,这个就不解释了。排第二位是携程,我们就分析一下为啥携程会排名这么靠前。

  通过查看百度快照,可以一目了然的看到页面上究竟哪些地方命中了这些词。

  页面头部

  页面底部

  页面很清晰表明了关键词出现的地方,我们发现这个页面除了正文部分外,还设置了许多的模块,这些模块看似只是一些简单的链接,实际上他们更重要的使命就是服务SEO,提升关键词的密度。同时,这些链接都是指向网站内部的链接,通过这样的方式,还可以在不同的页面之间相互投权重。可以说小链接,大学问!

  你以为到此结束了?并没有。请仔细观察页面上这些模块的内容设置。分别覆盖了地区、景点、攻略、导航、住宿、交通等等,可以说是涵盖了你要旅游所需要的任何需求。这样一来,不管你搜哪些有关于“北京旅游”的关键词,比如“北京旅游住宿”,“北京特色美食”等都会命中这个页面的词,这使得这个页面的关键词数量得到提升,更容易得到曝光。

  1. 细枝末节但不可忽视的优化

  页面上经常会有各种图片,对于搜索引擎来说,它是不识别图片上的内容的。你可能知道代码中img标签的alt属性是为了图片加载失败的时候,给用户看的。这个属性表明了这张图的内容。其实搜索引擎在分析页面的时候,也会根据这个词去判断图片的内容,所以要给页面上有意义的图片都加上alt属性,写清楚图片索要反映的内容。

  页面上的出站链接(也就是指向别的网站的A标签),我们要给它加上nofollow标签,避免它向别的网站输出权重。百度蜘蛛会忽略加了nofollow 的链接。你也可以在网页的meta标签里这么写<meta name="robots" content="nofollow" />,这样一来,百度蜘蛛将不追踪页面上的所有链接,但不建议这么做,除非这个页面的所有链接都指向了别的域名。

  ......

其他优化点

友情链接

  我们经常会在页面底部看到友情链接。友情链接是作为网站之间相互交换流量,互惠互利的合作形式。事实上,友情链接对网站权重提升有着至关重要的作用。友链不仅可以引导用户浏览,而且搜索引擎也会顺着链接形成循环爬取,可以有效提升网站流量和快照的更新速度。

关键词筛选

  借助站长工具爱站网或者各种站长后台我们可以分析出ip来路,以及关键词的搜索热度和相关词,我们再把这些词以一定的密度添加到页面中,以此来提升命中率。这里主要是运营同学的工作,我不专业,也就不展开说了,更多功能大家自行摸索。

利用好分析工具

  我们要在自己的站点安装百度统计代码,这样就可以分析出站点内用户的关注度和浏览流程,以此来不断优化站点结构,提升用户的留存率。同时也可以做用户画像,分析用户数据等等。

结语

  想要做好SEO并不是一件简单的事,需要持之以恒,面面俱到。对网站持续关注,并保持更新。从长远打算,切不可投机取巧,只图一时的效果做违背搜索引擎的操作,也就是常说的黑帽SEO,否则被百度K掉就得不偿失了。

  以上观点只是我最近学习的一些总结,并不权威,希望给不了解这块的研发同学简单扫个盲,如有错误,还请各位指正与补充!

查看原文

永不消失的彩虹 提出了问题 · 2019-09-28

canvas应该怎么做镂空(透明色)文字呢?

在有背景(蓝色)的情况下应该怎么做呢?
我实在是不想用一个setInterval一直刷新画布,太损耗性能了。
请教一下还有没有其他的思路?

原始需求:
AAAAA作为镂空透明色字体,背景为一个正在播放的video,并且video的内容会动态的填充在文字AAAAAA上
图片描述

AAAAAA的背景要一直展示video的动画效果:
图片描述

关注 5 回答 3

永不消失的彩虹 收藏了文章 · 2019-08-23

JS开发常用工具函数

1、isStatic:检测数据是不是除了symbol外的原始数据

function isStatic(value) {
    return(
        typeof value === 'string' ||
        typeof value === 'number' ||
        typeof value === 'boolean' ||
        typeof value === 'undefined' ||
        value === null
    )
}

2、isPrimitive:检测数据是不是原始数据

function isPrimitive(value) {
    return isStatic(value) || typeof value === 'symbol'
}

3、isObject:判断数据是不是引用类型的数据 (例如: arrays, functions, objects, regexes, new Number(0),以及 new String(''))

function isObject(value) {
      let type = typeof value;
      return value != null && (type == 'object' || type == 'function');
}

4、isObjectLike:检查 value 是否是 类对象。 如果一个值是类对象,那么它不应该是 null,而且 typeof 后的结果是 "object"

function isObjectLike(value) {
      return value != null && typeof value == 'object';
}

5、getRawType:获取数据类型,返回结果为 Number、String、Object、Array等

function getRawType(value) {
    return Object.prototype.toString.call(value).slice(8, -1)
}
//getoRawType([]) ==> Array

6、isPlainObject:判断数据是不是Object类型的数据

function isPlainObject(obj) {
    return Object.prototype.toString.call(obj) === '[object Object]'
}

7、isArray:判断数据是不是数组类型的数据

function isArray(arr) {
    return Object.prototype.toString.call(arr) === '[object Array]'
}

将isArray挂载到Array上

Array.isArray = Array.isArray || isArray;

8、isRegExp:判断数据是不是正则对象

function isRegExp(value) {
    return Object.prototype.toString.call(value) === '[object RegExp]'
}

9、isDate:判断数据是不是时间对象

function isDate(value) {
    return Object.prototype.toString.call(value) === '[object Date]'
}

10、isNative:判断 value 是不是浏览器内置函数

内置函数toString后的主体代码块为 [native code] ,而非内置函数则为相关代码,所以非内置函数可以进行拷贝(toString后掐头去尾再由Function转)

function isNative(value) {
    return typeof value === 'function' && /native code/.test(value.toString())
}

11、isFunction:检查 value 是不是函数

function isFunction(value) {
    return Object.prototype.toString.call(value) === '[object Function]'
}

12、isLength:检查 value 是否为有效的类数组长度

function isLength(value) {
      return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= Number.MAX_SAFE_INTEGER;
}

13、isArrayLike:检查 value 是否是类数组

如果一个值被认为是类数组,那么它不是一个函数,并且value.length是个整数,大于等于 0,小于或等于 Number.MAX_SAFE_INTEGER。这里字符串也将被当作类数组

function isArrayLike(value) {
      return value != null && isLength(value.length) && !isFunction(value);
}

14、isEmpty:检查 value 是否为空

如果是null,直接返回true;如果是类数组,判断数据长度;如果是Object对象,判断是否具有属性;如果是其他数据,直接返回false(也可改为返回true)

function isEmpty(value) {
    if (value == null) {
        return true;
    }
    if (isArrayLike(value)) {
        return !value.length;
    }else if(isPlainObject(value)){
          for (let key in value) {
            if (hasOwnProperty.call(value, key)) {
              return false;
            }
        }
        return true;
    }
    return false;
}

15、cached:记忆函数:缓存函数的运算结果

function cached(fn) {
    let cache = Object.create(null);
    return function cachedFn(str) {
        let hit = cache[str];
        return hit || (cache[str] = fn(str))
    }
}

16、camelize:横线转驼峰命名

let camelizeRE = /-(\w)/g;
function camelize(str) {
    return str.replace(camelizeRE, function(_, c) {
        return c ? c.toUpperCase() : '';
    })
}
//ab-cd-ef ==> abCdEf
//使用记忆函数
let _camelize = cached(camelize)

17、hyphenate:驼峰命名转横线命名:拆分字符串,使用 - 相连,并且转换为小写

let hyphenateRE = /\B([A-Z])/g;
function hyphenate(str){
    return str.replace(hyphenateRE, '-$1').toLowerCase()
}
//abCd ==> ab-cd
//使用记忆函数
let _hyphenate = cached(hyphenate);

18、capitalize:字符串首位大写

function capitalize(str){
    return str.charAt(0).toUpperCase() + str.slice(1)
}
// abc ==> Abc
//使用记忆函数
let _capitalize = cached(capitalize)

19、extend:将属性混合到目标对象中

function extend(to, _from) {
    for(let key in _from) {
        to[key] = _from[key];
    }
    return to
}

20、Object.assign:对象属性复制,浅拷贝

Object.assign = Object.assign || function(){
    if(arguments.length == 0) throw new TypeError('Cannot convert undefined or null to object');
    
    let target = arguments[0],
        args = Array.prototype.slice.call(arguments, 1),
        key
    args.forEach(function(item){
        for(key in item){
            item.hasOwnProperty(key) && ( target[key] = item[key] )
        }
    })
    return target
}

使用Object.assign可以浅克隆一个对象:

let clone = Object.assign({}, target)

简单的深克隆可以使用JSON.parse()和JSON.stringify(),这两个api是解析json数据的,所以只能解析除symbol外的原始类型及数组和对象

let clone = JSON.parse( JSON.stringify(target) )

21、clone:克隆数据,可深度克隆

这里列出了原始类型,时间、正则、错误、数组、对象的克隆规则,其他的可自行补充

function clone(value, deep) {
    if (isPrimitive(value)) {
        return value
    }

    if (isArrayLike(value)) { //是类数组
        value = Array.prototype.slice.call(value)
        return deep ? value.map(item => clone(item, deep)) : value
    } else if (isPlainObject(value)) { //是对象
        let target = {}, key;
        for (key in value) {
            value.hasOwnProperty(key) && (target[key] = deep ? clone(value[key], deep) : value[key])
        }
        return target
    }

    let type = getRawType(value)

    switch (type) {
        case 'Date':
        case 'RegExp':
        case 'Error': value = new window[type](value); break;
    }
    return value
}~~~~

22、识别各种浏览器及平台

//运行环境是浏览器
let inBrowser = typeof window !== 'undefined';
//运行环境是微信
let inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform;
let weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();
//浏览器 UA 判断
let UA = inBrowser && window.navigator.userAgent.toLowerCase();
let isIE = UA && /msie|trident/.test(UA);
let isIE9 = UA && UA.indexOf('msie 9.0') > 0;
let isEdge = UA && UA.indexOf('edge/') > 0;
let isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android');
let isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios');
let isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge;

23、getExplorerInfo:获取浏览器信息

function getExplorerInfo() {
    let t = navigator.userAgent.toLowerCase();
    return 0 <= t.indexOf("msie") ? { //ie < 11
        type: "IE",
        version: Number(t.match(/msie ([\d]+)/)[1])
    } : !!t.match(/trident\/.+?rv:(([\d.]+))/) ? { // ie 11
        type: "IE",
        version: 11
    } : 0 <= t.indexOf("edge") ? {
        type: "Edge",
        version: Number(t.match(/edge\/([\d]+)/)[1])
    } : 0 <= t.indexOf("firefox") ? {
        type: "Firefox",
        version: Number(t.match(/firefox\/([\d]+)/)[1])
    } : 0 <= t.indexOf("chrome") ? {
        type: "Chrome",
        version: Number(t.match(/chrome\/([\d]+)/)[1])
    } : 0 <= t.indexOf("opera") ? {
        type: "Opera",
        version: Number(t.match(/opera.([\d]+)/)[1])
    } : 0 <= t.indexOf("Safari") ? {
        type: "Safari",
        version: Number(t.match(/version\/([\d]+)/)[1])
    } : {
        type: t,
        version: -1
    }
}

24、isPCBroswer:检测是否为PC端浏览器模式

function isPCBroswer() {
    let e = navigator.userAgent.toLowerCase()
        , t = "ipad" == e.match(/ipad/i)
        , i = "iphone" == e.match(/iphone/i)
        , r = "midp" == e.match(/midp/i)
        , n = "rv:1.2.3.4" == e.match(/rv:1.2.3.4/i)
        , a = "ucweb" == e.match(/ucweb/i)
        , o = "android" == e.match(/android/i)
        , s = "windows ce" == e.match(/windows ce/i)
        , l = "windows mobile" == e.match(/windows mobile/i);
    return !(t || i || r || n || a || o || s || l)
}

25、unique:数组去重,返回一个新数组

function unique(arr){
    if(!isArrayLink(arr)){ //不是类数组对象
        return arr
    }
    let result = []
    let objarr = []
    let obj = Object.create(null)
    
    arr.forEach(item => {
        if(isStatic(item)){//是除了symbol外的原始数据
            let key = item + '_' + getRawType(item);
            if(!obj[key]){
                obj[key] = true
                result.push(item)
            }
        }else{//引用类型及symbol
            if(!objarr.includes(item)){
                objarr.push(item)
                result.push(item)
            }
        }
    })
    
    return resulte
}

26、Set简单实现

window.Set = window.Set || (function () {
    function Set(arr) {
        this.items = arr ? unique(arr) : [];
        this.size = this.items.length; // Array的大小
    }
    Set.prototype = {
        add: function (value) {
            // 添加元素,若元素已存在,则跳过,返回 Set 结构本身。
            if (!this.has(value)) {
                this.items.push(value);
                this.size++;
            }
            return this;
        },
        clear: function () {
            //清除所有成员,没有返回值。
            this.items = []
            this.size = 0
        },
        delete: function (value) {
            //删除某个值,返回一个布尔值,表示删除是否成功。
            return this.items.some((v, i) => {
                if(v === value){
                    this.items.splice(i,1)
                    return true
                }
                return false
            })
        },
        has: function (value) {
            //返回一个布尔值,表示该值是否为Set的成员。
            return this.items.some(v => v === value)
        },
        values: function () {
            return this.items
        },
    }

    return Set;
}());

27、repeat:生成一个重复的字符串,有n个str组成,可修改为填充为数组等

function repeat(str, n) {
    let res = '';
    while(n) {
        if(n % 2 === 1) {
            res += str;
        }
        if(n > 1) {
            str += str;
        }
        n >>= 1;
    }
    return res
};
//repeat('123',3) ==> 123123123

28、dateFormater:格式化时间

function dateFormater(formater, t){
    let date = t ? new Date(t) : new Date(),
        Y = date.getFullYear() + '',
        M = date.getMonth() + 1,
        D = date.getDate(),
        H = date.getHours(),
        m = date.getMinutes(),
        s = date.getSeconds();
    return formater.replace(/YYYY|yyyy/g,Y)
        .replace(/YY|yy/g,Y.substr(2,2))
        .replace(/MM/g,(M<10?'0':'') + M)
        .replace(/DD/g,(D<10?'0':'') + D)
        .replace(/HH|hh/g,(H<10?'0':'') + H)
        .replace(/mm/g,(m<10?'0':'') + m)
        .replace(/ss/g,(s<10?'0':'') + s)
}
// dateFormater('YYYY-MM-DD HH:mm', t) ==> 2019-06-26 18:30
// dateFormater('YYYYMMDDHHmm', t) ==> 201906261830

29、dateStrForma:将指定字符串由一种时间格式转化为另一种

from的格式应对应str的位置

function dateStrForma(str, from, to){
    //'20190626' 'YYYYMMDD' 'YYYY年MM月DD日'
    str += ''
    let Y = ''
    if(~(Y = from.indexOf('YYYY'))){
        Y = str.substr(Y, 4)
        to = to.replace(/YYYY|yyyy/g,Y)
    }else if(~(Y = from.indexOf('YY'))){
        Y = str.substr(Y, 2)
        to = to.replace(/YY|yy/g,Y)
    }

    let k,i
    ['M','D','H','h','m','s'].forEach(s =>{
        i = from.indexOf(s+s)
        k = ~i ? str.substr(i, 2) : ''
        to = to.replace(s+s, k)
    })
    return to
}
// dateStrForma('20190626', 'YYYYMMDD', 'YYYY年MM月DD日') ==> 2019年06月26日
// dateStrForma('121220190626', '----YYYYMMDD', 'YYYY年MM月DD日') ==> 2019年06月26日
// dateStrForma('2019年06月26日', 'YYYY年MM月DD日', 'YYYYMMDD') ==> 20190626

// 一般的也可以使用正则来实现
//'2019年06月26日'.replace(/(\d{4})年(\d{2})月(\d{2})日/, '$1-$2-$3') ==> 2019-06-26

30、getPropByPath:根据字符串路径获取对象属性 : 'obj[0].count'

function getPropByPath(obj, path, strict) {
      let tempObj = obj;
      path = path.replace(/\[(\w+)\]/g, '.$1'); //将[0]转化为.0
      path = path.replace(/^\./, ''); //去除开头的.

      let keyArr = path.split('.'); //根据.切割
      let i = 0;
      for (let len = keyArr.length; i < len - 1; ++i) {
        if (!tempObj && !strict) break;
        let key = keyArr[i];
        if (key in tempObj) {
            tempObj = tempObj[key];
        } else {
            if (strict) {//开启严格模式,没找到对应key值,抛出错误
                throw new Error('please transfer a valid prop path to form item!');
            }
            break;
        }
      }
      return {
        o: tempObj, //原始数据
        k: keyArr[i], //key值
        v: tempObj ? tempObj[keyArr[i]] : null // key值对应的值
      };
};

31、GetUrlParam:获取Url参数,返回一个对象

function GetUrlParam(){
    let url = document.location.toString();
    let arrObj = url.split("?");
    let params = Object.create(null)
    if (arrObj.length > 1){
        arrObj = arrObj[1].split("&");
        arrObj.forEach(item=>{
            item = item.split("=");
            params[item[0]] = item[1]
        })
    }
    return params;
}
// ?a=1&b=2&c=3 ==> {a: "1", b: "2", c: "3"}

32、downloadFile:base64数据导出文件,文件下载

function downloadFile(filename, data){
    let DownloadLink = document.createElement('a');

    if ( DownloadLink ){
        document.body.appendChild(DownloadLink);
        DownloadLink.style = 'display: none';
        DownloadLink.download = filename;
        DownloadLink.href = data;

        if ( document.createEvent ){
            let DownloadEvt = document.createEvent('MouseEvents');

            DownloadEvt.initEvent('click', true, false);
            DownloadLink.dispatchEvent(DownloadEvt);
        }
        else if ( document.createEventObject )
            DownloadLink.fireEvent('onclick');
        else if (typeof DownloadLink.onclick == 'function' )
            DownloadLink.onclick();

        document.body.removeChild(DownloadLink);
    }
}

33、toFullScreen:全屏

function toFullScreen(){
    let el = document.documentElement;
    let rfs = el.requestFullScreen || el.webkitRequestFullScreen || el.mozRequestFullScreen || el.msRequestFullScreen;

    //typeof rfs != "undefined" && rfs
    if (rfs) {
        rfs.call(el);
    }else if (typeof window.ActiveXObject !== "undefined") {
        //for IE,这里其实就是模拟了按下键盘的F11,使浏览器全屏
        let wscript = new ActiveXObject("WScript.Shell");
        if (wscript != null) {
            wscript.SendKeys("{F11}");
        }
    }else{
        alert("浏览器不支持全屏");
    }
}

34、exitFullscreen:退出全屏

function exitFullscreen(){
    let el = parent.document;
    let cfs = el.cancelFullScreen || el.webkitCancelFullScreen || el.mozCancelFullScreen || el.exitFullScreen;

    //typeof cfs != "undefined" && cfs
    if (cfs) {
        cfs.call(el);
    }else if (typeof window.ActiveXObject !== "undefined") {
        //for IE,这里和fullScreen相同,模拟按下F11键退出全屏
        let wscript = new ActiveXObject("WScript.Shell");
        if (wscript != null) {
            wscript.SendKeys("{F11}");
        }
    }else{
        alert("切换失败,可尝试Esc退出")
    }
}

35、requestAnimationFrame:window动画

window.requestAnimationFrame = window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    function (callback) {
        //为了使setTimteout的尽可能的接近每秒60帧的效果
        window.setTimeout(callback, 1000 / 60);
    };
    
window.cancelAnimationFrame = window.cancelAnimationFrame ||
    Window.webkitCancelAnimationFrame ||
    window.mozCancelAnimationFrame ||
    window.msCancelAnimationFrame ||
    window.oCancelAnimationFrame ||
    function (id) {
        //为了使setTimteout的尽可能的接近每秒60帧的效果
        window.clearTimeout(id);
    }

36、_isNaN:检查数据是否是非数字值

原生的isNaN会把参数转换成数字(valueof),而null、true、false以及长度小于等于1的数组(元素为非NaN数据)会被转换成数字,这不是我想要的。Symbol类型的数据不具有valueof接口,所以isNaN会抛出错误,这里放在后面,可避免错误

function _isNaN(v){
    return !(typeof v === 'string' || typeof v === 'number') || isNaN(v)
}

37、max:求取数组中非NaN数据中的最大值

function max(arr){
    arr = arr.filter(item => !_isNaN(item))
    return arr.length ? Math.max.apply(null, arr) : undefined
}
//max([1, 2, '11', null, 'fdf', []]) ==> 11

38、min:求取数组中非NaN数据中的最小值

function min(arr){
    arr = arr.filter(item => !_isNaN(item))
    return arr.length ? Math.min.apply(null, arr) : undefined
}
//min([1, 2, '11', null, 'fdf', []]) ==> 1

39、random:返回一个lower - upper之间的随机数

lower、upper无论正负与大小,但必须是非NaN的数据

function random(lower, upper){
    lower = +lower || 0
    upper = +upper || 0
    return Math.random() * (upper - lower) + lower;
}
//random(0, 0.5) ==> 0.3567039135734613
//random(2, 1) ===> 1.6718418553475423
//random(-2, -1) ==> -1.4474325452361945

40、Object.keys:返回一个由一个给定对象的自身可枚举属性组成的数组

Object.keys = Object.keys || function keys(object) {
    if(object === null || object === undefined){
        throw new TypeError('Cannot convert undefined or null to object');
    }
    let result = []
    if(isArrayLike(object) || isPlainObject(object)){
        for (let key in object) {
            object.hasOwnProperty(key) && ( result.push(key) )
        }
    }
    return result
}

41、Object.values:返回一个给定对象自身的所有可枚举属性值的数组

Object.values = Object.values || function values(object) {
    if(object === null || object === undefined){
        throw new TypeError('Cannot convert undefined or null to object');
    }
    let result = []
    if(isArrayLike(object) || isPlainObject(object)){
        for (let key in object) {
            object.hasOwnProperty(key) && ( result.push(object[key]) )
        }
    }
    return result
}

42、arr.fill:使用 value 值来填充 array,从start位置开始, 到end位置结束(但不包含end位置),返回原数组

Array.prototype.fill = Array.prototype.fill || function fill(value, start, end) {
    let ctx = this
    let length = ctx.length;
    
    start = parseInt(start)
    if(isNaN(start)){
        start = 0
    }else if (start < 0) {
        start = -start > length ? 0 : (length + start);
      }
      
      end = parseInt(end)
      if(isNaN(end) || end > length){
          end = length
      }else if (end < 0) {
        end += length;
    }
    
    while (start < end) {
        ctx[start++] = value;
    }
    return ctx;
}
//Array(3).fill(2) ===> [2, 2, 2]

43、arr.includes:用来判断一个数组是否包含一个指定的值,如果是返回 true,否则false,可指定开始查询的位置

Array.prototype.includes = Array.prototype.includes || function includes(value, start){
    let ctx = this
    let length = ctx.length;
    
    start = parseInt(start)
    if(isNaN(start)){
        start = 0
    }else if (start < 0) {
        start = -start > length ? 0 : (length + start);
      }
    
    let index = ctx.indexOf(value)
    
    return index >= start;
}

44、arr.find:返回数组中通过测试(函数fn内判断)的第一个元素的值

Array.prototype.find = Array.prototype.find || function find(fn, ctx){
    fn = fn.bind(ctx)
    
    let result;
    this.some((value, index, arr), thisValue) => {
        return fn(value, index, arr) ? (result = value, true) : false
    })
    
    return result
}

45、arr.findIndex :返回数组中通过测试(函数fn内判断)的第一个元素的下标

Array.prototype.findIndex = Array.prototype.findIndex || function findIndex(fn, ctx){
    fn = fn.bind(ctx)
    
    let result;
    this.some((value, index, arr), thisValue) => {
        return fn(value, index, arr) ? (result = index, true) : false
    })
    
    return result
}

46、performance.timing:利用performance.timing进行性能分析

window.onload = function(){
    setTimeout(function(){
        let t = performance.timing
        console.log('DNS查询耗时 :' + (t.domainLookupEnd - t.domainLookupStart).toFixed(0))
        console.log('TCP链接耗时 :' + (t.connectEnd - t.connectStart).toFixed(0))
        console.log('request请求耗时 :' + (t.responseEnd - t.responseStart).toFixed(0))
        console.log('解析dom树耗时 :' + (t.domComplete - t.domInteractive).toFixed(0))
        console.log('白屏时间 :' + (t.responseStart - t.navigationStart).toFixed(0))
        console.log('domready时间 :' + (t.domContentLoadedEventEnd - t.navigationStart).toFixed(0))
        console.log('onload时间 :' + (t.loadEventEnd - t.navigationStart).toFixed(0))

        if(t = performance.memory){
            console.log('js内存使用占比 :' + (t.usedJSHeapSize / t.totalJSHeapSize * 100).toFixed(2) + '%')
        }
    })
}

47、禁止某些键盘事件

document.addEventListener('keydown', function(event){
    return !(
        112 == event.keyCode || //F1
        123 == event.keyCode || //F12
        event.ctrlKey && 82 == event.keyCode || //ctrl + R
        event.ctrlKey && 78 == event.keyCode || //ctrl + N
        event.shiftKey && 121 == event.keyCode || //shift + F10
        event.altKey && 115 == event.keyCode || //alt + F4
        "A" == event.srcElement.tagName && event.shiftKey //shift + 点击a标签
    ) || (event.returnValue = false)
});

48、禁止右键、选择、复制

['contextmenu', 'selectstart', 'copy'].forEach(function(ev){
    document.addEventListener(ev, function(event){
        return event.returnValue = false
    })
});

github地址:https://github.com/hfhan/tools

查看原文

永不消失的彩虹 赞了文章 · 2019-08-05

Ant Design Pro - 实践React Hooks - 页面

背景

React Hooks目前是React社区最炙手可热的新技术,我们准备追一下热度,在当前的项目中实践一下Hooks技术。
我们的项目使用的脚手架是Ant Design Pro 2.0,初步想法是把现有的一个有状态页面组件重构成Hooks函数组件。

设计

动手之前,先理一下思路,用Hook函数重构的话,需要调整哪些部分:

  • 组件类要拆解成Hooks函数
  • 组件状态改成Hooks状态
  • 类属性改成Hooks函数本地变量
  • 类方法改成Hooks函数的嵌套函数,可公用的也可以作为Hooks函数平级的函数

再理一下过程中的问题:

  • 函数和类不一样,它的this是由调用方决定的,是不确定的,所以不要使用this(Hooks函数里面也没必要用this)。
  • 没有this,就需要用传参了,Hooks函数就是props转递。
  • Dva的使用,Ant Design Pro使用的Dva来管理数据(里面封装的是Redux),这里我们还是可以继续使用,通过connect把Redux管理的model数据注入props。

    这里有个小知识点,组件类的model注入,使用了注解形式的装饰器,函数是不能使用装饰器的,所以需要我们手工注入。理解一点:这里的装饰器就是高阶函数
  • 状态共享,Hooks状态如何共享给其他Hooks函数或普通函数?Hooks状态声明时会返回:状态、修改此状态的方法,这两个返回值是可以传递和共享的

    这里也补充一个小知识,我们是可以声明一个服务类的Hooks,然后把状态和状态修改器返回来,这个是可以共享和传递的,可以实现类似全局状态管理器的功能,Hooks也提供了额外的辅助方法:useReducer。这里可以深入去学习一下。

解决了这些问题,我们就可以实践了。

实现

我们拆成了两个Hooks组件:分页列表 + 查询表单,来看看最终实现的代码。

入口 - 分页列表Hooks组件

function TableList(props) {
  const {
    dispatch,
    account: { accountList },
    loading,
  } = props;

  const [formValues, setFormValues] = useState({});
  const [selectedRows] = useState([]);

  const columns = [
    {
      title: '序号',
      dataIndex: '_index',
    },
    {
      title: '账户名称',
      dataIndex: 'accountName',
    },
  ];

  function handleSearch(e) {
    e && e.preventDefault();
    const { form } = props;

    form.validateFields((err, fieldsValue) => {
      if (err) return;

      dispatch({
        type: 'account/accountList',
        payload: fieldsValue,
      });
    });
  }

  function handleStandardTableChange(pagination, filtersArg, sorter) {
    const filters = Object.keys(filtersArg).reduce((obj, key) => {
      const newObj = { ...obj };
      newObj[key] = getValue(filtersArg[key]);
      return newObj;
    }, {});

    const params = {
      pageNumber: pagination.current,
      pageSize: pagination.pageSize,
      ...formValues,
      ...filters,
    };
    if (sorter.field) {
      params.sort = sorter.order === 'ascend' ? 'asc' : 'desc';
    }

    dispatch({
      type: 'account/accountList',
      payload: params,
    });
  }

  useEffect(() => {
    dispatch({
      type: 'account/clear',
    });
    // 自动查询
    handleSearch();
  }, []);

  return (
    <PageHeaderWrapper>
      <Card bordered={false}>
        <div className={styles.tableList}>
          <div className={styles.tableListForm}>
            <SearchForm formValues={formValues} handleSearch={handleSearch} {...props} />
          </div>
          <StandardTable
            loading={loading}
            data={accountList}
            columns={columns}
            rowKey="_index"
            selectedRows={selectedRows}
            hideAlert
            disablePagination={false}
            onChange={handleStandardTableChange}
          />
        </div>
      </Card>
    </PageHeaderWrapper>
  );
}

const tableList = connect(({ accountaging, loading }) => ({
  accountaging,
  loading: loading.models.accountaging,
}))(Form.create()(TableList));

export default tableList;

这里我们注意几个点:

  • props我们是透传给列表查询表单的。
  • 查询方法也是传过去的。
  • 存放表单值的状态是声明在列表组件,传给表单组件。
  • 这里的userEffect实现了之前ComponentDisMount的功能,注意:依赖传入了一个空数组,这里表示不依赖任何状态,所以只会在初次加载时执行,否则会出现死循环
  • 组件导出前,有个model和form的注入。

列表查询表单

function SearchForm(props) {
  const {
    account: { accountList },
    form,
    formValues,
    handleSearch,
  } = props;
  const { getFieldDecorator } = form;

  return (
    <Form onSubmit={handleSearch} layout="inline">
      <Row gutter={16} justify="start">
        <Col xl={8} md={12} sm={24}>
          <FormItem labelCol={{ span: 6 }} wrapperCol={{ span: 16 }} label="账户名称">
            {getFieldDecorator('accountName', {
              initialValue: '',
            })(<Input placeholder="账户名称" />)}
          </FormItem>
        </Col>
        <Col xl={8} md={24}>
          <Button className="fun-button" type="primary" htmlType="submit">
            查询
          </Button>
        </Col>
      </Row>
    </Form>
  );
}

表单组件这块,东西不多,理解props就好。

想法

其实整个实现下来,并不难。但是还有些问题需要我们考虑一下:

  • 使用Hooks之后,带来了哪些改变?
  • 代码逻辑是不是更优了?
  • 代码结构是否清晰?
  • 什么样是最佳实践?

欢迎讨论。

查看原文

赞 4 收藏 0 评论 5

永不消失的彩虹 赞了文章 · 2019-07-09

三目(三元)运算符??::的形式

三目运算符相信大家都很熟悉了:

foo ? 'foo == true' : 'foo == false'

而三目运算符?:?:?.....的调用方式大家也不陌生, 就相当于一堆if - else if语句:

foo ? 'foo == true' : bar ? 'bar == true' : 'bar == false'

但是在zepto.js里有一段代码:

slice.call(
         isSimple && !maybeID && element.getElementsByClassName ? // DocumentFragment doesn't have getElementsByClassName/TagName
           maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class
           element.getElementsByTagName(selector) : // Or a tag
           element.querySelectorAll(selector) // Or it's not simple, and we need to query all
       )

这里的三目运算符用的是??::的形式,我搞不太懂,所以打算做个实验搞懂它:

var bool1 = true, bool2 = true, val1 = 'val1', val2 = 'val2', val3 = 'val3';
  console.log(bool1 ? bool2 ? val1 : val2 : val3);

用表格记录下4个不同点情况:

bool1    bool2    值
true     true     val1
true     false    val2
false    true     val3
false    false    val3

可以看出上面的代码等价于:

console.log(bool1 ? ( bool2 ? val1 : val2 ) : val3);

条件(三元)运算符 -mdn上说三目运算符具有右结合性,根据以上两个例子,我总结三目运算符右结合性的意思是:

从最右边取":", 然后看它左边相邻的符号,如果是"?",那么它和这个"?"结合起来,可以用一个()把它俩包住;如果左边相邻的符号是":",那么取左边的":",再重复这个判断.

比如:

?:?:?:?:?:

可以取为

?:(?:(?:(?:(?:))))

???:::

可以取为

?(?(?:):):

然后根据你加上的括号,可以写出等价的if判断语句,这样就能理解复杂三目运算符所包含的意义了。

注意,三目运算符中"?"和":"是成对出现的,最起码数量上,有几个"?"就会有几个":".

查看原文

赞 3 收藏 0 评论 1

认证与成就

  • 获得 4 次点赞
  • 获得 19 枚徽章 获得 0 枚金徽章, 获得 3 枚银徽章, 获得 16 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-03-08
个人主页被 376 人浏览