关于交互设计:Mac动画交互设计软件Principle
Principle for Mac,这款软件的设计动画让交互式用户界面变得容易,反对多种尺寸的原型设计,用很短的工夫就能够制作出一个具备残缺交互动画的原型
Principle for Mac,这款软件的设计动画让交互式用户界面变得容易,反对多种尺寸的原型设计,用很短的工夫就能够制作出一个具备残缺交互动画的原型
摘要:一文助你深刻了解设计模式七大准则。本文分享自华为云社区《零代码以“王者光荣”为例解析设计七准则,助你面试拿“五杀”》,作者: 陈言必行 。 前言:所有举例都是王者光荣相干内容(不玩王者光荣的同学,看起来稍吃力)。为了减少浏览趣味和不便把握这个七大准则,举例和准则的连贯,我曾经用尽毕生所学。陆陆续续写了一周还多,不喜勿喷哈~ 有播种的同学,记得点个赞再走… PS:文中波及到王者光荣的相干名字局部应用绿色3号字标识,所以有了不晓得是什么的小伙伴不必追溯,了解为一个配备名,英雄名或者间接了解为类名都是能够的。 一,繁多职责准则1.1 举例说明: 惩戒上单工夫:某休息日,地点:王者峡谷,人物:惩戒白起 版本形容:这个版本双烧流上单玩法很风行,这导致很多肉坦上单英雄都违心携带惩戒,而后出红莲斗篷(日炎)。既能反野放慢发育也能进步挫伤加成… 情景再现:敌我单方拖至20分钟风暴龙王现身,能够说实力相当局面非常焦灼。到了争夺风暴龙王一局定输赢的场面。话说我方双惩戒+白起长时间团战管制有更大的劣势… 局内对话: 打野:对面要打龙逼团了,我绕后找机会切对面射手法师,你们侧面拉扯下,白起尝试抢龙辅助:白起一会打团你间接入场管制开团,我保双C(我方射手法师)上路白起:好的,龙马上快到斩杀血线了,大家筹备… 我入场了,龙没抢到射手:没事没事,你找机会配合打野切对面鲁班,鲁班Si了就能打上路白起:打野筹备切入,鲁班闪现了我大招间隔不够,打野看你的了,鲁班没闪…5S后,对面凭借风暴龙王Buff和鲁班输入,团灭我方。带好兵线就能够间接推掉我方水晶,取得胜利。 赛后复盘: 尽管白起携带惩戒,然而并没有抢到龙王。也是因为携带惩戒,所以团站也没有管制到对面外围鲁班七号,导致输掉游戏。凡是这两点能做到任意一点也不至于输掉游戏。 1.2 准则解析: 繁多职责其实大多数时候,一个地位的英雄简略一些,职责繁多一些, 或者是更好的抉择。这就和设计模式中的一大准则 —— 繁多职责的情理是一样的。 就一个类而言,应该仅有一个引起它变动的起因,咱们在写代码的时候,很天然的就会给一个类加各种各样的性能。比方咱们写一款游戏,个别定义一个GameManager这样的类,于是咱们就把各种各样代码,像解决逻辑算法啊,拜访数据库啊什么的都写在这个类中。这就意味着,只有有需要改变,咱们都须要批改这个游戏管理器,这其实是很差的写法,保护麻烦,不能复用,也短少灵活性。 咱们刚开始学习面向对象的时候,就晓得面向对象的益处:可保护、可扩大、可复用、灵活性好。 所以这种写法是须要进行改过的。 如果一个类承当的职责过多,就等于吧这些职责耦合在一起,一个职责的变动可能减弱或者克制这个类实现其余职责的能力。这种耦合会导致软弱的设计,当变动产生时,设计会蒙受到意想不到的毁坏。 繁多职责的定义:繁多职责准则:就一个类而言,应该仅有一个引起它变动的起因。 二,凋谢关闭准则2.1 举例说明: 黄刀由来黄色打野刀上线也有几个版本了,简略猜想一下它的代码层面是如何实现的。 既然是打野刀,那么也就有打野刀的通用属性(可对野怪开释)-- 能够通过继承实现。既然是新配备,那么也就有和其余打野刀不同的属性 – 创立本人的类实现。像这种批改就合乎开闭准则。对扩大开启,对批改关闭。这时候你可能在想,我这不是说一堆废话嘛。新增加了一个配备可不要扩大吗,怎么也不会在红色打野刀类中去写黄色打野刀的逻辑啊… 的确是这样,可是你想过没有,这是在一个成熟的框架上来增加新配备。若这是刚开始开发的程序呢?咱们实现的时候不会将所有的二级打野刀都写在一个类中,而后应用属性或者枚举来辨别以后应用的打野刀是什么,而后进行相应的逻辑解决… 2.2 准则解析: 开闭准则因为咱们在最后写代码的时候,都假如需要不会产生扭转。当需要变动时,咱们就创立形象来隔离当前产生同类的变动。 比方原来王者中只有两种类型的打野刀:一个是物理挫伤,一个是法术挫伤的。其余各种属性都一样,那么此时咱们写代码的时候齐全可能将这两个打野刀写在一个类中。起初又来一个打野刀,它也是物理挫伤的,然而属性从加挫伤变成加进攻了。 那么此时咱们就须要思考将来游戏均衡会不会再增加新的打野刀,会不会批改现有繁多打野刀的属性和数值…这时候咱们的原来写的一个类中实现的两个打野刀,就会天然的演变成一个打野刀基类,两个子类继承的模式。进而有了后续增加打野刀时的增加形式。 咱们在做任何利用的时候,都不要指望一开始时需要确定,就再也不会有批改。既然需要肯定会变动,那么如何在面对需要变动时,使得咱们的程序能够绝对容易的批改,不至于说,新需要一来,咱们要删除原来局部代码,从新写一套。这就是凋谢关闭准则存在的意义。 对于开发时呈现出频繁变动的那些局部做出形象,然而,对于程序中的每个局部都能够的进行形象同样是一种不好的做法。回绝不成型的形象和形象自身一样重要。 凋谢-关闭准则是面向对象设计的外围所在。遵循这个原 则能够带来面向对象技术所宣称的微小益处,也就是可保护、可扩 展、可复用、灵活性好。 开闭准则的定义:凋谢-关闭准则:是说软件实体(类,模块,函数等等)应该是能够扩大,然而不可批改。 三,里氏代换准则3.1 举例说明: 吸血之镰吸血之镰俗称小吸血刀,可合成配备如下: 由上图咱们能够看到小吸血刀的属性: +10 物理攻击+8% 物理吸血当咱们点击它可合成配备时,能够看到三个配备的属性值都是蕴含 +物理攻击 和 +百分比物理吸血 的。这就是说明,大件配备由小件配备合成,并且继承了小件配备的属性值(多进去的局部时大件公有的)。 在游戏中不论你此时购买了末世,泣血,制裁这三个配备中的哪一个,你都取得了其父类小吸血刀的属性值。在程序的角度讲应用到小吸血刀(父类)的代码齐全能够被这三个配备(子类)任意一个去替换,并且不会对游戏逻辑产生影响,这就是里氏代换准则了。 3.2 准则解析: 里氏代换进一步形容: 子类对象可能替换程序中的父类对象呈现的任何对象,并且保障原来的程序逻辑行为不变及正确性不被毁坏。这么一说有点相似多态,多态是面向对象编程的一大个性,也是面向对象编程语言的一种语法。他是一种代码实现思路。而里氏替换是一种设计准则,是用来领导继承关系中子类该如何设计,子类的设计要保障在替换父类的时候,不扭转原有程序逻辑以及不毁坏原有程序的正确性。 回到举例: 若在咱们下面的举例中有一个小吸血刀类中(父类)GetAttribute()办法能够返回以后配备的属性,此时父类返回【 +10物理攻击,+8%物理吸血】;在大吸血刀类中(子类)GetAttribute()返回【 +100物理攻击,+25%物理吸血】,那么此时这个子类的设计就违反了里氏替换准则。 一个软件实体如果应用的是一个父类的话,那么肯定实用于其子类,而且觉察不出父类对象和子类对象的区别;也就是说,在程序外面,把父类都替换成它的子类,程序的行为没有变动;简略地说,子类型必须可能替换掉它们的父类型。 里氏准则的定义:里氏代换准则:子类型必须可能替换掉他们的父类型。 四,迪米特法令又称:起码晓得法令 4.1 举例说明: 妲己抓人工夫:某休息日,地点:王者峡谷,人物:亚瑟,妲己 ...
导读:C++内存泄漏问题的分析、定位一直是Android平台上困扰开发人员的难题。因为地图渲染、导航等核心功能对性能要求很高,高德地图APP中存在大量的C++代码。解决这个问题对于产品质量尤为重要和关键,高德技术团队在实践中形成了一套自己的解决方案。 分析和定位内存泄漏问题的核心在于分配函数的统计和栈回溯。如果只知道内存分配点不知道调用栈会使问题变得格外复杂,增加解决成本,因此两者缺一不可。 Android中Bionic的malloc_debug模块对内存分配函数的监控及统计是比较完善的,但是栈回溯在Android体系下缺乏高效的方式。随着Android的发展,Google也提供了栈回溯的一些分析方法,但是这些方案存在下面几个问题: 1.栈回溯的环节都使用的libunwind,这种获取方式消耗较大,在Native代码较多的情况下,频繁调用会导致应用很卡,而监控所有内存操作函数的调用栈正需要高频的调用libunwind的相关功能。 2.有ROM要求限制,给日常开发测试带来不便。 3.用命令行或者DDMS进行操作,每排查一次需准备一次环境,手动操作,最终结果也不够直观,同时缺少对比分析。 因此,如何进行高效的栈回溯、搭建系统化的Android Native内存分析体系显得格外重要。 高德地图基于这两点做了一些改进和扩展,经过这些改进,通过自动化测试可及时发现并解决这些问题,大幅提升开发效率,降低问题排查成本。 一、栈回溯加速Android平台上主要采用libunwind来进行栈回溯,可以满足绝大多数情况。但是libunwind实现中的全局锁及unwind table解析,会有性能损耗,在多线程频繁调用情况下会导致应用变卡,无法使用。 加速原理 编译器的-finstrument-functions编译选项支持编译期在函数开始和结尾插入自定义函数,在每个函数开始插入对__cyg_profile_func_enter的调用,在结尾插入对__cyg_profile_func_exit的调用。这两个函数中可以获取到调用点地址,通过对这些地址的记录就可以随时获取函数调用栈了。 插桩后效果示例: 这里需要格外注意,某些不需要插桩的函数可以使用__attribute__((no_instrument_function))来向编译器声明。 如何记录这些调用信息?我们想要实现这些信息在不同的线程之间读取,而且不受影响。一种办法是采用线程的同步机制,比如在这个变量的读写之处加临界区或者互斥量,但是这样又会影响效率了。 能不能不加锁?这时就想到了线程本地存储,简称TLS。TLS是一个专用存储区域,只能由自己线程访问,同时不存在线程安全问题,符合这里的场景。 于是采用编译器插桩记录调用栈,并将其存储在线程局部存储中的方案来实现栈回溯加速。具体实现如下: 1.利用编译器的-finstrument-functions编译选项在编译阶段插入相关代码。 2.TLS中对调用地址的记录采用数组+游标的形式,实现最快速度的插入、删除及获取。 定义数组+游标的数据结构: typedef struct { void* stack[MAX_TRACE_DEEP]; int current;} thread_stack_t;初始化TLS中thread_stack_t的存储key: static pthread_once_t sBackTraceOnce = PTHREAD_ONCE_INIT;static void __attribute__((no_instrument_function))destructor(void* ptr) { if (ptr) { free(ptr); }}static void __attribute__((no_instrument_function))init_once(void) { pthread_key_create(&sBackTraceKey, destructor);}初始化thread_stack_t放入TLS中: get_backtrace_info() { thread_stack_t* ptr = (thread_stack_t*) pthread_getspecific(sBackTraceKey); if (ptr) return ptr; ptr = (thread_stack_t*)malloc(sizeof(thread_stack_t)); ptr->current = MAX_TRACE_DEEP - 1; pthread_setspecific(sBackTraceKey, ptr); return ptr;}3.实现__cyg_profile_func_enter和__cyg_profile_func_exit,记录调用地址到TLS中。 ...
Aliyun Serverless VSCode ExtensionAliyun Serverless VSCode Extension 是阿里云 Serverless 产品 函数计算 Function Compute 的 VSCode 插件,该插件是结合了函数计算 Fun 工具以及函数计算 SDK ,为用户提供 VSCode 图形化开发调试函数计算以及操作函数计算资源的工具。 通过该插件,您可以: 快速地在本地初始化项目、创建函数运行、调试本地函数(调试功能目前支持 nodejs、python、php)拉取云端的服务函数列表,执行云端函数部署服务函数至云端,并更新相关配置前置需求如果您期望使用 Aliyun Serverless VSCode Extension 的所有功能,那么您需要确保系统中有以下组件: VSCode:在 Visual Studio Code 官网 中可以下载安装函数计算 Fun 工具以及 Docker:可以在 aliyun/fun 中根据教程安装配置 Fun 以及 Docker安装插件打开 VSCode 并进入插件市场。在插件市场中搜索 “Aliyun Serverless”,查看详情并安装。重启 VSCode,左侧边栏中会展示已安装的 Aliyun Serverless VSCode Extension 插件。快速入门绑定阿里云账户打开左侧 Aliyun Serverless VSCode Extension,单击绑定阿里云账户的按钮。 依次输入阿里云 Account ID,阿里云 Access Key ID,阿里云 Access Key Secret。 绑定完成后,可以看到所绑定的阿里云账户的云端服务与函数列表。 您可以通过切换区域 Region 来查看不同区域的服务与函数。单击云端资源面板的切换区域按钮或 VSCode 下方的区域信息。 ...
场景介绍阿尔茨海默病,是导致中老年人认知功能障碍的最常见疾病之一,是发生在老年期及老年前期的一种原发性退行性脑病。据估计,全世界痴呆症患者数量为4700万,到2030年将达到7500万人。痴呆症患者数量到2050年预计将是现在的近三倍。疾病的高昂费用给卫生系统应对未来预计不断增加的病例构成挑战。据估计,目前每年的支出为8180亿美元,而支出的增长速度预计会比疾病流行率上升还要快。照料痴呆症患者给照护者带来巨大压力,包括身体上、情感上和经济上的压力。(by世界卫生组织) 用技术解决阿尔茨海默病护理的问题,让老人和其护理者有更好的生活质量,是我们可以解决的方法。基于物联网技术,已经有一些设备实现了阿尔茨海默病老人走失定位。但是我们要做更高一层,除了单独分发的硬件之外,我们要使用开发工具IoT Studio帮助医疗机构做一个硬件SaaS管理系统,让他们可以随时监控旗下所有阿尔兹海默护理设备的数据以及定位,对老人的情况实现实时监控。同时也有能力对掌控的设备进行增删改查,方便他们自己管理设备。通过IoT Studio赋能开发者,让他们帮助包括医疗在内的各个行业用上物联网技术,惠及百姓。 我们首先构建一个可以拍照,检测心跳的手环设备,然后基于这个设备帮助护理机构开发一个集合管理监控告警的SaaS系统。设备由一个可以检测心跳的光学模块,一个可以检测老人所在地场景的摄像头,一个GPS定位模块,一个物联网通讯模块(一般为GPRS),MCU和电源组成。云端由物联网平台为基础建立设备与云端通讯,配合RDS存储心跳&GPS数据,OSS存储图片数据,最后用IoT Studio的服务开发与Web可视化开发功能完成功能页面搭建。整个云端开发过程只需要2小时以内即可。 最终效果如图。 硬件部分在demo阶段,我们采用树莓派3B+摄像头+心跳模块+GPS+电池的方法,验证不同数据的上报方法以及数据存储链路。考虑简单化,联网暂时采用WIFI方法。如果觉得使用电路比较麻烦,也可以使用服务开发+虚拟设备上报的方式,具体查看这篇文档。 虽然带手环的老人不一样,但是每个手环上报的属性类别是一样的,我们可以类似编程开发里把它们归结为同一个类(class)。我们首先需要在物联网平台上为我们的demo手环建立一个设备类(即产品),这样我们才能在以后不断的往这个产品下实例化新的设备。进入阿里云物联网平台,在产品页面新建一个产品,选择自定义品类即可,命名为“阿尔茨海默氏症老人监控手环”。 进入产品的功能定义页,定义5个自定义功能——剩余电量,地理位置,心跳,图片地址(存放摄像机上传图片的URL)。 地理位置只需要在“添加功能”里用标准的功能即可,如图,其他全部配置项默认即可。 心跳为一个整型数据,剩余电量为浮点型数据,图片地址为字符型数据,如图。 在设备面板点击“添加设备”,选择刚才创建的手环产品,然后输入随意的设备名称即可。 IoT Studio为交付型业务做了项目维度的隔离,因此需要将用到的设备导入到对应的项目中。首先打开物联网平台的“开发服务”选项进入IoT Studio。点击某个项目名称的“查看”进入项目详情页。然后点击右上角的“导入产品”。选择刚才的手环产品,然后导入,可以在设备管理页看到产品以及下属的设备已经导入项目里。 这样就完成了产品的定义,实例化与项目维度的隔离了。 上云部分树莓派采用python编程,因此我们需要参考物联网平台的python SDK,同时开发者社区也有很多相关文章。在这里我们直接跳过。由于物联网平台的属性不支持直接存储图片,因此我们暂时使用oss进行存储。你也可以选择使用HTTP/2通道(已支持python SDK)将图片上传至物联网平台每个设备单独的存储空间,不过从该存储空间调用图片的URL需要动态生成,可以参考这篇文档。 OSS存储空间准备阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以通过调用 API,在任何应用、任何时间、任何地点上传和下载数据,也可以通过 Web 控制台对数据进行简单的管理。OSS 适合存放任意类型的文件,适合各种网站、开发企业及开发者使用。 首先点击“立即开通”进入开通页面并点击同意协议。 然后进入控制台,新建一个Bucket,一个Bucket相当于一个文件夹,可以通过API路径访问里面的文件。在这里我们选择公共读写。 然后可以看到OSS控制台新建了一个bucket,里面是空的,我们可以尝试上传一些图片。 然后点击文件右方的操作项里的“复制文件URL”,把复制的URL粘贴到浏览器,看看能否预览。 可以看到通过URL访问我们就能看到图片了,这样OSS的配置就完成了。 考虑到Bucket的公共读写特性,安全性会有一定的问题,可以考虑将bucket私有化,然后图片上传的时候设置图片为公共读写,并采用时间戳加盐等方式将图片文件名随机化的方式解决。当然安全性上HTTP/2通道为更优方案。 树莓派代码树莓派的配置与连接在此不再赘述,可以在树莓派新建py文件,直接将此份代码复制过去,并且设置为开机执行,也可以参考这篇文档。代码如下(基于python 3.6),需要根据备注填入自己的账号信息,产品信息等: ##注意,本demo代码忽略了电池电量检测模块import aliyunsdkiotclient.AliyunIotMqttClient as iot ##导入阿里云的设备MQTT库,如果import失败需要先pip3 install 一下import jsonimport multiprocessingimport timeimport randomimport oss2 ##导入阿里云的OSS库,如果import失败需要先pip3 install oss2from picamera import PiCamera ##树莓派的摄像头,系统自带import RPi.GPIO as GPIO ##GPIO口,接红外PIR用import serialimport pynmea2from pulsesensor import Pulsesensor ##导入树莓派的pulsesensor库,https://github.com/tutRPi/Raspberry-Pi-Heartbeat-Pulse-Sensor/blob/master/example.pyauth = oss2.Auth('**AccessId*****','**AccessSecret*****') ##OSS的授权需要阿里云账号AccessId和AccessSecret,具体查看https://usercenter.console.aliyun.com/#/manage/akbucket = oss2.Bucket(auth,'http://oss-cn-beijing.aliyuncs.com','***你的bucket名称***') ##需要根据服务器区域修改节点路径,见文档global picURLtoIoTcamera = PiCamera()camera.resolution = (800,600) ##拍照分辨率,越高越容易分析,但是上次越慢GlobalBpm = 0 ##记录心跳数据Latitude = 0 ##记录GPS数据Longtitude = 0##初始化树莓派def init(): GPIO.setwarnings(False) GPIO.setmode(GPIO.BOARD) GPIO.setup(3, GPIO.IN) passdef take_photo(): ticks = int(time.time()) fileName = 'test%s.jpg' % ticks ##在文件名加入了时间戳作为简易加密手段 filePath = '/home/pi/Pictures/%s' % fileName camera.capture(filePath) bucket.put_object_from_file('bucket_file_name/%s', fileName) ##在这里改bucket名字 global picURLtoIoT picURLtoIoT = 'http://***你的bucket名称**.oss-cn-beijing.aliyuncs.com/bucket_file_name/%s' % fileName ##在这里改bucket名字和bucket内文件夹的名字 print(str(picURLtoIoT))def detect_Heartbeat(): p = Pulsesensor() p.startAsyncBPM() try: while True: bpm = p.BPM if bpm > 0: print("BPM: %d" % bpm) GlobalBpm = bpm; else: print("No Heartbeat found") time.sleep(1) except: p.stopAsyncBPM()def get_GPS(): ser = serial.Serial("/dev/ttyAMA0",9600) while True: line = ser.readline() if line.startswith('$GNRMC'): rmc = pynmea2.parse(line) print "Latitude: ", float(rmc.lat)/100 print "Longitude: ", float(rmc.lon)/100 Latitude = float(rmc.lat)/100 Longtitude = float(rmc.lon)/100 break options = { 'productKey':'**你的ProductKey**', 'deviceName':'**你的deviceName**', 'deviceSecret':'**你的deviceSecret**', 'port':1883, 'host':'iot-as-mqtt.cn-shanghai.aliyuncs.com' ##注意阿里云IoT国内都是华东2,不一定跟OSS的节点一致}host = options['productKey'] + '.' + options['host']def on_message(client, userdata, msg): topic = '/' + productKey + '/' + deviceName + '/update' print(msg.payload)def on_connect(client, userdata, flags_dict, rc): print("Connected with result code " + str(rc))def on_disconnect(client, userdata, flags_dict, rc): print("Disconnected.")##设备上报的定义def upload_device(client): topic = '/sys/'+options['productKey']+'/'+options['deviceName']+'/thing/event/property/post' while True: payload_json = { 'id': int(time.time()), 'params': { 'BPM': GlobalBpm, 'picURL': picURLtoIoT, 'Geo': { 'CoordinateSystem":1, 'Latitude':Latitdue, 'Longitude':Longtitude, 'Altitude':0 }, }, 'method': "thing.event.property.post" } print('send data to iot server: ' + str(payload_json)) client.publish(topic, payload=str(payload_json))if __name__ == '__main__': client = iot.getAliyunIotMqttClient(options['productKey'], options['deviceName'], options['deviceSecret'], secure_mode=3) client.on_connect = on_connect client.connect(host=host, port=options['port'], keepalive=60) p = multiprocessing.Process(target=upload_device, args=(client,)) p.start() get_GPS() detect_Heartbeat() take_photo() GPIO.cleanup() client.loop_forever()结束,把这个python文件设置为开机运行即可。 ...
摘要: 想和业界的技术大牛并肩作战?想学习最前沿的技术?想参与超酷的大项目?想获得亿万用户的点赞?蚂蚁金服春季校招开启了!面试很难?没有信心?这都不是事儿,这儿有一系列的“面试宝典”。文/图 源介我是2018年3月入职蚂蚁的应届前端工程师,来自于北京邮电大学。2年前的此刻,我也是实习求职大潮中的一员,在这里,分享一下我从准备面试到实习,再到最终正式入职的经历。选择蚂蚁阿里的前端水平在业界有着很高的评价,以至于知乎上经常有人这么描述,“国内前端,不是在阿里,就在去阿里的路上”。相信对于每一个做技术的小伙伴,进到顶级的技术团队进行工作,一定是自己最大的梦想。我也不例外,而且进入蚂蚁的梦想也是随着我的前端学习过程逐步形成的:在学校时曾使用 koa 框架及其周边配套工具,后来发现 koa 团队的两位核心成员 dead-horse (死马)和 fengmk2 (苏千)竟然都是中国人而且都是在支付宝工作。后来使用 Antd 和 dva 时发现这两款 github star 数上万的开源作品也出自蚂蚁前端,便开始认识到蚂蚁金服有着相当强的前端实力。2017年初,我关注了很久、并从他博客里学到太多知识的民工叔徐飞,也跳槽进入到蚂蚁,一下子让自己对于蚂蚁前端有了向往之心。不久之后,通过苏千在 CNode 社区发的一篇招聘贴,我才知道原来这些大牛们竟然都在一个部门:蚂蚁金服体验技术部,这里还有 Antd 作者偏右、 dva 作者云谦,有 echarts 作者林峰,有著名博主阮一峰,还有 egg.js、AntV 等等业界知名作品,而部门负责人,则是著名的玉伯大大。内心被这个部门的强大实力所折服的同时,也真切的渴望自己未来能进入到这个部门工作。学习与沉淀在真正开始面试之前,自己经历了比较长的准备期。同很多学弟学妹一样,我在学校的课程里也没有前端相关的内容,机缘巧合,在导师实验室的一些项目中负责网站相关的部分,书写php的同时也需要完成大量前端代码。也正是因此,开始喜欢上前端并决定以前端作为自己未来求职方向。于是在研一时,一方面开始阅读各类前端书籍,犀牛书、红宝书、蝴蝶书、CSS 权威指南等,一方面在实验室的一些项目中尝试前沿的前端技术,比如当时还处于 0.12版本的 Vue ,比如 node 。后来实验室几位已毕业的师兄辞职创业,邀我暑期前去实习,实习中对于工业界前端开发的全流程都有了一定的了解,也掌握了 Webpack、ES6、 Vue 1.0 全家桶等等技术栈,并将 koa 落地到公司的一个后台管理业务中。2016年研二时,自己已经具备了一定的前端能力,而实验室的项目都比较琐碎零散,难以获得个人成长。便开始自己完成一些开源项目,自己给自己提需求,在做项目中学,在实践中挖掘可以深入的点,完成了 AMD 模块加载器,基于 koa、MongoDB 和 Vue 的博客, Vue 源码解析, node 爬虫等等小项目,并在书写代码过程中通过博客总结和输出。这一过程让自己得到了快速成长,同时收获的1千多个 github star 也增长了自己的自信,开始更多地投入到前端技术的深入研究当中。在不断深入前端领域的同时,从2016年研二开始,自己也开始为实习面试进行准备,在大量查阅往届同学的面试经验后,开始有目的地准备前端面试:前端基础、前端框架、网络、安全、前端性能、浏览器原理、工程化、node相关、数据库、操作系统等部分的内容,并以周为维度逐一复习和总结。同时,开始学习算法和数据结构,然后在 leetcode 上刷了200多题,算法对于前端同学也是非常重要的内容,当时投入了大约3个月的时间单独提升算法能力。面试与实习在内推蚂蚁之前,自己先面试了一些互联网公司,算是积攒了不少经验,但在蚂蚁开始面试时,依然比较紧张,毕竟是自己最为看重的工作,好在一路顺利,最终拿到实习 offer ,并于6月入职实习。入职之后才是惊喜真正到来的时刻,这一次的实习是双部门联合培养的方式,由体验技术部的师兄指导我前端部分的工作,而数据平台部的师兄指导我后端 java 部分的工作,参与一项重要业务的全栈开发当中。自己java实战经验不多,师兄便制定了 Mybatis 插件开发、 ReentrantLock 源码阅读等多个小任务以在业务之余逐步提升技能。同时在体验技术部,则开展了实习生“花骨朵”计划:连续六周,每周指定一个前端相关主题( esnext、react、node 等等),每位实习生围绕主题进行分享,并邀请该领域的专家进行点评(比如 esnext 主题就由阮一峰师兄进行点评),最后大家投票评出最佳分享。大家分享的内容精彩纷繁,评委的点评环节也能直接带给大家更深层次的思考与实践经验,每次分享完,大家都能学到相当多的知识。同时,还开展了集体 code review 活动,由多位经验丰富的师兄 review 大家平时书写的代码,提供优化建议。实习的几个月非常充实,既有复杂业务的挑战,也有许多的技术内容需要学习,更要时常反思总结、分享输出。但这样的环境让每个人都非常快速的成长,同时师兄们也一直进行着细致的辅导,有任何问题,师兄们都会耐心的解答、沟通,也可以直接咨询部门里的大牛们,说到这里,不得不说一个来体验技术部实习的大福利:以往自己使用各种开源框架、类库时,遇到问题,肯定会选择到 github 上提 issue 、读源码。现在,作者就在身边,就在几米外的工位上,直接带着代码去问就行!面试经验分享最近实习春招开始,学弟学妹们应该也是在准备面试当中,前端同学的复习准备则可以参考我前文所述的实习准备过程。对于实习生同学,面试时比较看重编程语言及相关工具的掌握,以及对数据结构、算法等计算机学科要点的理解要扎实。往届同学的面试经验是很重要的信息来源,同学们在面试前一定要多多查阅,并参照着进行个人知识的完善。同时学弟学妹们也要注意自己知识面覆盖的完备性,尽量避免有知识空白的出现,比如一些前端同学在网络(比如 http/https/http2/tcp/udp 等)和安全等领域知识的缺失就会影响自己的面试表现。同时,每次面试之后一定要立即做认真的记录,记下自己认为没有答好的点,然后做全面的查阅、理解,确保后续再问到类似问题时,能给到面试官最为全面、准确的回答。此外,面试时一定要调整好心态,即使被面到不会的问题,也要让自己保持冷静、快速思考,尝试逐步分解问题,并一步步建立自己的思考逻辑,吐露自己的每一个想法,保持一种努力解决问题的姿态,而不是简单的“我不会”就放弃,这样即使给出的方案不好、甚至有错误,但是面试官也能了解你的构思过程和考虑边界。期待与你相遇一转眼距离2017年实习时已经快两年了,而我自去年3月入职以来,也已在蚂蚁正式工作一年了。入职蚂蚁后参与一款数据产品的开发工作,从前端埋点、数据清洗、计算再到前端数据可视化呈现都需全流程参与,很多场景在业界没有相似方案,我们都在采用自己创新性的算法、机制进行尝试、突破,采集一些前人没有采集过的数据,深入少有人深入的领域。大家吃饭时、回家路上也在讨论解决方案,这种浓郁的技术氛围对于应届生的技术提升和眼界拓宽是非常有用的。而入职半年后, leader 开始让自己负责一块独立的业务,从方案设计、项目排期、工作分派都由自己负责,踩坑的同时也在快速反思和总结,技术快速提升的同时,也增长了各项软技能。紧张的实习春招季已经到来,相信不少学弟学妹们正在繁忙的复习、面试。祝愿学弟学妹们能好好把握机会,充分展示自己,拿到自己心仪的 offer ,同时也十分欢迎学弟学妹们加入蚂蚁金服,加入体验技术部,一同为亿万支付宝用户打造良好的用户体验,也同其他入职蚂蚁的同学一样,在极其有挑战的环境中,不断提升,快速成长。主管寄语聪明、皮实、乐观、自省是阿里的人才观,这在源介身上就表现得十分亮眼。北邮国重出来的研究生,成绩和动手能力都很好,聪明无疑。虽然每一个新同学都有一名师兄辅导,但每一位新同学都是要独立完成一些事情的。面对蚂蚁复杂的业务挑战又有身经百战的高年级同学把控,自信满满的产出也有可能被各种挑战,斗志昂扬地进入项目评审会议室,垂头丧气的出来不是什么稀奇事,这就要求我们的同学有越战越勇的魄力,哪里跌倒就在哪里站起来的皮实。源介参与了一个极具挑战的创新性项目,个中挫败难免,但能看到源介反脆弱的每一次反弹都让他到达了一个更高的水平,这就是皮实。还有无惧困难,善于发现积极一面的乐观态度以及自律学习、认真总结的自省习惯。从源介身上能看到阿里的人才标准,也是我们对新加入同学的要求,希望这可以给到大家一些参考。阿里聚集了大量这样优秀的人才,这里不缺老师,不管高低年级,每一位同学都有值得自己去学习的地方;这里不缺挑战,我们每天面临的都是十亿级用户需求背后所带来的技术挑战;这里是开放和平等的,不管高低年级我们也都直呼其(花)名,平等对话与协作;我想这或许就是体验技术部对新同学来说最大的吸引力吧。因为时间是一去不返的,当下的自己用这最宝贵的时间去换取什么?我想,对于刚入职场的同学来说,一段快速成长的经历是尤其重要。在我这些年遇到过的人总结来看,快慢车道上的起步或许决定了未来可达的高度。祝同学们找到自己满意的工作~加入我们前端工程师技术基础扎实,熟悉 JavaScript 、 HTML 、 CSS 、计算机与网络、数据结构与算法等知识熟悉模块化、前端编译与构建工具,了解 React、Vue 等主流前端框架,能理解其设计原理具备强烈的技术进取心,有良好的沟通与合作精神,拥有优秀的问题分析及解决能力 加分项:参与过开源项目,或有个人作品(需提供案例)有服务端开发经验(微服务、Serverless、PaaS 等,使用过 Node、Java、Go 等服务端语言)了解可视化知识,熟悉 SVG、Canvas 或 WebGL,使用过 D3、G2、ECharts 等类库算法工程师实现智能用户行为数据采集,用户行为语义化,识别用户关键操作。 发现用户行为模式,分析异常行为,并进行归因。 实现数据的智能可视分析,涉及用户分析目的理解、UI 推荐、UI 自动生成等。 实现自然语言的意图分析,并用形式语言和特定数据格式来描述。岗位要求:计算机或数学相关专业,有良好的英文读写能力熟练使用 Python/Java/SQL,有良好的编程习惯有扎实的算法基础,关注算法效率,熟练使用相关工具类库有大数据开发、数据挖掘、机器学习、自然语言处理、3D 重建等一个或多个领域理论知识或实践经验者优先有体系化思维能力,善于发现、定义、分析和解决问题,能将问题转化为计算模型设计师必须同时具备交互和视觉方面的设计能力(用作品说话)具有良好的艺术修养,有扎实的设计基础,对设计趋势有敏锐感受同时熟练掌握 PS、AE、AI、PR、Sketch 等设计软件积极主动,善于沟通,有良好的团队合作能力,能很好的阐述视觉观点和设计价值有担当有责任感,能合理安排优先级,有较强的抗压能力加分项:了解WEB /移动产品设计经验,有中台类设计经验者可加分如果视觉设计方面同时具备手绘、品牌、GUI、3D、动效,影视等相关特殊能力可加分具备体系化的思维,已有相关设计规范和方法论总结可加分拥有良好的英文读写能力和国际视野,有阅读国外先进作品和文献的能力可加分有用户研究经验和能力加分有兴趣的同学们,快快把简历发到这里:zhizheng.ck@antfin.com。想“近距离”、“无死角”地了解前端大神们?那就赶紧去看看蚂蚁金服体验技术部的团队博客:https://www.yuque.com/afx/blog本文作者:华蒙阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...
到目前为止,Flexbox布局应该是目前最流行的布局方式之一了。而Flexbox布局的最大特性就是让Flex项目可伸缩,也就是让Flex项目的宽度和高度可以自动填充Flex容器剩余的空间或者缩小Flex项目适配Flex容器不足的宽度。而这一切都是依赖于Flexbox属性中的flex属性来完成。一个Flex容器会等比的按照各Flex项目的扩展比率分配Flex容器剩余空间,也会按照收缩比率来缩小各Flex项目,以免Flex项目溢出Flex容器。但其中Flex项目又是如何计算呢?他和扩展比率或收缩比率之间又存在什么关系呢?在这篇文章中我们将一起来探来。在Flexbox布局中,容器中显示式使用display设置为flex或inline-flex,那么该容器就是Flex容器,而该容器的所有子元素就是Flex项目。简介在这篇文章中,我们将要聊的是有关于flex属性的事情,特别是如何使用该属性来计算Flex项目?在开始之前,先来简单的了解一下flex属性。在Flexbox中,flex属性是flex-grow(扩展比率)、flex-shrink(收缩比率)和flex-basis(伸缩基准)三个属性的简称。这三个属性可以控制一个Flex项目(也有人称为Flex元素),主要表现在以下几个方面:flex-grow:Flex项目的扩展比率,让Flex项目得到(伸张)多少Flex容器多余的空间(Positive free space)flex-shrink:Flex项目收缩比率,让Flex项目减去Flex容器不足的空间(Negative free space)flex-basis:Flex项目未扩展或收缩之前,它的大小是多少在Flexbox布局中,只有充分理解了这三个属性才能彻底的掌握Flex项目是如何扩展和收缩的,也才能更彻底的掌握Flexbox布局。因此掌握这三个属性,以及他们之间的计算关系才是掌握Flexbox布局的关键所在。相关概念在具体介绍flex相关的技术之前,先对几个概念进行描述,因为理解了这几个概念更有易于大家对后面知识的理解。主轴长度和主轴长度属性Flex项目在主轴方向的宽度或高度就是Flex项目的主轴长度,Flex项目的主轴长度属性是width或height属性,具体是哪一个属性,将会由主轴方向决定。剩余空间和不足空间在Flexbox布局中,Flex容器中包含一个或多个Flex项目(该容器的子元素或子节点)。Flex容器和Flex项目都有其自身的尺寸大小,那么就会有:Flex项目尺寸大小之和大于或小于Flex容器 情景:当所有Flex项目尺寸大小之和小于Flex容器时,Flex容器就会有多余的空间没有被填充,那么这个空间就被称为Flex容器的剩余空间(Positive Free Space)当所有Flex项目尺寸大小之和大于Flex容器时,Flex容器就没有足够的空间容纳所有Flex项目,那么多出来的这个空间就被称为负空间(Negative Free Space)举个例子向大家阐述这两个情形:“假设我们有一个容器(Flex容器),显式的给其设置了width为800px,padding为10px,并且box-sizing设置为border-box”。根据CSS的盒模型原理,我们可以知道Flex容器的内宽度(Content盒子的宽度)为800px - 10px * 2 = 780px:假设Flex容器中包含了四个Flex项目,而且每个Flex项目的width都为100px,那么所有Flex项目的宽度总和则是100px * 4 = 400px(Flex项目没有设置其他任何有关于盒模型的尺寸),那么Flex容器将会有剩余的空间出来,即780px - 400px = 380px。这个380px就是我们所说的Flex容器的剩余空间:假设把Flex项目的width从100px调到300px,那么所有Flex项目的宽度总和就变成了300px * 4 = 1200px。这个时候Flex项目就溢出了Flex容器,这个溢出的宽度,即1200px - 780px = 420px。这个420px就是我们所说的Flex容器的不足空间:上面演示的是主轴在x轴方向,如果主轴变成y轴的方向,同样存在上述两种情形,只不过把width变成了height。接下来的内容中,如果没有特殊说明,那么所看到的示例都仅演示主轴在x轴的方向,即flex-direction为row!min-content 和 max-contentmin-content和max-content是CSS中的一个新概念,隶属于CSS Intrinsic and Extrinsic Sizing Specification模块。简单的可以这么理解。CSS可以给任何一个元素显式的通过width属性指定元素内容区域的宽度,内容区域在元素padding、border和margin里面。该属性也是CSS盒模型众多属性之一。记住,CSS的box-sizing可以决定width的计算方式。如果我们显式设置width为关键词auto时,元素的width将会根据元素自身的内容来决定宽度。而其中的min-content和max-content也会根据元素的内容来决定宽度,只不过和auto有较大的差异min-content: 元素固有的最小宽度max-content: 元素固有的首选宽度比如下面这个示例:如果内容是英文的话,min-content的宽度将取决于内容中最长的单词宽度,中文就有点怪异(其中之因目前并未深究),而max-content则会计算内容排整行的宽度,有点类似于加上了white-space:nowrap一样。上例仅展示了min-content和max-content最基本的渲染效果(Chrome浏览器渲染行为)。这里不做深入的探讨论,毕竟不是本文的重点,如果感兴趣,欢迎关注后续的相关更新,或者先阅读@张鑫旭 老师写的一篇文章《理解CSS3 max/min-content及fit-content等width值》回到我们自己的主题上来。前面在介绍Flex剩余空间和不足空间的时候,我们可以得知,出现这两种现象取决于Flex容器和Flex项目的尺寸大小。而flex属性可以根据Flex容器的剩余空间(或不足空间)对Flex项目进行扩展(或收缩)。那么为了计算出有多少Flex容器的剩余空间能用于Flex项目上,客户端(浏览器)就必须知道Flex项目的尺寸大小。要是没有显式的设置元素的width属性,那么问题就来了,浏览器它是如何解决没有应用于绝对单位的宽度(或高度)的Flex项目,即如何计算?这里所说的min-content和max-content两个属性值对于我们深入的探讨flex属性中的flex-grow和 flex-grow属性有一定的影响。所以提前向大家简单的阐述一正是这两个属性值在浏览器中的渲染行为。简单的总结一下:min-content的大小,从本质上讲,是由字符串中最长的单词决定了大小;max-content则和min-content想反. 它会变得尽可能大, 没有自动换行的机会。如果Flex容器太窄, 它就会溢出其自身的盒子!Flex项目的计算在Flexbox布局当中,其中 flex-grow、flex-shrink和flex-basis都将会影响Flex项目的计算。接下来我们通过一些简单的示例来阐述这方面的知识。flex-basisflex-basis属性在任何空间分配发生之前初始化Flex项目的尺寸。其默认值为auto。如果flex-basis的值设置为auto,浏览器将先检查Flex项目的主尺寸是否设置了绝对值再计算出Flex项目的初始值。比如说,你给Flex项目设置的width为200px,那么200px就是Flex项目的flex-basis值。如果你的Flex项目可以自动调整大小,则auto会解析为其内容的大小,这个时候,min-content和max-content变会起作用。此时将会把Flex项目的max-content作为 flex-basise的值。比如,下面这样的一个简单示例:flex-grow和flex-shrink的值都为0,第一个Flex项目的width为150px,相当于flex-basis的值为150px,而另外两个Flex项目在没有设置宽度的情况之下,其宽度由内容的宽度来设置。如果flex-basis的值设置为关键词content,会导致Flex项目根据其内容大小来设置Flex项目,叧怕是Flex项目显式的设置了width的值。到目前为止,content还未得到浏览器很好的支持。flex-basis除了可以设置auto、content、fill、max-content、min-content和fit-content关键词之外,还可以设置<length>值。如果<length>值是一个百分比值,那么Flex项目的大小将会根据Flex容器的width进行计算。比如下面这个示例:Flex容器显式设置了width(和box-sizing取值有关系,上图为border-box的示例结果),那么flex-basis会根据Flex容器的width计算出来,如果Flex容器未显示设置width值,则计算出来的结果将是未定义的(会自动根据Flex容器的宽度进行计算)。在Flexbox布局中,如果你想完全忽略Flex项目的尺寸,则可以将flex-basis设置为0。这样的设置,基本上是告诉了浏览器,Flex容器所有空间都可以按照相关的比例进行分配。来看一个简单的示例,Flex项目未显式设置width情况之下,flex-basis不同取值的渲染效果。到写这篇文章为止,使用Firefox浏览器查看效果更佳。当Flex项目显式的设置了min-width或max-width的值时,就算Flex项目显式的设置了flex-basis的值,也会按min-width和max-width设置Flex项目宽度。当计算的值大于max-width时,则按max-width设置Flex项目宽度;当计算的值小于min-width时,则按min-width设置Flex项目宽度:有关于flex-basis属性相关的运用简单的小结一下:flex-basis默认值为auto如果Flex项目显式的设置了width值,同时flex-basis为auto时,则Flex项目的宽度为按width来计算,如果未显式设置width,则按Flex项目的内容宽度来计算如果Flex项目显式的设置了width值,同时显式设置了flex-basis的具体值,则Flex项目会忽略width值,会按flex-basis来计算Flex项目当Flex容器剩余空间不足时,Flex项目的实际宽度并不会按flex-basis来计算,会根据flex-grow和flex-shrink设置的值给Flex项目分配相应的空间对于Flexbox布局中,不建议显式的设置Flex项目的width值,而是通过flex-basis来控制Flex项目的宽度,这样更具弹性如果Flex项目显式的设置了min-width或max-width值时,当flex-basis计算出来的值小于min-width则按min-width值设置Flex项目宽度,反之,计算出来的值大于max-width值时,则按max-width的值设置Flex项目宽度flex-grow前面提到过,flex-grow是一个扩展因子(扩展比例)。其意思是,当Flex容器有一定的剩余空间时,flex-grow可以让Flex项目分配Flex容器剩余的空间,每个Flex项目将根据flex-grow因子扩展,从而让Flex项目布满整个Flex容器(有效利用Flex容器的剩余空间)。flex-grow的默认值是0,其接受的值是一个数值,也可以是一个小数值,但不支持负值。一旦flex-grow的值是一个大于0的值时,Flex项目就会占用Flex容器的剩余空间。在使用flex-grow时可以按下面的方式使用:所有Flex项目设置相同的flex-grow值每个Flex项目设置不同的flex-grow值不同的设置得到的效果将会不一样,但flex-grow的值始终总量为1,即Flex项目占有的量之和(分子)和分母相同。我们来具体看看flex-grow对Flex项目的影响。当所有的Flex项目具有一个相同的flex-grow值时,那么Flex项目将会平均分配Flex容器剩余的空间。在这种情况之下将flex-grow的值设置为1。比如下面这个示例,Flex容器(width: 800px,padding: 10px)中有四个子元素(Flex项目),显式的设置了flex-basis为150px,根据前面介绍的内容,我们可以知道每个Flex项目的宽度是150px,这样一来,所有Flex项目宽度总和为150px * 4 = 600px。容器的剩余空间为780px - 600px = 180px。当显式的给所有Flex项目设置了flex-grow为1(具有相同的值)。这样一来,其告诉浏览器,把Flex容器剩余的宽度(180px)平均分成了四份,即:180px / 4 = 45px。而flex-grow的特性就是按比例把Flex容器剩余空间分配给Flex项目(当然要设置了该值的Flex项目),就该例而言,就是给每个Flex项目添加了45px,也就是说,此时Flex项目的宽度从150px扩展到了195px(150px + 45px = 195px)。如下图所示:特别声明,如果Flex项目均分Flex容器剩余的空间,只要给Flex项目设置相同的flex-grow值,大于1即可。比如把flex-grow设置为10,就上例而言,把剩余空间分成了40份,每个Flex项目占10份。其最终的效果和设置为1是等效的。上面我们看到的均分Flex容器剩余空间,事实上我们也可以给不同的Flex项目设置不同的flex-grow值,这样一来就会让每个Flex项目根据自己所占的比例来占用Flex容器剩余的空间。比如上面的示例,把Flex项目的flex-grow分别设置为1:2:3:4。也就是说把Flex容器的剩余空间分成了10份(1 + 2 + 3 + 4 = 10),而每个Flex项目分别占用Flex容器剩余空间的1/10、2/10、3/10和4/10。就上例而言,Flex容器剩余空间是180px,按这样的计算可以得知,每一份的长度是180px / 10 = 18px,如此一来,每个Flex项目的宽度则变成:Flex1: 150px + 18px * 1 = 168pxFlex2: 150px + 18px * 2 = 186pxFlex3: 150px + 18px * 3 = 204pxFlex4: 150px + 18px * 4 = 222px最终效果如下图所示:前面两个示例向大家演示了,Flex项目均分和非均分Flex容器剩余的空间。从示例中可以看出来,flex-grow的值都是大于或等于1的数值。事实上,flex-grow还可以设置小数。比如,给所有Flex项目设置flex-grow的值为0.2。由于Flex项目的flex-grow的值都相等,所以扩展的值也是一样的,唯一不同的是,所有的Flex项目并没有把Flex容器剩余空间全部分完。就我们这个示例而言,四个Flex项目的flex-grow加起来的值是0.8,小于1。换句话说,四个Flex项目只分配了Flex容器剩余空度的80%,按上例的数据来计算,即是180px * .8 = 144px(只分去了144px),而且每个Flex项目分得都是36px(144px / 4 = 36px 或者 144px * 0.2 / 0.8 = 36px)。最终效果如下图所示:上面的示例中,flex-basis都显式的设置了值。事实上,flex-grow和flex-basis会相互影响的。这也令我们的Flex项目计算变得复杂化了。比如说,flex-basis的值为auto,而且没有给Flex项目显式的设置width。根据前面的内容我们可以得知,此时Flex项目的大小都取决于其内容的max-content大小。此时Flex容器的剩余的空间将由浏览器根据Flex项目的内容宽度来计算。比如接下来的这个示例,四个Flex项目都是由其内容max-content大小决定。同时将flex-grow都设置为1(均匀分配Flex容器剩余空间)。具体的数据由下图所示(Chrome浏览器计算得出的值):特别注意,不同浏览器对小数位的计算略有差异,上图是在Chrome浏览器下得出的值。所以最终加起来的值略大于Flex容器的宽度708px。针对这样的使用场景,如果你想让所有Flex项目具有相同的尺寸,那么可以显式的设置Flex项目的flex-basis值为0(flex: 1 1 0)。从flex-basis一节中可以得知,当flex-basis值为0时,表示所有空间都可以用来分配,而且flex-grow具有相同的值,因此Flex项目可以获取均匀的空间。如此一来Flex项目宽度将会相同。flex-basis还可以由其他值为设置Flex项目的宽度,这里不再一一演示。感兴趣的同学可以自己根据flex-basis的取值写测试用例。换句话说,如果你理解了前面介绍的flex-basis内容,就能更好的理解flex-grow和flex-basis相结合对Flex项目分配Flex容器剩余空间的计算。也将不会再感到困惑。flex-shrinkflex-shrink和flex-grow类似,只不过flex-shrink是用来控制Flex项目缩放因子。当所有Flex项目宽度之和大于Flex容器时,将会溢出容器(flex-wrap为nowrap时),flex-shrink就可以根据Flex项目设置的数值比例来分配Flex容器的不足空间,也就是按比例因子缩小自身的宽度,以免溢出Flex容器。flex-shrink接收一个<number>值,其默认值为1。也就是说,只要容器宽度不足够容纳所有Flex项目时,所有Flex项目默认都会收缩。如果你不想让Flex项目进行收缩时,可以设置其值为0,此时Flex项目始终会保持原始的fit-content宽度。同样的,flex-shrink也不接受一个负值做为属性值。基于上面的示例,简单的调整一下参数,所有Flex项目都设置了flex: 0 0 300px,可以看到Flex项目溢出了Flex容器:在这个示例中,由于flex-shrink显式的设置了值为0,Flex项目不会进行收缩。如果你想让Flex项目进行收缩,那么可以把flex-shrink设置为1。从上图的结果我们可以看出,当所有Flex项目的flex-shrink都设置为相同的值,比如1,将会均分Flex容器不足空间。比如此例,所有Flex项目的宽度总和是1200px(flex-basis: 300px),而Flex容器宽度是780px(width: 800px,padding: 10px,盒模型是border-box),可以算出Flex容器不足空间为420px(1200 - 780 = 420px),因为所有Flex项目的flex-shrink为1,其告诉浏览器,将Flex容器不足空间均分成四份,那么每份则是105px(420 / 4 = 105px),这个时候Flex项目就会自动缩放105px,其宽度就由当初的300px变成了195px(300 - 105 = 195px)。这个示例演示的是Flex项目设置的值都是相同的值,其最终结果是将会均分Flex容器不足空间。其实flex-shrink也可以像flex-grow一样,为不同的Flex项目设置不同的比例因子。比如1:2:3:4,这个时候Flex项目就不会均分了,而是按自己的比例进行收缩,比例因子越大,收缩的将越多。如下图所示:就上图而言,所有Flex项目的flex-shrink之和为10(1 + 2 + 3 + 4 = 10),此时把Flex容器不足空间420px分成了十份,每一份42px(420 / 10 = 42px),每个Flex项目按照自己的收缩因子相应的去收缩对应的宽度,此时每个Flex项目的宽度就变成:Flex1: 300 - 42 * 1 = 258pxFlex2: 300 - 42 * 2 = 216pxFlex3: 300 - 42 * 3 = 174pxFlex4: 300 - 42 * 4 = 132px按照该原理来计算的话,当某个Flex项目的收缩因子设置较大时,就有可能会出现小于0的现象。基于上例,如果把第四个Flex项目的flex-shrink设置为15。这样一来,四个Flex项目的收缩因子就变成:1:2:3:15。也就是说把Flex容器不足空间分成了21份,每份占据的宽度是20px(420 / 21 = 20px)。那么Flex项目的宽度就会出现0的现象(300 - 15 * 20 = 0)。这个时候会不会出现无空间容纳Flex项目的内容呢?事实上并不会这样:在Flexbox布局当中,会阻止Flex项目元素宽度缩小至0。此时Flex项目会以min-content的大小进行计算,这个大小是它们利用任何可以利用的自动断行机会后所变成的如果某个Flex项目按照收缩因子计算得出宽度趋近于0时,Flex项目将会按照该元素的min-content的大小来设置宽度,同时这个宽度将会转嫁到其他的Flex项目,再按相应的收缩因子进行收缩。比如上例,Flex项目四,其flex-shrink为15,但其宽度最终是以min-content来计算(在该例中,Chrome浏览器渲染的宽度大约是22.09px)。而这个22.09px最终按照1:2:3的比例分配给了Flex项目一至三(Flex1,Flex2和Flex3)。对应的Flex项目宽度就变成:Flex1: 300 - 20 * 1 - 22.09 / 6 * 1 = 276.334pxFlex2: 300 - 20 * 2 - 22.09 / 6 * 2 = 252.636pxFlex3: 300 - 20 * 3 - 22.09 / 6 * 3 = 228.955pxFlex4: min-content,在该例中大约是22.09px对于该情形,计算相对而言就更为复杂一些了。但浏览器会很聪明的帮你处理这些场景,会倾向于给你合理的结果。只不过大家需要知道这样的一个细节,碰到类似的场景才不会一脸蒙逼(^_^)。flex-grow可以设置一个小于0的值,同样的,flex-shrink也可以设置一个小于0的值,比如我们给所有的Flex项目设置flex-shrink的值为0.2,你将看到的结果如下:从结果的示例图中我们可以看出来,当所有Flex项目的收缩因子(flex-shrink)总和小于1时,Flex容器不足空间不会完全分配完,依旧会溢出Flex容器。好比该例,flex-shrink的总和是.8,分配了Flex容器剩余空间420px的80%,即336px(还有84px剩余空间未完全分配完),由于每个Flex项目的收缩因子是相同的,好比前面的示例,都设置了1类似,把分配的空间336px均分为四份,也就是84px,因此每个Flex项目的宽度由当初的300px变成了216px(300 - 84 = 216px)。这个其实和flex-grow类似,只不过flex-shrink只是收缩而以。Flex项目计算公式Flex项目伸缩计算是一个较为复杂的过程,但它们之间还是有据可查。@Chris和@Otree对该方面就有深入的研究。他们给Flex项目的计算总结出了一套计算公式,具体公式如下:@Chris还依据这套公式写了一个JavaScript的案例,来模拟Flex项目计算。flex常见的值大部分情形之下,我们都是使用flex属性来设置Flex项目的伸缩的值。其常见值的效果有:flex: 0 auto和flex:initial,这两个值与flex: 0 1 auto相同,也是初始值。会根据width属性决定Flex项目的尺寸。当Flex容器有剩余空间时,Flex项目无法扩展;当Flex容器有不足空间时,Flex项目收缩到其最小值min-content。flex: auto与flex: 1 1 auto相同。Flex项目会根据width来决定大小,但是完全可以扩展Flex容器剩余的空间。如果所有Flex项目均为flex: auto、flex:initial或flex: none,则Flex项目尺寸决定后,Flex容器剩余空间会被平均分给是flex:a uto的Flex项目。flex: none与flex: 0 0 auto相同。Flex项目根据width决定大小,但是完全不可伸缩,其效果和initial类似,这种情况下,即使在Flex容器空间不够而溢出的情况之下,Flex项目也不会收缩。flex: <positive-number>(正数)与flex: 1 0px相同。该值使Flex项目可伸缩,并将flex-basis值设置为0,导致Flex项目会根据设置的比例因子来计算Flex容器的剩余空间。如果所有Flex项目都使用该模式,则它们的尺寸会正比于指定的伸缩比。默认状态下,伸缩项目不会收缩至比其最小内容尺寸(最长的英文词或是固定尺寸元素的长度)更小。可以靠设置min-width属性来改变这个默认状态。如何掌握Flex项目的大小通过前面的内容介绍,应该可以了解到Flex项目的大小计算是非常的复杂。如果要真正的理解Flex项目是如何工作的话,最为关键的是理解有多少东西参与影响Flex项目。我们可以按下面这样的方式来进行思考。怎么设置Flex项目的基本大小在CSS中设置一个元素的基本大小可以通过width来设置,或者通过min-width或max-width来设置元素的最小或最大宽度,在未来我们还可以通过content、min-content、max-content或fit-content等关键词来设置元素的大小。对于Flex项目,我们还可以通过flex-basis设置Flex项目大小。对于如何设置Flex项目的基本大小,我们可以围绕以下几点来进行思考:flex-basis的值是auto?Flex项目显式的设置了宽度吗?如果设置了,Flex项目的大小将会基于设置的宽度flex-basis的值是auto还是content?如果是auto,Flex项目的大小为原始大小flex-basis的值是0的长度单位吗?如果是这样那这就是Flex项目的大小flex-basis的值是0呢? 如果是这样,则Flex项目的大小不在Flex容器空间分配计算的考虑之内更为具体的可以参阅flex-basis相关的介绍。我们有可用空间吗?如果Flex容器没有剩余空间,Flex项目就不会扩展;如果Flex容器没有不足空间,Flex项目就不会收缩:所有的Flex项目的宽度总和是否小于Flex容器的总宽度? 如果是这样,那么Flex容器有剩余空间,flex-grow会发挥作用, 具体如何发挥作用,可以参阅flex-grow相关的介绍所有的Flex项目的宽度总和是否大于Flex容器的总宽度? 如果是这样,那么Flex容器有不足空间,flex-shrink会发挥作用,具体如何发挥作用,可以参阅flex-shrink相关的介绍分配空间的其他方式如果我们不想把Flex容器的剩余空间扩展到Flex项目中,我们可以使用Flexbox中其他属性,比如justify-content属性来分配剩余空间。当然也可以给Flex项目设置margin值为处理Flex容器剩余空间。不过这一部分没有在这里阐述,如果感兴趣的话,不仿阅读一下Flexbox相关的介绍。总结很久以为,一直以为Flexbox布局中,Flex项目都会根据Flex容器自动计算。而事实上呢?正如文章中介绍的一样,Flex项目的计算是相当的复杂。设置Flex项目大小的值以及flex-basis、flex-grow和flex-shrink的设置都会对其有较大的影响,而且它们的组合场景也是非常的多,并且不同的场景会造成不一样的结果。当然,文章中所介绍的内容或许没有覆盖到所有的场景,但这些基本的演示或许能帮助大家更好的理解Flex项目是如何计算的。最后希望该文对大家有所帮助,如果你有更深的了解欢迎在下面的评论中与我一起分享。如果文章中有不对之处,还望各路大婶拍正。本文作者:大漠_w3cplus阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...
Decorator装饰器主要用于:装饰类装饰方法或属性装饰类@annotationclass MyClass { }function annotation(target) { target.annotated = true;}装饰方法或属性class MyClass { @readonly method() { }}function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}Babel安装编译我们可以在 Babel 官网的 Try it out,查看 Babel 编译后的代码。不过我们也可以选择本地编译:npm initnpm install –save-dev @babel/core @babel/clinpm install –save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties新建 .babelrc 文件{ “plugins”: [ ["@babel/plugin-proposal-decorators", { “legacy”: true }], ["@babel/plugin-proposal-class-properties", {“loose”: true}] ]}再编译指定的文件babel decorator.js –out-file decorator-compiled.js装饰类的编译编译前:@annotationclass MyClass { }function annotation(target) { target.annotated = true;}编译后:var _class;let MyClass = annotation(_class = class MyClass {}) || _class;function annotation(target) { target.annotated = true;}我们可以看到对于类的装饰,其原理就是:@decoratorclass A {}// 等同于class A {}A = decorator(A) || A;装饰方法的编译编译前:class MyClass { @unenumerable @readonly method() { }}function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}function unenumerable(target, name, descriptor) { descriptor.enumerable = false; return descriptor;}编译后:var _class;function _applyDecoratedDescriptor(target, property, decorators, descriptor, context ) { /** * 第一部分 * 拷贝属性 / var desc = {}; Object“ke” + “ys”.forEach(function(key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if (“value” in desc || desc.initializer) { desc.writable = true; } /* * 第二部分 * 应用多个 decorators / desc = decorators .slice() .reverse() .reduce(function(desc, decorator) { return decorator(target, property, desc) || desc; }, desc); /* * 第三部分 * 设置要 decorators 的属性 / if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object[“define” + “Property”](target, property, desc); desc = null; } return desc;}let MyClass = ((_class = class MyClass { method() {}}),_applyDecoratedDescriptor( _class.prototype, “method”, [readonly], Object.getOwnPropertyDescriptor(_class.prototype, “method”), _class.prototype),_class);function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}装饰方法的编译源码解析我们可以看到 Babel 构建了一个 _applyDecoratedDescriptor 函数,用于给方法装饰。Object.getOwnPropertyDescriptor()在传入参数的时候,我们使用了一个 Object.getOwnPropertyDescriptor() 方法,我们来看下这个方法:Object.getOwnPropertyDescriptor() 方法返回指定对象上的一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)顺便注意这是一个 ES5 的方法。举个例子:const foo = { value: 1 };const bar = Object.getOwnPropertyDescriptor(foo, “value”);// bar {// value: 1,// writable: true// enumerable: true,// configurable: true,// }const foo = { get value() { return 1; } };const bar = Object.getOwnPropertyDescriptor(foo, “value”);// bar {// get: /the getter function/,// set: undefined// enumerable: true,// configurable: true,// }第一部分源码解析在 _applyDecoratedDescriptor 函数内部,我们首先将 Object.getOwnPropertyDescriptor() 返回的属性描述符对象做了一份拷贝:// 拷贝一份 descriptorvar desc = {};Object“ke” + “ys”.forEach(function(key) { desc[key] = descriptor[key];});desc.enumerable = !!desc.enumerable;desc.configurable = !!desc.configurable;// 如果没有 value 属性或者没有 initializer 属性,表明是 getter 和 setterif (“value” in desc || desc.initializer) { desc.writable = true;}那么 initializer 属性是什么呢?Object.getOwnPropertyDescriptor() 返回的对象并不具有这个属性呀,确实,这是 Babel 的 Class 为了与 decorator 配合而产生的一个属性,比如说对于下面这种代码:class MyClass { @readonly born = Date.now();}function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor;}var foo = new MyClass();console.log(foo.born);Babel 就会编译为:// …(_descriptor = _applyDecoratedDescriptor(_class.prototype, “born”, [readonly], { configurable: true, enumerable: true, writable: true, initializer: function() { return Date.now(); }}))// …此时传入 _applyDecoratedDescriptor 函数的 descriptor 就具有 initializer 属性。第二部分源码解析接下是应用多个 decorators:/* * 第二部分 * @type {[type]} /desc = decorators .slice() .reverse() .reduce(function(desc, decorator) { return decorator(target, property, desc) || desc; }, desc);对于一个方法应用了多个 decorator,比如:class MyClass { @unenumerable @readonly method() { }}Babel 会编译为:_applyDecoratedDescriptor( _class.prototype, “method”, [unenumerable, readonly], Object.getOwnPropertyDescriptor(_class.prototype, “method”), _class.prototype)在第二部分的源码中,执行了 reverse() 和 reduce() 操作,由此我们也可以发现,如果同一个方法有多个装饰器,会由内向外执行。第三部分源码解析/* * 第三部分 * 设置要 decorators 的属性 /if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined;}if (desc.initializer === void 0) { Object[“define” + “Property”](target, property, desc); desc = null;}return desc;如果 desc 有 initializer 属性,意味着当装饰的是类的属性时,会将 value 的值设置为:desc.initializer.call(context)而 context 的值为 _class.prototype,之所以要 call(context),这也很好理解,因为有可能class MyClass { @readonly value = this.getNum() + 1; getNum() { return 1; }}最后无论是装饰方法还是属性,都会执行:Object[“define” + “Property”](target, property, desc);由此可见,装饰方法本质上还是使用 Object.defineProperty() 来实现的。应用1.log为一个方法添加 log 函数,检查输入的参数:class Math { @log add(a, b) { return a + b; }}function log(target, name, descriptor) { var oldValue = descriptor.value; descriptor.value = function(…args) { console.log(Calling ${name} with, args); return oldValue.apply(this, args); }; return descriptor;}const math = new Math();// Calling add with [2, 4]math.add(2, 4);再完善点:let log = (type) => { return (target, name, descriptor) => { const method = descriptor.value; descriptor.value = (…args) => { console.info((${type}) 正在执行: ${name}(${args}) = ?); let ret; try { ret = method.apply(target, args); console.info((${type}) 成功 : ${name}(${args}) => ${ret}); } catch (error) { console.error((${type}) 失败: ${name}(${args}) => ${error}); } return ret; } }};2.autobindclass Person { @autobind getPerson() { return this; }}let person = new Person();let { getPerson } = person;getPerson() === person;// true我们很容易想到的一个场景是 React 绑定事件的时候:class Toggle extends React.Component { @autobind handleClick() { console.log(this) } render() { return ( <button onClick={this.handleClick}> button </button> ); }}我们来写这样一个 autobind 函数:const { defineProperty, getPrototypeOf} = Object;function bind(fn, context) { if (fn.bind) { return fn.bind(context); } else { return function autobind() { return fn.apply(context, arguments); }; }}function createDefaultSetter(key) { return function set(newValue) { Object.defineProperty(this, key, { configurable: true, writable: true, enumerable: true, value: newValue }); return newValue; };}function autobind(target, key, { value: fn, configurable, enumerable }) { if (typeof fn !== ‘function’) { throw new SyntaxError(@autobind can only be used on functions, not: ${fn}); } const { constructor } = target; return { configurable, enumerable, get() { /* * 使用这种方式相当于替换了这个函数,所以当比如 * Class.prototype.hasOwnProperty(key) 的时候,为了正确返回 * 所以这里做了 this 的判断 */ if (this === target) { return fn; } const boundFn = bind(fn, this); defineProperty(this, key, { configurable: true, writable: true, enumerable: false, value: boundFn }); return boundFn; }, set: createDefaultSetter(key) };}3.debounce有的时候,我们需要对执行的方法进行防抖处理:class Toggle extends React.Component { @debounce(500, true) handleClick() { console.log(’toggle’) } render() { return ( <button onClick={this.handleClick}> button </button> ); }}我们来实现一下:function _debounce(func, wait, immediate) { var timeout; return function () { var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(function(){ timeout = null; }, wait) if (callNow) func.apply(context, args) } else { timeout = setTimeout(function(){ func.apply(context, args) }, wait); } }}function debounce(wait, immediate) { return function handleDescriptor(target, key, descriptor) { const callback = descriptor.value; if (typeof callback !== ‘function’) { throw new SyntaxError(‘Only functions can be debounced’); } var fn = _debounce(callback, wait, immediate) return { …descriptor, value() { fn() } }; }}4.time用于统计方法执行的时间:function time(prefix) { let count = 0; return function handleDescriptor(target, key, descriptor) { const fn = descriptor.value; if (prefix == null) { prefix = ${target.constructor.name}.${key}; } if (typeof fn !== ‘function’) { throw new SyntaxError(@time can only be used on functions, not: ${fn}); } return { …descriptor, value() { const label = ${prefix}-${count}; count++; console.time(label); try { return fn.apply(this, arguments); } finally { console.timeEnd(label); } } } }}5.mixin用于将对象的方法混入 Class 中:const SingerMixin = { sing(sound) { alert(sound); }};const FlyMixin = { // All types of property descriptors are supported get speed() {}, fly() {}, land() {}};@mixin(SingerMixin, FlyMixin)class Bird { singMatingCall() { this.sing(’tweet tweet’); }}var bird = new Bird();bird.singMatingCall();// alerts “tweet tweet"mixin 的一个简单实现如下:function mixin(…mixins) { return target => { if (!mixins.length) { throw new SyntaxError(@mixin() class ${target.name} requires at least one mixin as an argument); } for (let i = 0, l = mixins.length; i < l; i++) { const descs = Object.getOwnPropertyDescriptors(mixins[i]); const keys = Object.getOwnPropertyNames(descs); for (let j = 0, k = keys.length; j < k; j++) { const key = keys[j]; if (!target.prototype.hasOwnProperty(key)) { Object.defineProperty(target.prototype, key, descs[key]); } } } };}6.redux实际开发中,React 与 Redux 库结合使用时,常常需要写成下面这样。class MyReactComponent extends React.Component {}export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);有了装饰器,就可以改写上面的代码。@connect(mapStateToProps, mapDispatchToProps)export default class MyReactComponent extends React.Component {};相对来说,后一种写法看上去更容易理解。7.注意以上我们都是用于修饰类方法,我们获取值的方式为:const method = descriptor.value;但是如果我们修饰的是类的实例属性,因为 Babel 的缘故,通过 value 属性并不能获取值,我们可以写成:const value = descriptor.initializer && descriptor.initializer();参考ECMAScript 6 入门core-decoratorsES7 Decorator 装饰者模式JS 装饰器(Decorator)场景实战ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。本文作者:冴羽阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...
前言在上一篇 《 ES6 系列 Babel 是如何编译 Class 的(上)》,我们知道了 Babel 是如何编译 Class 的,这篇我们学习 Babel 是如何用 ES5 实现 Class 的继承。ES5 寄生组合式继承function Parent (name) { this.name = name;}Parent.prototype.getName = function () { console.log(this.name)}function Child (name, age) { Parent.call(this, name); this.age = age;}Child.prototype = Object.create(Parent.prototype);var child1 = new Child(‘kevin’, ‘18’);console.log(child1);原型链示意图为:关于寄生组合式继承我们在 《JavaScript深入之继承的多种方式和优缺点》 中介绍过。引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。ES6 extendClass 通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。以上 ES5 的代码对应到 ES6 就是:class Parent { constructor(name) { this.name = name; }}class Child extends Parent { constructor(name, age) { super(name); // 调用父类的 constructor(name) this.age = age; }}var child1 = new Child(‘kevin’, ‘18’);console.log(child1);值得注意的是:super 关键字表示父类的构造函数,相当于 ES5 的 Parent.call(this)。子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。也正是因为这个原因,在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。子类的 proto在 ES6 中,父类的静态方法,可以被子类继承。举个例子:class Foo { static classMethod() { return ‘hello’; }}class Bar extends Foo {}Bar.classMethod(); // ‘hello’这是因为 Class 作为构造函数的语法糖,同时有 prototype 属性和 proto 属性,因此同时存在两条继承链。(1)子类的 proto 属性,表示构造函数的继承,总是指向父类。(2)子类 prototype 属性的 proto 属性,表示方法的继承,总是指向父类的 prototype 属性。class Parent {}class Child extends Parent {}console.log(Child.proto === Parent); // trueconsole.log(Child.prototype.proto === Parent.prototype); // trueES6 的原型链示意图为:我们会发现,相比寄生组合式继承,ES6 的 class 多了一个 Object.setPrototypeOf(Child, Parent)的步骤。继承目标extends 关键字后面可以跟多种类型的值。class B extends A {}上面代码的 A,只要是一个有 prototype 属性的函数,就能被 B 继承。由于函数都有 prototype 属性(除了 Function.prototype 函数),因此 A 可以是任意函数。除了函数之外,A 的值还可以是 null,当 extend null 的时候:class A extends null {}console.log(A.proto === Function.prototype); // trueconsole.log(A.prototype.proto === undefined); // trueBabel 编译那 ES6 的这段代码:class Parent { constructor(name) { this.name = name; }}class Child extends Parent { constructor(name, age) { super(name); // 调用父类的 constructor(name) this.age = age; }}var child1 = new Child(‘kevin’, ‘18’);console.log(child1);Babel 又是如何编译的呢?我们可以在 Babel 官网的 Try it out 中尝试:‘use strict’;function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(“this hasn’t been initialised - super() hasn’t been called”); } return call && (typeof call === “object” || typeof call === “function”) ? call : self;}function _inherits(subClass, superClass) { if (typeof superClass !== “function” && superClass !== null) { throw new TypeError(“Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.proto = superClass;}function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); }}var Parent = function Parent(name) { _classCallCheck(this, Parent); this.name = name;};var Child = function(_Parent) { _inherits(Child, _Parent); function Child(name, age) { _classCallCheck(this, Child); // 调用父类的 constructor(name) var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, name)); _this.age = age; return _this; } return Child;}(Parent);var child1 = new Child(‘kevin’, ‘18’);console.log(child1);我们可以看到 Babel 创建了 _inherits 函数帮助实现继承,又创建了 _possibleConstructorReturn 函数帮助确定调用父类构造函数的返回值,我们来细致的看一看代码。_inheritsfunction _inherits(subClass, superClass) { // extend 的继承目标必须是函数或者是 null if (typeof superClass !== “function” && superClass !== null) { throw new TypeError(“Super expression must either be null or a function, not " + typeof superClass); } // 类似于 ES5 的寄生组合式继承,使用 Object.create,设置子类 prototype 属性的 proto 属性指向父类的 prototype 属性 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); // 设置子类的 proto 属性指向父类 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.proto = superClass;}关于 Object.create(),一般我们用的时候会传入一个参数,其实是支持传入两个参数的,第二个参数表示要添加到新创建对象的属性,注意这里是给新创建的对象即返回值添加属性,而不是在新创建对象的原型对象上添加。举个例子:// 创建一个以另一个空对象为原型,且拥有一个属性 p 的对象const o = Object.create({}, { p: { value: 42 } });console.log(o); // {p: 42}console.log(o.p); // 42再完整一点:const o = Object.create({}, { p: { value: 42, enumerable: false, // 该属性不可写 writable: false, configurable: true }});o.p = 24;console.log(o.p); // 42那么对于这段代码:subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });作用就是给 subClass.prototype 添加一个可配置可写不可枚举的 constructor 属性,该属性值为 subClass。_possibleConstructorReturn函数里是这样调用的:var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, name));我们简化为:var _this = _possibleConstructorReturn(this, Parent.call(this, name));_possibleConstructorReturn 的源码为:function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(“this hasn’t been initialised - super() hasn’t been called”); } return call && (typeof call === “object” || typeof call === “function”) ? call : self;}在这里我们判断 Parent.call(this, name) 的返回值的类型,咦?这个值还能有很多类型?对于这样一个 class:class Parent { constructor() { this.xxx = xxx; }}Parent.call(this, name) 的值肯定是 undefined。可是如果我们在 constructor 函数中 return 了呢?比如:class Parent { constructor() { return { name: ‘kevin’ } }}我们可以返回各种类型的值,甚至是 null:class Parent { constructor() { return null }}我们接着看这个判断:call && (typeof call === “object” || typeof call === “function”) ? call : self;注意,这句话的意思并不是判断 call 是否存在,如果存在,就执行 (typeof call === “object” || typeof call === “function”) ? call : self因为 && 的运算符优先级高于 ? :,所以这句话的意思应该是:(call && (typeof call === “object” || typeof call === “function”)) ? call : self;对于 Parent.call(this) 的值,如果是 object 类型或者是 function 类型,就返回 Parent.call(this),如果是 null 或者基本类型的值或者是 undefined,都会返回 self 也就是子类的 this。这也是为什么这个函数被命名为 _possibleConstructorReturn。总结var Child = function(_Parent) { _inherits(Child, _Parent); function Child(name, age) { _classCallCheck(this, Child); // 调用父类的 constructor(name) var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, name)); _this.age = age; return _this; } return Child;}(Parent);最后我们总体看下如何实现继承:首先执行 _inherits(Child, Parent),建立 Child 和 Parent 的原型链关系,即 Object.setPrototypeOf(Child.prototype, Parent.prototype) 和 Object.setPrototypeOf(Child, Parent)。然后调用 Parent.call(this, name),根据 Parent 构造函数的返回值类型确定子类构造函数 this 的初始值 _this。最终,根据子类构造函数,修改 _this 的值,然后返回该值。ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。本文作者:冴羽阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...
前言我们或多或少都听过“数据绑定”这个词,“数据绑定”的关键在于监听数据的变化,可是对于这样一个对象:var obj = {value: 1},我们该怎么知道 obj 发生了改变呢?definePropetyES5 提供了 Object.defineProperty 方法,该方法可以在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。语法Object.defineProperty(obj, prop, descriptor)参数obj: 要在其上定义属性的对象。prop: 要定义或修改的属性的名称。descriptor: 将被定义或修改的属性的描述符。举个例子:var obj = {};Object.defineProperty(obj, “num”, { value : 1, writable : true, enumerable : true, configurable : true});// 对象 obj 拥有属性 num,值为 1虽然我们可以直接添加属性和值,但是使用这种方式,我们能进行更多的配置。函数的第三个参数 descriptor 所表示的属性描述符有两种形式:数据描述符和存取描述符。两者均具有以下两种键值:configurable当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,也能够被删除。默认为 false。enumerable当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。数据描述符同时具有以下可选键值:value该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。writable当且仅当该属性的 writable 为 true 时,该属性才能被赋值运算符改变。默认为 false。存取描述符同时具有以下可选键值:get一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。set一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。值得注意的是:属性描述符必须是数据描述符或者存取描述符两种形式之一,不能同时是两者。这就意味着你可以:Object.defineProperty({}, “num”, { value: 1, writable: true, enumerable: true, configurable: true});也可以:var value = 1;Object.defineProperty({}, “num”, { get : function(){ return value; }, set : function(newValue){ value = newValue; }, enumerable : true, configurable : true});但是不可以:// 报错Object.defineProperty({}, “num”, { value: 1, get: function() { return 1; }});此外,所有的属性描述符都是非必须的,但是 descriptor 这个字段是必须的,如果不进行任何配置,你可以这样:var obj = Object.defineProperty({}, “num”, {});console.log(obj.num); // undefinedSetters 和 Getters之所以讲到 defineProperty,是因为我们要使用存取描述符中的 get 和 set,这两个方法又被称为 getter 和 setter。由 getter 和 setter 定义的属性称做”存取器属性“。当程序查询存取器属性的值时,JavaScript 调用 getter方法。这个方法的返回值就是属性存取表达式的值。当程序设置一个存取器属性的值时,JavaScript 调用 setter 方法,将赋值表达式右侧的值当做参数传入 setter。从某种意义上讲,这个方法负责“设置”属性值。可以忽略 setter 方法的返回值。举个例子:var obj = {}, value = null;Object.defineProperty(obj, “num”, { get: function(){ console.log(‘执行了 get 操作’) return value; }, set: function(newValue) { console.log(‘执行了 set 操作’) value = newValue; }})obj.value = 1 // 执行了 set 操作console.log(obj.value); // 执行了 get 操作 // 1这不就是我们要的监控数据改变的方法吗?我们再来封装一下:function Archiver() { var value = null; // archive n. 档案 var archive = []; Object.defineProperty(this, ’num’, { get: function() { console.log(‘执行了 get 操作’) return value; }, set: function(value) { console.log(‘执行了 set 操作’) value = value; archive.push({ val: value }); } }); this.getArchive = function() { return archive; };}var arc = new Archiver();arc.num; // 执行了 get 操作arc.num = 11; // 执行了 set 操作arc.num = 13; // 执行了 set 操作console.log(arc.getArchive()); // [{ val: 11 }, { val: 13 }]watch API既然可以监控数据的改变,那我可以这样设想,即当数据改变的时候,自动进行渲染工作。举个例子:HTML 中有个 span 标签和 button 标签<span id=“container”>1</span><button id=“button”>点击加 1</button>当点击按钮的时候,span 标签里的值加 1。传统的做法是:document.getElementById(‘button’).addEventListener(“click”, function(){ var container = document.getElementById(“container”); container.innerHTML = Number(container.innerHTML) + 1;});如果使用了 defineProperty:var obj = { value: 1}// 储存 obj.value 的值var value = 1;Object.defineProperty(obj, “value”, { get: function() { return value; }, set: function(newValue) { value = newValue; document.getElementById(‘container’).innerHTML = newValue; }});document.getElementById(‘button’).addEventListener(“click”, function() { obj.value += 1;});代码看似增多了,但是当我们需要改变 span 标签里的值的时候,直接修改 obj.value 的值就可以了。然而,现在的写法,我们还需要单独声明一个变量存储 obj.value 的值,因为如果你在 set 中直接 obj.value = newValue 就会陷入无限的循环中。此外,我们可能需要监控很多属性值的改变,要是一个一个写,也很累呐,所以我们简单写个 watch 函数。使用效果如下:var obj = { value: 1}watch(obj, “num”, function(newvalue){ document.getElementById(‘container’).innerHTML = newvalue;})document.getElementById(‘button’).addEventListener(“click”, function(){ obj.value += 1});我们来写下这个 watch 函数:(function(){ var root = this; function watch(obj, name, func){ var value = obj[name]; Object.defineProperty(obj, name, { get: function() { return value; }, set: function(newValue) { value = newValue; func(value) } }); if (value) obj[name] = value } this.watch = watch;})()现在我们已经可以监控对象属性值的改变,并且可以根据属性值的改变,添加回调函数,棒棒哒~proxy使用 defineProperty 只能重定义属性的读取(get)和设置(set)行为,到了 ES6,提供了 Proxy,可以重定义更多的行为,比如 in、delete、函数调用等更多行为。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。我们来看看它的语法:var proxy = new Proxy(target, handler);proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。var proxy = new Proxy({}, { get: function(obj, prop) { console.log(‘设置 get 操作’) return obj[prop]; }, set: function(obj, prop, value) { console.log(‘设置 set 操作’) obj[prop] = value; }});proxy.time = 35; // 设置 set 操作console.log(proxy.time); // 设置 get 操作 // 35除了 get 和 set 之外,proxy 可以拦截多达 13 种操作,比如 has(target, propKey),可以拦截 propKey in proxy 的操作,返回一个布尔值。// 使用 has 方法隐藏某些属性,不被 in 运算符发现var handler = { has (target, key) { if (key[0] === ‘_’) { return false; } return key in target; }};var target = { _prop: ‘foo’, prop: ‘foo’ };var proxy = new Proxy(target, handler);console.log(’_prop’ in proxy); // false又比如说 apply 方法拦截函数的调用、call 和 apply 操作。apply 方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组,不过这里我们简单演示一下:var target = function () { return ‘I am the target’; };var handler = { apply: function () { return ‘I am the proxy’; }};var p = new Proxy(target, handler);p();// “I am the proxy"又比如说 ownKeys 方法可以拦截对象自身属性的读取操作。具体来说,拦截以下操作:Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Object.keys()下面的例子是拦截第一个字符为下划线的属性名,不让它被 for of 遍历到。let target = { _bar: ‘foo’, prop: ‘bar’, prop: ‘baz’};let handler = { ownKeys (target) { return Reflect.ownKeys(target).filter(key => key[0] !== ‘’); }};let proxy = new Proxy(target, handler);for (let key of Object.keys(proxy)) { console.log(target[key]);}// “baz"更多的拦截行为可以查看阮一峰老师的 《ECMAScript 6 入门》值得注意的是,proxy 的最大问题在于浏览器支持度不够,而且很多效果无法使用 poilyfill 来弥补。watch API 优化我们使用 proxy 再来写一下 watch 函数。使用效果如下:(function() { var root = this; function watch(target, func) { var proxy = new Proxy(target, { get: function(target, prop) { return target[prop]; }, set: function(target, prop, value) { target[prop] = value; func(prop, value); } }); if(target[name]) proxy[name] = value; return proxy; } this.watch = watch;})()var obj = { value: 1}var newObj = watch(obj, function(key, newvalue) { if (key == ‘value’) document.getElementById(‘container’).innerHTML = newvalue;})document.getElementById(‘button’).addEventListener(“click”, function() { newObj.value += 1});我们也可以发现,使用 defineProperty 和 proxy 的区别,当使用 defineProperty,我们修改原来的 obj 对象就可以触发拦截,而使用 proxy,就必须修改代理对象,即 Proxy 的实例才可以触发拦截。ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。本文作者:冴羽阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...