bt365体育投注.主頁欢迎您!!

    <acronym id="zvmrr"></acronym>
    <td id="zvmrr"></td>
  • <tr id="zvmrr"><label id="zvmrr"></label></tr>
  • <acronym id="zvmrr"></acronym>
  • 前端小智

    前端小智 查看完整档案

    厦门编辑Plymouth  |  前端 编辑大迁世界  |  Web前端攻城狮 编辑 github.com/qq449245884/xiaozhi 编辑
    编辑

    我不是什么大牛,我其实想做的就是一个传播者。内容可能过于基础,但对于刚入门的人来说或许是一个窗口,一个解惑之窗。我要先坚持分享20年,大家来一起见证吧。

    个人动态

    前端小智 发布了文章 · 12月2日

    26 个 CSS 面试题目增强你的 CSS 基础

    译者:前端小智
    来源:codersera.
    点赞再看,微信搜索【大迁世界】关注这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。**

    最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

    github 地址:https://github.com/qq44924588...

    CSS是层叠样式表( Cascading Style Sheets )的缩写,是一种样式表语言,用于描述以 HTML 之类的标记语言编写的文档的布局。 它是用于设计Web页面的三剑客之一,另外两位浩客是HTMLJavascript

    CSS 的设计目的是使样式和内容分离,包括布局、颜色和字体。这种分离可以提高内容的可访问性,在样式特征的规范中提供更多的灵活性和控制,通过在一个单独的. .css 文件中指定相关的 CSS,使多个 web 页面能够共享格式,并减少结构内容中的复杂性和重复。

    它具有简单的语法,并使用大量的英文关键字来指定各种样式属性的名称。

    既然我们已经讨论了CSS的基础知识,让我们来观察一下基于CSS的重要面试问题。

    clipboard.png

    问题1:什么是 CSS?

    CSS(层叠样式表)是一种样式语言,对于 HTML 元素来说足够简单。 它在网页设计中非常流行,其应用在XHTML中也很常见。

    问题2:为什么要开发CSS?

    CSS是在1997年开发的,作为一种web开发人员设计他们正在创建的web页面布局的方法。它的目的是让开发者将网站代码的内容和结构从视觉设计中分离出来。

    这种结构和设计的分离允许HTML执行比原来更多的功能。

    问题3:CSS的主要版本有哪些?

    CSS的不同版本:

    1. CSS1
    2. CSS2
    3. CSS2.1
    4. CSS3

    问题4:CSS样式的组成部分是什么?

    一个样式规则由三部分组成:

    1. 选择器–选择器是 HTML 标记,用于选择要设置样式的内容。 它根据其ID,类和名称选择 HTML元素。
    2. 属性–属性是 HTML 标签的一种属性。 简而言之,所有 HTML 属性都转换为 CSS 属性。
    3. – CSS中的值定义CSS属性的一组有效值。

    问题 5:有多少种方法可以将 CSS 集成为 web 页面

    CSS 可以集成为三种方式

    1. 内联:直接在HTML元素上使用
    <p style=”colour:skyblue;”>hello world</p>
    
    1. 外部:在工作空间中创建单独的CSS文件,然后在创建的每个web页面中链接它们
    <head>
    
    <link rel=”text/css”href=”your_CSS_file_location”/>
    
    </head>
    • 内部: web 页面的 head 元素在其中实现了内部 CSS。
    head>
         <style> 
                 P{
                       color : lime;
                   background-color:black;
                    }
         </style>
    </head>

    问题 6:谁在维护 CSS 规范?

    万维网协会维护 CSS规范。

    问题 7:伪元素是什么意思?

    伪元素是添加到选择器的关键字,它允许一种样式,即所选元素的特定部分。CSS用于在HTML标记中应用样式,它允许在不影响实际文档的情况下对文档进行额外标记。它可以用来:

    1. 为第一个字母、行或元素设置样式。
    2. 插入内容

    语法:

    Selector: :pseudo-element
    {Property1 :value;
    Property2 :value;}

    问题 8:CSS有什么优势?

    CSS的优点是:

    1. 一致性 – CSS有助于构建一致的框架,设计人员可以使用该框架来构建其他站点。 因此,网页设计师的效率也提高了。
    2. 易于使用 – CSS 是非常容易学习和简化网站开发。所有代码都放在一个页面上,这意味着对代码行进行改进或编辑不需要重复修改多个页面.
    3. *网站速度 *– 通常,一个网站使用的代码最多可以达到 2 页或更多。但是对于CSS,这不是问题。它只需要2-3行代码,因此,网站数据库保持整洁,消除任何网站加载问题。
    4. 设备兼容性 – 由于人们使用不同类型的智能设备访问互联网,因此需要响应式web设计。CSS 在这里的作用是使 web 页面的响应性更好,这样它们就可以在所有设备中以相同的方式显示。
    5. 多浏览器支持 – CSS享有多浏览器的支持,它与所有主要的互联网浏览器兼容。
    6. 重新定位 – CSS允许您定义页面上 web 元素位置的变化。通过它的实现,开发人员可以将 HTML 元素放置在他们喜欢的位置,以便与页面的美学吸引力或其他考虑因素保持一致。

    问题9:CSS 渐变是什么?

    渐变是指我们在两幅图像之间创建中间帧,以获得第一幅图像的外观,然后发展成第二幅图像的过程,它主要用于创建动画。

    问题10:什么是 CSS 特异性?

    CSS 特定性是一个分数或等级,它决定了元素必须使用哪种样式声明。 CSS 中有四类可以授权选择器的特异性级别:

    1. 内联样式
    2. ID
    3. 类,属性和伪类
    4. 元素和伪元素

    问题12:CSS有什么缺点

    CSS的缺点有:

    1. 版本太多 – 与HTML或Javascript等其他参数相比,CSS有很多版本-CSS1,CSS2,CSS2.1,CSS3。 因此,CSS变得非常混乱,尤其是对于初学者。
    2. 缺乏安全性 - 由于CSS是基于开放文本的系统,因此它没有内置的安全系统来防止其被覆盖。 通过对其读/写操作的访问,任何人都可以更改 CSS 文件并更改链接。
    3. Fragmentation - 使用 CSS,可能无法在一个浏览器上使用另一浏览器。 因此,在网站上线之前,Web 开发人员必须通过在多个浏览器上运行程序来测试兼容性。
    4. 复杂性–使用 Microsoft FrontPage 等第三方软件会使CSS变得复杂。

    问题13:什么是 RWD (Responsive Web Design)?

    RWD(响应式Web设计)技术用于在每种屏幕尺寸以及移动,平板电脑,台式机和笔记本电脑等设备上完美显示设计页面,让我们无需为每个设备创建不同的页面。

    问题14:CSS 精灵有什么好处?

    CSS精灵的好处有:

    1. 通过将各种小图像组合成一个图像,减少了web页面的加载时间。
    2. 减少HTTP请求,从而减少加载时间。

    问题 15:什么是 CSS 上下文选择器?

    上下文选择器,严格来讲,叫后代组合式选择器,就是一组以空格分隔的标签名。用于选择作为指定祖先元素后代的标签。只要有标签在它的层次结构“上游”存在这么一个祖先,那么就会选中该标签。无论从该标签到作为祖先的上下文之间隔着多少层次都没有关系。


    大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

    我和阿里云合作服务器,折扣价比较便宜:89/年,223/3年,比学生9.9每月还便宜,买了搭建个项目,熟悉技术栈比较香(老用户用家人账号买就好了,我用我妈的)推荐买三年的划算点,点击本条就可以查看。


    问题 16:什么是渐进增强和平稳退化?

    渐进增强的概念是指从最基本的可用性出发,在保证站点页面在低级浏览器中 的可用性和可访问性的基础上,逐步增加功能及提高用户体验。本质上讲,我们日常的一些开发习惯,例如首先使用标记语言编写页面,然后通过样式表来控制页面 样式等,都属于渐进增强的概念;其他更为明显的行为包括使用HTML5、CSS3等新技术,针对高级浏览器为页面提高用户体验的丰富程度。

    平稳退化的概念是指首先使用最新的技术面向高级浏览器构建最强的功能及用户体验,然后针对低级浏览器的限制,逐步衰减那些无法被支持的功能及体验;在我们日常的开 发中,一个典型的平稳退化的例子就是首先针对Chrome编写页面代码,然后修复IE中的异常或针对IE去除那些无法被实现的功能特色.

    所以, 这两个概念方法其实早已并存在我们的日常开发工作中了,只是“渐进增强”与“平稳退化”这样的措辞是近些年才开始被普及。在我们眼下的HTML5与 CSS3实战用,这两个概念就尤其重要了,怎样保证使用不断变化的新技术来构建在主流浏览器下都具有基本可用性的站点,并针对高级浏览器进行体验提升,这 些是我们在开发过程中需要明确的思路。

    问题 17:我们如何在网页上添加图标?

    我们可以使用诸如font-awesome或者阿里的 iconfont 之类的图标库将图标添加到HTML网页。 我们必须将给定图标类的名称添加到任何内联HTML元素中。 (<i><span>)。 图标库中的图标是可缩放的矢量,可以使用CSS进行自定义。

    问题 18:哪个属性指定边框的宽度?

    border-width指定边框的宽度。

    问题 19:如何区分物理标签和逻辑标签?

    物理标签被称为表示标记,而逻辑标签对于外观是无用的。物理标签是较新的版本,而逻辑标签是旧的并且专注于内容。

    如题,我们的标签元素写上后,浏览器就会渲染出结果,但不仅仅是这么简单

    //物理元素
    <b>我想用b标签加粗</b>
     
    //逻辑元素
    <strong>我想用strong标签加粗</strong>
     
    //两段文字都加粗了,而且视觉效果完全一样
    
    

    确实,文字加粗了,两者都达到了我们想要的目的,但是我们忽略了一个问题,既然b标签可以加粗,那么strong这个标签同样是加粗,存在的 意义又是什么呢?既然W3C定义了两个,它们之间的不同点是什么呢?它们之间的相同点又是什么呢?

    物理元素

    物理元素,又叫实体标签,它所做的是一种物理行为,比如上面我把一段文字用b标签加粗了,它所传达的给浏览器,告诉浏览器 我要加粗这段文字,从单词Bold中也可以看出来,英文中仅仅是加粗的意思,并没有其他作用。总结来说就是一句话: 物理元素就是告诉浏览器该怎么显示出来。

    逻辑元素

    逻辑元素,从英文字面上Strong就可以看出它是强调的意思,所以我们用这个逻辑元素(如上strong)来向浏览器传达 一个强调某段文字重要性的消息,说明此文字较为重要,也有利于搜索引擎收录。

    Web标准主张XHTML不涉及具体的表现形式,“强调”可以用加粗来强调,也可以用别的方式强调,也可以通过css来改变strong的具体表现 ,还有就是并不是有了strong逻辑标签,就不用b标签来表示字体加粗了,b标签和strong标签默认情况下强调的效果一致,strong完全可以定义成别的样式,用来强调 效果,但是最好符合W3C标准,它更提倡内容与样式分离,所以单纯为了达到加粗而使用b标签不建议这样做, 从XHTML文档有意义性及用户体验角度来说,strong逻辑标签更加合适,而SEO方面,则针对优化情况而定。

    问题 20:如何在CSS中定义一个伪类?它们是用来干什么的

    CSS伪类是用来添加一些选择器的特殊效果。伪类的语法

    selector:pseudo-class{property:value;}

    问题 21:CSS和SCSS有什么区别?

    CSSSCSS 之间的区别如下:

    1. CSS是一种用于设计web页面的样式语言,而SCSS用于为浏览器组合CSS样式表。
    2. SCSS 提供了一些变量,可以使用这些变量来缩短代码,这是与 CSS 相比的一大优势。

    问题 22:嵌入式样式表的优缺点是什么?

    嵌入式样式表的优点:

    1. 可以在一个文档中创建多种标签类型。
    2. 在复杂情况下,可以使用选择器和分组方法来应用样式。
    3. 无需额外下载。

    嵌入式样式表的缺点:

    无法控制多个文档。

    问题 23:列出使用的各种媒体类型。

    不同的介质不区分大小写,因此它们具有不同的属性。 他们是:

    1. aural - 用于语音和音频合成器
    2. print - 用于打印机
    3. projection - 用于方案展示,比如幻灯片
    4. handheld - 用于小的手持的设备
    5. screen - 用于电脑显示器

    问题 24:font 的属性有哪些?

    1. Font-style
    2. Font-variant
    3. Font-weight
    4. Font-size/line-weight
    5. Font-family

    问题 25:“规则集”是什么意思?

    该指令告诉浏览器如何在HTML页面上渲染特定元素。 它由一个选择器和一个遵循规则集的声明块组成。 选择器可以附加到其他选择器,以通过规则集进行标识。

    问题 26:什么是 CSS 框架?

    CSS 框架是一个库,它允许使用CSS语言进行更轻松,更符合标准的Web设计。 这些框架中的大多数至少包含一个网格以及更多功能和其他基于Javascript的功能。 一些著名的CSS框架有:ACSS,Bulma,YAML,Foundation等。


    代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

    原文:https://codersera.com/blog/to...


    交流

    文章每周持续更新,可以微信搜索【大迁世界 】第一时间阅读,回复【福利】有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

    查看原文

    赞 2 收藏 1 评论 0

    前端小智 发布了文章 · 11月30日

    window.location 备忘单,助你理解有着地址问题!

    作者:Samantha Ming
    译者:前端小智
    来源:medium
    点赞再看,微信搜索【大迁世界】关注这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。**

    最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

    github 地址:https://github.com/qq44924588...

    如果你想获取站点的URL信息,那么window.location对象什么很适合你! 使用其属性获取有关当前页面地址的信息,或使用其方法进行某些页面重定向或刷新??

    http://www.wgoodp.icu/sear...
    window.location.origin    → '"http://www.wgoodp.icu'
                   .protocol  → 'https:'
                   .host      → 'www.wgoodp.icu'
                   .hostname  → 'www.wgoodp.icu'
                   .port      → ''
                   .pathname  → '/search'
                   .search    → '?q=前端小智'
                   .hash      → '#2'
                   .href      → 'http://www.wgoodp.icu/search?q=前端小智#2'
    window.location.assign('url')
                   .replace('url')
                   .reload()
                   .toString()

    window.location 属性

    window.location返回值
    .origin站点主地址(协议 + 主机名 + 端口)
    .protocol协议架构 (http: 或者 htts:)
    .host域名 + 端口
    .port端口
    .pathname最前页的 '/' 后面跟的路径
    .search? 后跟的查询字符串
    .hash# 号开始的部分
    .href完整网址

    host 和 hostname 的区别

    在上面的示例中,你可能注意到hosthostname返回相同的值。 那么为什么要这些属性。 好吧,这与端口号有关,让我们来看看。

    没有端口的 URL

    http://www.wgoodp.icu/search
    window.location.host; // 'www.wgoodp.icu'
    window.location.hostname; // 'www.wgoodp.icu'
    
    window.location.port; // ''

    带端口的 URL

    http://www.wgoodp.icu/search"8080
    window.location.host; // 'www.wgoodp.icu:8080'
    window.location.hostname; // 'www.wgoodp.icu'
    
    window.location.port; // '8080'

    因此,host将包括端口号,而hostname将仅返回主机名。

    如何更改 URL 属性

    我们不仅可以调用location` 属性来检索URL信息,还可以使用它来设置新属性并更改URL。

    // 开始 'http://www.wgoodp.icu/'
    
    window.location.pathname = '/tidbits'; // 设置 pathname
    
    // 结果 'http://www.wgoodp.icu/tidbits'

    下面是你可以更改的属性的完整列表

    // 事例
    window.location.protocol = 'https'
                   .host     = 'localhost:8080'
                   .hostname = 'localhost'
                   .port     = '8080'
                   .pathname = 'path'
                   .search   = 'query string' // (这里不用写 `?`)
                   .hash     = 'hash' // (这里不用写 `#`)
                   .href     = 'url'

    唯一不能设置的属性是window.location.origin,此属性是只读的。

    Location 对象

    window.location返回一个Location对象。 它为我们提供有关页面当前地址的信息。 但是我们还可以通过几种方式访问??Location对象。

    window.location          → Location
    window.document.location → Location
    document.location        → Location
    location                 → Location

    我们这样做的原因是这些是我们浏览器中的全局变量。

    clipboard.png

    window.location vs location

    上面四个属性都指向同一个Location对象。 我个人更喜欢window.location并且实际上会避免使用location。 主要是因为location看起来像一个普通变量,并且我们有时可能会不小心将其命名为变量,这将覆盖全局变量。 举个例子:

    // https://www.samanthaming.com
    
    location.protocol; // 'https'
    
    function localFile() {
      const location = '/sam';
    
      return location.protocol;
      // ? undefined
      //    b/c local "location" has override the global variable
    }

    我想大多数开发人员都知道window是一个全局变量。这样就不太可能引起混淆。老实说,直到我写了这篇文章,我才知道location 是一个全局变量。建议大家多使用 window.location 来代替其它写法。

    window.location 方法

    方法作用
    .assign()加载一个新的文档
    .replace()用新的文档替换当前文档
    .reload()重新加载当前页面
    .reload()返回的URL

    大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

    window.location.toString

    根据 MDN :

    此方法返回 URL 的 USVString,它是 Location.href 的只读版本。

    换句话说,我们可以这样得到 href 的值:

    // https://www.samanthaming.com
    
    window.location.href; // https://www.samanthaming.com
    window.location.toString(); // https://www.samanthaming.com

    assign vs replace

    这两种方法都是重定向或导航到另一个URL。 区别在于assign 是将当前页面保存在历史记录中,因此用户可以使用“后退”按钮导航到该页面。 而使用replace方法时,不会保存它。 让我们来看一个例子。

    Assign

    1. 打开一个新的空白页
    2. 输入 www.samanthaming.com (当前页)
    
    3. 使用 `window.location.assign('https://www.w3schools.com')` 载入新页面
    4. 按 "返回上一页"
    5. 返回到了 ?? www.samanthaming.com

    Replace

    1. 打开一个新的空白页
    2. 输入 www.samanthaming.com (当前页)
    
    3. 使用 `window.location.assign('https://www.w3schools.com')` 载入新页面
    4. 按 "返回上一页"
    5. 返回到一个空白页

    如何让页面重定向

    如何重定向到另一个页面,有3种方法。

    window.location.;
    
    window.location.assign('https://www.samanthaming.com');
    
    window.location.replace('https://www.samanthaming.com');

    replace vs assign vs href

    这三个都可以重定向,区别在于浏览器的历史记录。 hrefassign 会把当前页面保存在历史记录中,而replace则不会。 因此,如果你想创建一种导航无法回到原始页面的体验,请使用replace??

    现在的问题是hrefassign。 我更喜欢assign,因为它是一种方法,因此感觉好像我正在执行一些操作。 还有一个额外的好处是它更易于测试。 我已经编写了许多Jest测试,因此通过使用一种方法,它使其更易于模拟。

    window.location.assign = jest.fn();
    
    myUrlUpdateFunction();
    
    expect(window.location.assign).toBeCalledWith('http://my.url');

    最终希望备忘单,希望能对你有所帮助,在需要的时候,能快速给你带来答案。

    人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。


    原文:https://morioh.com/p/b444d291...

    代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug


    交流

    文章每周持续更新,可以微信搜索【大迁世界 】第一时间阅读,回复【福利】有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

    查看原文

    赞 5 收藏 4 评论 1

    前端小智 发布了文章 · 11月23日

    箭头函数适合哪些场景?

    作者:Dmitri Pavlutin
    译者:前端小智
    来源:Dmitri Pavlutin

    点赞再看,养成习惯

    本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

    最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

    github 地址:https://github.com/qq44924588...

    这些年来,ES6 将 JS 的可用性提升到一个新的水平时: 箭头函数、类等等,这些都很棒。

    箭头函数是最有价值的新功能之一,有很多好文章描述了它的上下文透明性和简短的语法。

    但每个事务都有两面。通常,新特性会带来一些混乱,其中之一就是箭头函数被误导了。本文将介绍一些场景,在这些场景中,你应该绕过箭头函数,转而使用良好的旧函数表达式或较新的简写语法。并且要注意缩短代码,因为这会影响代码的可读性。

    1.定义对象上的方法

    在JS中,方法是存储在对象属性中的函数。当调用该方法时,this 将指向该方法所属的对象。

    Object literal

    由于箭头函数语法简短,所以使用它来定义方法是很有吸引力的,让咱们来试一试:

    const calculate = {
      array: [1, 2, 3],
      sum: () => {
        console.log(this === window); // => true
        return this.array.reduce((result, item) => result + item);
      }
    };
    console.log(this === window); // => true
    // Throws "TypeError: Cannot read property 'reduce' of undefined"
    calculate.sum();
    

    calculate.sum方法用箭头函数定义。 但是在调用时,calculate.sum() 会抛出一个TypeError,因为this.arrayundefined

    当调用calculate对象上的方法sum()时,上下文仍然是 window。之所以会发生这种情况,是因为箭头函数按词法作用域将上下文绑定到 window 对象。

    执行this.array等同于window.array,它是undefined

    解决方法是使用常规函数表达式来定义方法。 this 是在调用时确定的,而不是由封闭的上下文决定的,来看看修复后的版本:

    const calculate = {  
      array: [1, 2, 3],
      sum() {
        console.log(this === calculate); // => true
        return this.array.reduce((result, item) => result + item);
      }
    };
    calculate.sum(); // => 6
    

    因为sum是常规函数,所以在调用 calculate.sum()thiscalculate 对象。 this.array是数组引用,因此正确计算元素之和:6

    Object prototype

    同样的规则也适用于在原型对象上定义方法。使用一个箭头函数来定义sayCatName方法,this 指向 window

    function MyCat(name) {
      this.catName = name;
    }
    MyCat.prototype.sayCatName = () => {
      console.log(this === window); // => true
      return this.catName;
    };
    const cat = new MyCat('Mew');
    cat.sayCatName(); // => undefined
    

    使用早期的方式定义函数表达式:

    function MyCat(name) {
      this.catName = name;
    }
    MyCat.prototype.sayCatName = function() {
      console.log(this === cat); // => true
      return this.catName;
    };
    const cat = new MyCat('Mew');
    cat.sayCatName(); // => 'Mew'
    

    sayCatName常规函数在作为方法调用时将上下文更改为cat对象:cat.sayCatName()

    2. 动态上下文的回调函数

    this 在JS中是一个强大的特性,它允许根据调用函数的方式更改上下文。通常,上下文是调用发生的目标对象,这使得代码更加自然,就像这个对象发生了什么。

    但是,箭头函数会在声明上静态绑定上下文,并且无法使其动态化,但这种方式有坏也有好,有时候我们需要动态绑定。

    在客户端编程中,将事件侦听器附加到DOM元素是一项常见的任务。事件触发处理程序函数,并将this作为目标元素,这里如果使用箭头函数就不够灵活。

    下面的示例尝试为这样的处理程序使用箭头函数:

    const button = document.getElementById('myButton');
    button.addEventListener('click', () => {
      console.log(this === window); // => true
      this.innerHTML = 'Clicked button';
    });
    

    在全局上下文中 this 指向 window。 当发生单击事件时,浏览器尝试使用按钮上下文调用处理函数,但箭头函数不会更改其预定义的上下文。this.innerHTML相当于window.innerHTML,没有任何意义。

    必须应用函数表达式,该表达式允许根据目标元素更改 this

    const button = document.getElementById('myButton');
    button.addEventListener('click', function() {
      console.log(this === button); // => true
      this.innerHTML = 'Clicked button';
    });
    

    当用户单击按钮时,处理程序函数中的 this 指向 button。因此这个问题。innerHTML = 'Clicked button' 正确地修改按钮文本以反映已单击状态。

    3.调用构造函数

    this 在构造调用中是新创建的对象。当执行new MyFunction()时,构造函数MyFunction的上下文是一个新对象:this instanceof MyFunction === true

    注意,箭头函数不能用作构造函数。 JavaScript通过抛出异常隐式阻止这样做。

    无论如何,this是来自封闭上下文的设置,而不是新创建的对象。换句话说,箭头函数构造函数调用没有意义,而且是模糊的。

    让我们看看如果尝试这样做会发生什么:

    const Message = (text) => {
      this.text = text;
    };
    // Throws "TypeError: Message is not a constructor"
    const helloMessage = new Message('Hello World!');
    

    执行new Message('Hello World!'),其中Message是一个箭头函数,JavaScript抛出一个 TypeError 错误,Message不能用作构造函数。

    上面的例子可以使用函数表达式来修复,这是创建构造函数的正确方法(包括函数声明):

    const Message = function(text) {
      this.text = text;
    };
    const helloMessage = new Message('Hello World!');
    

    简写语法

    箭头函数有一个很好的属性,它可以省略参数圆括号()、块大括号{},如果函数主体只有一条语句,则返回。这有助于编写非常短的函数。

    原文作者的大学编程教授给学生一个有趣的任务:编写 用C语言计算字符串长度的最短函数,这是学习和探索新语言的好方式。

    然而,在实际应用程序中,许多开发人员都会阅读代码。 最短的语法并不总是适合帮助你的同事即时了解该方法的用途。

    在某种程度上,简写的函数变得难以阅读,所以尽量不要过度使用。让各位们看一个例子

    const multiply = (a, b) => b === undefined ? b => a * b : a * b;
    const double = multiply(2);
    double(3);      // => 6
    multiply(2, 3); // => 6
    
    

    multiply返回两个数字的乘法结果或与第一个参数绑定的闭包,以便以后的乘法运算。

    该函数运行良好,看起来很短。但从一开始就很难理解它是做什么的。

    为了使其更具可读性,可以从箭头函数恢复可选花括号和return语句,或使用常规函数:

    function multiply(a, b) {
      if (b === undefined) {
        return function(b) {
          return a * b;
        }
      }
      return a * b;
    }
    const double = multiply(2);
    double(3);      // => 6
    multiply(2, 3); // => 6
    

    在简短和冗长之间找到一个平衡点是很好的,这样可以使代码更加直观。

    总结

    毫无疑问,箭头函数是一个很好的补充。当正确使用时,它会使前面必须使用.bind()或试图捕获上下文的地方变得简单,它还简化了代码。

    某些情况下的优点会给其他情况带来不利。 当需要动态上下文时,不能使用箭头函数:定义方法,使用构造函数创建对象,在处理事件时从 this 获取目标。


    代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

    原文:https://dmitripavlutin.com/wh...


    交流

    文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

    查看原文

    赞 3 收藏 1 评论 1

    前端小智 发布了文章 · 11月16日

    【前端面试】如果使用 JavaScript 原型实现继承

    作者:Indermohan Sing
    译者:前端小智
    来源:blog
    点赞再看,养成习惯

    本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

    最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

    github 地址:https://github.com/qq44924588...

    在这篇文章中,我们将讨论原型以及如何在 JS 中使用它们进行继承。我们还将会看到原型方法与基于类的继承有何不同。

    继承

    继承是编程语言的一个显著特征,随着面向对象编程语言的引入而出现。这些语言大多是基于类的语言。在这里,类就像一个蓝图,对象是它的展现形式。就是说,要创建一个对象,首先我们必须创建一个类,然后我们可以从一个类创建任意数量的对象。

    想象一下,我们有一个表示智能手机的类。这个类具有像其他智能手机一样的可以拍照、有GPS定位等功能。下面是使用 c++ 来描述这样的一个类:

    class SmartPhone {
      public:
      void captureImages() {}
    }
    
    SmartPhone x;
    x.captureImages()
    

    我们创建了一个名为SmartPhone的类,它有一个名为capturePictures的方法用来拍照。

    如果我们需要一个iPhone类,它可以捕捉图像和一些特殊的功能,比如面部ID扫描。下面是两种可能的解决方案:

    1.将捕获图像功能与其他常见的智能手机功能,以及iPhone的特定功能一起重写到一个新类中。但是这种方法需要更多的时间和精力,并且会引入更多的bug。

    1. 重用SmartPhone类中的功能,这就是继承的作用,继承也是重用其他类/对象中功能的一种方式。

    这里是我们如何从SmartPhone类中继承capturePictures方法,使用 c++ 实现如下:

    class Iphone: public SmartPhone {
      public:
      void faceIDScan() {}
    }
    
    Iphone x
    
    x.faceIDScan()
    
    x.captureImages()
    

    上面是一个简单的继承示例。 但是,它表明继承可以使我们以某种方式重用代码,从而使所生成的程序更不易出错,并且花费更少的时间进行开发。

    以下是关于类的一些重要信息:

    • 继承该功能的类称为子类
    • 被继承的类称为父类
    • 一个类可以同时从多个类中继承
    • 我们可以具有多个继承级别。 例如,类C继承自类B,而类B继承自类A

    值得注意的是,类本身并没有做任何事情。在从类创建对象之前,实际上没有完成任何工作。我们将看到它为什么不同于JavaScript。

    大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

    原型是什么?

    在 JS 中,所有对象都有一个特殊的内部属性,该属性基本上是对另一个对象的引用。 此引用取决于对象的创建方式。 在 ECMAScript/JavaScript规范中,它表示为[[Prototype]]

    由于[[Prototype]]链接到一个对象,所以该对象有自己的[[Prototype]]引用。这就是建立原型链的方式。

    这个[[Prototype]]链是 JS 中继承的构建块。

    __proto__ 对象

    为了访问对象的[[Prototype]],大多数浏览器都提供__proto__属性。访问方式如下:

    obj.__proto__
    

    需要注意的是,这个属性不是 ECMAScript 标准的一部分,它实际上是由浏览器实现的。

    获取和设置原型方法

    除了__proto__属性外,还有一种访问[[Prototype]]的标准方法:

    Object.getPrototypeOf(obj);
    

    对应的有个类似的方法来设置对象的[[Prototype]]

    Object.setPrototypeOf(obj, prototype);
    

    [[Prototype]].prototype属性

    [[Prototype]] 只不过是一种用来表示物体原型的标准符号。 许多开发人员将其与.prototype属性混淆,这是完全不同的事情,接着我们来研究一下.prototype属性。

    在 JS 中,有许多创建对象的方法。一种方法是使用构造函数,像这样使用new关键字来调用它:

    function SmartPhone(os) {
      this.os = os
    }
    
    let phone = new SmartPhone('Android')
    

    在控制台打印 phone 对象:

    {
      os: "IPhone"
      __proto__{
        constructor: ? SmartPhone(os)
       __proto__: Object
      }
    }
    

    现在,如果我们希望在phone对象上有一些方法,我们可以在函数上使用.prototype属性,如下所示:

    SmartPhone.prototype.isAndroid = function () {
      return this.os === 'Android' || 'android'
    }
    

    再次创建phone对象时,打印 phone 对象如下:

    {
      os: "Android"
      __proto__{
        isAndroid: ?()
        constructor: ? SmartPhone(os)
       __proto__: Object
      }
    }
    

    我们可以在对象的[[Prototype]]中看到isAndroid()方法。

    简而言之,.prototype属性基本上就像由给定的构造函数创建的[[Prototype]]对象的蓝图。 在.prototype属性/对象中声明的所有内容都会在对象的[[Prototype]]中弹出。

    实上,如果将 SmartPhone.prototype 与phone 的[[Prototype]]进行比较,就会发现它们是相同的:

    console.log(Object.getPrototypeOf(phone) === SmartPhone.prototype);
    // true
    

    值得注意的是,我们还可以在构造函数中创建方法:

    function ObjectA() {
      this.methodA = function () {}
    }
    
    let firstObj = new ObjectA()
    console.log(firstObj)
    

    这种方法的问题是当我们初始化一个新对象时。所有实例都有自己methodA的副本。相反,当我们在函数的原型上创建它时,对象的所有实例只共享方法的一个副本,显然使用原型的方式效率会过高。

    大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

    当我们访问属性时这里发生了什么?

    当我们访问一个属性以获取它时,会发生以下情况:

    JS 引擎查找对象上的属性,如果找到了该属性,然后返回它。否则,JS 引擎将通过查看[[Prototype]]来检查对象的继承属性,如果找到该属性,则返回它,否则,它会查找 [[Prototype]][[Prototype]]。 找到属性或没有[[Prototype]]时,该链结束,这意味着我们已经到达原型链的末端。

    当我们设置/创建属性时,JS 总是在对象本身上进行设置。 即使[[Prototype]]链上存在相同的属性,下面是一个例子:

    function MyObject() {}
    MyObject.prototype.propA = 10; // 在原型上创建属性
    
    let myObject = new MyObject();
    console.log(myObject.propA); // [[Prototype]]上的属性
    // 10
    
    myObject.propA = 20; // 对象的属性
    console.log(myObject.propA);
    // 20
    

    在上面的示例中,我们创建了一个构造函数,该函数的[[Prototype]]上具有属性propA。 当我们尝试对其进行读取操作时,会在控制台中看到该值。 但是,当我们尝试在对象本身上设置相同的属性时;JS 使用给定值在对象上创建一个新属性。 现在,如果我们不能直接访问[[Prototype]]上的属性。

    值得注意的是,普通对象的[[Prototype]]链的末尾是内置的Object.prototype。 这就是为什么大多数对象共享许多方法(例如toString())的原因。 因为它们实际上是在Object.prototype上定义的。

    使用原型继承的各种方法

    在 JS 中,无论我们如何创建对象,只有原型继承,但这些方式还有一些区别,来看看:

    对象字面量

    在JavaScript中创建对象的最简单方法是使用对象字面量:

    let obj = {}
    

    如果在浏览器的控制台中打印obj,我们将看到以下内容:

    clipboard.png

    基本上,所有用文字面量创建的对象都继承了Object.prototype的属性。

    需要注意的是__proto__对象引用了创建它的构造函数。 在这种情况下,constructor属性指向Object构造函数。

    使用对象构造函数

    另一种不太常见的创建对象的方法是使用对象构造函数。JS 提供了一个名为Object的内置构造函数方法来创建对象。

    let obj = new Object();
    

    这种方法的结果与对象字面量的方式相同。它从Object.prototype继承属性。因为我们使用Object作为构造函数。

    Object.create 方法

    使用此辅助方法,我们可以创建一个带有[[Prototype]]的对象,如下所示:

    let SmartPhone = {
      captureImages: function() {}
    }
    
    let Iphone = Object.create(SmartPhone)
    
    Iphone.captureImages()
    

    这是在 JS 中使用继承的最简单方法之一。猜猜我们如何在没有任何[[Prototype]]引用的情况下创建对象?

    构造方法

    与 JS 运行时提供的对象构造函数相似。 我们还可以创建自己的构造函数,以创建适合我们需求的对象,如下所示:

    function SmartPhone(os) {
      this.os = os;
    }
    
    SmartPhone.prototype.isAndroid = function() {
      return this.os === 'Android';
    };
    
    SmartPhone.prototype.isIOS = function() {
      return this.os === 'iOS';
    };
    

    现在,我们想创建一个iPhone类,它应该有'iOS'作为它 os 属性的值。它还应该有faceIDScan方法。

    首先,我们必须创建一个Iphone构造函数,在其中,我们应该调用SmartPhone构造函数,如下所示:

    function Iphone() {
       SmartPhone.call(this, 'iOS');
    }
    

    这会将Iphone构造函数中的this.os属性设置为’iOS‘

    之所以调用SmartPhone.call方法,是因为我们需要更改 this 值以引用Iphone。 这类似于在面向对象的世界中调用父级的构造函数。

    接下来的事情是,我们必须从SmartPhone构造函数继承方法。 我们可以在此处使用Object.create朋友,如下所示:

    Iphone.prototype = Object.create(SmartPhone.prototype);
    

    现在,我们可以使用.prototypeIphone添加方法,如下所示:

    Iphone.prototype.faceIDScan = function() {};
    

    最后,我们可以使用Iphone创建一个对象,如下所示:

    let x = new Iphone();
    
    // calling inherited method
    console.log(x.isIOS()):
    // true
    
    

    ES6 class

    使用ES6,整个过程非常简单。 我们可以创建类(它们与C ++或其他任何基于类的语言中的类不同,只是在原型继承之上的语法糖),然后从其他类派生新的类。

    下面是我们如何在ES6中创建类:

    class SmartPhone {
      constructor(os) {
        this.os = os;
      }
      isAndroid() {
        return this.os === 'Android';
      }
      isIos() {
        return this.os === 'iOS';
      }
    };

    现在,我们可以创建一个派生自SmartPhone的新类,如下所示:

    class Iphone extends SmartPhone {
       constructor() {
         super.call('iOS');
       }
       faceIDScan() {}
    }

    我们不是调用SmartPhone.call,而是调用super.call。 在内部,JavaScript引擎会自动为我们执行此操作。

    最后,我们可以使用Iphone创建一个对象,如下所示

    let x = new Iphone();
    
    x.faceIDScan();
    
    // calling inherited method
    console.log(x.isIos()):
    // true

    该ES6示例与先前的构造方法示例相同。 但是阅读和理解起来要干净得多。


    原文:https://javascript.info/proto...

    代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug


    交流

    文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

    查看原文

    赞 9 收藏 5 评论 0

    前端小智 发布了文章 · 11月13日

    要提升前端布局能力,这些 CSS 属性需要学习下!

    作者:John
    译者:前端小智
    来源:smashingmagazine
    点赞再看,养成习惯

    本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

    最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

    github 地址:https://github.com/qq44924588...

    属性选择器非常神奇。它们可以使你摆脱棘手的问题,帮助你避免添加类,并指出代码中的一些问题。但是不要担心,虽然属性选择器非常复杂和强大,但是它们很容易学习和使用。在本文中,我们将讨论它们是如何运行的,并给出一些如何使用它们的想法。

    通常将 HTML 属性放在方括号中,称为属性选择器,如下:

    [href] {
       color: red;
    }
    

    这样任何具有href属性的且没有更特定选择器的元素的文本颜色都会是红色的。属性选择器的特性与类相同。

    :更多关于笼匹配的CSS特异性,你可以阅读CSS特性:你应该知道的事情,或者如果你喜欢星球大战:CSS特性战争

    但是你可以使用属性选择器做得更多。就像你的 DNA 一样,它们有内在的逻辑来帮助你选择各种属性组合和值。它们可以匹配属性中的任何属性,甚至字符串值,而不是像标签、类或id选择器那样精确匹配。

    属性选择器

    属性选择器可以独立存在,更具体地说,如果需要选择所有具有title属性的div标签,可以这么做:

    div[title]
    

    但你也可以通过以下操作选择具有 title 属性的 div 的子元素

    div [title]
    

    需要说明的是,它们之间没有空格意味着属性位于相同的元素上(就像元素和类之间没有空格一样),而它们之间的空格意味着后代选择器,即选择具有该属性的元素的子元素。

    你可以更精细地选择具体属性值的属性。

    div[title="dna"]
    

    上面选择了所有具有确切名称dna的div,虽然有选择器算法可以处理每种情况(以及更多),但这里不会选择“dna is awesome”“dnamutation”的标题。

    注意:在大多数情况下,属性选择器中不需要引号,但是我使用它们,因为我相信它可以提高清代码的可读性,并确保边界用例能够正常工作。

    如果你想选择 title 包含 dna的元素,如 “my beautiful dna” 或者 “mutating dna is fun!” ,可以使用波浪号(~)。

    div[title~="dna"]
    

    如果你想匹配以 dna 结尾的 title,如  “dontblamemeblamemydna” 或 “his-stupidity-is-from-upbringing-not-dna” ,刚可以使用$标志符:

    [title$="dna"]
    

    如果你想匹配以 dna 开头的 title,如  “dnamutants” 或 “dna-splicing-for-all” ,刚可以使用^标志符:

    [title^="dna"]
    
    

    虽然精确匹配是有帮助的,但它可能选择太紧,并且^符号匹配可能太宽而无法满足你的需要。 例如,可能不想选择 “genealogy” 的标题,但仍然选择“gene”和“gene-data”。 管道特征(|)就是这样,属性中必须是完整且唯一的单词,或者以-分隔开。

    [title|="gene"]
    

    最后,还有一个匹配任何子字符串的模糊搜索属性操作符,属性中做字符串拆分,只要能拆出来dna这个词就行:

    [title*="dna"]
    

    使这些属性选择器更加强大的是,它们是可堆叠的,允许你选择具有多个匹配因子的元素。

    如果你需要找到一个a 标签,它有一个 title ,并且有一个以“genes” 结尾的 class,可以使用如下方式:

    a[title][class$="genes"]
    

    你不仅可以选择 HTML 元素的属性,还可以使用伪类型元素来打印出文本:

        <span class="joke" title="Gene Editing!">What’s the first thing a biotech journalist does after finishing the first draft of an article?</span>
    .joke:hover:after {
       content: "Answer:" attr(title);
       display: block;
    }

    上面的代码在鼠标悬停时将显示一串自定义的字符串。

    最后要知道的是,您可以添加一个标志,让属性搜索不区分大小写。 在结束方括号之前添加i

    [title*="DNA" i]
    

    因此它会匹配dna, DNA, dnA等。

    现在我们已经看到了如何使用属性选择器进行选择,让我们看看一些用例。 我将它们分为两类:一般用途诊断

    一般用途

    输入类型样式的设置

    你可以对输入类型使用不同的样式,例如电子邮件和电话。

    input[type="email"] {
       color: papayawhip;
    }
    input[type="tel"] {
       color: thistle;
    }
    

    显示电话链接

    你可以隐藏特定尺寸的电话号码并显示电话链接,以便在手机上轻松拨打电话。

    span.phone {
       display: none;
    }
    a[href^="tel"] {
       display: block;
    }
    

    内部链接 vs 外部链接,安全链接 vs 非安全链接

    你可以区别对待内部和外部链接,并将安全链接设置为与不安全链接不同:

    a[href^="http"]{
       color: bisque;
    }
    a:not([href^="http"]) {
      color: darksalmon;
    }
    
    a[href^="http://"]:after {
       content: url(unlock-icon.svg);
    }
    a[href^="https://"]:after {
       content: url(lock-icon.svg);
    }
    

    下载图标

    HTML5 给我们的一个属性是“下载”,它告诉浏览器,你猜对了,下载该文件而不是试图打开它。这对于你希望人们访问但不希望它们立即打开的 PDFDOC 非常有用。它还使得连续下载大量文件的工作流程更加容易。下载属性的缺点是没有默认的视觉效果将其与更传统的链接区分开来。通常这是你想要的,但如果不是,你可以做类似下面的事情:

    a[download]:after { 
       content: url(download-arrow.svg);
    }
    

    还可以使用不同的图标(如PDF与DOCX与ODF等)来表示文件类型,等等。

    a[href$="pdf"]:after {
       content: url(pdf-icon.svg);
    }
    a[href$="docx"]:after {
       content: url(docx-icon.svg);
    }
    a[href$="odf"]:after {
       content: url(open-office-icon.svg);
    }
    

    你还可以通过叠加属性选择器来确保这些图标只出现在可下载链接上。

    a[download][href$="pdf"]:after {
       content: url(pdf-icon.svg);
    }
    
    

    覆盖或重新使用已废弃/弃用的代码

    我们都遇到过时代码过时的旧网站,在 HTML5 之前,你可能需要覆盖甚至重新应用作为属性实现的样式。

    <div bgcolor="#000000" color="#FFFFFF">Old, holey genes</div>
    
    div[bgcolor="#000000"] { /*override*/
       background-color: #222222 !important;
    }
    div[color="#FFFFFF"] { /*reapply*/
       color: #FFFFFF;
    }
    

    重写特定的内联样式

    有时候你会遇到一些内联样式,这些样式会影响布局,但这些内联样式我们又没修改。那么以下是一种方法。

    如果你道要覆盖的确切属性和值,并且希望在它出现的任何地方覆盖它,那么这种方法的效果最好。

    对于此示例,元素的边距以像素为单位设置,但需要在 em 中进行扩展和设置,以便在用户更改默认字体大小时可以正确地重新调整元素。

    <div style="color: #222222; margin: 8px; background-color: #EFEFEF;"Teenage Mutant Ninja Myrtle</div>
    
    div[style*="margin: 8px"] {
       margin: 1em !important;
    }
    

    显示文件类型

    默认情况下,文件输入的可接受文件列表是不可见的。 通常,我们使用伪元素来暴露它们:

    <input type="file" accept="pdf,doc,docx">
    
    [accept]:after {
       content: "Acceptable file types: " attr(accept);
    }
    

    html 手风琴菜单

    detailssummary标签是一种只用HTML做扩展/手风琴菜单的方法,details 包括了summary标签和手风琴打开时要展示的内容。点击summary会展开details标签并添加open属性,我们可以通过open属性轻松地为打开的details标签设置样式:

    details[open] {
       background-color: hotpink;
    }
    

    打印链接

    在打印样式中显示URL使我走上了理解属性选择器的道路。 你现在应该知道如何自己构建它, 你只需选择带有href的所有标签,添加伪元素,然后使用attr()content打印它们。

    a[href]:after {
       content: " (" attr(href) ") ";
    }
    

    自定义提示

    使用属性选择器创建自定义工具提示既有趣又简单:

    [title] {
      position: relative;
      display: block;
    }
    [title]:hover:after {
      content: attr(title);
      color: hotpink;
      background-color: slateblue;
      display: block;
      padding: .225em .35em;
      position: absolute;
      right: -5px;
      bottom: -5px;
    }
    
    

    便捷键

    web 的一大优点是它提供了许多不同的信息访问选项。一个很少使用的属性是设置accesskey的能力,这样就可以通过键组合和accesskey设置的字母直接访问该项目(确切的键组合取决于浏览器)。但是要想知道网站上设置了哪些键并不是件容易的事

    下面的代码将显示这些键:focus。我不使用鼠标悬停,因为大多数时候需要accesskey的人是那些使用鼠标有困难的人。你可以将其添加为第二个选项,但要确保它不是惟一的选项。

    a[accesskey]:focus:after {
       content: " AccessKey: " attr(accesskey);
    }
    

    诊断

    这些选项用于帮助我们在构建过程中或在尝试修复问题时在本地识别问题。将这些内容放在我们的生产网站上会使用户产生错误。

    没有 controls 属性的 audio

    我不经常使用audio标签,但是当我使用它时,我经常忘记包含controls属性。 结果:没有显示任何内容。 如果你在 Firefox,如果隐藏了音频元素,或者语法或其他一些问题阻止它出现(仅适用于Firefox),此代码可以帮助你解决问题:

    audio:not([controls]) {
      width: 100px;
      height: 20px;
      background-color: chartreuse;
      display: block;
    }
    
    

    没有 alt 文本

    没有 alt 文本的图像是可访问性的噩梦。只需查看页面就很难找到它们,但如果添加它们,它们就会弹出来(当页面图片加载失败时,alt文字可以更好的解释图片的作用):

    img:not([alt]) { /* no alt attribute */ 
      outline: 2em solid chartreuse; 
    }
    img[alt=""] { /* alt attribute is blank */ 
      outline: 2em solid cadetblue; 
    }
    
    

    异步 Javascript 文件

    网页可以是内容管理系统和插件,框架和代码的集合,确定哪些JavaScript异步加载以及哪些不加载可以帮助你专注于提高页面性能。

    script[src]:not([async]) {
      display: block;
      width: 100%;
      height: 1em;
      background-color: red;
    }
    script:after {
      content: attr(src);
    }
    

    JavaScript 事件元素

    你可以突出显示具有JavaScript事件属性的元素,以便将它们重构到JavaScript文件中。这里我主要关注OnMouseOver属性,但是它适用于任何JavaScript事件属性。

    [OnMouseOver] {
       color: burlywood;
    }
    [OnMouseOver]:after {
       content: "JS: " attr(OnMouseOver);
    }
    

    隐藏项

    如果需要查看隐藏元素或隐藏输入的位置,可以使用它们来显示

    [hidden], [type="hidden"] {
      display: block;
    }
    

    原文:https://www.smashingmagazine....

    代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug


    交流

    文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

    查看原文

    赞 19 收藏 11 评论 0

    前端小智 发布了文章 · 11月10日

    学了这 7 个 CSS 属性,我的 CSS 技能又进步啦!

    作者:Mustapha Aouas
    译者:前端小智
    来源:dev
    点赞再看,养成习惯

    本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

    大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

    学习CSS是构建好看网页的一种方式。 但是,在学习过程中,我们倾向于(大部分时间)限制自己,一遍又一遍地使用相同的属性。 毕竟,我们是一种习惯性的动物,我们会使用自己习惯且熟悉的东西。

    因此,在这篇文章中,向你介绍7个 比较少见且好用的 CSS 属性,希望对你有所帮助。

    1. vertical-align

    CSS 的属性 vertical-align 用来指定行内元素(inline)或表格单元格(table-cell)元素的垂直对齐方式。

    就像定义说的,这个属性允许你垂直对齐文本。它对于顺序指示器(st, nd等)、需要的输入星号(*)或没有正确居中的图标特别有用。vertical-align取其中一个值:super | top | middle | bottom | baseline (default) | sub | text-top | text-bottom,或从基线开始的长度(px%em, rem等等)。

    baseline: 使元素的基线与父元素的基线对齐。HTML规范没有详细说明部分可替换元素的基线,如<textarea> ,这意味着这些元素使用此值的表现因浏览器而异。

    sub:使元素的基线与父元素的下标基线对齐。

    super:使元素的基线与父元素的上标基线对齐。

    text-top:使元素的基线与父元素的上标基线对齐。

    text-bottom:使元素的底部与父元素的字体底部对齐。

    middle:使元素的中部与父元素的基线加上父元素x-height(译注:x高度)的一半对齐。

    图片描述

    注意 vertical-align 只对行内元素、表格单元格元素生效:不能用它垂直对齐块级元素。

    资源:MDN

    2. writing-mode

    writing-mode 属性定义了文本水平或垂直排布以及在块级元素中文本的行进方向。为整个文档设置书时,应在根元素上设置它(对于 HTML 文档应该在 html 元素上设置)。 它采用以下值之一horizontal-tb (default) | vertical-rl | vertical-lr

    clipboard.png

    horizontal-tb:对于左对齐(ltr)脚本,内容从左到右水平流动。对于右对齐(rtr)脚本,内容从右到左水平流动。下一水平行位于上一行下方。

    vertical-rl:对于左对齐(ltr)脚本,内容从上到下垂直流动,下一垂直行位于上一行左侧。对于右对齐(rtr)脚本,内容从下到上垂直流动,下一垂直行位于上一行右侧。

    vertical-lr:对于左对齐(ltr)脚本,内容从上到下垂直流动,下一垂直行位于上一行右侧。对于右对齐(rtr)脚本,内容从下到上垂直流动,下一垂直行位于上一行左侧。

    资源:MDN

    大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

    3. font-variant-numeric

    font-variant-numeric CSS属性控制数字,分数和序号标记的替代字形的使用。

    它采用以下这些值之一: normal | ordinal | slashed-zero | lining-nums | oldstyle-nums | proportional-nums | tabular-nums | diagonal-fractions | stacked-fractions

    此属性对于设置数字样式很有用。 根据情况,你可能希望显示老式的数字或带有斜杠的零,对于这些情况,font-feature-settings很有用。

    图片描述

    请注意,font-variant-numericfont-feature-settings组属性的一部分。 诸如font-variant-capsfont-variant-ligatures之类的属性也属于该组。
    还要注意,像所有font-feature-settings属性一样,你的字体需要实现上述功能才能正常工作。 我使用的字体是Fira Sans

    资源:MDN

    4. user-select

    每当我们有不想让用户选择的文本,或者相反,如果发生了双击或上下文单击,希望选择所有文本时,user-select属性将非常有用。

    此属性采用以下值之一:none | auto | text | all

    none:元素及其子元素的文本不可选中。 请注意这个Selection 对象可以包含这些元素。 从Firefox 21开始, none 表现的像 -moz-none,因此可以使用 -moz-user-select: text 在子元素上重新启用选择。

    auto
    auto 的具体取值取决于一系列条件,具体如下:

    • ::before::after 伪元素上,采用的属性值是 none
    • 如果元素是可编辑元素,则采用的属性值是 contain
    • 否则,如果此元素的父元素的 user-select 采用的属性值为 all,则该元素采用的属性值也为 all
    • 否则,如果此元素的父元素的 user-select 采用的属性值为 none,则该元素采用的属性值也为 none
    • 否则,采用的属性值为 text

    text:用户可以选择文本。
    all:在一个HTML编辑器中,当双击子元素或者上下文时,那么包含该子元素的最顶层元素也会被选中。

    图片描述

    资源:MDN

    大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

    5. clip-path

    clip-path CSS 属性可以创建一个只有元素的部分区域可以显示的剪切区域。区域内的部分显示,区域外的隐藏。剪切区域是被引用内嵌的URL定义的路径或者外部svg的路径,或者作为一个形状例如circle()clip-path属性代替了现在已经弃用的剪切 clip属性。

    此属性采用以下值之一:circle() | ellipse() | polygon() | path() | url()

    由于这是对该属性的介绍,因此,这里不会深入研究每个值。

    我使用最多的两个值是circlepolygoncircle(radius at pair)值有两个参数,第一个参数是圆的半径,第二个参数是表示圆心的点。polygon(pair, pair, pair ...)值取3个或更多的点,表示一个三角形、一个矩形等等。

    图片描述

    6. shape-outside

    shape-outside的CSS 属性定义了一个可以是非矩形的形状,相邻的内联内容应围绕该形状进行包装。 默认情况下,内联内容包围其边距框; shape-outside提供了一种自定义此包装的方法,可以将文本包装在复杂对象周围而不是简单的框中。它采用与clip-path相同的值。

    clip-path定义用户如何查看元素,shape-outside定义其他HTML元素如何查看元素。

    clipboard.png

    资源:MDN

    7. background-clip

    最后,backgroundclip CSS属性设置元素的背景是否扩展到其borderpaddingcontent 框之下。

    此属性采用以下值之一:border-box (default) | padding-box | content-box | text

    图片描述

    资源:MDN

    总结

    下图是结合上面 7 个属性实现的布局,让大家加深一下印象。

    clipboard.png

    如果你还知道一些新奇的属性,欢迎留言。


    代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

    原文:https://dev.to/mustapha/7-ama...


    交流

    文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

    查看原文

    赞 29 收藏 21 评论 0

    前端小智 发布了文章 · 11月4日

    为了面试能通过,我要看完这75道面试题(下)

    作者:Mark A
    译者:前端小智
    来源:dev
    点赞再看,微信搜索 【大迁世界】 关注这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

    编程,建网站必备的阿里云服务器竟然免费送了!

    51. 什么是 async/await 及其如何工作?

    async/await是 JS 中编写异步或非阻塞代码的新方法。它建立在Promises之上,让异步代码的可读性和简洁度都更高。

    async/await是 JS 中编写异步或非阻塞代码的新方法。 它建立在Promises之上,相对于 Promise 和回调,它的可读性和简洁度都更高。 但是,在使用此功能之前,我们必须先学习Promises的基础知识,因为正如我之前所说,它是基于Promise构建的,这意味着幕后使用仍然是Promise

    使用 Promise

    function callApi() {
      return fetch("url/to/api/endpoint")
        .then(resp => resp.json())
        .then(data => {
          //do something with "data"
        }).catch(err => {
          //do something with "err"
        });
    }
    

    使用async/await

    async/await,我们使用 tru/catch 语法来捕获异常。

    async function callApi() {
      try {
        const resp = await fetch("url/to/api/endpoint");
        const data = await resp.json();
        //do something with "data"
      } catch (e) {
        //do something with "err"
      }
    }
    

    注意:使用 async关键声明函数会隐式返回一个Promise

    const giveMeOne = async () => 1;
    
    giveMeOne()
      .then((num) => {
        console.log(num); // logs 1
      });
    

    注意:await关键字只能在async function中使用。在任何非async function的函数中使用await关键字都会抛出错误。await关键字在执行下一行代码之前等待右侧表达式(可能是一个Promise)返回。

    const giveMeOne = async () => 1;
    
    function getOne() {
      try {
        const num = await giveMeOne();
        console.log(num);
      } catch (e) {
        console.log(e);
      }
    }
    
    // Uncaught SyntaxError: await is only valid in async function
    
    async function getTwo() {
      try {
        const num1 = await giveMeOne(); // 这行会等待右侧表达式执行完成
        const num2 = await giveMeOne(); 
        return num1 + num2;
      } catch (e) {
        console.log(e);
      }
    }
    
    await getTwo(); // 2
    

    52. 展开(spread )运算符和 剩余(Rest) 运算符有什么区别?

    展开运算符(spread)是三个点(...),可以将一个数组转为用逗号分隔的参数序列。说的通俗易懂点,有点像化骨绵掌,把一个大元素给打散成一个个单独的小元素。

    剩余运算符也是用三个点(...)表示,它的样子看起来和展开操作符一样,但是它是用于解构数组和对象。在某种程度上,剩余元素和展开元素相反,展开元素会“展开”数组变成多个元素,剩余元素会收集多个元素和“压缩”成一个单一的元素。

    function add(a, b) {
      return a + b;
    };
    
    const nums = [5, 6];
    const sum = add(...nums);
    console.log(sum);
    

    在本例中,我们在调用add函数时使用了展开操作符,对nums数组进行展开。所以参数a的值是5b的值是6,所以sum11

    function add(...rest) {
      return rest.reduce((total,current) => total + current);
    };
    
    console.log(add(1, 2)); // 3
    console.log(add(1, 2, 3, 4, 5)); // 15
    

    在本例中,我们有一个add函数,它接受任意数量的参数,并将它们全部相加,然后返回总数。

    const [first, ...others] = [1, 2, 3, 4, 5];
    console.log(first); // 1
    console.log(others); // [2,3,4,5]
    

    这里,我们使用剩余操作符提取所有剩余的数组值,并将它们放入除第一项之外的其他数组中。

    53. 什么是默认参数?

    默认参数是在 JS 中定义默认变量的一种新方法,它在ES6或ECMAScript 2015版本中可用。

    //ES5 Version
    function add(a,b){
      a = a || 0;
      b = b || 0;
      return a + b;
    }
    
    //ES6 Version
    function add(a = 0, b = 0){
      return a + b;
    }
    add(1); // returns 1 
    

    我们还可以在默认参数中使用解构。

    function getFirst([first, ...rest] = [0, 1]) {
      return first;
    }
    
    getFirst();  // 0
    getFirst([10,20,30]);  // 10
    
    function getArr({ nums } = { nums: [1, 2, 3, 4] }){
        return nums;
    }
    
    getArr(); // [1, 2, 3, 4]
    getArr({nums:[5,4,3,2,1]}); // [5,4,3,2,1]
    

    我们还可以使用先定义的参数再定义它们之后的参数。

    function doSomethingWithValue(value = "Hello World", callback = () => { console.log(value) }) {
      callback();
    }
    doSomethingWithValue(); //"Hello World"
    

    54. 什么是包装对象(wrapper object)?

    我们现在复习一下JS的数据类型,JS数据类型被分为两大类,基本类型引用类型

    基本类型:Undefined,Null,Boolean,Number,String,Symbol,BigInt

    引用类型:Object,Array,Date,RegExp等,说白了就是对象。

    其中引用类型有方法和属性,但是基本类型是没有的,但我们经常会看到下面的代码:

    let name = "marko";
    
    console.log(typeof name); // "string"
    console.log(name.toUpperCase()); // "MARKO"
    

    name类型是 string,属于基本类型,所以它没有属性和方法,但是在这个例子中,我们调用了一个toUpperCase()方法,它不会抛出错误,还返回了对象的变量值。

    原因是基本类型的值被临时转换或强制转换为对象,因此name变量的行为类似于对象。 除nullundefined之外的每个基本类型都有自己包装对象。也就是:StringNumberBooleanSymbolBigInt。 在这种情况下,name.toUpperCase()在幕后看起来如下:

    console.log(new String(name).toUpperCase()); // "MARKO"
    

    在完成访问属性或调用方法之后,新创建的对象将立即被丢弃。

    55. 隐式和显式转换有什么区别)?

    隐式强制转换是一种将值转换为另一种类型的方法,这个过程是自动完成的,无需我们手动操作。

    假设我们下面有一个例子。

    console.log(1 + '6'); // 16
    console.log(false + true); // 1
    console.log(6 * '2'); // 12
    

    第一个console.log语句结果为16。在其他语言中,这会抛出编译时错误,但在 JS 中,1被转换成字符串,然后与+运算符连接。我们没有做任何事情,它是由 JS 自动完成。

    第二个console.log语句结果为1,JS 将false转换为boolean 值为 0,,true1,因此结果为1

    第三个console.log语句结果12,它将'2'转换为一个数字,然后乘以6 * 2,结果是12。

    而显式强制是将值转换为另一种类型的方法,我们需要手动转换。

    console.log(1 + parseInt('6'));
    

    在本例中,我们使用parseInt函数将'6'转换为number ,然后使用+运算符将16相加。

    56. 什么是NaN? 以及如何检查值是否为NaN?

    NaN表示“非数字”是 JS 中的一个值,该值是将数字转换或执行为非数字值的运算结果,因此结果为NaN

    let a;
    
    console.log(parseInt('abc')); // NaN
    console.log(parseInt(null)); // NaN
    console.log(parseInt(undefined)); // NaN
    console.log(parseInt(++a)); // NaN
    console.log(parseInt({} * 10)); // NaN
    console.log(parseInt('abc' - 2)); // NaN
    console.log(parseInt(0 / 0)); // NaN
    console.log(parseInt('10a' * 10)); // NaN
    

    JS 有一个内置的isNaN方法,用于测试值是否为isNaN值,但是这个函数有一个奇怪的行为。

    console.log(isNaN()); // true
    console.log(isNaN(undefined)); // true
    console.log(isNaN({})); // true
    console.log(isNaN(String('a'))); // true
    console.log(isNaN(() => { })); // true
    

    所有这些console.log语句都返回true,即使我们传递的值不是NaN

    ES6中,建议使用Number.isNaN方法,因为它确实会检查该值(如果确实是NaN),或者我们可以使自己的辅助函数检查此问题,因为在 JS 中,NaN是唯一的值,它不等于自己。

    function checkIfNaN(value) {
      return value !== value;
    }
    

    57. 如何判断值是否为数组?

    我们可以使用Array.isArray方法来检查值是否为数组。 当传递给它的参数是数组时,它返回true,否则返回false

    console.log(Array.isArray(5));  // false
    console.log(Array.isArray("")); // false
    console.log(Array.isArray()); // false
    console.log(Array.isArray(null)); // false
    console.log(Array.isArray({ length: 5 })); // false
    
    console.log(Array.isArray([])); // true
    

    如果环境不支持此方法,则可以使用polyfill实现。

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

    当然还可以使用传统的方法:

    let a = []
    if (a instanceof Array) {
      console.log('是数组')
    } else {
      console.log('非数组')
    }
    

    58. 如何在不使用%模运算符的情况下检查一个数字是否是偶数?

    我们可以对这个问题使用按位&运算符,&对其操作数进行运算,并将其视为二进制值,然后执行与运算。

    function isEven(num) {
      if (num & 1) {
        return false
      } else {
        return true
      }
    }
    

    0 二进制数是 000
    1 二进制数是 001
    2 二进制数是 010
    3 二进制数是 011
    4 二进制数是 100
    5 二进制数是 101
    6 二进制数是 110
    7 二进制数是 111

    以此类推...

    与运算的规则如下:

    aba & b
    000
    010
    111

    因此,当我们执行console.log(5&1)这个表达式时,结果为1。首先,&运算符将两个数字都转换为二进制,因此5变为1011变为001

    然后,它使用按位怀运算符比较每个位(01)。 101&001,从表中可以看出,如果a & b1,所以5&1结果为1

    101 & 001
    101
    001
    001
    • 首先我们比较最左边的1&0,结果是0
    • 然后我们比较中间的0&0,结果是0
    • 然后我们比较最后1&1,结果是1
    • 最后,得到一个二进制数001,对应的十进制数,即1

      由此我们也可以算出console.log(4 & 1) 结果为0。知道4的最后一位是0,而0 & 1 将是0。如果你很难理解这一点,我们可以使用递归函数来解决此问题。

      function isEven(num) {

       if (num < 0 || num === 1) return false;
       if (num == 0) return true;
       return isEven(num - 2);

      }

    59. 如何检查对象中是否存在某个属性?

    检查对象中是否存在属性有三种方法。

    第一种使用 in 操作符号:

    const o = { 
      "prop" : "bwahahah",
      "prop2" : "hweasa"
    };
    
    console.log("prop" in o); // true
    console.log("prop1" in o); // false
    

    第二种使用 hasOwnProperty 方法,hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。

    console.log(o.hasOwnProperty("prop2")); // true
    console.log(o.hasOwnProperty("prop1")); // false
    

    第三种使用括号符号obj["prop"]。如果属性存在,它将返回该属性的值,否则将返回undefined

    console.log(o["prop"]); // "bwahahah"
    console.log(o["prop1"]); // undefined
    

    60. AJAX 是什么?

    即异步的 JavaScript 和 XML,是一种用于创建快速动态网页的技术,传统的网页(不使用 AJAX)如果需要更新内容,必需重载整个网页面。使用AJAX则不需要加载更新整个网页,实现部分内容更新

    用到AJAX的技术:

    • HTML - 网页结构
    • CSS - 网页的样式
    • JavaScript - 操作网页的行为和更新DOM
    • XMLHttpRequest API - 用于从服务器发送和获取数据
    • PHP,Python,Nodejs - 某些服务器端语言

    61. 如何在 JS 中创建对象?

    使用对象字面量:

    const o = {
      name: "前端小智",
      greeting() {
        return `Hi, 我是${this.name}`;
      }
    };
    
    o.greeting(); // "Hi, 我是前端小智"
    

    使用构造函数:

    function Person(name) {
       this.name = name;
    }
    
    Person.prototype.greeting = function () {
       return `Hi, 我是${this.name}`;
    }
    
    const mark = new Person("前端小智");
    
    mark.greeting(); // "Hi, 我是前端小智"
    

    使用 Object.create 方法:

    const n = {
       greeting() {
          return `Hi, 我是${this.name}`;
       }
    };
    
    const o = Object.create(n); 
    o.name = "前端小智";
    

    62. Object.seal 和 Object.freeze 方法之间有什么区别?

    Object.freeze()

    Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。

    Object.seal()

    Object.seal()方法封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。
    

    方法的相同点:

    1. ES5新增。
    2. 对象不可能扩展,也就是不能再添加新的属性或者方法。
    3. 对象已有属性不允许被删除。
    4. 对象属性特性不可以重新配置。

    方法不同点:

    • Object.seal方法生成的密封对象,如果属性是可写的,那么可以修改属性值。

    * Object.freeze方法生成的冻结对象,属性都是不可写的,也就是属性值无法更改。

    63. in 运算符和 Object.hasOwnProperty 方法有什么区别?

    hasOwnPropert方法

    hasOwnPropert()方法返回值是一个布尔值,指示对象自身属性中是否具有指定的属性,因此这个方法会忽略掉那些从原型链上继承到的属性。

    看下面的例子:

    Object.prototype.phone= '15345025546';
    
    let obj = {
        name: '前端小智',
        age: '28'
    }
    console.log(obj.hasOwnProperty('phone')) // false
    console.log(obj.hasOwnProperty('name')) // true
    

    可以看到,如果在函数原型上定义一个变量phonehasOwnProperty方法会直接忽略掉。

    in 运算符

    如果指定的属性在指定的对象或其原型链中,则in 运算符返回true

    还是用上面的例子来演示:

    console.log('phone' in obj) // true
    

    可以看到in运算符会检查它或者其原型链是否包含具有指定名称的属性。

    64. 有哪些方法可以处理 JS 中的异步代码?

    • 回调
    • Promise
    • async/await
    • 还有一些库: async.js, bluebird, q, co

    65. 函数表达式和函数声明之间有什么区别?

    看下面的例子:

    hoistedFunc();
    notHoistedFunc();
    
    function hoistedFunc(){
      console.log("注意:我会被提升");
    }
    
    var notHoistedFunc = function(){
      console.log("注意:我没有被提升");
    }
    

    notHoistedFunc调用抛出异常:Uncaught TypeError: notHoistedFunc is not a function,而hoistedFunc调用不会,因为hoistedFunc会被提升到作用域的顶部,而notHoistedFunc 不会。

    66. 调用函数,可以使用哪些方法?

    在 JS 中有4种方法可以调用函数。

    作为函数调用——如果一个函数没有作为方法、构造函数、applycall 调用时,此时 this 指向的是 window 对象(非严格模式)

      //Global Scope
    
      function add(a,b){
        console.log(this);
        return a + b;
      }  
    
      add(1,5); // 打印 "window" 对象和 6
    
      const o = {
        method(callback){
          callback();
        }
      }
    
      o.method(function (){
          console.log(this); // 打印 "window" 对象
      });
    

    作为方法调用——如果一个对象的属性有一个函数的值,我们就称它为方法。调用该方法时,该方法的this值指向该对象。

    const details = {
      name : "Marko",
      getName(){
        return this.name;
      }
    }
    
    details.getName(); // Marko
    

    作为构造函数的调用-如果在函数之前使用new关键字调用了函数,则该函数称为构造函数。构造函数里面会默认创建一个空对象,并将this指向该对象。

    function Employee(name, position, yearHired) {
      // 创建一个空对象 {}
      // 然后将空对象分配给“this”关键字
      // this = {};
      this.name = name;
      this.position = position;
      this.yearHired = yearHired;
      // 如果没有指定 return ,这里会默认返回 this
    };
    
    const emp = new Employee("Marko Polo", "Software Developer", 2017);
    

    使用applycall方法调用——如果我们想显式地指定一个函数的this值,我们可以使用这些方法,这些方法对所有函数都可用。

    const obj1 = {
     result:0
    };
    
    const obj2 = {
     result:0
    };
    
    
    function reduceAdd(){
       let result = 0;
       for(let i = 0, len = arguments.length; i < len; i++){
         result += arguments[i];
       }
       this.result = result;
    }
    
    
    reduceAdd.apply(obj1, [1, 2, 3, 4, 5]);  // reduceAdd 函数中的 this 对象将是 obj1
    reduceAdd.call(obj2, 1, 2, 3, 4, 5); // reduceAdd 函数中的 this 对象将是 obj2
    

    67. 什么是缓存及它有什么作用?

    缓存是建立一个函数的过程,这个函数能够记住之前计算的结果或值。使用缓存函数是为了避免在最后一次使用相同参数的计算中已经执行的函数的计算。这节省了时间,但也有不利的一面,即我们将消耗更多的内存来保存以前的结果。

    68. 手动实现缓存方法]

    function memoize(fn) {
      const cache = {};
      return function (param) {
        if (cache[param]) {
          console.log('cached');
          return cache[param];
        } else {
          let result = fn(param);
          cache[param] = result;
          console.log(`not cached`);
          return result;
        }
      }
    }
    
    const toUpper = (str ="")=> str.toUpperCase();
    
    const toUpperMemoized = memoize(toUpper);
    
    toUpperMemoized("abcdef");
    toUpperMemoized("abcdef");
    

    这个缓存函数适用于接受一个参数。 我们需要改变下,让它接受多个参数。

    const slice = Array.prototype.slice;
    function memoize(fn) {
      const cache = {};
      return (...args) => {
        const params = slice.call(args);
        console.log(params);
        if (cache[params]) {
          console.log('cached');
          return cache[params];
        } else {
          let result = fn(...args);
          cache[params] = result;
          console.log(`not cached`);
          return result;
        }
      }
    }
    const makeFullName = (fName, lName) => `${fName} ${lName}`;
    const reduceAdd = (numbers, startingValue = 0) => numbers.reduce((total, cur) => total + cur, startingValue);
    
    const memoizedMakeFullName = memoize(makeFullName);
    const memoizedReduceAdd = memoize(reduceAdd);
    
    memoizedMakeFullName("Marko", "Polo");
    memoizedMakeFullName("Marko", "Polo");
    
    memoizedReduceAdd([1, 2, 3, 4, 5], 5);
    memoizedReduceAdd([1, 2, 3, 4, 5], 5);
    
    

    69. 为什么typeof null 返回 object? 如何检查一个值是否为 null?

    typeof null == 'object'总是返回true,因为这是自 JS 诞生以来null的实现。曾经有人提出将typeof null == 'object'修改为typeof null == 'null',但是被拒绝了,因为这将导致更多的bug

    我们可以使用严格相等运算符===来检查值是否为null

    function isNull(value){
      return value === null;
    }
    

    70. new 关键字有什么作用?

    new关键字与构造函数一起使用以创建对象:

    function Employee(name, position, yearHired) {
      this.name = name;
      this.position = position;
      this.yearHired = yearHired;
    };
    
    const emp = new Employee("Marko Polo", "Software Developer", 2017);
    

    new关键字做了4件事:

    • 创建空对象 {}
    • 将空对象分配给 this
    • 将空对象的__proto__指向构造函数的prototype
    • 如果没有使用显式return语句,则返回this

    看下面事例:

    function Person() {
    this.name = '前端小智'
    }

    根据上面描述的,new Person()做了:

    • 创建一个空对象:var obj = {}
    • 将空对象分配给 this 值:this = obj
    • 将空对象的__proto__指向构造函数的prototype:this.__proto__ = Person().prototype
    • 返回this:return this

    71. 什么时候不使用箭头函数? 说出三个或更多的例子?

    不应该使用箭头函数一些情况:

    • 当想要函数被提升时(箭头函数是匿名的)
    • 要在函数中使用this/arguments时,由于箭头函数本身不具有this/arguments,因此它们取决于外部上下文
    • 使用命名函数(箭头函数是匿名的)
    • 使用函数作为构造函数时(箭头函数没有构造函数)
    • 当想在对象字面是以将函数作为属性添加并在其中使用对象时,因为咱们无法访问 this 即对象本身。

    72. Object.freeze() 和 const 的区别是什么?]

    constObject.freeze是两个完全不同的概念。

    const 声明一个只读的变量,一旦声明,常量的值就不可改变:

    const person = {
        name: "Leonardo"
    };
    let animal = {
        species: "snake"
    };
    person = animal; // ERROR "person" is read-only    
    

    Object.freeze适用于值,更具体地说,适用于对象值,它使对象不可变,即不能更改其属性。

    let person = {
        name: "Leonardo"
    };
    let animal = {
        species: "snake"
    };
    Object.freeze(person);
    person.name = "Lima"; //TypeError: Cannot assign to read only property 'name' of object
    console.log(person); 
    

    73. 如何在 JS 中“深冻结”对象?

    如果咱们想要确保对象被深冻结,就必须创建一个递归函数来冻结对象类型的每个属性:

    没有深冻结

    let person = {
        name: "Leonardo",
        profession: {
            name: "developer"
        }
    };
    Object.freeze(person); 
    person.profession.name = "doctor";
    console.log(person); //output { name: 'Leonardo', profession: { name: 'doctor' } }
    

    深冻结

    function deepFreeze(object) {
        let propNames = Object.getOwnPropertyNames(object);
        for (let name of propNames) {
            let value = object[name];
            object[name] = value && typeof value === "object" ?
                deepFreeze(value) : value;
        }
        return Object.freeze(object);
    }
    let person = {
        name: "Leonardo",
        profession: {
            name: "developer"
        }
    };
    deepFreeze(person);
    person.profession.name = "doctor"; // TypeError: Cannot assign to read only property 'name' of object
    

    74. Iterator是什么,有什么作用?

    遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

    Iterator 的作用有三个:

    1. 为各种数据结构,提供一个统一的、简便的访问接口;
    2. 使得数据结构的成员能够按某种次序排列;
    3. ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

    遍历过程:

    1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
    2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
    3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
    4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

    每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含valuedone两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

    //obj就是可遍历的,因为它遵循了Iterator标准,且包含[Symbol.iterator]方法,方法函数也符合标准的Iterator接口规范。
    //obj.[Symbol.iterator]() 就是Iterator遍历器
    let obj = {
      data: [ 'hello', 'world' ],
      [Symbol.iterator]() {
        const self = this;
        let index = 0;
        return {
          next() {
            if (index < self.data.length) {
              return {
                value: self.data[index++],
                done: false
              };
            } else {
              return { value: undefined, done: true };
            }
          }
        };
      }
    };
    

    75. Generator 函数是什么,有什么作用?

    如果说 JavaScrip 是 ECMAScript 标准的一种具体实现、Iterator遍历器是Iterator的具体实现,那么Generator函数可以说是Iterator接口的具体实现方式。

    执行Generator函数会返回一个遍历器对象,每一次Generator函数里面的yield都相当一次遍历器对象的next()方法,并且可以通过next(value)方法传入自定义的value,来改变Generator函数的行为。

    Generator函数可以通过配合Thunk 函数更轻松更优雅的实现异步编程和控制流管理。


    代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

    原文:

    https://dev.to/macmacky/70-ja...


    交流

    文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq44924588... 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

    clipboard.png

    查看原文

    赞 48 收藏 41 评论 2

    前端小智 发布了文章 · 11月3日

    为了面试能通过,我要看完这75道面试题(上)

    作者:Mark A
    译者:前端小智
    来源:dev
    点赞再看,微信搜索 【大迁世界】 关注这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

    编程,建网站必备的阿里云服务器竟然免费送了!

    服务器如何搭建博客

    考题列表

    1.undefined 和 null 有什么区别?

    在理解undefinednull之间的差异之前,我们先来看看它们的相似类。

    它们属于 JavaScript 的 7 种基本类型。

     let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint'];
    

    它们是属于虚值,可以使用Boolean(value)!!value将其转换为布尔值时,值为false

    console.log(!!null); // false
    console.log(!!undefined); // false
    
    console.log(Boolean(null)); // false
    console.log(Boolean(undefined)); // false
    

    接着来看看它们的区别。

    undefined是未指定特定值的变量的默认值,或者没有显式返回值的函数,如:console.log(1),还包括对象中不存在的属性,这些 JS 引擎都会为其分配 undefined 值。

    let _thisIsUndefined;
    const doNothing = () => {};
    const someObj = {
      a : "ay",
      b : "bee",
      c : "si"
    };
    
    console.log(_thisIsUndefined); // undefined
    console.log(doNothing()); // undefined
    console.log(someObj["d"]); // undefined
    

    null“不代表任何值的值”null是已明确定义给变量的值。 在此示例中,当fs.readFile方法未引发错误时,我们将获得null值。

    fs.readFile('path/to/file', (e,data) => {
       console.log(e); // 当没有错误发生时,打印 null
       if(e){
         console.log(e);
       }
       console.log(data);
     });
    

    在比较nullundefined时,我们使用==时得到true,使用===时得到false:

     console.log(null == undefined); // true
     console.log(null === undefined); // false
    

    2. && 运算符能做什么

    && 也可以叫逻辑与,在其操作数中找到第一个虚值表达式并返回它,如果没有找到任何虚值表达式,则返回最后一个真值表达式。它采用短路来防止不必要的工作。

    console.log(false && 1 && []); // false
    console.log(" " && true && 5); // 5
    

    使用if语句

    const router: Router = Router();
    
    router.get('/endpoint', (req: Request, res: Response) => {
       let conMobile: PoolConnection;
       try {
          //do some db operations
       } catch (e) {
       if (conMobile) {
        conMobile.release();
       }
      }
    });
    

    使用&&操作符

    const router: Router = Router();
    
    router.get('/endpoint', (req: Request, res: Response) => {
      let conMobile: PoolConnection;
      try {
         //do some db operations
      } catch (e) {
        conMobile && conMobile.release()
      }
    });
    
    

    3. || 运算符能做什么

    ||也叫或逻辑或,在其操作数中找到第一个真值表达式并返回它。这也使用了短路来防止不必要的工作。在支持 ES6 默认函数参数之前,它用于初始化函数中的默认参数值。

    console.log(null || 1 || undefined); // 1
    
    function logName(name) {
      var n = name || "Mark";
      console.log(n);
    }
    
    logName(); // "Mark"
    

    4. 使用 + 或一元加运算符是将字符串转换为数字的最快方法吗?

    根据MDN文档+是将字符串转换为数字的最快方法,因为如果值已经是数字,它不会执行任何操作。

    5. DOM 是什么?

    DOM 代表文档对象模型,是 HTML 和 XML 文档的接口(API)。当浏览器第一次读取(解析)HTML文档时,它会创建一个大对象,一个基于 HTM L文档的非常大的对象,这就是DOM。它是一个从 HTML 文档中建模的树状结构。DOM 用于交互和修改DOM结构或特定元素或节点。

    假设我们有这样的 HTML 结构:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
       <meta charset="UTF-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <meta http-equiv="X-UA-Compatible" content="ie=edge">
       <title>Document Object Model</title>
    </head>
    
    <body>
       <div>
          <p>
             <span></span>
          </p>
          <label></label>
          <input>
       </div>
    </body>
    
    </html>
    

    等价的DOM是这样的:

    clipboard.png

    JS 中的document对象表示DOM。它为我们提供了许多方法,我们可以使用这些方法来选择元素来更新元素内容,等等。

    6. 什么是事件传播?

    事件发生在DOM元素上时,该事件并不完全发生在那个元素上。 在“冒泡阶段”中,事件冒泡或向上传播至父级,祖父母,祖父母或父级,直到到达window为止;而在“捕获阶段”中,事件从window开始向下触发元素 事件或event.target

    事件传播有三个阶段:

    1. 捕获阶段–事件从 window 开始,然后向下到每个元素,直到到达目标元素。
    2. 目标阶段–事件已达到目标元素。
    3. 冒泡阶段–事件从目标元素冒泡,然后上升到每个元素,直到到达 window

    clipboard.png

    7. 什么是事件冒泡?

    事件发生在DOM元素上时,该事件并不完全发生在那个元素上。 在冒泡阶段,事件冒泡,或者事件发生在它的父代,祖父母,祖父母的父代,直到到达window为止。

    假设有如下的 HTML 结构:

    <div class="grandparent">
      <div class="parent">
        <div class="child">1</div>
      </div>
    </div>
    

    对应的 JS 代码:

    function addEvent(el, event, callback, isCapture = false) {
      if (!el || !event || !callback || typeof callback !== 'function') return;
      if (typeof el === 'string') {
        el = document.querySelector(el);
      };
      el.addEventListener(event, callback, isCapture);
    }
    
    addEvent(document, 'DOMContentLoaded', () => {
      const child = document.querySelector('.child');
      const parent = document.querySelector('.parent');
      const grandparent = document.querySelector('.grandparent');
    
      addEvent(child, 'click', function (e) {
        console.log('child');
      });
    
      addEvent(parent, 'click', function (e) {
        console.log('parent');
      });
    
      addEvent(grandparent, 'click', function (e) {
        console.log('grandparent');
      });
    
      addEvent(document, 'click', function (e) {
        console.log('document');
      });
    
      addEvent('html', 'click', function (e) {
        console.log('html');
      })
    
      addEvent(window, 'click', function (e) {
        console.log('window');
      })
    
    });

    addEventListener方法具有第三个可选参数useCapture,其默认值为false,事件将在冒泡阶段中发生,如果为true,则事件将在捕获阶段中发生。 如果单击child元素,它将分别在控制台上记录childparentgrandparenthtmldocumentwindow,这就是事件冒泡。

    8. 什么是事件捕获?

    当事件发生在 DOM 元素上时,该事件并不完全发生在那个元素上。在捕获阶段,事件从window开始,一直到触发事件的元素。

    假设有如下的 HTML 结构:

    <div class="grandparent">
      <div class="parent">
        <div class="child">1</div>
      </div>
    </div>
    

    对应的 JS 代码:

    function addEvent(el, event, callback, isCapture = false) {
      if (!el || !event || !callback || typeof callback !== 'function') return;
      if (typeof el === 'string') {
        el = document.querySelector(el);
      };
      el.addEventListener(event, callback, isCapture);
    }
    
    addEvent(document, 'DOMContentLoaded', () => {
      const child = document.querySelector('.child');
      const parent = document.querySelector('.parent');
      const grandparent = document.querySelector('.grandparent');
    
      addEvent(child, 'click', function (e) {
        console.log('child');
      });
    
      addEvent(parent, 'click', function (e) {
        console.log('parent');
      });
    
      addEvent(grandparent, 'click', function (e) {
        console.log('grandparent');
      });
    
      addEvent(document, 'click', function (e) {
        console.log('document');
      });
    
      addEvent('html', 'click', function (e) {
        console.log('html');
      })
    
      addEvent(window, 'click', function (e) {
        console.log('window');
      })
    
    });

    addEventListener方法具有第三个可选参数useCapture,其默认值为false,事件将在冒泡阶段中发生,如果为true,则事件将在捕获阶段中发生。 如果单击child元素,它将分别在控制台上打印windowdocumenthtmlgrandparentparent,这就是事件捕获

    9. event.preventDefault() 和 event.stopPropagation()方法之间有什么区别?

    event.preventDefault() 方法可防止元素的默认行为。 如果在表单元素中使用,它将阻止其提交。 如果在锚元素中使用,它将阻止其导航。 如果在上下文菜单中使用,它将阻止其显示或显示。 event.stopPropagation()方法用于阻止捕获和冒泡阶段中当前事件的进一步传播。

    10. 如何知道是否在元素中使用了event.preventDefault()方法?

    我们可以在事件对象中使用event.defaultPrevented属性。 它返回一个布尔值用来表明是否在特定元素中调用了event.preventDefault()

    11. 为什么此代码 obj.someprop.x 会引发错误?

    const obj = {};
    console.log(obj.someprop.x);
    

    显然,由于我们尝试访问someprop属性中的x属性,而 someprop 并没有在对象中,所以值为 undefined。 记住对象本身不存在的属性,并且其原型的默认值为undefined。因为undefined没有属性x,所以试图访问将会报错。

    12. 什么是 event.target ?

    简单来说,event.target是发生事件的元素或触发事件的元素。

    假设有如下的 HTML 结构:

    <div onclick="clickFunc(event)" style="text-align: center;margin:15px;
    border:1px solid red;border-radius:3px;">
        <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;">
            <div style="margin:25px;border:1px solid skyblue;border-radius:3px;">
              <button style="margin:10px">
                 Button
              </button>
            </div>
        </div>
     </div>
    

    JS 代码如下:

    function clickFunc(event) {
      console.log(event.target);
    }
    

    如果单击 button,即使我们将事件附加在最外面的div上,它也将打印 button 标签,因此我们可以得出结论event.target是触发事件的元素。

    13. 什么是 event.currentTarget??

    event.currentTarget是我们在其上显式附加事件处理程序的元素。

    假设有如下的 HTML 结构:

    <div onclick="clickFunc(event)" style="text-align: center;margin:15px;
    border:1px solid red;border-radius:3px;">
        <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;">
            <div style="margin:25px;border:1px solid skyblue;border-radius:3px;">
              <button style="margin:10px">
                 Button
              </button>
            </div>
        </div>
     </div>
    

    JS 代码如下:

    function clickFunc(event) {
      console.log(event.currentTarget);
    }
    

    如果单击 button,即使我们单击该 button,它也会打印最外面的div标签。 在此示例中,我们可以得出结论,event.currentTarget是附加事件处理程序的元素。

    14. == 和 === 有什么区别?

    ==用于一般比较,===用于严格比较,==在比较的时候可以转换数据类型,===严格比较,只要类型不匹配就返回flase

    先来看看 == 这兄弟:

    强制是将值转换为另一种类型的过程。 在这种情况下,==会执行隐式强制。 在比较两个值之前,==需要执行一些规则。

    假设我们要比较x == y的值。

    1. 如果xy的类型相同,则 JS 会换成===操作符进行比较。
    2. 如果xnull, yundefined,则返回true
    3. 如果xundefinedynull,则返回true
    4. 如果x的类型是number, y的类型是string,那么返回x == toNumber(y)
    5. 如果x的类型是string, y的类型是number,那么返回toNumber(x) == y
    6. 如果x为类型是boolean,则返回toNumber(x)== y
    7. 如果y为类型是boolean,则返回x == toNumber(y)
    8. 如果xstringsymbolnumber,而yobject类型,则返回x == toPrimitive(y)
    9. 如果xobjectystringsymbol 则返回toPrimitive(x) == y
    10. 剩下的 返回 false

    注意:toPrimitive首先在对象中使用valueOf方法,然后使用toString方法来获取该对象的原始值。

    举个例子。

    xyx == y
    55true
    1'1'true
    nullundefinedtrue
    0falsetrue
    '1,2'[1,2]true
    '[object Object]'{}true

    这些例子都返回true

    第一个示例符合条件1,因为xy具有相同的类型和值。

    第二个示例符合条件4,在比较之前将y转换为数字。

    第三个例子符合条件2

    第四个例子符合条件7,因为yboolean类型。

    第五个示例符合条件8。 使用toString()方法将数组转换为字符串,该方法返回1,2

    最后一个示例符合条件8。 使用toString()方法将对象转换为字符串,该方法返回[object Object]

    xyx === y
    55true
    1'1'false
    nullundefinedfalse
    0falsefalse
    '1,2'[1,2]false
    '[object Object]'{}false

    如果使用===运算符,则第一个示例以外的所有比较将返回false,因为它们的类型不同,而第一个示例将返回true,因为两者的类型和值相同。

    具体更多规则可以对参考我之前的文章:

    我对 JS 中相等和全等操作符转化过程一直很迷惑,直到有了这份算法

    15. 为什么在 JS 中比较两个相似的对象时返回 false?

    先看下面的例子:

    let a = { a: 1 };
    let b = { a: 1 };
    let c = a;
    
    console.log(a === b); // 打印 false,即使它们有相同的属性
    console.log(a === c); // true
    

    JS 以不同的方式比较对象和基本类型。在基本类型中,JS 通过值对它们进行比较,而在对象中,JS 通过引用或存储变量的内存中的地址对它们进行比较。这就是为什么第一个console.log语句返回false,而第二个console.log语句返回trueac有相同的引用地址,而ab没有。

    16. !! 运算符能做什么?

    !!运算符可以将右侧的值强制转换为布尔值,这也是将值转换为布尔值的一种简单方法。

    console.log(!!null); // false
    console.log(!!undefined); // false
    console.log(!!''); // false
    console.log(!!0); // false
    console.log(!!NaN); // false
    console.log(!!' '); // true
    console.log(!!{}); // true
    console.log(!![]); // true
    console.log(!!1); // true
    console.log(!![].length); // false
    

    17. 如何在一行中计算多个表达式的值?

    可以使用逗号运算符在一行中计算多个表达式。 它从左到右求值,并返回右边最后一个项目或最后一个操作数的值。

    let x = 5;
    
    x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10);
    
    function addFive(num) {
      return num + 5;
    }
    

    上面的结果最后得到x的值为27。首先,我们将x的值增加到6,然后调用函数addFive(6)并将6作为参数传递并将结果重新分配给x,此时x的值为11。之后,将x的当前值乘以2并将其分配给xx的更新值为22。然后,将x的当前值减去5并将结果分配给xx更新后的值为17。最后,我们将x的值增加10,然后将更新的值分配给x,最终x的值为27

    18. 什么是提升?

    提升是用来描述变量和函数移动到其(全局或函数)作用域顶部的术语。

    为了理解提升,需要来了解一下执行上下文执行上下文是当前正在执行的“代码环境”。执行上下文有两个阶段:编译执行

    编译-在此阶段,JS 引荐获取所有函数声明并将其提升到其作用域的顶部,以便我们稍后可以引用它们并获取所有变量声明(使用var关键字进行声明),还会为它们提供默认值: undefined

    执行——在这个阶段中,它将值赋给之前提升的变量,并执行或调用函数(对象中的方法)。

    注意:只有使用var声明的变量,或者函数声明才会被提升,相反,函数表达式或箭头函数,letconst声明的变量,这些都不会被提升。

    假设在全局使用域,有如下的代码:

    console.log(y);
    y = 1;
    console.log(y);
    console.log(greet("Mark"));
    
    function greet(name){
      return 'Hello ' + name + '!';
    }
    
    var y;
    

    上面分别打印:undefined,1, Hello Mark!

    上面代码在编译阶段其实是这样的:

    function greet(name) {
      return 'Hello ' + name + '!';
    }
    
    var y; // 默认值 undefined
    
    // 等待“编译”阶段完成,然后开始“执行”阶段
    
    /*
    console.log(y);
    y = 1;
    console.log(y);
    console.log(greet("Mark"));
    */
    

    编译阶段完成后,它将启动执行阶段调用方法,并将值分配给变量。

    function greet(name) {
      return 'Hello ' + name + '!';
    }
    
    var y;
    
    //start "execution" phase
    
    console.log(y);
    y = 1;
    console.log(y);
    console.log(greet("Mark"));
    

    19. 什么是作用域?

    JavaScript 中的作用域是我们可以有效访问变量或函数的区域。JS 有三种类型的作用域:全局作用域函数作用域块作用域(ES6)

    • 全局作用域——在全局命名空间中声明的变量或函数位于全局作用域中,因此在代码中的任何地方都可以访问它们。
    //global namespace
    var g = "global";
    
    function globalFunc(){
      function innerFunc(){
        console.log(g); // can access "g" because "g" is a global variable
      }
     innerFunc();
    }  
    • 函数作用域——在函数中声明的变量、函数和参数可以在函数内部访问,但不能在函数外部访问。
    function myFavoriteFunc(a) {
      if (true) {
        var b = "Hello " + a;
      }
      return b;
    }
    
    myFavoriteFunc("World");
    
    console.log(a); // Throws a ReferenceError "a" is not defined
    console.log(b); // does not continue here 
    • 块作用域-在块{}中声明的变量(let,const)只能在其中访问。
     function testBlock(){
       if(true){
         let z = 5;
       }
       return z; 
     }
    
     testBlock(); // Throws a ReferenceError "z" is not defined

    作用域也是一组用于查找变量的规则。 如果变量在当前作用域中不存在,它将向外部作用域中查找并搜索,如果该变量不存在,它将再次查找直到到达全局作用域,如果找到,则可以使用它,否则引发错误,这种查找过程也称为作用域链

       /* 作用域链
         
         内部作用域->外部作用域-> 全局作用域
      */
    
      // 全局作用域
      var variable1 = "Comrades";   
      var variable2 = "Sayonara";
    
      function outer(){
      // 外部作用域
        var variable1 = "World";
        function inner(){
        // 内部作用域
          var variable2 = "Hello";
          console.log(variable2 + " " + variable1);
        }
        inner();
      }  
      outer(); // Hello World
    

    clipboard.png

    20. 什么是闭包?

    这可能是所有问题中最难的一个问题,因为闭包是一个有争议的话题,这里从个人角度来谈谈,如果不妥,多多海涵。

    闭包就是一个函数在声明时能够记住当前作用域、父函数作用域、及父函数作用域上的变量和参数的引用,直至通过作用域链上全局作用域,基本上闭包是在声明函数时创建的作用域。

    看看小例子:

       // 全局作用域
       var globalVar = "abc";
    
       function a(){
         console.log(globalVar);
       }
    
       a(); // "abc" 

    在此示例中,当我们声明a函数时,全局作用域是a闭包的一部分。

    clipboard.png

    变量globalVar在图中没有值的原因是该变量的值可以根据调用函数a的位置和时间而改变。但是在上面的示例中,globalVar变量的值为abc

    来看一个更复杂的例子:

    var globalVar = "global";
    var outerVar = "outer"
    
    function outerFunc(outerParam) {
      function innerFunc(innerParam) {
        console.log(globalVar, outerParam, innerParam);
      }
      return innerFunc;
    }
    
    const x = outerFunc(outerVar);
    outerVar = "outer-2";
    globalVar = "guess"
    x("inner");
    
    

    clipboard.png

    上面打印结果是 guess outer inner

    当我们调用outerFunc函数并将返回值innerFunc函数分配给变量x时,即使我们为outerVar变量分配了新值outer-2outerParam也继续保留outer值,因为重新分配是在调用outerFunc之后发生的,并且当我们调用outerFunc函数时,它会在作用域链中查找outerVar的值,此时的outerVar的值将为 "outer"

    现在,当我们调用引用了innerFuncx变量时,innerParam将具有一个inner值,因为这是我们在调用中传递的值,而globalVar变量值为guess,因为在调用x变量之前,我们将一个新值分配给globalVar

    下面这个示例演示没有理解好闭包所犯的错误:

    const arrFuncs = [];
    for(var i = 0; i < 5; i++){
      arrFuncs.push(function (){
        return i;
      });
    }
    console.log(i); // i is 5
    
    for (let i = 0; i < arrFuncs.length; i++) {
      console.log(arrFuncs[i]()); // 都打印 5
    }
    

    由于闭包,此代码无法正常运行。var关键字创建一个全局变量,当我们 push 一个函数时,这里返回的全局变量i。 因此,当我们在循环后在该数组中调用其中一个函数时,它会打印5,因为我们得到i的当前值为5,我们可以访问它,因为它是全局变量。

    因为闭包在创建变量时会保留该变量的引用而不是其值。 我们可以使用IIFES或使用 let 来代替 var 的声明。

    21. JavaScript 中的虚值是什么?

     const falsyValues = ['', 0, null, undefined, NaN, false];
    

    简单的来说虚值就是是在转换为布尔值时变为 false 的值。

    22. 如何检查值是否虚值?

    使用 Boolean 函数或者 !! 运算符。

    23. 'use strict' 是干嘛用的?

    "use strict"ES5 特性,它使我们的代码在函数或整个脚本中处于严格模式严格模式帮助我们在代码的早期避免 bug,并为其添加限制。

    严格模式的一些限制:

    1. 变量必须声明后再使用
    2. 函数的参数不能有同名属性,否则报错
    3. 不能使用with语句
    4. 不能对只读属性赋值,否则报错
    5. 不能使用前缀 0 表示八进制数,否则报错
    6. 不能删除不可删除的属性,否则报错
    7. 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
    8. eval不能在它的外层作用域引入变量
    9. evalarguments不能被重新赋值
    10. arguments不会自动反映函数参数的变化
    11. 不能使用arguments.callee
    12. 不能使用arguments.caller
    13. 禁止this指向全局对象
    14. 不能使用fn.callerfn.arguments获取函数调用的堆栈
    15. 增加了保留字(比如protectedstaticinterface

    设立”严格模式”的目的,主要有以下几个:

    1. 消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
    2. 消除代码运行的一些不安全之处,保证代码运行的安全;
    3. 提高编译器效率,增加运行速度;
    4. 为未来新版本的Javascript做好铺垫。

    24. JavaScript 中 this 值是什么?

    基本上,this指的是当前正在执行或调用该函数的对象的值。this值的变化取决于我们使用它的上下文和我们在哪里使用它。

    const carDetails = {
      name: "Ford Mustang",
      yearBought: 2005,
      getName(){
        return this.name;
      },
      isRegistered: true
    };
    
    console.log(carDetails.getName()); // Ford Mustang
    

    这通常是我们期望结果的,因为在getName方法中我们返回this.name,在此上下文中,this指向的是carDetails对象,该对象当前是执行函数的“所有者”对象。

    接下我们做些奇怪的事情:

    var name = "Ford Ranger";
    var getCarName = carDetails.getName;
    
    console.log(getCarName()); // Ford Ranger
    

    上面打印Ford Ranger,这很奇怪,因为在第一个console.log语句中打印的是Ford Mustang。这样做的原因是getCarName方法有一个不同的“所有者”对象,即window对象。在全局作用域中使用var关键字声明变量会在window对象中附加与变量名称相同的属性。请记住,当没有使用“use strict”时,在全局作用域中this指的是window对象。

    console.log(getCarName === window.getCarName); // true
    console.log(getCarName === this.getCarName); // true
    

    本例中的thiswindow引用同一个对象。

    解决这个问题的一种方法是在函数中使用applycall方法。

    console.log(getCarName.apply(carDetails)); // Ford Mustang
    console.log(getCarName.call(carDetails));  // Ford Mustang
    

    applycall方法期望第一个参数是一个对象,该对象是函数内部this的值。

    IIFE立即执行的函数表达式,在全局作用域内声明的函数,对象内部方法中的匿名函数和内部函数的this具有默认值,该值指向window对象。

       (function (){
         console.log(this);
       })(); // 打印 "window" 对象
    
       function iHateThis(){
          console.log(this);
       }
    
       iHateThis(); // 打印 "window" 对象
    
       const myFavoriteObj = {
         guessThis(){
            function getName(){
              console.log(this.name);
            }
            getName();
         },
         name: 'Marko Polo',
         thisIsAnnoying(callback){
           callback();
         }
       };
    
    
       myFavoriteObj.guessThis(); // 打印 "window" 对象
       myFavoriteObj.thisIsAnnoying(function (){
         console.log(this); // 打印 "window" 对象
       });

    如果我们要获取myFavoriteObj对象中的name属性(即Marko Polo)的值,则有两种方法可以解决此问题。

    一种是将 this 值保存在变量中。

    const myFavoriteObj = {
     guessThis(){
      const self = this; // 把 this 值保存在 self 变量中
      function getName(){
        console.log(self.name);
      }
      getName();
     },
     name: 'Marko Polo',
     thisIsAnnoying(callback){
       callback();
      }
    };

    第二种方式是使用箭头函数

    const myFavoriteObj = {
      guessThis(){
         const getName = () => { 
           console.log(this.name);
         }
         getName();
      },
      name: 'Marko Polo',
      thisIsAnnoying(callback){
       callback();
      }
    };
    

    箭头函数没有自己的 this。它复制了这个封闭的词法作用域中this值,在这个例子中,this值在getName内部函数之外,也就是myFavoriteObj对象。

    25. 对象的 prototype(原型) 是什么?

    简单地说,原型就是对象的蓝图。如果它存在当前对象中,则将其用作属性和方法的回退。它是在对象之间共享属性和功能的方法,这也是JavaScript实现继承的核心。

    const o = {};
    console.log(o.toString()); // logs [object Object] 
    

    即使o对象中不存在o.toString方法,它也不会引发错误,而是返回字符串[object Object]。 当对象中不存在属性时,它将查看其原型,如果仍然不存在,则将其查找到原型的原型,依此类推,直到在原型链中找到具有相同属性的属性为止。 原型链的末尾是Object.prototype

    console.log(o.toString === Object.prototype.toString); // logs true
    
    
    

    26. 什么是 IIFE,它的用途是什么?

    IIFE或立即调用的函数表达式是在创建或声明后将被调用或执行的函数。 创建IIFE的语法是,将function (){}包裹在在括号()内,然后再用另一个括号()调用它,如:(function(){})()

    (function(){
      ...
    } ());
    
    (function () {
      ...
    })();
    
    (function named(params) {
      ...
    })();
    
    (() => {
    
    });
    
    (function (global) {
      ...
    })(window);
    
    const utility = (function () {
      return {
        ...
      }
    })

    这些示例都是有效的IIFE。 倒数第二个救命表明我们可以将参数传递给IIFE函数。 最后一个示例表明,我们可以将IIFE的结果保存到变量中,以便稍后使用。

    IIFE的一个主要作用是避免与全局作用域内的其他变量命名冲突或污染全局命名空间,来个例子。

    <script data-original="https://cdnurl.com/somelibrary.js"></script>
    

    假设我们引入了一个omelibr.js的链接,它提供了一些我们在代码中使用的全局函数,但是这个库有两个方法我们没有使用:createGraphdrawGraph,因为这些方法都有bug。我们想实现自己的createGraphdrawGraph方法。

    解决此问题的一种方法是直接覆盖:

    <script data-original="https://cdnurl.com/somelibrary.js"></script>
    <script>
       function createGraph() {
          // createGraph logic here
       }
       function drawGraph() {
          // drawGraph logic here
       }
    </script>
    

    当我们使用这个解决方案时,我们覆盖了库提供给我们的那两个方法。

    另一种方式是我们自己改名称:

    <script data-original="https://cdnurl.com/somelibrary.js"></script>
    <script>
       function myCreateGraph() {
          // createGraph logic here
       }
       function myDrawGraph() {
          // drawGraph logic here
       }
    </script>
    

    当我们使用这个解决方案时,我们把那些函数调用更改为新的函数名。

    还有一种方法就是使用IIFE

    <script data-original="https://cdnurl.com/somelibrary.js"></script>
    <script>
       const graphUtility = (function () {
          function createGraph() {
             // createGraph logic here
          }
          function drawGraph() {
             // drawGraph logic here
          }
          return {
             createGraph,
             drawGraph
          }
       })
    </script>
    

    在此解决方案中,我们要声明了graphUtility 变量,用来保存IIFE执行的结果,该函数返回一个包含两个方法createGraphdrawGraph的对象。

    IIFE 还可以用来解决一个常见的面试题:

    var li = document.querySelectorAll('.list-group > li');
    for (var i = 0, len = li.length; i < len; i++) {
       li[i].addEventListener('click', function (e) {
          console.log(i);
       })
    

    假设我们有一个带有list-group类的ul元素,它有5li子元素。 当我们单击单个li元素时,打印对应的下标值。但在此外上述代码不起作用,这里每次点击 li 打印 i 的值都是5,这是由于闭包的原因。

    闭包只是函数记住其当前作用域,父函数作用域和全局作用域的变量引用的能力。 当我们在全局作用域内使用var关键字声明变量时,就创建全局变量i。 因此,当我们单击li元素时,它将打印5,因为这是稍后在回调函数中引用它时i的值。

    使用 IIFE 可以解决此问题:

    var li = document.querySelectorAll('.list-group > li');
    for (var i = 0, len = li.length; i < len; i++) {
       (function (currentIndex) {
          li[currentIndex].addEventListener('click', function (e) {
             console.log(currentIndex);
          })
       })(i);
    }
    

    该解决方案之所以行的通,是因为IIFE会为每次迭代创建一个新的作用域,我们捕获i的值并将其传递给currentIndex参数,因此调用IIFE时,每次迭代的currentIndex值都是不同的。

    27. Function.prototype.apply 方法的用途是什么?

    apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。

    const details = {
      message: 'Hello World!'
    };
    
    function getMessage(){
      return this.message;
    }
    
    getMessage.apply(details); // 'Hello World!'
    
    call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
    const person = {
      name: "Marko Polo"
    };
    
    function greeting(greetingMessage) {
      return `${greetingMessage} ${this.name}`;
    }
    
    greeting.apply(person, ['Hello']); // "Hello Marko Polo!"
    

    28. Function.prototype.call 方法的用途是什么?

    call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

    const details = {
      message: 'Hello World!'
    };
    
    function getMessage(){
      return this.message;
    }
    
    getMessage.call(details); // 'Hello World!'
    

    注意:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。

    const person = {
      name: "Marko Polo"
    };
    
    function greeting(greetingMessage) {
      return `${greetingMessage} ${this.name}`;
    }
    
    greeting.call(person, 'Hello'); // "Hello Marko Polo!"
    

    29. Function.prototype.apply 和 Function.prototype.call 之间有什么区别?

    apply()方法可以在使用一个指定的 this 值和一个参数数组(或类数组对象)的前提下调用某个函数或方法。call()方法类似于apply(),不同之处仅仅是call()接受的参数是参数列表。

    const obj1 = {
     result:0
    };
    
    const obj2 = {
     result:0
    };
    
    function reduceAdd(){
       let result = 0;
       for(let i = 0, len = arguments.length; i < len; i++){
         result += arguments[i];
       }
       this.result = result;
    }
    
    reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // 15
    reduceAdd.call(obj2, 1, 2, 3, 4, 5); // 15
    

    30. Function.prototype.bind 的用途是什么?

    bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

    import React from 'react';
    
    class MyComponent extends React.Component {
         constructor(props){
              super(props); 
              this.state = {
                 value : ""
              }  
              this.handleChange = this.handleChange.bind(this); 
              // 将 “handleChange” 方法绑定到 “MyComponent” 组件
         }
    
         handleChange(e){
           //do something amazing here
         }
    
         render(){
            return (
                  <>
                    <input type={this.props.type}
                            value={this.state.value}
                         onChange={this.handleChange}                      
                      />
                  </>
            )
         }
    }

    31. 什么是函数式编程? JavaScript 的哪些特性使其成为函数式语言的候选语言?

    函数式编程(通常缩写为FP)是通过编写纯函数,避免共享状态、可变数据、副作用 来构建软件的过程。数式编程是声明式 的而不是命令式 的,应用程序的状态是通过纯函数流动的。与面向对象编程形成对比,面向对象中应用程序的状态通常与对象中的方法共享和共处。

    函数式编程是一种编程范式 ,这意味着它是一种基于一些基本的定义原则(如上所列)思考软件构建的方式。当然,编程范示的其他示例也包括面向对象编程和过程编程。

    函数式的代码往往比命令式或面向对象的代码更简洁,更可预测,更容易测试 - 但如果不熟悉它以及与之相关的常见模式,函数式的代码也可能看起来更密集杂乱,并且 相关文献对新人来说是不好理解的。

    JavaScript支持闭包和高阶函数是函数式编程语言的特点。

    32. 什么是高阶函数?

    高阶函数只是将函数作为参数或返回值的函数。

    function higherOrderFunction(param,callback){
        return callback(param);
    }
    

    33. 为什么函数被称为一等公民?

    在JavaScript中,函数不仅拥有一切传统函数的使用方式(声明和调用),而且可以做到像简单值一样赋值(var func = function(){})、传参(function func(x,callback){callback();})、返回(function(){return function(){}}),这样的函数也称之为第一级函数(First-class Function)。不仅如此,JavaScript中的函数还充当了类的构造函数的作用,同时又是一个Function类的实例(instance)。这样的多重身份让JavaScript的函数变得非常重要。

    34. 手动实现 Array.prototype.map 方法

    map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。

    function map(arr, mapCallback) {
      // 首先,检查传递的参数是否正确。
      if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') { 
        return [];
      } else {
        let result = [];
        // 每次调用此函数时,我们都会创建一个 result 数组
        // 因为我们不想改变原始数组。
        for (let i = 0, len = arr.length; i < len; i++) {
          result.push(mapCallback(arr[i], i, arr)); 
          // 将 mapCallback 返回的结果 push 到 result 数组中
        }
        return result;
      }
    }
    

    35. 手动实现Array.prototype.filter方法

    filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

    function filter(arr, filterCallback) {
      // 首先,检查传递的参数是否正确。
      if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function') 
      {
        return [];
      } else {
        let result = [];
         // 每次调用此函数时,我们都会创建一个 result 数组
         // 因为我们不想改变原始数组。
        for (let i = 0, len = arr.length; i < len; i++) {
          // 检查 filterCallback 的返回值是否是真值
          if (filterCallback(arr[i], i, arr)) { 
          // 如果条件为真,则将数组元素 push 到 result 中
            result.push(arr[i]);
          }
        }
        return result; // return the result array
      }
    }
    

    36. 手动实现Array.prototype.reduce方法

    reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

    function reduce(arr, reduceCallback, initialValue) {
      // 首先,检查传递的参数是否正确。
      if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function') 
      {
        return [];
      } else {
        // 如果没有将initialValue传递给该函数,我们将使用第一个数组项作为initialValue
        let hasInitialValue = initialValue !== undefined;
        let value = hasInitialValue ? initialValue : arr[0];
       、
    
        // 如果有传递 initialValue,则索引从 1 开始,否则从 0 开始
        for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
          value = reduceCallback(value, arr[i], i, arr); 
        }
        return value;
      }
    }
    

    37. arguments 的对象是什么?

    arguments对象是函数中传递的参数值的集合。它是一个类似数组的对象,因为它有一个length属性,我们可以使用数组索引表示法arguments[1]来访问单个值,但它没有数组中的内置方法,如:forEachreducefiltermap

    我们可以使用Array.prototype.slicearguments对象转换成一个数组。

    function one() {
      return Array.prototype.slice.call(arguments);
    }
    

    注意:箭头函数中没有arguments对象。

    function one() {
      return arguments;
    }
    const two = function () {
      return arguments;
    }
    const three = function three() {
      return arguments;
    }
    
    const four = () => arguments;
    
    four(); // Throws an error  - arguments is not defined
    

    当我们调用函数four时,它会抛出一个ReferenceError: arguments is not defined error。使用rest语法,可以解决这个问题。

    const four = (...args) => args;
    

    这会自动将所有参数值放入数组中。

    38. 如何创建一个没有 prototype(原型)的对象?

    我们可以使用Object.create方法创建没有原型的对象。

    const o1 = {};
    console.log(o1.toString()); // [object Object]
    
    const o2 = Object.create(null);
    console.log(o2.toString());
    // throws an error o2.toString is not a function 
    

    39. 为什么在调用这个函数时,代码中的b会变成一个全局变量?

    function myFunc() {
      let a = b = 0;
    }
    
    myFunc();
    

    原因是赋值运算符是从右到左的求值的。这意味着当多个赋值运算符出现在一个表达式中时,它们是从右向左求值的。所以上面代码变成了这样:

    function myFunc() {
      let a = (b = 0);
    }
    
    myFunc();
    

    首先,表达式b = 0求值,在本例中b没有声明。因此,JS引擎在这个函数外创建了一个全局变量b,之后表达式b = 0的返回值为0,并赋给新的局部变量a

    我们可以通过在赋值之前先声明变量来解决这个问题。

    function myFunc() {
      let a,b;
      a = b = 0;
    }
    myFunc();
    
    
    

    40. ECMAScript 是什么?

    ECMAScript 是编写脚本语言的标准,这意味着JavaScript遵循ECMAScript标准中的规范变化,因为它是JavaScript的蓝图。

    ECMAScript 和 Javascript,本质上都跟一门语言有关,一个是语言本身的名字,一个是语言的约束条件
    只不过发明JavaScript的那个人(Netscape公司),把东西交给了ECMA(European Computer Manufacturers Association),这个人规定一下他的标准,因为当时有java语言了,又想强调这个东西是让ECMA这个人定的规则,所以就这样一个神奇的东西诞生了,这个东西的名称就叫做ECMAScript。

    javaScript = ECMAScript + DOM + BOM(自认为是一种广义的JavaScript)

    ECMAScript说什么JavaScript就得做什么!

    JavaScript(狭义的JavaScript)做什么都要问问ECMAScript我能不能这样干!如果不能我就错了!能我就是对的!

    ——突然感觉JavaScript好没有尊严,为啥要搞个人出来约束自己,

    那个人被创造出来也好委屈,自己被创造出来完全是因为要约束JavaScript。

    由于篇幅过长,我将此系列分成上下二篇,下篇我们在见。


    代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

    原文:https://dev.to/macmacky/70-ja...


    交流

    文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq44924588... 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

    clipboard.png

    查看原文

    赞 55 收藏 44 评论 3

    前端小智 发布了文章 · 11月2日

    28 个提升开发幸福度的 VsCode 插件

    点赞再看,微信搜索 【大迁世界】 关注这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

    双 1111 拼团低至 85 元,来一起拼团学习搭建自己的博客吧

    服务器如何搭建博客

    1. Quokka.js

    Quokka.js 是一个用于 JavaScript 和 TypeScript 的实时运行代码平台。这意味着它会实时运行你输入后的代码,并在编辑器中显示各种执行结果,建议亲自尝试一下。

    图片描述

    安装此扩展后,可以按Ctrl / Cmd(?)+ Shift + P显示编辑器的命令选项板,然后键入 Quokka 以查看可用命令的列表。选择并运行 “New JavaScript File”命令。你也可以按(?+ K + J)直接打开文件。在此文件中输入的任何内容都会立即执行。

    图片描述

    Quokka.js类似的扩展 –

    • Code Runner – 支持多种语言,如C,C ++,Java,JavaScript,PHP,Python,Perl,Perl 6等。
    • Runner

    2. 括号配对着色(Bracket Pair Colorizer) 和 彩虹缩进(Indent Rainbow)

    花括号和圆括号是许多编程语言不可分割的部分,在 JavaScript 等语言中,在一屏代码中花括号和园括号可能有多层嵌套,有些括号不太容易识别哪个对应哪个,然而却没有简单的方法来识别这些括号前后的对应关系。

    括号配对着色(Bracket Pair Colorizer)彩虹缩进(Indent Rainbow)。这是两个不同的扩展。然而,他们就像是一对情侣,可以完美的配合使用。这些扩展将为你的编辑器添加一系列颜色,并使代码块易于辨别,一旦你习惯了它们,如果 VSCode 没有它们就会让人觉得很平淡。

    不使用括号配对着色(Bracket Pair Colorizer) 和 彩虹缩进(Indent Rainbow)

    不使用括号配对着色(Bracket Pair Colorizer) 和 彩虹缩进(Indent Rainbow)

    使用括号配对着色(Bracket Pair Colorizer) 和 彩虹缩进(Indent Rainbow)后

    使用括号配对着色(Bracket Pair Colorizer) 和 彩虹缩进(Indent Rainbow)后

    3. snippets(代码片段)

    代码片段是编辑器中的短代码。因此,可以输入 imr 并按Tab 来展开该代码片段,而不是'import React from '。类似地,clg 变成了 console.log。

    各种各样的框架和类库都有很多代码片段:Javascript,React,Redux,Angular,Vue,Jest。 我个人认为 Javascript 代码片段非常有用,因为我主要使用 JS 。

    一些很好的代码片段扩展 –

    4. TODO高亮

    通常在进行编码时,你认为可能有更好的方法来执行相同的操作。这时你留下注释// TODO: 需要重构 或其他相关的东西。但是你很容易忘记了这个注释,并将你的代码推送到主版本库(master) 或者生产环境(production)。 但是你如果使用 Todo Highlighter(高亮),它会高亮的显示并让你容易看到这个注释。

    它以明亮的颜色突出代码中的 “TODO/FIXME” 或代码任何其他注释,以便始终清晰可见。另外还有一个很好的功能是 List Highlighted annotations ,它会在控制台中列出了所有 TODO。

    图片描述

    使用 Todo Highlighter(高亮)类似的扩展 –

    5. Import Cost

    扩展允许您查看导入模块的大小,它对 Webpack 中的 bundlers 有很大帮助,你可以查看是导入整个库还是只导入特定的实用程序。

    图片描述

    6. REST Client

    作为 web 开发人员,我们经常需要使用 REST api。为了检查url和检查响应,使用了 Postman 之类的工具。但是,既然编辑器可以轻松地完成相同的任务,为什么还要使用不同的应用程序呢? REST Client 它允许你发送 HTTP 请求并直接在 Visual Studio 代码中查看响应。

    图片描述

    7. 自动闭合标记(Auto Close Tag)和自动重命名标记(Auto Rename Tag)

    自从React的出现以及它在过去几年获得的吸引力以来,以 JSX 形式出现的类似 html 的语法现在非常流行。我们还必须使用 JavaScript 标签进行编码。任何web开发人员都会告诉你,输入标签是一件痛苦的事情。在大多数情况下,我们需要一个能够快速、轻松地生成标签及其子标签的工具。Emmet 是 VSCode 中一个很好的例子,然而,有时候,你只是想要一些简单明了的东西。例如自动更新标签,它在你输入开始标签时自动生成结束标签。当你更改相同的标签时,关闭标记会自动更改,这两个扩展就是这样做的。

    它还适用于JSX和许多其他语言,如XML,PHP,Vue,JavaScript,TypeScript,TSX。

    在这里获取这两个扩展 – 自动闭合标记(Auto Close Tag)自动重命名标记(Auto Rename Tag)

    Auto Rename Tag

    Auto Close Tag

    类似的扩展 –

    8. GitLens

    正如其作者所说,GitLens 增强了 Visual Studio Code 中内置的 Git 功能,它包含了许多强大的功能,例如通过跟踪代码显示的代码作者,提交搜索,历史记录和GitLens资源管理器。你可以在此处阅读这些功能的完整说明。

    图片描述

    类似的扩展 –

    9. Git项目管理器(Git Project Manager,GPM)

    Git项目管理器(Git Project Manager,GPM)允许你直接从 VSCode 窗口打开一个针对Git存储库的新窗口。 基本上,你可以打开另一个存储库而无需离开VSCode。

    安装此扩展后,您必须将 gitProjectManager.baseProjectsFolders 设置为包含 repos 的URL列表。例如:

    {
        "gitProjectManager.baseProjectsFolders": [
            "/home/user/nodeProjects",
            "/home/user/personal/pocs"
        ]
    } 
    

    图片描述

    类似的扩展 –

    Project Manager – 我没有亲自使用它,但它有百万+安装。所以建议你一定要看一下。

    10. Indenticator(缩进指示器)

    在视觉上突出显示当前的缩进个数,因此,你可以轻松区分在不同级别缩进的各种代码块。

    图片描述

    11. VSCode Icons

    使您的编辑更具吸引力的图标!

    图片描述

    类似的扩展 –

    12. Dracula (Theme)

    Dracula 是我最喜欢的主题。

    图片描述

    我们可以使用快捷键来快速的选择更换主题;

    首先:按下 Ctrl + k

    然后再按下:Ctrl + t

    13. 其它推荐

    • Fira Code?—?带编程连体字的等宽字体。 愚人码头注:clone 项目后,找到 ttf 文件夹,然后安装该文件夹中的字体文件。重新启动 VSCode ,选择TOOLS -> Options -> Fonts and Colors ,选择 Fira Code 即可。
    • Live Server?—?一个具有静态和动态页面的实时重新加载功能的本地开发服务器。
    • EditorConfig for VS Code – 此插件尝试使用.editorconfig文件中的设置覆盖用户/工作区设置,不需要其他或特定于 vscode 的文件。与任何EditorConfig插件一样,如果未指定root = true,EditorConfig将继续在项目外部查找.editorconfig文件。
    • Prettier for VSCode?—?一个代码格式化工具。
    • Bookmarks – 它可以帮助您在代码中导航,轻松快速地在重要位置之间移动。不再需要搜索代码,它还支持一组选择命令,允许您选择书签线和书签线之间的区域,它对日志文件分析非常有用。
    • Path Intellisense?—?Visual Studio Code插件,可自动填充文件名。
    • Version Lens?—?在Visual Studio代码编辑器中显示npm,jspm,bower,dub和dotnet核心的软件包版本信息。

    14. Material Theme & Icons

    这是 VS Code 主题中的重要角色。 作者认为重要的主题是在编辑器中用笔和纸书写最接近的东西(特别是在使用无对比变体主题时)。 从集成的工具到文本编辑器,你的编辑器看起来几乎是平的和无缝的。

    想象一个史诗般的主题加上史诗般的图标。 Material Theme Icons 是替换默认 VSCode 图标的绝佳选择。设计的大型图标目录与主题融为一体,使其更加美观,这有助于你在资源管理器中轻松找到你的文件。

    clipboard.png

    15. 具有居中布局的禅模式或者勿扰模式 (Zen Mode)

    为了让广大苦逼码农能够在 coding/docing 时有清晰的思路,代表最广大码农利益的 VSCode 也加入了“禅模式”。该模式可以在你在页面编辑文件时启用,效果是全屏化你的编辑框,然后带有若隐若现的云雾效果。

    打开方式:文件 > 首选项 > 设置 > 用户设置 > 工作台 > 禅模式

    clipboard.png

    16. 具有连字的字体

    文字的风格使阅读变得简单方便,你可以使用好看连字的字体使编辑器看起来更友好。 这里是支持连字的6种最佳字体 (根据www.slant.co)

    你可以尝试 Fira Code,它非常棒而且是开源的。 以下是引入 Fira Code 后在 VSCode 辊更改该字体的方法。

    "editor.fontFamily": "Fira Code",
    "editor.fontLigatures": true

    具体使用方法可以参考:

    vscode中修改字体,使用 Fira Code

    提高visual studio使用逼格的连体字(Fira code)以及多行编辑(MixEdit)

    17. 彩虹缩进 (indent-rainbow)

    缩进风格,这个扩展为文本前面的缩进着色,在每个步骤中交替使用四种不同的颜色。

    当然如果需要自定义自己喜欢的颜色,请将以下代码段复制并粘贴到 settings.json

    "indentRainbow.colors": [
    "rgba(16,16,16,0.1)",
    "rgba(16,16,16,0.2)",
    "rgba(16,16,16,0.3)",
    "rgba(16,16,16,0.4)",
    "rgba(16,16,16,0.5)",
    "rgba(16,16,16,0.6)",
    "rgba(16,16,16,0.7)",
    "rgba(16,16,16,0.8)",
    "rgba(16,16,16,0.9)",
    "rgba(16,16,16,1.0)"
    ],
    

    18. 自定义标题栏

    这是一个很棒的视觉调整,改变了不同项目的标题栏颜色,以便轻松识别它们。 如果你处理可能具有相同代码或文件名的应用程序(例如react-native 应用程序和 React Web应用程序),这非常有用

    设置方式:打开方式:文件 > 首选项 > 设置 > 工作区设置

    19. Tag Wrapping

    如果你不认识 Emmet,那么你可能是一个喜欢打字的人。Emmet 允许你写入缩写代码并返回的相应标记,目前 VSCode 已经内置,所以不用配置了。

    如果你想了解更多的 Emmet 的简写,可以查看 Emmet Cheatsheet

    20. 内外平衡

    这条建议来自 https://vscodecandothat.com/,作者非常推荐它。

    你可以使用 balance inwardbalance outward 的 Emmet 命令在 VS 代码中选择整个标记。 将这些命令绑定到键盘快捷键是有帮助的,例如 Ctrl + Shift + 向上箭头用于平衡向外,而 Ctrl + Shift +向下箭头 用于平衡向内。

    21. Turbo Console.log()

    没有人喜欢输入非常长的语句,比如 console.log()。这真的很烦人,尤其是当你只想快速输出一些东西,查看它的值,然后继续编码的时候。如果我告诉你,你可以像 Lucky Luke一样快速地控制台记录任何东西呢?

    这是通过名为 Turbo Console Log 的扩展来完成的。它支持对下面一行中的任何变量进行日志记录,并在代码结构之后自动添加前缀。你还可以 取消注释/注释 alt+shift+u / alt+shift+c 为所有由这个扩展添加的 console.log()

    此外,你也可以通过 alt+shift+d 删除所有:

    22. Live server

    这是一个非常棒的扩展,可以帮助你启动一个本地开发服务器,为静态和动态页面提供实时重新加载功能,它对 HTTPS、CORS、自定义本地主机地址和端口等主要特性提供了强大的支持。

    如果与 VSCode LiveShare 一起使用,它甚至可以让你共享本地主机。

    23. 使用多个游标 复制/粘贴

    Mac: opt+cmd+up or opt+cmd+down
    
    Windows: ctrl+alt+up or ctrl+alt+down
    
    Linux: alt+shift+up or alt+shift+down
    

    24. Breadcrumbs(面包屑)

    编辑器的内容上方现在有一个被称为 Breadcrumbs 的导航栏,它显示你的当前位置,并允许在符号和文件之间快速导航。要使用该功能,可使用 View > Toggle Breadcrumbs 命令或通过 breadcrumbs.enabled 设置启用。要与其交互,请使用 Focus Breadcrumbs 命令或按 Ctrl + Shift +

    25. Code CLI

    代码有一个强大的命令行界面,允许你控制如何启动编辑器。你可以通过命令行选项打开文件、安装扩展名、更改显示语言和输出诊断信息。

    想象一下,你通过 git clone <repo-url> 克隆一个远程库,你想要替换你正在使用的当前 VS Code实例。 通过命令 code . -r 将在不必离开 CLI 界面的情况下完成这一操作 (在此处了解更多信息)。

    26. Polacode


    你经常会看到带有定制字体和主题的代码截屏,如下所示。这是在VS代码与 x 扩展

    我知道 Carbon 也是一种更好,更可定制的替代品。 但是,Polacode 允许你保留在代码编辑器中并使用你可能已购买的任何专用字体,这些字体在 Carbon 中无法使用。

    27. Quokka (JS/TS ScratchPad)

    Quokka 是J avaScript 和 TypeScript 的快速原型开发平台。在你输入代码时,它将立即运行你的代码,并在代码编辑器中显示各种执行结果。

    Quokka 的一个很棒的扩展插件,当你准备技术面试时,你可以输出每个步骤,而不必在调试器中设置断点。它还可以帮助您在实际使用之前研究库的函数,如 Lodash 或 MomentJS,它甚至可以用于异步调用。

    28. WakaTime

    如果你想记录每天编程所花的时间,WakaTime 是一个扩展,它可以帮助记录和存储有关编程活动的指标和分析。


    交流

    文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq44924588... 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

    clipboard.png

    查看原文

    赞 28 收藏 19 评论 2

    前端小智 发布了文章 · 10月26日

    通过编写简易虚拟DOM,来学习虚拟DOM 的原理

    作者:deathmood
    译者:前端小智
    来源:medium

    1024程序员节,160就能买到400的书,红宝书 5 折

    为了保证的可读性,本文采用意译而非直译。

    要构建自己的虚拟DOM,需要知道两件事。你甚至不需要深入 React 的源代码或者深入任何其他虚拟DOM实现的源代码,因为它们是如此庞大和复杂——但实际上,虚拟DOM的主要部分只需不到50行代码。

    有两个概念:

    • Virtual DOM 是真实DOM的映射
    • 当虚拟 DOM 树中的某些节点改变时,会得到一个新的虚拟树。算法对这两棵树(新树和旧树)进行比较,找出差异,然后只需要在真实的 DOM 上做出相应的改变。

    用JS对象模拟DOM树

    首先,我们需要以某种方式将 DOM 树存储在内存中。可以使用普通的 JS 对象来做。假设我们有这样一棵树:

    <ul class=”list”>
      <li>item 1</li>
      <li>item 2</li>
    </ul>
    

    看起来很简单,对吧? 如何用JS对象来表示呢?

    { type: ‘ul’, props: { ‘class’: ‘list’ }, children: [
      { type: ‘li’, props: {}, children: [‘item 1’] },
      { type: ‘li’, props: {}, children: [‘item 2’] }
    ] }
    
    

    这里有两件事需要注意:

    • 用如下对象表示DOM元素
    { type: ‘…’, props: { … }, children: [ … ] }
    
    
    • 用普通 JS 字符串表示 DOM 文本节点

    但是用这种方式表示内容很多的 Dom 树是相当困难的。这里来写一个辅助函数,这样更容易理解:

    function h(type, props, …children) {
      return { type, props, children };
    }
    

    用这个方法重新整理一开始代码:

    h(‘ul’, { ‘class’: ‘list’ },
      h(‘li’, {}, ‘item 1’),
      h(‘li’, {}, ‘item 2’),
    );
    
    

    这样看起来简洁多了,还可以更进一步。这里使用 JSX,如下:

    <ul className=”list”>
      <li>item 1</li>
      <li>item 2</li>
    </ul>
    

    编译成:

    React.createElement(‘ul’, { className: ‘list’ },
      React.createElement(‘li’, {}, ‘item 1’),
      React.createElement(‘li’, {}, ‘item 2’),
    );
    
    

    是不是看起来有点熟悉?如果能够用我们刚定义的 h(...) 函数代替 React.createElement(…),那么我们也能使用JSX 语法。其实,只需要在源文件头部加上这么一句注释:

    /** @jsx h */
    <ul className=”list”>
      <li>item 1</li>
      <li>item 2</li>
    </ul>
    

    它实际上告诉 Babel ' 嘿,小老弟帮我编译 JSX 语法,用 h(...) 函数代替 React.createElement(…),然后 Babel 就开始编译。'

    综上所述,我们将DOM写成这样:

    /** @jsx h */
    const a = (
      <ul className=”list”>
        <li>item 1</li>
        <li>item 2</li>
      </ul>
    );
    

    Babel 会帮我们编译成这样的代码:

    const a = (
      h(‘ul’, { className: ‘list’ },
        h(‘li’, {}, ‘item 1’),
        h(‘li’, {}, ‘item 2’),
      );
    );
    
    

    当函数 “h” 执行时,它将返回普通JS对象-即我们的虚拟DOM:

    const a = (
      { type: ‘ul’, props: { className: ‘list’ }, children: [
        { type: ‘li’, props: {}, children: [‘item 1’] },
        { type: ‘li’, props: {}, children: [‘item 2’] }
      ] }
    );
    

    从Virtual DOM 映射到真实 DOM

    好了,现在我们有了 DOM 树,用普通的 JS 对象表示,还有我们自己的结构。这很酷,但我们需要从它创建一个真正的DOM。

    首先让我们做一些假设并声明一些术语:

    • 使用以' $ '开头的变量表示真正的DOM节点(元素,文本节点),因此 $parent 将会是一个真实的DOM元素
    • 虚拟 DOM 使用名为 node 的变量表示

    * 就像在 React 中一样,只能有一个根节点——所有其他节点都在其中

    那么,来编写一个函数 createElement(…),它将获取一个虚拟 DOM 节点并返回一个真实的 DOM 节点。这里先不考虑 propschildren 属性:

    function createElement(node) {
      if (typeof node === ‘string’) {
        return document.createTextNode(node);
      }
      return document.createElement(node.type);
    }
    
    

    上述方法我也可以创建有两种节点分别是文本节点和 Dom 元素节点,它们是类型为的 JS 对象:

    { type: ‘…’, props: { … }, children: [ … ] }
    
    

    因此,可以在函数 createElement 传入虚拟文本节点和虚拟元素节点——这是可行的。

    现在让我们考虑子节点——它们中的每一个都是文本节点或元素。所以它们也可以用 createElement(…) 函数创建。是的,这就像递归一样,所以我们可以为每个元素的子元素调用 createElement(…),然后使用 appendChild() 添加到我们的元素中:

    function createElement(node) {
      if (typeof node === ‘string’) {
        return document.createTextNode(node);
      }
      const $el = document.createElement(node.type);
      node.children
        .map(createElement)
        .forEach($el.appendChild.bind($el));
      return $el;
    }
    

    哇,看起来不错。先把节点 props 属性放到一边。待会再谈。我们不需要它们来理解虚拟DOM的基本概念,因为它们会增加复杂性。

    完整代码如下:

    /** @jsx h */
    
    function h(type, props, ...children) {
      return { type, props, children };
    }
    
    function createElement(node) {
      if (typeof node === 'string') {
        return document.createTextNode(node);
      }
      const $el = document.createElement(node.type);
      node.children
        .map(createElement)
        .forEach($el.appendChild.bind($el));
      return $el;
    }
    
    const a = (
      <ul class="list">
        <li>item 1</li>
        <li>item 2</li>
      </ul>
    );
    
    const $root = document.getElementById('root');
    $root.appendChild(createElement(a));
    

    比较两棵虚拟DOM树的差异

    现在我们可以将虚拟 DOM 转换为真实的 DOM,这就需要考虑比较两棵 DOM 树的差异。基本的,我们需要一个算法来比较新的树和旧的树,它能够让我们知道什么地方改变了,然后相应的去改变真实的 DOM。

    怎么比较 DOM 树?需要处理下面的情况:

    • 添加新节点,使用 appendChild(…) 方法添加节点

    图片描述

    • 移除老节点,使用 removeChild(…) 方法移除老的节点

    图片描述

    • 节点的替换,使用 replaceChild(…) 方法

    图片描述

    如果节点相同的——就需要需要深度比较子节点

    图片描述

    编写一个名为 updateElement(…) 的函数,它接受三个参数—— $parentnewNodeoldNode,其中 $parent 是虚拟节点的一个实际 DOM 元素的父元素。现在来看看如何处理上面描述的所有情况。

    添加新节点

    function updateElement($parent, newNode, oldNode) {
      if (!oldNode) {
        $parent.appendChild(
          createElement(newNode)
        );
      }
    }
    
    

    移除老节点

    这里遇到了一个问题——如果在新虚拟树的当前位置没有节点——我们应该从实际的 DOM 中删除它—— 这要如何做呢?

    如果我们已知父元素(通过参数传递),我们就能调用 $parent.removeChild(…) 方法把变化映射到真实的 DOM 上。但前提是我们得知道我们的节点在父元素上的索引,我们才能通过 $parent.childNodes[index] 得到该节点的引用。

    好的,让我们假设这个索引将被传递给 updateElement 函数(它确实会被传递——稍后将看到)。代码如下:

    function updateElement($parent, newNode, oldNode, index = 0) {
      if (!oldNode) {
        $parent.appendChild(
          createElement(newNode)
        );
      } else if (!newNode) {
        $parent.removeChild(
          $parent.childNodes[index]
        );
      }
    }
    
    

    节点的替换

    首先,需要编写一个函数来比较两个节点(旧节点和新节点),并告诉节点是否真的发生了变化。还有需要考虑这个节点可以是元素或是文本节点:

    function changed(node1, node2) {
      return typeof node1 !== typeof node2 ||
             typeof node1 === ‘string’ && node1 !== node2 ||
             node1.type !== node2.type
    }
    
    

    现在,当前的节点有了 index 属性,就可以很简单的用新节点替换它:

    function updateElement($parent, newNode, oldNode, index = 0) {
      if (!oldNode) {
        $parent.appendChild(
          createElement(newNode)
        );
      } else if (!newNode) {
        $parent.removeChild(
          $parent.childNodes[index]
        );
      } else if (changed(newNode, oldNode)) {
        $parent.replaceChild(
          createElement(newNode),
          $parent.childNodes[index]
        );
      }
    }
    

    比较子节点

    最后,但并非最不重要的是——我们应该遍历这两个节点的每一个子节点并比较它们——实际上为每个节点调用updateElement(…)方法,同样需要用到递归。

    • 当节点是 DOM 元素时我们才需要比较( 文本节点没有子节点 )
    • 我们需要传递当前的节点的引用作为父节点
    • 我们应该一个一个的比较所有的子节点,即使它是 undefined 也没有关系,我们的函数也会正确处理它。
    • 最后是 index,它是子数组中子节点的 index
    function updateElement($parent, newNode, oldNode, index = 0) {
      if (!oldNode) {
        $parent.appendChild(
          createElement(newNode)
        );
      } else if (!newNode) {
        $parent.removeChild(
          $parent.childNodes[index]
        );
      } else if (changed(newNode, oldNode)) {
        $parent.replaceChild(
          createElement(newNode),
          $parent.childNodes[index]
        );
      } else if (newNode.type) {
        const newLength = newNode.children.length;
        const oldLength = oldNode.children.length;
        for (let i = 0; i < newLength || i < oldLength; i++) {
          updateElement(
            $parent.childNodes[index],
            newNode.children[i],
            oldNode.children[i],
            i
          );
        }
      }
    }
    
    

    完整的代码

    Babel+JSX
    /* @jsx h /

    function h(type, props, ...children) {
      return { type, props, children };
    }
    
    function createElement(node) {
      if (typeof node === 'string') {
        return document.createTextNode(node);
      }
      const $el = document.createElement(node.type);
      node.children
        .map(createElement)
        .forEach($el.appendChild.bind($el));
      return $el;
    }
    
    function changed(node1, node2) {
      return typeof node1 !== typeof node2 ||
             typeof node1 === 'string' && node1 !== node2 ||
             node1.type !== node2.type
    }
    
    function updateElement($parent, newNode, oldNode, index = 0) {
      if (!oldNode) {
        $parent.appendChild(
          createElement(newNode)
        );
      } else if (!newNode) {
        $parent.removeChild(
          $parent.childNodes[index]
        );
      } else if (changed(newNode, oldNode)) {
        $parent.replaceChild(
          createElement(newNode),
          $parent.childNodes[index]
        );
      } else if (newNode.type) {
        const newLength = newNode.children.length;
        const oldLength = oldNode.children.length;
        for (let i = 0; i < newLength || i < oldLength; i++) {
          updateElement(
            $parent.childNodes[index],
            newNode.children[i],
            oldNode.children[i],
            i
          );
        }
      }
    }
    
    // ---------------------------------------------------------------------
    
    const a = (
      <ul>
        <li>item 1</li>
        <li>item 2</li>
      </ul>
    );
    
    const b = (
      <ul>
        <li>item 1</li>
        <li>hello!</li>
      </ul>
    );
    
    const $root = document.getElementById('root');
    const $reload = document.getElementById('reload');
    
    updateElement($root, a);
    $reload.addEventListener('click', () => {
      updateElement($root, b, a);
    });
    
    

    HTML

    <button id="reload">RELOAD</button>
    <div id="root"></div>
    

    CSS

    #root {
      border: 1px solid black;
      padding: 10px;
      margin: 30px 0 0 0;
    }
    
    

    打开开发者工具,并观察当按下“Reload”按钮时应用的更改。

    图片描述

    总结

    现在我们已经编写了虚拟 DOM 实现及了解它的工作原理。作者希望,在阅读了本文之后,对理解虚拟 DOM 如何工作的基本概念以及在幕后如何进行响应有一定的了解。

    然而,这里有一些东西没有突出显示(将在以后的文章中介绍它们):

    • 设置元素属性(props)并进行 diffing/updating
    • 处理事件——向元素中添加事件监听
    • 让虚拟 DOM 与组件一起工作,比如React
    • 获取对实际DOM节点的引用
    • 使用带有库的虚拟 DOM,这些库可以直接改变真实的 DOM,比如 jQuery 及其插件

    原文:
    https://medium.com/@deathmood...

    代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug


    交流

    文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

    clipboard.png

    查看原文

    赞 17 收藏 11 评论 1

    认证与成就

    • 认证信息 前端开发工程师
    • 获得 17670 次点赞
    • 获得 106 枚徽章 获得 2 枚金徽章, 获得 26 枚银徽章, 获得 78 枚铜徽章

    擅长技能
    编辑

    开源项目 & 著作
    编辑

    (??? )
    暂时没有

    注册于 2016-11-12
    个人主页被 70.6k 人浏览

    bt365体育投注