御风天流

御风天流 查看完整档案

杭州编辑合肥工业大学  |  信息管理与信息系统 编辑有赞  |  前端 编辑 wulv.site 编辑
编辑

闻道有先后,术业有专攻

个人动态

御风天流 发布了文章 · 2020-02-28

聊一聊钉钉,那个被小学生疯狂打一星的APP

仿佛一夜之间,钉钉彻底火了,从一个小众的企业办公软件,成为全名皆知的国民软件,连我老家十八线小县城的小学,也都用上了。🔥到什么程度呢?在 AppStore 应用商店免费APP排行榜已经连续霸榜首大半个月了


从1月25号开始飙升,到2月1号稳稳坐上头把交椅。同时也引来小学生的疯狂差评,在B站也出现大量吐槽视频,我们按照时间线来梳理一下

  1. 大概从正月初七开始,陆续有学校开始组织使用在家直播上课,很多学校选择了钉钉。
  2. 学生们在家吃着火锅唱着歌,想着延长假期爽歪歪,突然通知让上直播课。
  3. 2月1号开始,大量学生在各大应用商店给钉钉打差评,据说1星差评会让钉钉下架, 老师面前唯唯诺诺,App Store上重拳出击! 评分从4.3急剧下降。
  4. 2月10开始,B站出现关于钉钉的鬼畜视频,比如:https://www.bilibili.com/vide... ,《你钉起来真好听》
  5. 2月16号,钉钉官方发布《钉 钉 本 钉 ,在 线 求 饶》,地址:https://www.bilibili.com/vide... ,发布一小时立刻登上B站热搜第二,目前播放量在1600多万,钉钉评分缓慢回升到2.x分。随后阿里巴巴账号发布《【钉钉】钉 宝 岛》互动。
  6. 2月18-25号,钉钉官方持续在B站互动,up主调侃钉钉求饶的正确方式,https://www.bilibili.com/vide... ,蹭热度调侃钉钉搞笑差评,钉钉官方发布三部鬼畜视频:《【经典怀旧】甩 钉 歌》、《【又一个RAP】你说钉钉有啥好???》、《 【唱 首 歌 】我 钉 起 来 真 好 听》

同时出现大量调侃钉钉的段子,评论区各个都是人才。但热度归热度,还是要看到热度背后的东西。

首先因为疫情导致没发正常上课,选择一个教学软件为什么那么多学校选择钉钉呢?B站评论区不乏很多阴谋论,说钉钉贿赂讨好教育局,搞定教育局关系,下面的学校就强制要使用了,这里面有不可告人的交易,都是利益关系;还有一部分人说钉钉本来是一个企业办公软件,因为免费才被教育行业借用,钉钉根本就不是用来教学用的。这两种说法其实都不对,隔壁腾讯难道就不认识几个领导?腾讯会议也免费啊。其实钉钉本来就有家校版,发力教育行业已经几年了,一切都是巧合中带着必然,意料之外,情理之中。教育局领导表示傻子,选择钉钉完全是经过调研评估过的,价格免费,功能强大,打卡、作业本、班级群、学生健康等功能都是针对家校场景量身定制的,很多之前收费的功能也在疫情期间免费试用,这才是选择钉钉的原因。

其次,小学生们为啥去打一星?同理,为啥很多员工都很厌恶钉钉?因为钉钉是违反“人性”的,人天生向往自由,爱玩,懒,不喜欢别人压着,这些天性有的好有的需要克服。

学生时期能多放半天假都高兴的不行,结果被网课安排的满满当当,老师布置作业还需要在线提交,一键提醒没做作业的学生交作业,心里自然会有情绪,而这情绪又没法发泄给老师家长,只能发泄到钉钉上,疯狂给一星。

员工上班肯定是觉得你给多少钱我办多少事,加班又不给加班费,下班后自然不想理你,没用钉钉最多微信发个消息,要是想打个电话还得去通讯录找找号码,无形的增加了成本。用钉钉at你立马能查看已读未读、还不行就ding一下,再没反应,直接免费电话打过去,员工完全没有自由时间,装死都不行。

但是这是钉钉的错吗?在我看来这是一种社会现状,你上班的舒适程度是由你的老板决定的,不是使用什么软件决定的,而老板不停的压迫员工,是历史阶段决定的,中国的产品服务竞争不过国外的,勤能补拙,只能通过加班加点追赶。老板也没有英明到只需要通过策略就能按部就班超越同行,大家都是在不断试错摸索。而钉钉的问题在于这把刀太锋利了,造成很多管理者滥用。未来如何把合适的管理方法和理念布道出去,才是钉钉更重要的。

记得上大学的时候,一旦放假之前,辅导员总会发给班长一个Excel表格,记录学生出行情况,甚至统计回家时间和哪趟火车,身份证号手机号等信息总是重复填写,我当时就很纳闷,学籍系统不是有这些东西么,为啥每次还要跑班长寝室填表格?其实都是信息系统没打通的问题,从侧面说明,其实中国的信息化程度还很低,收集一个信息很多人还是用Excel表格写好再发过去的方式,可以对比一下,中国的SaaS软件,出名的也就金蝶国际、金山软件、中国有赞、微盟等市值,加起来也就1600多亿港币,而国外光Salesforce一家企业就市值1586亿美元,workday 都市值3000多亿港币,差的不是一点半点。而钉钉将会作为企业级应用的入口,配合CRMOAERPHR等企业软件,推动整个社会数字化办公的进程。

03年非典,在家购物的需求让淘宝爆发了,20年新冠肺炎,在家办公的需求必定让钉钉爆发。客观条件让原来线下的工作模式搬到线上,无论是日志、工作流审批、远程会议、客户管理等等功能都将会极大提升工作效率,教育行业使用钉钉远程直播教学,会改变教育资源不平衡的问题,让偏远地区的学生能够和xx一中的学生一起上课。政府部门使用钉钉快速下发通知,上报问题,协同办公。甚至小区物业、医院、连锁店等都会大量使用钉钉办公协同。随着社会信息技术的发展,年轻一代从小就是用手机电脑,未来钉钉一定会发展远大。


华丽丽的的分割线,BTW,由于春节期间钉钉用户量暴增,无论是业务规模还是组织规模都在以每年翻番的速度增长,集团给的HC大大超出了之前的预计,并且钉钉还是少数阿里内部单独发期权的BU,类似蚂蚁金服,成长和收益可期。

下面介绍一下我们团队,钉钉智能办公应用平台,主要负责审批、智能表单、CRM等应用,在这里你可以负责千万级别DAU的产品,参与可视化表单搭建系统,渲染引擎,流程引擎,开放平台等业务的建设。如果感兴趣可以直接和我们TL联系,聊一聊业务,聊一聊技术。也可以发简历到我的邮箱:d3VxaS53bEBhbGliYWJhLWluYy5jb20= ,或者通过邮箱联系我。前端、Java等都招人,base可以杭州或者北京。

如果对钉钉有想法和意见,欢迎评论!

查看原文

赞 0 收藏 0 评论 0

御风天流 评论了文章 · 2018-11-01

从零开发一个健壮的npm包

最近写 node 的时候遇到一个需求,需要清理某目录下超过3天图片,本来想在 npm 找个包直接用用,结果没找到合适的,于是就自己撸一个了。

本文主要讲述如何从零开始开发一个完善健壮的 npm 包,主要涉及到一些工具的使用配置,包的功能不是重点。

一、配置eslint

ESLint是一个代码风格检测工具,比如使用空格还是tab,要不要加分号,使用驼峰命名还是下划线等等。可以保证一个团队的代码风格保持一致。

npm install eslint -g
eslint init

根据 eslint 提供的选项结合自己的需求,一路选择好后,会在根目录创建一个 .eslintrc.json 文件,里面一系列的规则配置,这个时候你再写代码,如果不符合规范,编辑器就会报错提示,如果某些目录不想使用校验,可以创建一个 .eslintignore,把不需要校验的目录放进入。为了方便执行校验,我们在 package.json 里配置一下 scripts:

"scripts": {
  "lint": "eslint --fix src"
}

可以配合githook强制每次提交的时候校验代码:使用git钩子做eslint校验

二、编写代码

我们在src目录下编写我们的代码,拆分成具体步骤为

  1. 读取目录下所有文件
  2. 筛选出我们需要处理的文件,比如创建或者修改时间超过3天的图片或者日志
  3. 删除这些文件

我们分成3个函数:

// readAllFileInfo.js
// 使用fs.readdir读取目录下所有文件
fs.readdir(filePath, function(err, files) {
    if (err) {
      reject(err);
    } else {
      Promise.all(files.map(file => {
        return filterFile(file, options);
      }))
        .then(deleteFiles => {
          resolve(deleteFiles.filter(deleteFile => deleteFile));
        });
    }
  });

// filterFile.js
// 使用fs.stat读取文件信息,然后筛选出需要删除的文件
fs.stat(fileName, (err, stats) => {
  if (err) {
    reject(err);
  } else {
    const time = stats[expiredType];
    const distanceTime = formatDate(date);
    const extName = path.extname(fileName);
    if (now - time > distanceTime && extName === `.${ext}`) {
      deleteFile(fileName)
        .then((res) => {
          resolve(res);
        });
    } else {
      resolve();
    }
  }
});

// deleteFile.js
// 使用fs.unlink删除文件
fs.unlink(fileName, err => {
  if (err) {
    reject(err);
  } else {
    resolve(fileName);
  }
});

这里主要讲一下解决问题的思路,首先整理一下解决这个问题需要哪些步骤,然后每一个步骤可以抽象成一个函数,想一下函数的传参和返回值,最后可以设计一下更加兼容易扩展的 API,具体代码可以查看仓库:https://github.com/wulv/del-e...

三、使用babel

在低版本的 node 可能还不支持某些 es6 语法,比如对象解构等,所以需要使用 babel 编译成低版本也能识别的语法。我们把 src 目录里的代码编译到 lib 目录,然后我们在 package.json 里,把 "main" 改为 "lib/index.js",这样对外暴露出去的代码就不会出现兼容性问题。

下载 babel-cli 依赖:

npm install --save-dev babel-cli
// 下载预设,预设就是别人配置好的一系列规则,编译在规则内的语法
npm install --save-dev babel-preset-es2015

配置好 .babelrc:

{
    "presets": ["es2015"]
}

package.json 里配置一下 scripts:

"scripts": {
  "build": "babel src -d lib",
  "build:watch": "npm run build -- --watch"
}

这样运行 npm run build 就会编译源文件到 lib 目录了。

四、编写测试

为了保障程序的稳定性,我们一定要写测试用例,特别是当你的程序依赖越来越多的时候,比如你改了A模块,你觉得你的改动都没问题,但一发布出去就会出现意想不到的 bug 因为影响到了你不知道的某个模块。这个时候测试就尤为重要,可以帮你避免很多低级错误。我们使用 mochachai 做测试框架和断言,下载依赖。

npm install mocha chai --save-dev

我们在项目根目录建立一个 test 目录。创建 index.formatDate.js,内容为:

'use strict';
const chai = require('chai');
const formatDate = require('../lib/formatDate');

const expect = chai.expect;
const S = 1000;

describe('format dete', () => {
  it('test 2s', function() {
    expect(formatDate('2s')).to.be.equal(2 * S);
  });
});

这里只是演示一下基本的语法,代码太多,具体看仓库。然后在 package.json 里再添加一个 scripts:

"test": "npm run build && mocha -t 5000"

五、Travis-CI+Coveralls

Travis-CI 是一个持续集成构建项目,结合github 可以实现很强大的功能,比如你给一个仓库提交 PR 后,可以自动帮你跑完测试用例,如果测试没有通过,就不能 mergemasterCoveralls 是一个自动化测试覆盖率的服务,用于收集测试覆盖率报告,对于开源项目免费,配置好这个后,就可以生成一个显示你代码测试覆盖率的 badgeCoveralls 可以使用 GitHub 账号登录,登录之后可以在 https://coveralls.io/repos/new 添加需要收集报告的 repo

首先安装一下 istanbul 这个工具来检测代码的测试覆盖率:

npm install istanbul --save-dev

然后在 package.json 中的 scripts 里添加:

"cover": "istanbul cover node_modules/mocha/bin/_mocha"

运行 npm run cover 就可以看到你的代码测试覆盖率了

========= Coverage summary =========
Statements   : 92.65% ( 63/68 )
Branches     : 75% ( 15/20 )
Functions    : 100% ( 14/14 )
Lines        : 92.65% ( 63/68 )
====================================

将测试覆盖率报告提交给 Coveralls,首先安装 coveralls:

npm install coveralls --save-dev

然后在 package.json 中的 scripts 里添加

 "coveralls": "npm run cover -- --report lcovonly && cat ./coverage/lcov.info | coveralls"

然后创建 .travis.yml 文件,

sudo: false
language: node_js
os:
  - linux
  - osx
node_js:
  - 6
  - 8
  - 9
  - 10
branches:
  only:
  - master
install:
- npm install
script:
  - npm run lint
  - npm run build
  - npm run cover
after_success:
- npm run coveralls

将代码推到 github 后,打开 https://travis-ci.org/ ,点开右上角头像 -> profile,把自己仓库的开关打开:

README.md 里添加代码测试覆盖率的 badge,我们可以使用 http://shields.io 来添加 badge,比如还有下载量,star数等这些badge

[![Build Status](https://travis-ci.org/wulv/del-expired-file.png)](https://travis-ci.org/wulv/del-expired-file)
[![Coverage Status](https://img.shields.io/coveralls/wulv/del-expired-file/master.svg?style=flat)](https://coveralls.io/github/wulv/del-expired-file?branch=master)

最终可以看到以下效果,当你看到一个 npm 如果显示了测试覆盖率,是不是顿时放心很多了呢?

六、发布

至此我们基本已经写好了一个相对比较健壮的仓库了,现在发布到 npm 上,如果没有 npm 账号的话,需要先注册一下,然后运行

npm adduser

输入用户名和密码,npm publish,这样你的仓库就发布好了,如果要更新版本,要遵循 Semver(语义化版本号) 规范:

  1. 升级补丁版本号:npm version patch
  2. 升级小版本号:npm version minor
  3. 升级大版本号:npm version major

总结

总结一下上面使用到的工具和技术:

我们可以看到,如果只是实现功能,把目标定在仅仅实现需求上,写这个 npm 包,用一个文件三个函数就完成了,但是这样的话,这个包估计也就只有你自己用了,以后的维护,修改都会比较麻烦,而使用这一套工具后,就健壮很多了。工作上也是这样,我们需要追求卓越,不断打磨自己的手艺,做出更完美的作品。

打个广告,杭州有赞诚招前端开发工程师,我们在4月举办了一次前端技术开放日,详情查看链接:https://tech.youzan.com/fe-op...。公司福利多多:

  • 标配:MacBook,报销:显示屏、鼠标、机械键盘
  • 五险一金、980元/月的餐补、加班打车费报销、年度outing和体检、每人每年有机会参加外部大会/培训等
  • 各种高大上的聚餐、千奇百怪的团建,长期投食的零食架...
  • 高配电视、游戏机、桌球、乒乓球、台球、健身器械,还有四驱赛道等,等你来战~

有意向的话请发送简历到wulv#youzan.com

参考链接

查看原文

御风天流 赞了文章 · 2018-01-11

React 中常见的动画实现方式

现在,用户对于前端页面的要求已经不能满足于实现功能,更要有颜值,有趣味。除了整体 UI 的美观,在合适的地方添加合适的动画效果往往比静态页面更具有表现力,达到更自然的效果。比如,一个简单的 loading 动画或者页面切换效果不仅能缓解用户的等待情绪,甚至通过使用品牌 logo 等形式,默默达到品牌宣传的效果。

React 作为最近几年比较流行的前端开发框架,提出了虚拟 DOM 概念,所有 DOM 的变化都先发生在虚拟 DOM 上,通过 DOM diff 来分析网页的实际变化,然后反映在真实 DOM 上,从而极大地提升网页性能。然而,在动画实现方面,React 作为框架并不会直接给组件提供动画效果,需要开发者自行实现,而传统 web 动画大多数都通过直接操作实际 DOM 元素来实现,这在 React 中显然是不被提倡的。那么,在 React 中动画都是如何实现的呢?

所有动画的本质都是连续修改 DOM 元素的一个或者多个属性,使其产生连贯的变化效果,从而形成动画。在 React 中实现动画本质上与传统 web 动画一样,仍然是两种方式: 通过 css3 动画实现和通过 js 修改元素属性。只不过在具体实现时,要更为符合 React 的框架特性,可以概括为几类:

  1. 基于定时器或 requestAnimationFrame(RAF) 的间隔动画;
  2. 基于 css3 的简单动画;
  3. React 动画插件 CssTransitionGroup
  4. 结合 hook 实现复杂动画;
  5. 其他第三方动画库。

一、基于定时器或 RAF 的间隔动画

最早,动画的实现都是依靠定时器 setIntervalsetTimeout 或者 requestAnimationFrame(RAF) 直接修改 DOM 元素的属性。不熟悉 React 特性的开发者可能会习惯性地通过 ref 或者 findDOMNode() 获取真实的 DOM 节点,直接修改其样式。然而,通过 ref 直接获取真实 DOM 并对其操作是是不被提倡使用,应当尽量避免这种操作。

因此,我们需要将定时器或者 RAF 等方法与 DOM 节点属性通过 state 联系起来。首先,需要提取出与变化样式相关的属性,替换为 state,然后在合适的生命周期函数中添加定时器或者 requestAnimationFrame 不断修改 state,触发组件更新,从而实现动画效果。

示例

以一个进度条为例,代码如下所示:

// 使用requestAnimationFrame改变state
import React, { Component } from 'react';

export default class Progress extends Component {
    constructor(props) {
        super(props);
        this.state = {
            percent: 10
        };
    }

    increase = () => {
        const percent = this.state.percent;
        const targetPercent = percent >= 90 ? 100 : percent + 10;
        const speed = (targetPercent - percent) / 400;
        let start = null;
        const animate = timestamp => {
            if (!start) start = timestamp;
            const progress = timestamp - start;
            const currentProgress = Math.min(parseInt(speed * progress + percent, 10), targetPercent);
            this.setState({
                percent: currentProgress
            });
            if (currentProgress < targetPercent) {
                window.requestAnimationFrame(animate);
            }
        };
        window.requestAnimationFrame(animate);
    }

    decrease = () => {
        const percent = this.state.percent;
        const targetPercent = percent < 10 ? 0 : percent - 10;
        const speed = (percent - targetPercent) / 400;
        let start = null;
        const animate = timestamp => {
            if (!start) start = timestamp;
            const progress = timestamp - start;
            const currentProgress = Math.max(parseInt(percent - speed * progress, 10), targetPercent);
            this.setState({
                    percent: currentProgress
                });
            if (currentProgress > targetPercent) {
                window.requestAnimationFrame(animate);
            }
        };
        window.requestAnimationFrame(animate);
    }

    render() {
        const { percent } = this.state;

        return (
            <div>
                <div className="progress">
                    <div className="progress-wrapper" >
                        <div className="progress-inner" style = {{width: `${percent}%`}} ></div>
                    </div>
                    <div className="progress-info" >{percent}%</div>
                </div>
                <div className="btns">
                    <button onClick={this.decrease}>-</button>
                    <button onClick={this.increase}>+</button>
                </div>
            </div>
        );
    }
}

在示例中,我们在 increasedecrease 函数中构建线性过渡函数 animationrequestAnimationFrame 在浏览器每次重绘前执行会执行过渡函数,计算当前进度条width 属性并更新该 state,使得进度条重新渲染。该示例的效果如下所示:

RAF实现进度条效果

这种实现方式在使用 requestAnimationFrame 时性能不错,完全使用纯 js 实现,不依赖于 css,使用定时器时可能出现掉帧卡顿现象。此外,还需要开发者根据速度函数自己计算状态,比较复杂。

二、基于 css3 的简单动画

当 css3 中的 animationtransition 出现和普及后,我们可以轻松地利用 css 实现元素样式的变化,而不用通过人为计算实时样式。

示例

我们仍以上面的进度条为例,使用 css3 实现进度条动态效果,代码如下所示:

import React, { Component } from 'react';

export default class Progress extends Component {
    constructor(props) {
        super(props);
        this.state = {
            percent: 10
        };
    }

    increase = () => {
        const percent = this.state.percent + 10;
        this.setState({
            percent: percent > 100 ? 100 : percent,
        })
    }

    decrease = () => {
        const percent = this.state.percent - 10;
        this.setState({
            percent: percent < 0 ? 0 : percent,
        })
    }

    render() {
        // 同上例, 省略
        ....
    }
}
.progress-inner {
  transition: width 400ms cubic-bezier(0.08, 0.82, 0.17, 1);
  // 其他样式同上,省略
  ...
}

在示例中,increasedecrease 函数中不再计算 width,而是直接设置增减后的宽度。需要注意的是,在 css 样式中设置了 transition 属性,当 width 属性发生变化时自动实现样式的动态变化效果,并且可以设置不同的速度效果的速度曲线。该示例的效果如下图所示,可以发现,与上一个例子不同的是,右侧的进度数据是直接变化为目标数字,没有具体的变化过程,而进度条的动态效果因为不再是线性变化,效果更为生动。

进度条效果

基于 css3 的实现方式具有较高的性能,代码量少,但是只能依赖于 css 效果,对于复杂动画也很难实现。此外,通过修改 state 实现动画效果,只能作用于已经存在于 DOM 树中的节点。如果想用这种方式为组件添加入场和离场动画,需要维持至少两个 state 来实现入场和离场动画,其中一个 state 用于控制元素是否显示,另一个 state 用于控制元素在动画中的变化属性。在这种情况下,开发者需要花费大量精力来维护组件的动画逻辑,十分复杂繁琐。

三、React 动画插件 CssTransitionGroup

React 曾为开发者提供过动画插件 react-addons-css-transition-group,后交由社区维护,形成现在的 react-transition-group,该插件可以方便地实现组件的入场和离场动画,使用时需要开发者额外安装。react-transition-group 包含 CSSTransitionGroupTransitionGroup 两个动画插件,其中,后者是底层 api,前者是后者的进一步封装,可以较为便捷地实现 css 动画。

示例

以一个动态增加tab的为例,代码如下:

import React, { Component } from 'react';
import { CSSTransitionGroup } from 'react-transition-group';

let uid = 2;
export default class Tabs extends Component {
    constructor(props) {
        super(props);
        this.state = {
            activeId: 1,
            tabData: [{
                id: 1,
                panel: '选项1'
            }, {
                id: 2,
                panel: '选项2'
            }]
        };
    }

    addTab = () => {
        // 添加tab代码
        ...
    }

    deleteTab = (id) => {
        // 删除tab代码
        ...
    }

    render() {
        const { tabData, activeId } = this.state;

        const renderTabs = () => {
            return tabData.map((item, index) => {
                return (
                    <div
                        className={`tab-item${item.id === activeId ? ' tab-item-active' : ''}`}
                        key={`tab${item.id}`}
                    >
                        {item.panel}
                        <span className="btns btn-delete" onClick={() => this.deleteTab(item.id)}>✕</span>
                    </div>
                );
            })
        }

        return (
            <div>
                <div className="tabs" >
                    <CSSTransitionGroup
                      transitionName="tabs-wrap"
                      transitionEnterTimeout={500}
                      transitionLeaveTimeout={500}
                    >
                      {renderTabs()}
                    </CSSTransitionGroup>
                    <span className="btns btn-add" onClick={this.addTab}>+</span>
                </div>
                <div className="tab-cont">
                    cont
                </div>
            </div>
        );
    }
}
/* tab动态增加动画 */
.tabs-wrap-enter {
  opacity: 0.01;
}

.tabs-wrap-enter.tabs-wrap-enter-active {
  opacity: 1;
  transition: all 500ms ease-in;
}

.tabs-wrap-leave {
  opacity: 1;
}

.tabs-wrap-leave.tabs-wrap-leave-active {
  opacity: 0.01;
  transition: all 500ms ease-in;
}

CSSTransitionGroup 可以为其子节点添加额外的 css 类,然后通过 css 动画达到入场和离场动画效果。为了给每个 tab 节点添加动画效果,需要先将它们包裹在 CSSTransitionGroup 组件中。 当设定 transitionName 属性为 'tabs-wrapper'transitionEnterTimeout 为400毫秒后,一旦 CSSTransitionGroup 中新增节点,该新增节点会在出现时被添加上 css 类 'tabs-wrapper-enter',然后在下一帧时被添加上 css 类 'tabs-wrapper-enter-active'。由于这两个 css 类中设定了不同的透明度和 css3 transition 属性,所以节点实现了透明度由小到大的入场效果。400毫秒后 css 类 'tabs-wrapper-enter''tabs-wrapper-enter-active' 将会同时被移除,节点完成整个入场动画过程。离场动画的实现类似于入场动画,只不过被添加的 css 类名为 'tabs-wrapper-leave''tabs-wrapper-leave-active'。该示例效果如下图所示:

动态增加tab效果

CSSTransitionGroup 支持以下7个属性:
CSSTransitionGroup 属性

其中,入场和离场动画是默认开启的,使用时需要设置 transitionEnterTimeouttransitionLeaveTimeout。值得注意的是,CSSTransitionGroup 还提供出现动画(appear),使用时需要设置 transitionAppearTimeout。那么,出现动画和入场动画有什么区别呢?当设定 transitionAppeartrue 时,CSSTransitionGroup初次渲染时,会添加一个出现阶段。在该阶段中,CSSTransitionGroup 的已有子节点都会被相继添加 css 类 'tabs-wrapper-appear''tabs-wrapper-appear-active',实现出现动画效果。因此,出现动画仅适用于 CSSTransitionGroup 在初次渲染时就存在的子节点,一旦 CSSTransitionGroup 完成渲染,其子节点就只可能有入场动画(enter),不可能有出现动画(appear)。

此外,使用 CSSTransitionGroup 需要注意以下几点:

  • CSSTransitionGroup 默认在 DOM 树中生成一个 span 标签包裹其子节点,如果想要使用其他 html 标签,可设定 CSSTransitionGroupcomponent 属性;
  • CSSTransitionGroup 的子元素必须添加 key 值才会在节点发生变化时,准确地计算出哪些节点需要添加入场动画,哪些节点需要添加离场动画;
  • CSSTransitionGroup 的动画效果只作用于直接子节点,不作用于其孙子节点;
  • 动画的结束时间不以 css 中 transition-duration 为准,而是以 transitionEnterTimeouttransitionLeaveTimeoutTransitionAppearTimeout 为准,因为某些情况下 transitionend 事件不会被触发,详见MDN transitionend

CSSTransitionGroup 实现动画的优点是:

  • 简单易用,可以方便快捷地实现元素的入场和离场动画;
  • 与 React 结合,性能比较好。

CSSTransitionGroup 缺点也十分明显:

  • 局限于出现动画,入场动画和离场动画;
  • 由于需要制定 transitionName,灵活性不够;
  • 只能依靠 css 实现简单的动画。

四、结合 hook 实现复杂动画

在实际项目中,可能需要一些更炫酷的动画效果,这些效果仅依赖于 css3 往往较难实现。此时,我们不妨借助一些成熟的第三方库,如 jQuery 或 GASP,结合 React 组件中的生命周期钩子方法 hook 函数,实现复杂动画效果。除了 React 组件正常的生命周期外,CSSTransitionGroup 的底层 api TransitonGroup 还为其子元素额外提供了一系列特殊的生命周期 hook 函数,在这些 hook 函数中结合第三方动画库可以实现丰富的入场、离场动画效果。

TransisitonGroup 分别提供一下六个生命周期 hook 函数:

  • componentWillAppear(callback)
  • componentDidAppear()
  • componentWillEnter(callback)
  • componentDidEnter()
  • componentWillLeave(callback)
  • componentDidLeave()

它们的触发时机如图所示:
TransitionGroup组件生命周期与自组件生命周期的关系

示例

GASP 是一个 flash 时代发展至今的动画库,借鉴视频帧的概念,特别适合做长时间的序列动画效果。本文中,我们用 TransitonGroupreact-gsap-enhancer(一个可以将 GSAP 应用于 React 的增强库)完成一个图片画廊,代码如下:

import React, { Component } from 'react';
import { TransitionGroup } from 'react-transition-group';
import GSAP from 'react-gsap-enhancer'
import { TimelineMax, Back, Sine } from 'gsap';

class Photo extends Component {
    constructor(props) {
        super(props);
    }

    componentWillEnter(callback) {
        this.addAnimation(this.enterAnim, {callback: callback})
    }

    componentWillLeave(callback) {
        this.addAnimation(this.leaveAnim, {callback: callback})
    }

    enterAnim = (utils) => {
        const { id } = this.props;
        return new TimelineMax()
            .from(utils.target, 1, {
                x: `+=${( 4 - id ) * 60}px`,
                autoAlpha: 0,
                onComplete: utils.options.callback,
            }, id * 0.7);
    }

    leaveAnim = (utils) => {
        const { id } = this.props;
        return new TimelineMax()
            .to(utils.target, 0.5, {
                scale: 0,
                ease: Sine.easeOut,
                onComplete: utils.options.callback,
            }, (4 - id) * 0.7);
    }

    render() {
        const { url } = this.props;
        return (
            <div className="photo">
                <img data-original={url} />
            </div>
        )
    }
}

const WrappedPhoto = GSAP()(Photo);

export default class Gallery extends Component {
    constructor(props) {
        super(props);
        this.state = {
            show: false,
            photos: [{
                id: 1,
                url: 'http://img4.imgtn.bdimg.com/it/u=1032683424,3204785822&fm=214&gp=0.jpg'
            }, {
                id: 2,
                url: 'http://imgtu.5011.net/uploads/content/20170323/7488001490262119.jpg'
            }, {
                id: 3,
                url: 'http://tupian.enterdesk.com/2014/lxy/2014/12/03/18/10.jpg'
            }, {
                id: 4,
                url: 'http://img4.imgtn.bdimg.com/it/u=360498760,1598118672&fm=27&gp=0.jpg'
            }]
        };
    }

    toggle = () => {
        this.setState({
            show: !this.state.show
        })
    }

    render() {
        const { show, photos } = this.state;

        const renderPhotos = () => {
            return photos.map((item, index) => {
                return <WrappedPhoto id={item.id} url={item.url} key={`photo${item.id}`} />;
            })
        }

        return (
            <div>
                <button onClick={this.toggle}>toggle</button>
                <TransitionGroup component="div">
                    {show && renderPhotos()}
                </TransitionGroup>
            </div>
        );
    }
}

在该示例中,我们在子组件 PhotocomponentWillEntercomponentWillLeave 两个 hook 函数中为每个子组件添加了入场动画 enterAnim 和 离场动画 LeaveAnim。在入场动画中,使用 TimeLineMax.from(target, duration, vars, delay) 方式建立时间轴动画,指定了每个子组件的动画移动距离随 id 增大而减小,延期时间随着 id 增大而增大,离场动画中每个子组件的延期时间随着 id 增大而减小,从而实现根据组件 id 不同具有不同的动画效果。实际使用时,你可以根据需求对任一子组件添加不同的效果。该示例的效果如下图所示:

图片画廊效果

在使用 TransitionGroup 时,在 componentnWillAppear(callback)componentnWillEntercallback)componentnWillLeave(callback) 函数中一定要在函数逻辑结束后调用 callback,以保证 TransitionGroup 能正确维护子节点的状态序列。关于 GASP 的详细使用方法可参考GASP官方文档和博文GSAP,专业的Web动画库,本文不再赘述。

结合 hook 实现动画可以支持各种复杂动画,如时间序列动画等,由于依赖第三方库,往往动画效果比较流畅,用户体验较好。但是第三方库的引入,需要开发者额外学习对应的 api,也提升了代码复杂度。

五、其他第三方动画库

此外,还有很多优秀的第三方动画库,如 react-motion,Animated,velocity-react等,这些动画库在使用时也各有千秋。

Animated

Animated 是一个跨平台的动画库,兼容 React 和 React Native。由于在动画过程中,我们只关心动画的初始状态、结束状态和变化函数,并不关心每个时刻元素属性的具体值,所以 Animated 采用声明式的动画,通过它提供的特定方法计算 css 对象,并传入 Animated.div 实现动画效果。

示例

我们使用 Animated 实现一个图片翻转的效果,代码如下。

import React, { Component } from 'react';
import Animated from 'animated/lib/targets/react-dom';

export default class PhotoPreview extends Component {
    constructor(props) {
        super(props);
        this.state = {
            anim: new Animated.Value(0)
        };
    }

    handleClick = () => {
        const { anim } = this.state;
        anim.stopAnimation(value => {
            Animated.spring(anim, {
                toValue: Math.round(value) + 1
            }).start();
        });
    }

    render() {
        const { anim } = this.state;

        const rotateDegree = anim.interpolate({
            inputRange: [0, 4],
            outputRange: ['0deg', '360deg']
        });

        return (
            <div>
                <button onClick={this.handleClick}>向右翻转</button>
                <Animated.div
                    style={{
                        transform: [{
                            rotate: rotateDegree
                        }]
                    }}
                    className="preivew-wrapper"
                >
                    <img
                        alt="img"
                        data-original="http://img4.imgtn.bdimg.com/it/u=1032683424,3204785822&fm=214&gp=0.jpg"
                    />
                </Animated.div>
            </div>
        );
    }
}

在该示例中,我们希望实现每点击一次按钮,图片向右旋转90°。在组件初始化时新建了一个初始值为 0 的 Animated 对象 this.state.animAnimated 对象中有插值函数 interpolate,当设定输入区间 inputRange 和输出区间 outputRange 后,插值函数可以根据 Animated 对象的当前值进行线性插值,计算得到对应的映射值。

在本例中,我们假设每点击一次按钮,this.state.anim 的值加 1,图像需要转动90°。在 render 函数中,我们设置插值函数 this.state.anim.interpolate 的输入区间为[0, 4],输出区间为['0deg', '360deg']。当执行动画时,this.state.anim 的值发生变化,插值函数根据 this.state.anim 当前值,计算得到旋转角度 rotateDegree,触发组件的重新渲染。因此,如果 Animated 对象当前值为 2,对应的旋转角度就是 180deg。在组件渲染结构中,需要使用 Animated.div 包裹动画节点,并将 rotateDegree 封装为 css 对象作为 stlye 传入 Animated.div 中,实现节点 css 属性的变化。

在点击事件中,考虑到按钮可能连续多次点击,我们首先使用 stopAnimation 停止当前正在进行的动画,该函数会在回调函数中返回一个 {value : number} 对象,value 对应最后一刻的动画属性值。根据获取的 value 值,随后使用 Animated.spring 函数开启一次新的弹簧动画过程,从而实现一个流畅的动画效果。由于每次转动停止时,我们希望图片的翻转角度都是90°的整数倍,所以需要对 Animated.spring 的终止值进行取整。最终我们实现了如下效果:

image

使用时需要注意一下几点:

  • Animated 对象的值和其插值结果只能作用于 Animated.div 节点;
  • interpolate 默认会根据输入区间和输出区间进行线性插值,如果输入值超出输入区间不受影响,插值结果默认会根据输出区间向外延展插值,可以通过设置 extrapolate 属性限制插值结果区间。

Animated 在动画过程中不直接修改组件 state,而是通过其新建对象的组件和方法直接修改元素的属性,不会重复触发 render 函数,是 React Native 中非常稳定的动画库。但是在 React 中存在低版本浏览器兼容问题,且具有一定学习成本。

结语

当我们在 React 中实现动画时,首先要考量动画的难易程度和使用场景,对于简单动画,优先使用 css3 实现,其次是基于 js 的时间间隔动画。如果是元素入场动画和离场动画,则建议结合 CSSTransitionGroup 或者 TransitionGroup 实现。当要实现的动画效果较为复杂时,不妨尝试一些优秀的第三方库,打开精彩的动效大门。

Ps. 本文所有示例代码可访问 github 查看

参考资料:

react-transition-group

react-gsap-enhancer

A Comparison of Animation Technologies

React Animations in Depth

本文首发于有赞技术博客
查看原文

赞 34 收藏 50 评论 0

御风天流 关注了问题 · 2017-12-15

segmentfault!你家技术回家泡妞了还是搞基去了!连个基本的刷帖屏蔽都不会!!!!!!!

既然有人出来替你洗地了,叫什么j神的,我都邀请你了,请你来解释一下.我不相信就一个人被改了答案.还是赶紧去数据库搜搜吧.忙了,爱咋咋地!
原贴:
正儿八经的答案被这位justjavac给改成了广告!号称技术问答社群就是这样的水准??
图片描述
图片描述

关注 8 回答 4

御风天流 发布了文章 · 2017-12-12

2017年经历的那些灵异事件

2017年快要过去了,回顾这一年来,在业务代码里,开发新功能占据70%,修复BUG占了30%,在解决的这些BUG中,大部分都是代码级别的错误,使用 Chrome Devtools 基本都可以解决,但其中有三个比较神奇,算得上是灵异事件了。

鬼打墙

有一次后端同学重构了一下 DSP 广告平台的接口,让 Java 服务化提供接口, PHPweb 控制层掌管路由和透传接口,于是对之前的接口URL重新规划统一了一下,内测没问题后就高高兴兴上线了,然而没过多久就有商家上报说页面报错没数据,于是我赶紧复现,但怎么都复现不出来,然后问商家浏览器是不是版本太低,网络是不是不稳定之类的,但商家的浏览器和网络环境都没问题,这就纳闷了,于是果断找了一台 Windows 机器(因为我们都是Mac而且没装虚拟机),让商家加 QQ 远程协助看一下到底报了什么错,倒腾了半天,连上商家电脑,复现果然报错了 NetWork Error ,打开 Chrome Devtools 一查, ajax 请求居然没没发出去。看了一眼浏览器上那一排插件,怀疑是不是插件搞的鬼,发现居然有屏蔽广告的插件,大哥,你特么自己都在我们平台上投广告,你还装屏蔽广告插件。果断让他关闭这个插件,然后果然没问题。原来我们的接口URL里有 advertisement 这个单词,插件直接屏蔽了这个URL。没过多久,又有一个商家报页面没数据,呵呵,我们直接叫他关闭浏览器屏蔽广告插件,结果商家告诉我还是不行。?,还是远程协助查一下,发现开了隐身窗口,接口还是没返回数据,看到商家电脑右下角运行的系统杀毒软件,眉头一皱,难道是这货搞的鬼?打开设置一看,赫然有屏蔽广告这个选项。果然国产软件都流氓,你这所有的浏览记录都被人家知道了啊。第二天果断把 advertisement 改成gg(guanggao),整个世界都清净了。

断头鬼

过了一段时间,我司搭了一个前端错误监控平台,可以收集客户端错误,上报到这个平台然后邮件告警开发者。收集的信息包括用户操作系统、浏览器版本、 IP 、操作路径等,这样就不需要再用 Windows 远程了。某一天,告警平台发邮件报错,店铺选择页面 js 运行报错,那还得了,选择不了店铺,相当于我们的后台入口挂了啊。果断按照报错的操作路径操作一次,又没复现。再一看操作系统与浏览器版本,找了一个一模一样的环境,还是复现不出来。晕了,还是用远程协助吧。商家那里确实有 js 运行报错,由于线上 js 也没有 source map ,压缩的代码也看不懂,查半天也没查出什么东西。回到监控平台后台,反复比对各条报错。结果发现 IP 都是差不多的范围,一查都是合肥电信的运营商,难道所有合肥电信的用户加载的这个 js 有问题?然后报给运维同学,他把那个 js 下载下来一看,长度不一样,和正常的版本比,少了一小段。肯定是 cdn 同步的时候,出了故障,果断把锅丢给七牛。

替死鬼

前几天,有一次发布后,一直收到邮件告警某个 basejs 运行报错,涉及的浏览器版本都是Chrome 31到37,轻车熟路开虚拟机复现,找半天找了一个Chrome 31,确实报错了,然而报错内容看不懂。想着那天发布内容包括升级基础 react 组件,加了一个 babel runtime ,还有一些其他的改动,难道是这些问题引起的?
然后尝试想让虚拟机 Chrome 运行本地代码,于是在 win 里面装 nodegit 、下载仓库、打包、把线上代码代理到本地。结果node-modules都装不上去。然后再试试装 fiddler 抓包软件,把打包后的代码放在 win 里面,抓取那几个 js ,替换成打包后的本地代码,然而还是看不懂,只知道是一个基础函数,可能是 babel polyfill 的问题,于是尝试把前端仓库那几天的改成一一 revert ,看看到底是哪个改动导致的问题。结果回退到发布之前都还是报错,这就尴尬了,至此我已经花了一天时间去排查这个问题,期间让几个同事一起排查也没发现问题本源。
最后想着把 win 的网络设置成 Mac 一样的网络,把 win 浏览器使用 SwitchySharp 设置成 Mac 的代理,这样就可以在 win 里面使用 Mac 的开发环境,(其实就是在 Mac 起一个 node 服务,监听一个端口,在这个服务里把所有的线上的前端资源( js , css )替换成本地代码,本地这个服务相当于一个网关服务器,还可以把网址指向不同环境的服务器。)结果惊奇的发现在预发环境是没有问题的,只有线上环境才有报错。我的第一反应是难道后端改造了什么数据类型?把线上数据和预发环境数据比对一下,然而一模一样。这个时候,对比两个环境只有两个差异了,一个线上环境多一个统计日志上报的 js ,还有一个就是错误收集上报的 js ,问了一下这两个 js 的维护者,果然统计日志的js,在那次发布的时候改动了,为了使用Object.assign,加了一个 polyfill ,然后和 base js 里面的 polyfill 冲突了,由于统计日志的 js 先加载,所以报错是在 base js 里面。
这个问题比较难排查的地方是:

  1. 复现环境比较苛刻
  2. 后端仓库的改动(统计日志的 js )和前端仓库产生了关联,版本回退难以排查
  3. 产生问题的 js 并不是出现报错的 js

总结

出现BUG在所难免,代码逻辑、浏览器兼容性、网络故障等等都会导致一些意想不到的问题,遇到问题首先不要慌,解决问题要有方法论,先把问题复现出来,然后使用 Chrome Devtools ,设置断点,观察数据条件,基本可以找出代码错误,其他问题,可以类似高中生物实验,结合条件对照,找出差异的条件,定位到问题,这个过程中需要敏锐的观察力。

查看原文

赞 8 收藏 0 评论 0

御风天流 发布了文章 · 2017-12-05

php多人开发环境原理解析

PHP 作为世界上最“好”的语言,在web里占据着大概80%的份额,中小公司基本都说 lnmp 架构。当一个仓库开发人员大于1,20人的时候,每个人可能开发不同的模块和功能,用代码版本控制工具比如 git 开不同的分支,流程大概是先在本地搭一套完整的环境,开发好部署在测试环境,自测或者测试人员测试好之后部署在预发布环境,预发布基本和线上环境一样,然后给产品验收,验收完成后再发布上线。

由于是并行开发,肯定存在好几个功能同时验收或者测试的情况,这个时候预发环境到底部署谁的代码呢?切换到A的分支,B就不能验收了。所以希望存在一个多人开发环境,每个人的开发流程互不影响。

PHP运行原理

首先我们来分析下 PHP 的运行原理,看看 PHP 的语言特点。当我们从浏览器发起一个请求,我们的web服务器(NginxApache等)监听了80或者443端口,我们来看一个最简单的 Nginxvhost 配置:

server {
  listen       80;
  server_name test.com;
 
  root /data/gateway/html;
  index   index.php;

  location ~ \.php$ {
    fastcgi_pass   127.0.0.1:9001; #unix:/Users/run/php-fcgi.sock;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include        fastcgi_params;
  }
}

Nginx 监听80端口,当匹配到用户访问的域名是test.com后使用对应的 vhost 配置。在服务器里PHP-FPM起一个服务,监听一个端口(比如9001)或者一个unix socket,Nginx通过fastcgi_pass配置,将请求传递给 PHP-FPM 来解析PHP代码,PHP解析器每次从index.php开始解析,一路处理下去、做一系列的逻辑处理、查询数据库或者缓存等操作,返回一个 HTML 或者其他结果给 NginxNginx 再返回给浏览器。流程如下图:

browser-nginx-php

  • CGI:是 NginxPHP_FPM 之间数据交换的一种协议。
  • FastCGI:同 CGI,是一种通信协议,但比 CGI 在效率上做了一些优化。
  • PHP-CGI:是 PHPNginx 提供的 CGI 协议的接口程序。
  • PHP-FPM:是 PHPNginx 提供的 FastCGI 协议的接口程序,额外还提供了相对智能一些任务管理。

多人开发环境

PHP 原理我们可以看到,PHP其实只是一个解释型的脚本语言,每次请求都要从index.php解析一次,那我们是不是可以在服务器根据不同开发者的名字,命名很多个文件夹,在各自文件夹里,clone 好代码仓库,切换到自己的分支。再让 Nginx 处理每个人目录下的index就可以了。比如直接访问http://wulv.test.com/,在 Nginx 获取到 wulv,把 root 设置到 wulv 这个目录,这样就访问到 wulv 这个目录下的代码了。可以让 Nginx 这样设置:

set $who www;
if ($http_who != "") {
   set $who $http_who;
}
root /data/gateway/$who/html;

我们可以让 URL 里携带用户的目录,在 Nginx 截取下来,可以在一下几个地方携带:

  • host: http://wulv.test.com
  • path: http://www.test.com/wulv
  • query: http://www.test.com?http_who=wulv

这样大体上可以实现需求了,但还是有点问题,比如页面里有些链接是写死的,没有使用相对路径,你一点击就又跑 www.test.com 去了,或者有些第三方应用比如 OAuth 等需要校验域名,你和线上域名不一致根本无法登陆。所以需要其他方式来实现,比如:

  • http request header
  • cookie

我们可以使用Modify Headers这个浏览器插件,修改http request 头信息,设置一个参数 http_whowulv,然后在 Nginx 获取。

拓展

如果有条件的话,其实还可以做一个网关服务器,做一个配置页面,在配置页面里配置一下需要访问的目录,下次访问,网关就直接帮你设置http header,代理到对应服务器。这样连浏览器插件都不需要装了,对运营和产品设计更加友好。

本文首发于我的个人博客:https://wulv.site/2017-11-05/...

查看原文

赞 2 收藏 5 评论 1

御风天流 赞了文章 · 2017-11-12

Zent - 源自有赞微商城的 React 组件库

Zent ( ˈzent ) 是有赞 PC 端 Web UI 规范的 React 实现版本,提供了一整套基础的 UI 组件以及常用的__业务组件__。通过 Zent,可以快速搭建出风格统一的页面,提升开发效率。目前我们有 45+ 组件,其中包括 Design 以及 SKU 等实用的业务组件。这些组件都已经在有赞的各类 PC 业务中广泛使用,我们会在此基础上,持续开发更多实用的新组件。

我们的目标是做东半球最好的 React 组件库,让 React 开发更快、更简单。

一、特性

  • 一套完整的 UI 组件库,组件都经过有赞的业务检验,实用又靠谱。

  • 完善的中英文文档,每个组件都有详细的 API 说明以及可以运行的示例。

  • 内置了 TypeScript 类型定义文件。

  • Zent 支持自定义主题,通过我们提供的工具你可以在不修改代码的情况下将组件库的整体色调改成你想要的任何颜色。

zent-theme

  • 一套有赞设计师绘制的图标库。

  • 单测覆盖率在 90% 以上。

  • 提供了一个 babel 插件自动化按需加载代码,只引入使用到的 JavaScript 以及 CSS 文件,减小 bundle 体积。

二、我们的优势: 丰富实用的组件

zent-components

下面是一些组件的简单展示,另外我们也提供了一些项目示例,可以帮助你快速使用 Zent 搭建一个页面。

时间选择

做过 Web 开发的都知道浏览器原生的时间选择组件不仅不好用,还有各种兼容性问题。为了解决这些问题,Zent 提供了一套自己的时间选择组件,包括日期选择、周选择组件、月选择以及时间区间选择。为了适应不同场景的需求,时间区间选择还提供了两种不同的交互模式。

zent-datepicker

颜色选择器

和时间选择一样,颜色选择在 Web 上也是一个问题,Zent 同样提供了一个功能强大又方便的颜色选择组件。

zent-colorpicker

除了常用的基础组件,Zent 还提供了丰富的__业务组件__,开发者可以使用这些组件快速实现业务功能。

微页面编辑

我们还开源了有赞的微页面编辑组件,支持自定义微页面内的组件,让你轻轻松松写出一个WebApp,让普通用户也可以搭建含动态数据的页面的。

zent-design

SKU 选择

商品规格是商品很重要的一个属性,Zent 的 SKU 选择组件封装了商品规格选择的逻辑,让你从复杂的交互中解放出来,有更多精力去优化业务的实现。

zent-sku

省市区选择

Zent 也提供了地址输入中常用的省市区选择组件,这个功能是由级联选择组件实现的。级联选择组件不仅仅可以用来实现省市区选择,很多有层次关系的内容选择都可以通过这个交互实现,例如店铺的主营业务类目选择等。

zent-cascader

我们会继续开放更多基于 Zent 的实用业务组件,敬请期待。

三、展望

Zent 还有不少功能没有完善,例如还没有动画基础设施,很期待得到大家的批评和帮助,一起打造一个更完善、更好用的 Zent。

完整代码请移步 Github,使用指南请移步文档网站

本文首发于有赞技术博客

查看原文

赞 19 收藏 31 评论 8

御风天流 回答了问题 · 2017-10-26

静态方法和实例方法的区别?

问题1,

var literal = 'hello';
literal.prop = 'xyz';
 
var copy = literal;
 
console.log(copy.prop)
-> undefined
var obj = new String('hello');
obj.prop = 'xyz';
 
var copy = obj;
console.log(copy.prop);
-> 'xyz'

参考链接:https://codeutopia.net/blog/2...
问题2:
你需要了解原型链,对象通过.调用方法,是有顺序的,比如

const Person = function() {
    this.getName = function(){ return 'aaa' }// 这里就是实例方法
}
Person.prototype.getName = function() { return 'bbb'; } // 这里是原型上的方法
const peter = new Person();
console.log(peter.getName()); // 'aaa'

这段代码可以看到,当调用一个对象的某个方法时,首先对象会查找本身有没有设置这个属性,如果找不到的话,其实每个对象都有一个__proto__属性,指向创建这个对象的构造函数的原型(这里也就是Person.prototype),原型也就是一个对象,也有自己的属性和__proto__,如果原型还找不到,就这样沿着__proto__一直找下去,这就构成了js的原型链。实例方法是每个对象都有一份的。
静态方法就是直接在构造函数上定义方法:

Person.toSmall = function() {
    return 'person';
}

静态方法和实例方法没什么关系。

关注 6 回答 5

御风天流 赞了回答 · 2017-10-26

解决this指向问题

1、2是控制台返回的 setTimeout 返回的 ID,是浏览器window对象返回的
在每个窗口,这个计数器 ID 都是唯一的,用以清除计数器

关注 5 回答 4

认证与成就

  • 获得 80 次点赞
  • 获得 8 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 7 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-11-14
个人主页被 815 人浏览