xiaopengji

xiaopengji 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

JavaScript 是一门弱类型的动态脚本语言,支持多种编程范式,包括面向对象和函数式编程,被广泛用于 web 开发。

一般来说,完整的JavaScript包括以下几个部分:

ECMAScript,描述了该语言的语法和基本对象
文档对象模型(DOM),描述处理网页内容的方法和接口
浏览器对象模型(BOM),描述与浏览器进行交互的方法和接口
它的基本特点如下:

是一种解释性脚本语言(代码不进行预编译)。
主要用来向HTML页面添加交互行为。
可以直接嵌入HTML页面,但写成单独的js文件有利于结构和行为的分离。
JavaScript常用来完成以下任务:

嵌入动态文本于HTML页面
对浏览器事件作出响应
读写HTML元素
在数据被提交到服务器之前验证数据
检测访客的浏览器信息

个人动态

xiaopengji 收藏了文章 · 2019-11-09

常用meta整理

< meta > 元素

概要

标签提供关于HTML文档的元数据。元数据不会显示在页面上,但是对于机器是可读的。它可用于浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他 web 服务。 —— W3School

必要属性

属性描述
contentsome text定义与http-equiv或name属性相关的元信息

可选属性

属性描述
http-equivcontent-type / expire / refresh / set-cookie把content属性关联到HTTP头部。
nameauthor / description / keywords / generator / revised / others把 content 属性关联到一个名称。
contentsome text定义用于翻译 content 属性值的格式。

SEO优化

参考文档

  • 页面关键词,每个网页应具有描述该网页内容的一组唯一的关键字。
    使用人们可能会搜索,并准确描述网页上所提供信息的描述性和代表性关键字及短语。标记内容太短,则搜索引擎可能不会认为这些内容相关。另外标记不应超过 874 个字符。
<meta name="keywords" content="your tags" />
  • 页面描述,每个网页都应有一个不超过 150 个字符且能准确反映网页内容的描述标签。
<meta name="description" content="150 words" />
  • 搜索引擎索引方式,robotterms是一组使用逗号(,)分割的值,通常有如下几种取值:none,noindex,nofollow,all,index和follow。确保正确使用nofollow和noindex属性值。
<meta name="robots" content="index,follow" />
<!--
    all:文件将被检索,且页面上的链接可以被查询;
    none:文件将不被检索,且页面上的链接不可以被查询;
    index:文件将被检索;
    follow:页面上的链接可以被查询;
    noindex:文件将不被检索;
    nofollow:页面上的链接不可以被查询。
 -->
  • 页面重定向和刷新:content内的数字代表时间(秒),既多少时间后刷新。如果加url,则会重定向到指定网页(搜索引擎能够自动检测,也很容易被引擎视作误导而受到惩罚)。
<meta http-equiv="refresh" content="0;url=" />
  • 其他
<meta name="author" content="author name" /> <!-- 定义网页作者 -->
<meta name="google" content="index,follow" />
<meta name="googlebot" content="index,follow" />
<meta name="verify" content="index,follow" />

移动设备

  • viewport:能优化移动浏览器的显示。如果不是响应式网站,不要使用initial-scale或者禁用缩放。
    大部分4.7-5寸设备的viewport宽设为360px;5.5寸设备设为400px;iphone6设为375px;ipone6 plus设为414px。
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no"/>
<!-- `width=device-width` 会导致 iPhone 5 添加到主屏后以 WebApp 全屏模式打开页面时出现黑边  -->
  1. width:宽度(数值 / device-width)(范围从200 到10,000,默认为980 像素)
  2. height:高度(数值 / device-height)(范围从223 到10,000)
  3. initial-scale:初始的缩放比例 (范围从>0 到10)
  4. minimum-scale:允许用户缩放到的最小比例
  5. maximum-scale:允许用户缩放到的最大比例
  6. user-scalable:用户是否可以手动缩 (no,yes)
  7. minimal-ui:可以在页面加载时最小化上下状态栏。(已弃用)

注意,很多人使用initial-scale=1到非响应式网站上,这会让网站以100%宽度渲染,用户需要手动移动页面或者缩放。如果和initial-scale=1同时使用user-scalable=no或maximum-scale=1,则用户将不能放大/缩小网页来看到全部的内容。

  • WebApp全屏模式:伪装app,离线应用。
<meta name="apple-mobile-web-app-capable" content="yes" /> <!-- 启用 WebApp 全屏模式 -->
  • 隐藏状态栏/设置状态栏颜色:只有在开启WebApp全屏模式时才生效。content的值为default | black | black-translucent 。
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
  • 添加到主屏后的标题
<meta name="apple-mobile-web-app-title" content="标题">

图片描述图片描述图片描述

  • 忽略数字自动识别为电话号码
<meta content="telephone=no" name="format-detection" /> 
  • 忽略识别邮箱
<meta content="email=no" name="format-detection" />
  • 添加智能 App 广告条 Smart App Banner:告诉浏览器这个网站对应的app,并在页面上显示下载banner(如下图)。参考文档
<meta name="apple-itunes-app" content="app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL"> 

![Alt text](./IMG_3701.jpg)

<!-- 针对手持设备优化,主要是针对一些老的不识别viewport的浏览器,比如黑莓 -->
<meta name="HandheldFriendly" content="true">
<!-- 微软的老式浏览器 -->
<meta name="MobileOptimized" content="320">
<!-- uc强制竖屏 -->
<meta name="screen-orientation" content="portrait">
<!-- QQ强制竖屏 -->
<meta name="x5-orientation" content="portrait">
<!-- UC强制全屏 -->
<meta name="full-screen" content="yes">
<!-- QQ强制全屏 -->
<meta name="x5-fullscreen" content="true">
<!-- UC应用模式 -->
<meta name="browsermode" content="application">
<!-- QQ应用模式 -->
<meta name="x5-page-mode" content="app">
<!-- windows phone 点击无高光 -->
<meta name="msapplication-tap-highlight" content="no">

网页相关

  • 申明编码
<meta charset='utf-8' />
  • 优先使用 IE 最新版本和 Chrome
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<!-- 关于X-UA-Compatible -->
<meta http-equiv="X-UA-Compatible" content="IE=6" ><!-- 使用IE6 -->
<meta http-equiv="X-UA-Compatible" content="IE=7" ><!-- 使用IE7 -->
<meta http-equiv="X-UA-Compatible" content="IE=8" ><!-- 使用IE8 -->
  • 浏览器内核控制:国内浏览器很多都是双内核(webkit和Trident),webkit内核高速浏览,IE内核兼容网页和旧版网站。而添加meta标签的网站可以控制浏览器选择何种内核渲染。参考文档
 <meta name="renderer" content="webkit|ie-comp|ie-stand">

国内双核浏览器默认内核模式如下:
1. 搜狗高速浏览器、QQ浏览器:IE内核(兼容模式)
2. 360极速浏览器、遨游浏览器:Webkit内核(极速模式)

  • 禁止浏览器从本地计算机的缓存中访问页面内容:这样设定,访问者将无法脱机浏览。
<meta http-equiv="Pragma" content="no-cache">
  • Windows 8
<meta name="msapplication-TileColor" content="#000"/> <!-- Windows 8 磁贴颜色 -->
<meta name="msapplication-TileImage" content="icon.png"/> <!-- Windows 8 磁贴图标 -->
  • 站点适配:主要用于PC-手机页的对应关系。
<meta name="mobile-agent"content="format=[wml|xhtml|html5]; url=url">
<!--
[wml|xhtml|html5]根据手机页的协议语言,选择其中一种;
url="url" 后者代表当前PC页所对应的手机页URL,两者必须是一一对应关系。
 -->
  • 转码申明:用百度打开网页可能会对其进行转码(比如贴广告),避免转码可添加如下meta
<meta http-equiv="Cache-Control" content="no-siteapp" />

其他参考文档

COMPLETE LIST OF HTML META TAGS
W3C META TAGS
METATAGES in HTML5
MDN META TAGS

查看原文

xiaopengji 关注了专栏 · 2019-11-09

Ciaocc

关注 22

xiaopengji 收藏了文章 · 2019-08-05

基于vue-cli3.0的项目工程重新构建空白版,拿来即用

写在前面

使用vue框架开发时,很多人会选择vue官方提供的cli脚手架,最新的cli已经更新到3.0完全无配置,只需下载就能方便的使用vuecli构建的项目工程,但基础的cli并不能满足正常的项目开发,在开发中我们需要根据自己的习惯和业务功能而添加些基础功能。

我在vuec-li3的基础之上重新做了些构建,分为pc,移动两版,主要区别是移动版添加了分辨率适配,本项目为空白版,拿来即用,省却每次手动构建项目工程!!

GitHub查看项目内容 https://github.com/wwenj/vueC...

clipboard.png

PC

  • pc版安装了elementUI做统一UI展示
  • 安装scss写样式

移动

基础配置

  • svg组件:

    文件在src/icons/中,组件在component公共组件中封装用于svg展示组件,用做小icon的展示,svg保存在icons/下,使用时组件中调用<svg-icon icon-class="iconName"/>即可

  • css样式

    src/style/中,全局公共样式,初始化样式。

  • 路由与vuex

    对vue router和vuex做了相同的模块化处理,在router/store/中统一写路由和vuex相关信息,可按功能分模块引入

  • 请求拦截与封装

    1. 使用axios做请求,在src/request/http.js中做了封装,用axios拦截器对请求和响应做统一拦截,处理每次请求的token信息或者响应状态码
    2. 对axios请求进行简单封装get,post请求,统一传参,响应后传回一个promise对象,在src/request/api.js中将每个接口封装,对外暴露请求改接口的方法名,返回一个promise,组件中直接调用api.js中的方法名即可
  • 工具类

    src/utils/中,对常用枚举值、全局组件注册、常用类功能模块做模块化封装

  • vue.config.js

    vuecli3隐藏所有配置,添加新配置项目中新建vue.config.js文件,本项目在其中加了一些常用配置,文件夹的别名,代理,打包路径等

  • Eslint

    eslint使用默认规则,在vscode编辑器中配合eslint插件的保存自动修复功能,每次保存后会按照eslit规则自动修复

  • 目录结构

    src/views文件下写业务模块,src/components写公共基础组件

写在结尾

本项目只是根据自己开发习惯整理的一套基于vuecli3的重新构建项目,为了让开发更方便,清晰,每次写新项目,个人项目,写demo下载即用,比较方便,如有问题欢迎指教

GitHub查看项目内容 https://github.com/wwenj/vueC...

查看原文

xiaopengji 收藏了文章 · 2019-07-15

JavaScript 进阶问题列表

作者:Lydia Hallie

翻译:疯狂的技术宅

原文:https://github.com/lydiahalli...

我在我的 Instagram 上每天都会发布 JavaScript 的选择题,并且同时也会在这个仓库中发布。

从基础到进阶,测试你有多了解 JavaScript,刷新你的知识,或者帮助你的 coding 面试!
我每周都会在这个仓库下更新新的问题。

答案在问题的下方。祝你好运


1. 输出是什么?

function sayHi() {
  console.log(name)
  console.log(age)
  var name = 'Lydia'
  let age = 21
}

sayHi()
  • A: Lydiaundefined
  • B: LydiaReferenceError
  • C: ReferenceError21
  • D: undefinedReferenceError

答案: D

在函数内部,我们首先通过 var 关键字声明了 name 变量。这意味着变量被提升了(内存空间在创建阶段就被设置好了),直到程序运行到定义变量位置之前默认值都是 undefined。因为当我们打印 name 变量时还没有执行到定义变量的位置,因此变量的值保持为 undefined

通过 letconst 关键字声明的变量也会提升,但是和 var 不同,它们不会被初始化。在我们声明(初始化)之前是不能访问它们的。这个行为被称之为暂时性死区。当我们试图在声明之前访问它们时,JavaScript 将会抛出一个 ReferenceError 错误。


2. 输出是什么?

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1)
}

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1)
}
  • A: 0 1 20 1 2
  • B: 0 1 23 3 3
  • C: 3 3 30 1 2

答案: C

由于 JavaScript 的事件循环,setTimeout 回调会在遍历结束后才执行。因为在第一个遍历中遍历 i 是通过 var 关键字声明的,所以这个值是全局作用域下的。在遍历过程中,我们通过一元操作符 ++ 来每次递增 i 的值。当 setTimeout 回调执行的时候,i 的值等于 3。

在第二个遍历中,遍历 i 是通过 let 关键字声明的:通过 letconst 关键字声明的变量是拥有块级作用域(指的是任何在 {} 中的内容)。在每次的遍历过程中,i 都有一个新值,并且每个值都在循环内的作用域中。


3. 输出是什么?

const shape = {
  radius: 10,
  diameter() {
    return this.radius * 2
  },
  perimeter: () => 2 * Math.PI * this.radius
}

shape.diameter()
shape.perimeter()
  • A: 2062.83185307179586
  • B: 20NaN
  • C: 2063
  • D: NaN63

答案: B

注意 diameter 的值是一个常规函数,但是 perimeter 的值是一个箭头函数。

对于箭头函数,this 关键字指向的是它当前周围作用域(简单来说是包含箭头函数的常规函数,如果没有常规函数的话就是全局对象),这个行为和常规函数不同。这意味着当我们调用 perimeter 时,this 不是指向 shape 对象,而是它的周围作用域(在例子中是 window)。

window 中没有 radius 这个属性,因此返回 undefined


4. 输出是什么?

+true;
!"Lydia";
  • A: 1false
  • B: falseNaN
  • C: falsefalse

答案: A

一元操作符加号尝试将 bool 转为 number。true 转换为 number 的话为 1false0

字符串 'Lydia' 是一个真值,真值取反那么就返回 false


5. 哪一个是无效的?

const bird = {
  size: 'small'
}

const mouse = {
  name: 'Mickey',
  small: true
}
  • A: mouse.bird.size
  • B: mouse[bird.size]
  • C: mouse[bird["size"]]
  • D: 全部有效

答案: A

在 JavaScript 中,所有对象的 keys 都是字符串(除非对象是 Symbol)。尽管我们可能不会定义它们为字符串,但它们在底层总会被转换为字符串。

当我们使用括号语法时([]),JavaScript 会解释(或者 unboxes)语句。它首先看到第一个开始括号 [ 并继续前进直到找到结束括号 ]。只有这样,它才会计算语句的值。

mouse[bird.size]:首先计算 bird.size,这会得到 smallmouse["small"] 返回 true

然后使用点语法的话,上面这一切都不会发生。mouse 没有 bird 这个 key,这也就意味着 mouse.birdundefined。然后当我们使用点语法 mouse.bird.size 时,因为 mouse.birdundefined,这也就变成了 undefined.size。这个行为是无效的,并且会抛出一个错误类似 Cannot read property "size" of undefined


6. 输出是什么?

let c = { greeting: 'Hey!' }
let d

d = c
c.greeting = 'Hello'
console.log(d.greeting)
  • A: Hello
  • B: undefined
  • C: ReferenceError
  • D: TypeError

答案: A

在 JavaScript 中,当设置两个对象彼此相等时,它们会通过引用进行交互。

首先,变量 c 的值是一个对象。接下来,我们给 d 分配了一个和 c 对象相同的引用。

clipboard.png

因此当我们改变其中一个对象时,其实是改变了所有的对象。


7. 输出是什么?

let a = 3
let b = new Number(3)
let c = 3

console.log(a == b)
console.log(a === b)
console.log(b === c)
  • A: truefalsetrue
  • B: falsefalsetrue
  • C: truefalsefalse
  • D: falsetruetrue

答案: C

new Number() 是一个内建的函数构造器。虽然它看着像是一个 number,但它实际上并不是一个真实的 number:它有一堆额外的功能并且它是一个对象。

当我们使用 == 操作符时,它只会检查两者是否拥有相同的。因为它们的值都是 3,因此返回 true

然后,当我们使用 === 操作符时,两者的值以及类型都应该是相同的。new Number() 是一个对象而不是 number,因此返回 false


8. 输出是什么?

class Chameleon {
  static colorChange(newColor) {
    this.newColor = newColor
    return this.newColor
  }

  constructor({ newColor = 'green' } = {}) {
    this.newColor = newColor
  }
}

const freddie = new Chameleon({ newColor: 'purple' })
freddie.colorChange('orange')
  • A: orange
  • B: purple
  • C: green
  • D: TypeError

答案: D

colorChange 是一个静态方法。静态方法被设计为只能被创建它们的构造器使用(也就是 Chameleon),并且不能传递给实例。因为 freddie 是一个实例,静态方法不能被实例使用,因此抛出了 TypeError 错误。


9. 输出是什么?

let greeting
greetign = {} // Typo!
console.log(greetign)
  • A: {}
  • B: ReferenceError: greetign is not defined
  • C: undefined

答案: A

代码打印出了一个对象,这是因为我们在全局对象上创建了一个空对象!当我们将 greeting 写错成 greetign 时,JS 解释器实际在上浏览器中将它视为 global.greetign = {} (或者 window.greetign = {})。

为了避免这个为题,我们可以使用 "use strict"。这能确保当你声明变量时必须赋值。


10. 当我们这么做时,会发生什么?

function bark() {
  console.log('Woof!')
}

bark.animal = 'dog'
  • A: 正常运行!
  • B: SyntaxError. 你不能通过这种方式给函数增加属性。
  • C: undefined
  • D: ReferenceError

答案: A

这在 JavaScript 中是可以的,因为函数是对象!(除了基本类型之外其他都是对象)

函数是一个特殊的对象。你写的这个代码其实不是一个实际的函数。函数是一个拥有属性的对象,并且属性也可被调用。


11. 输出是什么?

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

const member = new Person("Lydia", "Hallie");
Person.getFullName = function () {
  return `${this.firstName} ${this.lastName}`;
}

console.log(member.getFullName());
  • A: TypeError
  • B: SyntaxError
  • C: Lydia Hallie
  • D: undefinedundefined

答案: A

你不能像常规对象那样,给构造函数添加属性。如果你想一次性给所有实例添加特性,你应该使用原型。因此本例中,使用如下方式:

Person.prototype.getFullName = function () {
  return `${this.firstName} ${this.lastName}`;
}

这才会使 member.getFullName() 起作用。为什么这么做有益的?假设我们将这个方法添加到构造函数本身里。也许不是每个 Person 实例都需要这个方法。这将浪费大量内存空间,因为它们仍然具有该属性,这将占用每个实例的内存空间。相反,如果我们只将它添加到原型中,那么它只存在于内存中的一个位置,但是所有实例都可以访问它!


12. 输出是什么?

function Person(firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

const lydia = new Person('Lydia', 'Hallie')
const sarah = Person('Sarah', 'Smith')

console.log(lydia)
console.log(sarah)
  • A: Person {firstName: "Lydia", lastName: "Hallie"}undefined
  • B: Person {firstName: "Lydia", lastName: "Hallie"}Person {firstName: "Sarah", lastName: "Smith"}
  • C: Person {firstName: "Lydia", lastName: "Hallie"}{}
  • D:Person {firstName: "Lydia", lastName: "Hallie"}ReferenceError

答案: A

对于 sarah,我们没有使用 new 关键字。当使用 new 时,this 引用我们创建的空对象。当未使用 new 时,this 引用的是全局对象(global object)。

我们说 this.firstName 等于 "Sarah",并且 this.lastName 等于 "Smith"。实际上我们做的是,定义了 global.firstName = 'Sarah'global.lastName = 'Smith'。而 sarah 本身是 undefined


13. 事件传播的三个阶段是什么?

  • A: Target > Capturing > Bubbling
  • B: Bubbling > Target > Capturing
  • C: Target > Bubbling > Capturing
  • D: Capturing > Target > Bubbling

答案: D

捕获(capturing)阶段中,事件从祖先元素向下传播到目标元素。当事件达到目标(target)元素后,冒泡(bubbling)才开始。

clipboard.png


14. 所有对象都有原型。

  • A: true
  • B: false

答案: B

除了基本对象(base object),所有对象都有原型。基本对象可以访问一些方法和属性,比如 .toString。这就是为什么你可以使用内置的 JavaScript 方法!所有这些方法在原型上都是可用的。虽然 JavaScript 不能直接在对象上找到这些方法,但 JavaScript 会沿着原型链找到它们,以便于你使用。


15. 输出是什么?

function sum(a, b) {
  return a + b
}

sum(1, '2')
  • A: NaN
  • B: TypeError
  • C: "12"
  • D: 3

答案: C

JavaScript 是一种动态类型语言:我们不指定某些变量的类型。值可以在你不知道的情况下自动转换成另一种类型,这种类型称为隐式类型转换(implicit type coercion)。Coercion 是指将一种类型转换为另一种类型。

在本例中,JavaScript 将数字 1 转换为字符串,以便函数有意义并返回一个值。在数字类型(1)和字符串类型('2')相加时,该数字被视为字符串。我们可以连接字符串,比如 "Hello" + "World",这里发生的是 "1" + "2",它返回 "12"


16. 输出是什么?

let number = 0
console.log(number++)
console.log(++number)
console.log(number)
  • A: 112
  • B: 122
  • C: 022
  • D: 012

答案: C

一元后自增运算符 ++

  1. 返回值(返回 0
  2. 值自增(number 现在是 1

一元前自增运算符 ++

  1. 值自增(number 现在是 2
  2. 返回值(返回 2

结果是 0 2 2.


17. 输出是什么?

function getPersonInfo(one, two, three) {
  console.log(one)
  console.log(two)
  console.log(three)
}

const person = 'Lydia'
const age = 21

getPersonInfo`${person} is ${age} years old`
  • A: "Lydia"21["", " is ", " years old"]
  • B: ["", " is ", " years old"]"Lydia"21
  • C: "Lydia"["", " is ", " years old"]21

答案: B

如果使用标记模板字面量,第一个参数的值总是包含字符串的数组。其余的参数获取的是传递的表达式的值!


18. 输出是什么?

function checkAge(data) {
  if (data === { age: 18 }) {
    console.log('You are an adult!')
  } else if (data == { age: 18 }) {
    console.log('You are still an adult.')
  } else {
    console.log(`Hmm.. You don't have an age I guess`)
  }
}

checkAge({ age: 18 })
  • A: You are an adult!
  • B: You are still an adult.
  • C: Hmm.. You don't have an age I guess

答案: C

在测试相等性时,基本类型通过它们的值(value)进行比较,而对象通过它们的引用(reference)进行比较。JavaScript 检查对象是否具有对内存中相同位置的引用。

题目中我们正在比较的两个对象不是同一个引用:作为参数传递的对象引用的内存位置,与用于判断相等的对象所引用的内存位置并不同。

这也是 { age: 18 } === { age: 18 }{ age: 18 } == { age: 18 } 都返回 false 的原因。


19. 输出是什么?

function getAge(...args) {
  console.log(typeof args)
}

getAge(21)
  • A: "number"
  • B: "array"
  • C: "object"
  • D: "NaN"

答案: C

扩展运算符(...args)会返回实参组成的数组。而数组是对象,因此 typeof args 返回 "object"


20. 输出是什么?

function getAge() {
  'use strict'
  age = 21
  console.log(age)
}

getAge()
  • A: 21
  • B: undefined
  • C: ReferenceError
  • D: TypeError

答案: C

使用 "use strict",你可以确保不会意外地声明全局变量。我们从来没有声明变量 age,因为我们使用 "use strict",它将抛出一个引用错误。如果我们不使用 "use strict",它就会工作,因为属性 age 会被添加到全局对象中了。


21. 输出是什么?

const sum = eval('10*10+5')
  • A: 105
  • B: "105"
  • C: TypeError
  • D: "10*10+5"

答案: A

代码以字符串形式传递进来,eval 对其求值。如果它是一个表达式,就像本例中那样,它对表达式求值。表达式是 10 * 10 + 5。这将返回数字 105


22. cool_secret 可访问多长时间?

sessionStorage.setItem('cool_secret', 123)
  • A: 永远,数据不会丢失。
  • B: 当用户关掉标签页时。
  • C: 当用户关掉整个浏览器,而不只是关掉标签页。
  • D: 当用户关闭电脑时。

答案: B

关闭 tab 标签页 后,sessionStorage 存储的数据才会删除。

如果使用 localStorage,那么数据将永远在那里,除非调用了 localStorage.clear()


23. 输出是什么?

var num = 8
var num = 10

console.log(num)
  • A: 8
  • B: 10
  • C: SyntaxError
  • D: ReferenceError

答案: B

使用 var 关键字,你可以用相同的名称声明多个变量。然后变量将保存最新的值。

你不能使用 letconst 来实现这一点,因为它们是块作用域的。


24. 输出是什么?

const obj = { 1: 'a', 2: 'b', 3: 'c' }
const set = new Set([1, 2, 3, 4, 5])

obj.hasOwnProperty('1')
obj.hasOwnProperty(1)
set.has('1')
set.has(1)
  • A: falsetruefalsetrue
  • B: falsetruetruetrue
  • C: truetruefalsetrue
  • D: truetruetruetrue

答案: C

所有对象的键(不包括 Symbol)在底层都是字符串,即使你自己没有将其作为字符串输入。这就是为什么 obj.hasOwnProperty('1') 也返回 true

对于集合,它不是这样工作的。在我们的集合中没有 '1'set.has('1') 返回 false。它有数字类型为 1set.has(1) 返回 true


25. 输出是什么?

const obj = { a: 'one', b: 'two', a: 'three' }
console.log(obj)
  • A: { a: "one", b: "two" }
  • B: { b: "two", a: "three" }
  • C: { a: "three", b: "two" }
  • D: SyntaxError

答案: C

如果你有两个名称相同的键,则键会被替换掉。它仍然位于第一个键出现的位置,但是值是最后出现那个键的值。


26. JavaScript 全局执行上下文为你做了两件事:全局对象和 this 关键字。

  • A: true
  • B: false
  • C: it depends

答案: A

基本执行上下文是全局执行上下文:它是代码中随处可访问的内容。


27. 输出是什么?

for (let i = 1; i < 5; i++) {
  if (i === 3) continue
  console.log(i)
}
  • A: 12
  • B: 123
  • C: 124
  • D: 134

答案: C

如果某个条件返回 true,则 continue 语句跳过本次迭代。


28. 输出是什么?

String.prototype.giveLydiaPizza = () => {
  return 'Just give Lydia pizza already!'
}

const name = 'Lydia'

name.giveLydiaPizza()
  • A: "Just give Lydia pizza already!"
  • B: TypeError: not a function
  • C: SyntaxError
  • D: undefined

答案: A

String 是内置的构造函数,我们可以向它添加属性。我只是在它的原型中添加了一个方法。基本类型字符串被自动转换为字符串对象,由字符串原型函数生成。因此,所有 string(string 对象)都可以访问该方法!


29. 输出是什么?

const a = {}
const b = { key: 'b' }
const c = { key: 'c' }

a[b] = 123
a[c] = 456

console.log(a[b])
  • A: 123
  • B: 456
  • C: undefined
  • D: ReferenceError

答案: B

对象的键被自动转换为字符串。我们试图将一个对象 b 设置为对象 a 的键,且相应的值为 123

然而,当字符串化一个对象时,它会变成 "[object Object]"。因此这里说的是,a["[object Object]"] = 123。然后,我们再一次做了同样的事情,c 是另外一个对象,这里也有隐式字符串化,于是,a["[object Object]"] = 456

然后,我们打印 a[b],也就是 a["[object Object]"]。之前刚设置为 456,因此返回的是 456


30. 输出是什么?

const foo = () => console.log('First')
const bar = () => setTimeout(() => console.log('Second'))
const baz = () => console.log('Third')

bar()
foo()
baz()
  • A: FirstSecondThird
  • B: FirstThirdSecond
  • C: SecondFirstThird
  • D: SecondThirdFirst

答案: B

我们有一个 setTimeout 函数,并首先调用它。然而,它是最后打印日志的。

这是因为在浏览器中,我们不仅有运行时引擎,还有一个叫做 WebAPI 的东西。WebAPI 提供了 setTimeout 函数,也包含其他的,例如 DOM。

callback 推送到 WebAPI 后,setTimeout 函数本身(但不是回调!)将从栈中弹出。

clipboard.png

现在,foo 被调用,打印 "First"

foo 从栈中弹出,baz 被调用. 打印 "Third"

WebAPI 不能随时向栈内添加内容。相反,它将回调函数推到名为 queue 的地方。

clipboard.png

这就是事件循环开始工作的地方。一个事件循环查看栈和任务队列。如果栈是空的,它接受队列上的第一个元素并将其推入栈。

clipboard.png

bar 被调用,打印 "Second",然后它被栈弹出。


31. 当点击按钮时,event.target是什么?

<div onclick="console.log('first div')">
  <div onclick="console.log('second div')">
    <button onclick="console.log('button')">
      Click!
    </button>
  </div>
</div>
  • A: Outer div
  • B: Inner div
  • C: button
  • D: 一个包含所有嵌套元素的数组。

答案: C

导致事件的最深嵌套的元素是事件的 target。你可以通过 event.stopPropagation 来停止冒泡。


32. 当您单击该段落时,日志输出是什么?

<div onclick="console.log('div')">
  <p onclick="console.log('p')">
    Click here!
  
</div>
  • A: pdiv
  • B: divp
  • C: p
  • D: div

答案: A

如果我们点击 p,我们会看到两个日志:pdiv。在事件传播期间,有三个阶段:捕获、目标和冒泡。默认情况下,事件处理程序在冒泡阶段执行(除非将 useCapture 设置为 true)。它从嵌套最深的元素向外传播。


33. 输出是什么?

const person = { name: 'Lydia' }

function sayHi(age) {
  console.log(`${this.name} is ${age}`)
}

sayHi.call(person, 21)
sayHi.bind(person, 21)
  • A: undefined is 21Lydia is 21
  • B: functionfunction
  • C: Lydia is 21Lydia is 21
  • D: Lydia is 21function

答案: D

使用这两种方法,我们都可以传递我们希望 this 关键字引用的对象。但是,.call立即执行的。

.bind 返回函数的副本,但带有绑定上下文!它不是立即执行的。


34. 输出是什么?

function sayHi() {
  return (() => 0)()
}

typeof sayHi()
  • A: "object"
  • B: "number"
  • C: "function"
  • D: "undefined"

答案: B

sayHi 方法返回的是立即执行函数(IIFE)的返回值.此立即执行函数的返回值是 0, 类型是 number

参考:只有7种内置类型:nullundefinedbooleannumberstringobjectsymbolfunction 不是一种类型,函数是对象,它的类型是object


35. 下面哪些值是 falsy?

0
new Number(0)
('')
(' ')
new Boolean(false)
undefined
  • A: 0, '', undefined
  • B: 0, new Number(0), '', new Boolean(false), undefined
  • C: 0, '', new Boolean(false), undefined
  • D: 全都是 of them are falsy

答案: A

只有 6 种 falsy 值:

  • undefined
  • null
  • NaN
  • 0
  • '' (empty string)
  • false

Function 构造函数, 比如 new Numbernew Boolean,是 truthy


36. 输出是什么?

console.log(typeof typeof 1)
  • A: "number"
  • B: "string"
  • C: "object"
  • D: "undefined"

答案: B

typeof 1 返回 "number"
typeof "number" 返回 "string"


37. 输出是什么?

const numbers = [1, 2, 3]
numbers[10] = 11
console.log(numbers)
  • A: [1, 2, 3, 7 x null, 11]
  • B: [1, 2, 3, 11]
  • C: [1, 2, 3, 7 x empty, 11]
  • D: SyntaxError

答案: C

当你为数组设置超过数组长度的值的时候, JavaScript 会创建名为 "empty slots" 的东西。它们的值实际上是 undefined。你会看到以下场景:

[1, 2, 3, 7 x empty, 11]

这取决于你的运行环境(每个浏览器,以及 node 环境,都有可能不同)


38. 输出是什么?

(() => {
  let x, y
  try {
    throw new Error()
  } catch (x) {
    (x = 1), (y = 2)
    console.log(x)
  }
  console.log(x)
  console.log(y)
})()
  • A: 1undefined2
  • B: undefinedundefinedundefined
  • C: 112
  • D: 1undefinedundefined

答案: A

catch 代码块接收参数 x。当我们传递参数时,这与之前定义的变量 x 不同 。这个 x 是属于 catch 块级作用域的。

然后,我们将块级作用域中的变量赋值为 1,同时也设置了变量 y 的值。现在,我们打印块级作用域中的变量 x,值为 1

catch 块之外的变量 x 的值仍为 undefinedy 的值为 2。当我们在 catch 块之外执行 console.log(x) 时,返回 undefinedy 返回 2


39. JavaScript 中的一切都是?

  • A: 基本类型与对象
  • B: 函数与对象
  • C: 只有对象
  • D: 数字与对象

-

答案: A

JavaScript 只有基本类型和对象。

基本类型包括 boolean, null, undefined, bigint, number, string, symbol


40. 输出是什么?

[[0, 1], [2, 3]].reduce(
  (acc, cur) => {
    return acc.concat(cur)
  },
  [1, 2]
)
  • A: [0, 1, 2, 3, 1, 2]
  • B: [6, 1, 2]
  • C: [1, 2, 0, 1, 2, 3]
  • D: [1, 2, 6]

答案: C

[1, 2]是初始值。初始值将会作为首次调用时第一个参数 acc 的值。在第一次执行时, acc 的值是 [1, 2]cur 的值是 [0, 1]。合并它们,结果为 [1, 2, 0, 1]
第二次执行, acc 的值是 [1, 2, 0, 1]cur 的值是 [2, 3]。合并它们,最终结果为 [1, 2, 0, 1, 2, 3]


41. 输出是什么?

!!null
!!''
!!1
  • A: falsetruefalse
  • B: falsefalsetrue
  • C: falsetruetrue
  • D: truetruefalse

答案: B

nullfalsy!null 的值是 true!true 的值是 false

""falsy!"" 的值是 true!true 的值是 false

1truthy!1 的值是 false!false 的值是 true


42. setInterval 方法的返回值是什么?

setInterval(() => console.log('Hi'), 1000)
  • A: 一个唯一的id
  • B: 该方法指定的毫秒数
  • C: 传递的函数
  • D: undefined

答案: A

setInterval 返回一个唯一的 id。此 id 可被用于 clearInterval 函数来取消定时。


43. 输出是什么?

[...'Lydia']
  • A: ["L", "y", "d", "i", "a"]
  • B: ["Lydia"]
  • C: [[], "Lydia"]
  • D: [["L", "y", "d", "i", "a"]]

答案: A

string 类型是可迭代的。扩展运算符将迭代的每个字符映射成一个元素。


44. 输出是什么?

function* generator(i) {
  yield i;
  yield i * 2;
}

const gen = generator(10);

console.log(gen.next().value);
console.log(gen.next().value);
  • A: [0, 10], [10, 20]
  • B: 20, 20
  • C: 10, 20
  • D: 0, 10 和 10, 20

答案: C

一般的函数在执行之后是不能中途停下的。但是,生成器函数却可以中途“停下”,之后可以再从停下的地方继续。当生成器遇到yield关键字的时候,会生成yield后面的值。注意,生成器在这种情况下不 返回 (_return_ )值,而是 生成 (_yield_)值。

首先,我们用10作为参数i来初始化生成器函数。然后使用next()方法一步步执行生成器。第一次执行生成器的时候,i的值为10,遇到第一个yield关键字,它要生成i的值。此时,生成器“暂停”,生成了10

然后,我们再执行next()方法。生成器会从刚才暂停的地方继续,这个时候i还是10。于是我们走到了第二个yield关键字处,这时候需要生成的值是i*2i10,那么此时生成的值便是20。所以这道题的最终结果是10,20

45. 返回值是什么?

const firstPromise = new Promise((res, rej) => {
  setTimeout(res, 500, "one");
});

const secondPromise = new Promise((res, rej) => {
  setTimeout(res, 100, "two");
});

Promise.race([firstPromise, secondPromise]).then(res => console.log(res));
  • A: "one"
  • B: "two"
  • C: "two" "one"
  • D: "one" "two"

答案: B

当我们向Promise.race方法中传入多个Promise时,会进行 优先 解析。在这个例子中,我们用setTimeoutfirstPromisesecondPromise分别设定了500ms和100ms的定时器。这意味着secondPromise会首先解析出字符串two。那么此时res参数即为two,是为输出结果。


46. 输出是什么?

let person = { name: "Lydia" };
const members = [person];
person = null;

console.log(members);
  • A: null
  • B: [null]
  • C: [{}]
  • D: [{ name: "Lydia" }]

答案: D

首先我们声明了一个拥有name属性的对象 person

clipboard.png

然后我们又声明了一个变量members. 将首个元素赋值为变量person。 当设置两个对象彼此相等时,它们会通过 引用 进行交互。但是当你将引用从一个变量分配至另一个变量时,其实只是执行了一个 复制 操作。(注意一点,他们的引用 _并不相同_!)

clipboard.png

接下来我们让person等于null

clipboard.png

我们没有修改数组第一个元素的值,而只是修改了变量person的值,因为元素(复制而来)的引用与person不同。members的第一个元素仍然保持着对原始对象的引用。当我们输出members数组时,第一个元素会将引用的对象打印出来。


47. 输出是什么?

const person = {
  name: "Lydia",
  age: 21
};

for (const item in person) {
  console.log(item);
}
  • A: { name: "Lydia" }, { age: 21 }
  • B: "name", "age"
  • C: "Lydia", 21
  • D: ["name", "Lydia"], ["age", 21]

答案: B

for-in循环中,我们可以通过对象的key来进行迭代,也就是这里的nameage。在底层,对象的key都是字符串(如果他们不是Symbol的话)。在每次循环中,我们将item设定为当前遍历到的key.所以一开始,itemname,之后 item输出的则是age


48. 输出是什么?

console.log(3 + 4 + "5");
  • A: "345"
  • B: "75"
  • C: 12
  • D: "12"

答案: B

当所有运算符的 优先级 相同时,计算表达式需要确定运算符的结合顺序,即从右到左还是从左往右。在这个例子中,我们只有一类运算符+,对于加法来说,结合顺序就死从左到右。

3 + 4首先计算,得到数字7.

由于类型的强制转换,7 + '5'的结果是"75". JavaScript将7转换成了字符串,可以参考问题15.我们可以用+号把两个字符串连接起来。 "7" + "5" 就得到了"75".


49. num的值是什么?

const num = parseInt("7*6", 10);
  • A: 42
  • B: "42"
  • C: 7
  • D: NaN

答案: C

只返回了字符串中第一个字母. 设定了 进制 后 (也就是第二个参数,指定需要解析的数字是什么进制: 十进制、十六机制、八进制、二进制等等……),parseInt 检查字符串中的字符是否合法. 一旦遇到一个在指定进制中不合法的字符后,立即停止解析并且忽略后面所有的字符。

*就是不合法的数字字符。所以只解析到"7",并将其解析为十进制的7. num的值即为7.


50. 输出是什么?

[1, 2, 3].map(num => {
  if (typeof num === "number") return;
  return num * 2;
});
  • A: []
  • B: [null, null, null]
  • C: [undefined, undefined, undefined]
  • D: [ 3 x empty ]

答案: C

对数组进行映射的时候,num就是当前循环到的元素. 在这个例子中,所有的映射都是number类型,所以if中的判断typeof num === "number"结果都是true.map函数创建了新数组并且将函数的返回值插入数组。

但是,没有任何值返回。当函数没有返回任何值时,即默认返回undefined.对数组中的每一个元素来说,函数块都得到了这个返回值,所以结果中每一个元素都是undefined.


51. 输出的是什么?

function getInfo(member, year) {
  member.name = "Lydia";
  year = "1998";
}

const person = { name: "Sarah" };
const birthYear = "1997";

getInfo(person, birthYear);

console.log(person, birthYear);
  • A: { name: "Lydia" }, "1997"
  • B: { name: "Sarah" }, "1998"
  • C: { name: "Lydia" }, "1998"
  • D: { name: "Sarah" }, "1997"

答案: A

普通参数都是 传递的,而对象则不同,是 引用 传递。所以说,birthYear是值传递,因为他是个字符串而不是对象。当我们对参数进行值传递时,会创建一份该值的 复制 。(可以参考问题46)

变量birthYear有一个对"1997"的引用,而传入的参数也有一个对"1997"的引用,但二者的引用并不相同。当我们通过给 year赋值"1998"来更新year的值的时候我们只是更新了year(的引用)。此时birthYear仍然是"1997".

person是个对象。参数member引用与之 相同的 对象。当我们修改member所引用对象的属性时,person的相应属性也被修改了,因为他们引用了相同的对象. personname属性也变成了 "Lydia".


52. 输出是什么?

function greeting() {
  throw "Hello world!";
}

function sayHi() {
  try {
    const data = greeting();
    console.log("It worked!", data);
  } catch (e) {
    console.log("Oh no an error!", e);
  }
}

sayHi();
  • A: "It worked! Hello world!"
  • B: "Oh no an error: undefined
  • C: SyntaxError: can only throw Error objects
  • D: "Oh no an error: Hello world!

答案: D

通过throw语句,我么可以创建自定义错误。 而通过它,我们可以抛出异常。异常可以是一个字符串, 一个 数字, 一个 布尔类型 或者是一个 对象。在本例中,我们的异常是字符串'Hello world'.

通过 catch语句,我们可以设定当try语句块中抛出异常后应该做什么处理。在本例中抛出的异常是字符串'Hello world'. e就是这个字符串,因此被输出。最终结果就是'Oh an error: Hello world'.


53. 输出是什么?

function Car() {
  this.make = "Lamborghini";
  return { make: "Maserati" };
}

const myCar = new Car();
console.log(myCar.make);
  • A: "Lamborghini"
  • B: "Maserati"
  • C: ReferenceError
  • D: TypeError

答案: B

返回属性的时候,属性的值等于 返回的 值,而不是构造函数中设定的值。我们返回了字符串 "Maserati",所以 myCar.make等于"Maserati".


54. 输出是什么?

(() => {
  let x = (y = 10);
})();

console.log(typeof x);
console.log(typeof y);
  • A: "undefined", "number"
  • B: "number", "number"
  • C: "object", "number"
  • D: "number", "undefined"

答案: A

let x = y = 10; 是下面这个表达式的缩写:

y = 10;
let x = y;

我们设定y等于10时,我们实际上增加了一个属性y给全局对象(浏览器里的window, Nodejs里的global)。在浏览器中, window.y等于10.

然后我们声明了变量x等于y,也是10.但变量是使用 let声明的,它只作用于 _块级作用域_, 仅在声明它的块中有效;就是案例中的立即调用表达式(IIFE)。使用typeof操作符时, 操作值 x没有被定义:因为我们在x声明块的外部,无法调用它。这就意味着x未定义。未分配或是未声明的变量类型为"undefined". console.log(typeof x)返回"undefined".

而我们创建了全局变量y,并且设定y等于10.这个值在我们的代码各处都访问的到。 y已经被定义了,而且有一个"number"类型的值。 console.log(typeof y)返回"number".


55. 输出是什么?

class Dog {
  constructor(name) {
    this.name = name;
  }
}

Dog.prototype.bark = function() {
  console.log(`Woof I am ${this.name}`);
};

const pet = new Dog("Mara");

pet.bark();

delete Dog.prototype.bark;

pet.bark();
  • A: "Woof I am Mara", TypeError
  • B: "Woof I am Mara","Woof I am Mara"
  • C: "Woof I am Mara", undefined
  • D: TypeError, TypeError

答案: A

我们可以用delete关键字删除对象的属性,对原型也是适用的。删除了原型的属性后,该属性在原型链上就不可用了。在本例中,函数bark在执行了delete Dog.prototype.bark后不可用, 然而后面的代码还在调用它。

当我们尝试调用一个不存在的函数时TypeError异常会被抛出。在本例中就是 TypeError: pet.bark is not a function,因为pet.barkundefined.


56. 输出是什么?

const set = new Set([1, 1, 2, 3, 4]);

console.log(set);
  • A: [1, 1, 2, 3, 4]
  • B: [1, 2, 3, 4]
  • C: {1, 1, 2, 3, 4}
  • D: {1, 2, 3, 4}

答案: D

Set对象手机 独一无二 的值:也就是说同一个值在其中仅出现一次。

我们传入了数组[1, 1, 2, 3, 4],他有一个重复值1.以为一个集合里不能有两个重复的值,其中一个就被移除了。所以结果是 {1, 2, 3, 4}.


57. 输出是什么?

// counter.js
let counter = 10;
export default counter;
// index.js
import myCounter from "./counter";

myCounter += 1;

console.log(myCounter);
  • A: 10
  • B: 11
  • C: Error
  • D: NaN

答案: C

引入的模块是 只读 的: 你不能修改引入的模块。只有导出他们的模块才能修改其值。

当我们给myCounter增加一个值的时候会抛出一个异常: myCounter是只读的,不能被修改。


58. 输出是什么?

const name = "Lydia";
age = 21;

console.log(delete name);
console.log(delete age);
  • A: false, true
  • B: "Lydia", 21
  • C: true, true
  • D: undefined, undefined

答案: A

delete操作符返回一个布尔值: true指删除成功,否则返回false. 但是通过 var, constlet 关键字声明的变量无法用 delete 操作符来删除。

name变量由const关键字声明,所以删除不成功:返回 false. 而我们设定age等于21时,我们实际上添加了一个名为age的属性给全局对象。对象中的属性是可以删除的,全局对象也是如此,所以delete age返回true.


59. 输出是什么?

const numbers = [1, 2, 3, 4, 5];
const [y] = numbers;

console.log(y);
  • A: [[1, 2, 3, 4, 5]]
  • B: [1, 2, 3, 4, 5]
  • C: 1
  • D: [1]

答案: C

我们可以通过解构赋值来解析来自对象的数组或属性的值,比如说:

[a, b] = [1, 2];

clipboard.png

a的值现在是1b的值现在是2.而在题目中,我们是这么做的:

[y] = [1, 2, 3, 4, 5];

clipboard.png

也就是说,y等于数组的第一个值就是数字1.我们输出y, 返回1.


60. 输出是什么?

const user = { name: "Lydia", age: 21 };
const admin = { admin: true, ...user };

console.log(admin);
  • A: { admin: true, user: { name: "Lydia", age: 21 } }
  • B: { admin: true, name: "Lydia", age: 21 }
  • C: { admin: true, user: ["Lydia", 21] }
  • D: { admin: true }

答案: B

扩展运算符...为对象的组合提供了可能。你可以复制对象中的键值对,然后把它们加到另一个对象里去。在本例中,我们复制了user对象键值对,然后把它们加入到admin对象中。admin对象就拥有了这些键值对,所以结果为{ admin: true, name: "Lydia", age: 21 }.


61. 输出是什么?

const person = { name: "Lydia" };

Object.defineProperty(person, "age", { value: 21 });

console.log(person);
console.log(Object.keys(person));
  • A: { name: "Lydia", age: 21 }, ["name", "age"]
  • B: { name: "Lydia", age: 21 }, ["name"]
  • C: { name: "Lydia"}, ["name", "age"]
  • D: { name: "Lydia"}, ["age"]

答案: B

通过defineProperty方法,我们可以给对象添加一个新属性,或者修改已经存在的属性。而我们使用defineProperty方法给对象添加了一个属性之后,属性默认为 不可枚举(not enumerable)_. Object.keys方法仅返回对象中 _可枚举(enumerable) 的属性,因此只剩下了"name".

defineProperty方法添加的属性默认不可变。你可以通过writable, configurableenumerable属性来改变这一行为。这样的话, 相比于自己添加的属性,defineProperty方法添加的属性有了更多的控制权。


62. 输出是什么?

const settings = {
  username: "lydiahallie",
  level: 19,
  health: 90
};

const data = JSON.stringify(settings, ["level", "health"]);
console.log(data);
  • A: "{"level":19, "health":90}"
  • B: "{"username": "lydiahallie"}"
  • C: "["level", "health"]"
  • D: "{"username": "lydiahallie", "level":19, "health":90}"

答案: A

JSON.stringify的第二个参数是 _替代者(replacer)_. 替代者(replacer)可以是个函数或数组,用以控制哪些值如何被转换为字符串。

如果替代者(replacer)是个 数组 ,那么就只有包含在数组中的属性将会被转化为字符串。在本例中,只有名为"level""health" 的属性被包括进来, "username"则被排除在外。 data 就等于 "{"level":19, "health":90}".

而如果替代者(replacer)是个 _函数_,这个函数将被对象的每个属性都调用一遍。
函数返回的值会成为这个属性的值,最终体现在转化后的JSON字符串中(译者注:Chrome下,经过实验,如果所有属性均返回同一个值的时候有异常,会直接将返回值作为结果输出而不会输出JSON字符串),而如果返回值为undefined,则该属性会被排除在外。


63. 输出是什么?

let num = 10;

const increaseNumber = () => num++;
const increasePassedNumber = number => number++;

const num1 = increaseNumber();
const num2 = increasePassedNumber(num1);

console.log(num1);
console.log(num2);
  • A: 10, 10
  • B: 10, 11
  • C: 11, 11
  • D: 11, 12

答案: A

一元操作符 ++先返回 操作值, 再累加 操作值。num1的值是10, 因为increaseNumber函数首先返回num的值,也就是10,随后再进行 num的累加。

num210因为我们将 num1传入increasePassedNumber. number等于10num1的值。同样道理,++先返回 操作值, 再累加 操作值。) number10,所以num2也是10.


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎继续阅读本专栏其它高赞文章:


查看原文

xiaopengji 收藏了文章 · 2019-05-17

vue组件间通信六种方式(完整版)

前言

组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系:
image

如上图所示,A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)。

针对不同的使用场景,如何选择行之有效的通信方式?这是我们所要探讨的主题。本文总结了vue组件间通信的几种方式,如props、$emit/$on、vuex、$parent / $children$attrs/$listeners和provide/inject,以通俗易懂的实例讲述这其中的差别及使用场景,希望对小伙伴有些许帮助。

本文的代码请猛戳github博客,纸上得来终觉浅,大家动手多敲敲代码!

方法一、props/$emit

父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。

1.父组件向子组件传值

接下来我们通过一个例子,说明父组件如何向子组件传递值:在子组件Users.vue中如何获取父组件App.vue中的数据 users:["Henry","Bucky","Emily"]

//App.vue父组件
<template>
  <div id="app">
    <users v-bind:users="users"></users>//前者自定义名称便于子组件调用,后者要传递数据名
  </div>
</template>
<script>
import Users from "./components/Users"
export default {
  name: 'App',
  data(){
    return{
      users:["Henry","Bucky","Emily"]
    }
  },
  components:{
    "users":Users
  }
}
//users子组件
<template>
  <div class="hello">
    <ul>
      <li v-for="user in users">{{user}}</li>//遍历传递过来的值,然后呈现到页面
    </ul>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props:{
    users:{           //这个就是父组件中子标签自定义名字
      type:Array,
      required:true
    }
  }
}
</script>

总结:父组件通过props向下传递数据给子组件。注:组件中的数据共有三种形式:data、props、computed

2.子组件向父组件传值(通过事件形式)

接下来我们通过一个例子,说明子组件如何向父组件传递值:当我们点击“Vue.js Demo”后,子组件向父组件传递值,文字由原来的“传递的是一个值”变成“子向父组件传值”,实现子组件向父组件值的传递。

子组件向父组件传值之前

// 子组件
<template>
  <header>
    <h1 @click="changeTitle">{{title}}</h1>//绑定一个点击事件
  </header>
</template>
<script>
export default {
  name: 'app-header',
  data() {
    return {
      title:"Vue.js Demo"
    }
  },
  methods:{
    changeTitle() {
      this.$emit("titleChanged","子向父组件传值");//自定义事件  传递值“子向父组件传值”
    }
  }
}
</script>
// 父组件
<template>
  <div id="app">
    <app-header v-on:titleChanged="updateTitle" ></app-header>//与子组件titleChanged自定义事件保持一致
   // updateTitle($event)接受传递过来的文字
    <h2>{{title}}</h2>
  </div>
</template>
<script>
import Header from "./components/Header"
export default {
  name: 'App',
  data(){
    return{
      title:"传递的是一个值"
    }
  },
  methods:{
    updateTitle(e){   //声明这个函数
      this.title = e;
    }
  },
  components:{
   "app-header":Header,
  }
}
</script>

总结:子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。

方法二、$emit/$on

这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。

1.具体实现方式:

    var Event=new Vue();
    Event.$emit(事件名,数据);
    Event.$on(事件名,data => {});

2.举个例子

假设兄弟组件有三个,分别是A、B、C组件,C组件如何获取A或者B组件的数据

<div id="itany">
    <my-a></my-a>
    <my-b></my-b>
    <my-c></my-c>
</div>
<template id="a">
  <div>
    <h3>A组件:{{name}}</h3>
    <button @click="send">将数据发送给C组件</button>
  </div>
</template>
<template id="b">
  <div>
    <h3>B组件:{{age}}</h3>
    <button @click="send">将数组发送给C组件</button>
  </div>
</template>
<template id="c">
  <div>
    <h3>C组件:{{name}},{{age}}</h3>
  </div>
</template>
<script>
var Event = new Vue();//定义一个空的Vue实例
var A = {
    template: '#a',
    data() {
      return {
        name: 'tom'
      }
    },
    methods: {
      send() {
        Event.$emit('data-a', this.name);
      }
    }
}
var B = {
    template: '#b',
    data() {
      return {
        age: 20
      }
    },
    methods: {
      send() {
        Event.$emit('data-b', this.age);
      }
    }
}
var C = {
    template: '#c',
    data() {
      return {
        name: '',
        age: ""
      }
    },
    mounted() {//在模板编译完成后执行
     Event.$on('data-a',name => {
         this.name = name;//箭头函数内部不会产生新的this,这边如果不用=>,this指代Event
     })
     Event.$on('data-b',age => {
         this.age = age;
     })
    }
}
var vm = new Vue({
    el: '#itany',
    components: {
      'my-a': A,
      'my-b': B,
      'my-c': C
    }
});    
</script>

image
$on 监听了自定义事件 data-a和data-b,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。

方法三、vuex

image

1.简要介绍Vuex原理

Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。

2.简要介绍各模块在流程中的功能:

  • Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
  • dispatch:操作行为触发方法,是唯一能执行action的方法。
  • actions:操作行为处理模块,由组件中的$store.dispatch('action 名称', data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。
  • commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
  • mutations:状态改变操作方法,由actions中的commit('mutation 名称')来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。
  • state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
  • getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。

3.Vuex与localStorage

vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。

let defaultCity = "上海"
try {   // 用户关闭了本地存储功能,此时在外层加个try...catch
  if (!defaultCity){
    defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
  }
}catch(e){}
export default new Vuex.Store({
  state: {
    city: defaultCity
  },
  mutations: {
    changeCity(state, city) {
      state.city = city
      try {
      window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
      // 数据改变的时候把数据拷贝一份保存到localStorage里面
      } catch (e) {}
    }
  }
})

这里需要注意的是:由于vuex里,我们保存的状态,都是数组,而localStorage只支持字符串,所以需要用JSON转换:

JSON.stringify(state.subscribeList);   // array -> string
JSON.parse(window.localStorage.getItem("subscribeList"));    // string -> array 

方法四、$attrs/$listeners

1.简介

多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此Vue2.4 版本提供了另一种方法----$attrs/$listeners

  • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
  • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

接下来我们看个跨级通信的例子:

// index.vue
<template>
  <div>
    <h2>浪里行舟</h2>
    <child-com1
      :foo="foo"
      :boo="boo"
      :coo="coo"
      :doo="doo"
      title="前端工匠"
    ></child-com1>
  </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
  components: { childCom1 },
  data() {
    return {
      foo: "Javascript",
      boo: "Html",
      coo: "CSS",
      doo: "Vue"
    };
  }
};
</script>
// childCom1.vue
<template class="border">
  <div>
    <p>foo: {{ foo }}</p>
    <p>childCom1的$attrs: {{ $attrs }}</p>
    <child-com2 v-bind="$attrs"></child-com2>
  </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
  components: {
    childCom2
  },
  inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
  props: {
    foo: String // foo作为props属性绑定
  },
  created() {
    console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>
// childCom2.vue
<template>
  <div class="border">
    <p>boo: {{ boo }}</p>
    <p>childCom2: {{ $attrs }}</p>
    <child-com3 v-bind="$attrs"></child-com3>
  </div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
  components: {
    childCom3
  },
  inheritAttrs: false,
  props: {
    boo: String
  },
  created() {
    console.log(this.$attrs); // {"coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>
// childCom3.vue
<template>
  <div class="border">
    <p>childCom3: {{ $attrs }}</p>
  </div>
</template>
<script>
export default {
  props: {
    coo: String,
    title: String
  }
};
</script>

image
如上图所示$attrs表示没有继承数据的对象,格式为{属性名:属性值}。Vue2.4提供了$attrs , $listeners 来传递数据与事件,跨级组件之间的通讯变得更简单。

简单来说:$attrs$listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners里存放的是父组件中绑定的非原生事件。

方法五、provide/inject

1.简介

Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系

2.举个例子

假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件

// A.vue
export default {
  provide: {
    name: '浪里行舟'
  }
}
// B.vue
export default {
  inject: ['name'],
  mounted () {
    console.log(this.name);  // 浪里行舟
  }
}

可以看到,在 A.vue 里,我们设置了一个 provide: name,值为 浪里行舟,它的作用就是将 name 这个变量提供给它的所有子组件。而在 B.vue 中,通过 inject 注入了从 A 组件中提供的 name 变量,那么在组件 B 中,就可以直接通过 this.name 访问这个变量了,它的值也是 浪里行舟。这就是 provide / inject API 最核心的用法。

需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的----vue官方文档
所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是 浪里行舟。

3.provide与inject 怎么实现数据响应式

一般来说,有两种办法:

  • provide祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如props,methods
  • 使用2.6最新API Vue.observable 优化响应式 provide(推荐)

我们来看个例子:孙组件D、E和F获取A组件传递过来的color值,并能实现数据响应式变化,即A组件的color变化后,组件D、E、F会跟着变(核心代码如下:)

image

// A 组件 
<div>
      <h1>A 组件</h1>
      <button @click="() => changeColor()">改变color</button>
      <ChildrenB />
      <ChildrenC />
</div>
......
  data() {
    return {
      color: "blue"
    };
  },
  // provide() {
  //   return {
  //     theme: {
  //       color: this.color //这种方式绑定的数据并不是可响应的
  //     } // 即A组件的color变化后,组件D、E、F不会跟着变
  //   };
  // },
  provide() {
    return {
      theme: this//方法一:提供祖先组件的实例
    };
  },
  methods: {
    changeColor(color) {
      if (color) {
        this.color = color;
      } else {
        this.color = this.color === "blue" ? "red" : "blue";
      }
    }
  }
  // 方法二:使用2.6最新API Vue.observable 优化响应式 provide
  // provide() {
  //   this.theme = Vue.observable({
  //     color: "blue"
  //   });
  //   return {
  //     theme: this.theme
  //   };
  // },
  // methods: {
  //   changeColor(color) {
  //     if (color) {
  //       this.theme.color = color;
  //     } else {
  //       this.theme.color = this.theme.color === "blue" ? "red" : "blue";
  //     }
  //   }
  // }
// F 组件 
<template functional>
  <div class="border2">
    <h3 :style="{ color: injections.theme.color }">F 组件</h3>
  </div>
</template>
<script>
export default {
  inject: {
    theme: {
      //函数式组件取值不一样
      default: () => ({})
    }
  }
};
</script>

虽说provide 和 inject 主要为高阶插件/组件库提供用例,但如果你能在业务中熟练运用,可以达到事半功倍的效果!

方法六、$parent / $childrenref

  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  • $parent / $children:访问父 / 子实例

需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。我们先来看个用 ref来访问组件的例子:

// component-a 子组件
export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}
// 父组件
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // 弹窗
    }
  }
</script>

不过,这两种方法的弊端是,无法在跨级或兄弟间通信

// parent.vue
<component-a></component-a>
<component-b></component-b>
<component-b></component-b>

我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,就得配置额外的插件或工具了,比如 Vuex 和 Bus 的解决方案。

总结

常见使用场景可以分为三类:

  • 父子通信:

父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent / $children);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners

  • 兄弟通信:

Bus;Vuex

  • 跨级通信:

Bus;Vuex;provide / inject API、$attrs/$listeners

给大家推荐一个好用的BUG监控工具Fundebug,欢迎免费试用!

欢迎关注公众号:前端工匠,你的成长我们一起见证!
image

参考文章

查看原文

xiaopengji 收藏了文章 · 2019-05-13

Nuxt.js实战

一、为什么选择Nuxt.js

多数是基于webpack构建的项目,编译出来的html文件,资源文件都被打包到js中,以下图404页面代码为例。从代码中可以看出,这样的页面是不利于 搜索引擎优化(SEO, Search Engine Optimization) ,并且 内容到达时间(time-to-content) (或称之为首屏渲染时长)也有很大的优化空间。为了解决以上问题,引入了 Nuxt.js 框架。

vue官网对于Nuxt.js也是很推荐的,除此之外,Nuxt.js的开发者积极活跃,版本迭代迅速。经过一系列rc版本后,终于在1月9日发布了 v1.0.0 正式版本

clipboard.png

图1. 使用webpack构建的HTML(代码已格式化)

clipboard.png

图2. 使用 Nuxt.js 构建的HTML(代码已格式化)

二、Nuxt.js 简介

Nuxt.js 是一个基于 Vue.js 的通用应用框架,它预设了利用 Vue.js 开发 服务端渲染(SSR, Server Side Render) 的应用所需要的各种配置,同时也可以一键生成静态站点。

作为框架,Nuxt.js 为 客户端/服务端 这种典型的应用架构模式提供了许多有用的特性,例如异步数据加载、中间件支持、布局支持等。区别于其他 vue SSR 框架,Nuxt.js 有以下比较明显的特性。

  • 自动代码分层
  • 强大的路由功能,支持异步数据(路由无需额外配置)
  • HTML头部标签管理(依赖 vue-meta 实现)
  • 内置 webpack 配置,无需额外配置

三、项目实战

1、项目创建

官方提供了基于 vue-cli 脚手架工具,常用的有如下三个,更多脚手架工具可以查看 nuxt-community 。本项目使用的是 express-template

vue init nuxt-community/starter-template <project-name>

vue init nuxt-community/koa-template <project-name>
 
vue init nuxt-community/express-template <project-name>

2、开发

1)目录结构

├─assets                 资源目录,未编译的静态资源如less、js
├─components             组件目录
├─layouts                布局目录
├─mock                   mock数据
├─node_modules           
├─pages                  页面目录
  ├─index.vue
  ├─....                 
├─plugins                插件
├─server                 express服务
├─static                 静态文件目录
├─store                  vuex store
├─utils                  工具方法

2)配置

Nuxt.js 默认的配置涵盖了大部分使用情形,也可通过修改 nuxt.config.js 来覆盖默认配置。

// nuxt.config.js 文件配置
const path = require('path')

module.exports = {
  // Headers of the page
  head: {
    title: '默认共用title',
    meta: [
      { charset: 'utf-8' },
      { 'http-equiv': 'pragma', content: 'no-cache' },
      { 'http-equiv': 'cache-control', content: 'no-cache' },
      { 'http-equiv': 'expires', content: '0' },
      { content: 'telephone=no', name: 'format-detection' }
    ],
    // html head 中创建 script 标签
    script: [
      { innerHTML: require('./assets/js/flexible_nuxt'), type: 'text/javascript', charset: 'utf-8'}
    ],
    // 不对<script>标签中内容做转义处理
    __dangerouslyDisableSanitizers: ['script']
  },
  // Global CSS
  css: ['~/assets/css/reset.css', '~/assets/css/main.less'],
  // Global env
  env: {
    __ENV: process.env.__ENV
  },
  build: {
    vendor: ['axios'],
    postcss: [
      require('postcss-px2rem')({
        remUnit: 75
      })
    ],
    extend (config, ctx) {
      if (ctx.isClient) {
        // 拓展 webpack 配置
        config.entry['polyfill'] = ['babel-polyfill']
        config.module.rules.push({
          enforce: 'pre',
          test: /\.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
        // 添加 alias 配置
        Object.assign(config.resolve.alias, {
          'utils': path.resolve(__dirname, 'utils')
        })
      }
    }
  },
  plugins: [{src: '~plugins/toast', ssr: false}, {src: '~plugins/dialog', ssr: false}]
}

HTML头部标签管理

Nuxt.js 通过 vue-meta 实现头部标签管理,在 nuxt.config.js 中的 head 配置。所有的页面都会走这个配置,如果想要修改某一页面的title,可以在 pages/**.vue 文件下,添加如下配置,这时该页面的标题就变成了“收车费”,其余页面还保持原有标题不变。

clipboard.png

在config header配置中, __dangerouslyDisableSanitizers: ['script'] 主要是为了不对<script>标签中内容做转义处理。看下面的例子?:

head: {
    title: 'myTitle',
    meta: [
      { charset: 'utf-8' },
      { 'http-equiv': 'pragma', content: 'no-cache' },
      { 'http-equiv': 'cache-control', content: 'no-cache' },
      { 'http-equiv': 'expires', content: '0' },
      { content: 'telephone=no', name: 'format-detection' }
    ],
    script: [
      { innerHTML: 'console.log("hello")', type: 'text/javascript', charset: 'utf-8'}
    ]
  },

生成 html:

<script data-n-head="true" type="text/javascript" charset="utf-8">console.log(&quot;hello&quot;)</script>

我们发现 vue-meta 把引号做了转义处理,加入 __dangerouslyDisableSanitizers: ['script'] 后,就不会再对这些字符做转义了,该字段使用需慎重!

3)路由

Nuxt.js 依据 pages 目录结构,自动生成 vue-router 模块的路由配置。

假设 pages 的目录结构如下:

clipboard.png

那么,Nuxt.js 自动生成的路由配置如下:

clipboard.png

嵌套路由

创建内嵌子路由,需要添加一个 Vue 文件,同时添加一个与该文件同名的目录用来存放子视图组件。在父级 Vue 文件内增加 <nuxt-child/> 用于显示子视图内容。

4)布局

Nuxt.js布局方式如下图所示:

clipboard.png

layouts对应目录中的layouts文件夹,默认pages下的页面走的都是 layouts/default.vue 布局方式,如下图。其中<nuxt/>可以类似vueslot插槽的概念,pages/**.vue中的内容会插在<nuxt/>内。

clipboard.png

此外,如果想要某一页面,不走默认布局方式,可以在vue文件中配置layouts,如下。

<script>
export default {
  layout: 'demo_layout',
  ...
}
</script>

5)vuex

在根目录创建 store 目录,就会默认引用 vuex 模块,除此之外,还进行了以下的操作:1)将 vuex 模块 加到 vendors 构建配置中去;2)设置 Vue 根实例的 store 配置项。

Nuxt.js 支持两种使用 store 的方式:

  • 普通方式:store/index.js 返回一个 Vuex.Store 实例
  • 模块方式:store 目录下的每个 .js 文件会被转换成为状态树指定命名的子模块 (当然,index 是根模块,相当于设置了namespaced: true)

Nuxt.js提供了模块方式的简单写法:使用状态树模块化的方式,store/index.js 不需要返回 Vuex.Store 实例,直接将 state、mutations 和 actions 暴露出来即可。示例如下:

export const state = () => ({
  accesstoken: ''
})

export const mutations = {
  setAccesstoken (state, accesstoken) {
    state.accesstoken = accesstoken
  }
}

6)异步数据 asyncData

Nuxt.js 增加了一个 asyncData 方法,用于 在设置组件数据 之前 能够异步获取 或 处理数据。
由于 asyncData 是在组件 初始化 之前被调用的,所以不能通过 this 引用组件的实例对象,可以使用上下文对象来实现某些功能,可参考 context api
示例?:

asyncData (params) {
  let accesstoken = params.route.query.accesstoken
  // request 基于 axios 封装的函数
  return request({
    url: '/drivers/banks',
    method: 'get',
    headers: {
      accesstoken
    }
  })
    .then(res => {
      let {
        bankInfo
      } = res.data
      return {
        banksData: bankInfo,
        accesstoken
      }
    })
    .catch(err => {
      return error({ message: 'accesstoken not found', statusCode: 404 })
    })
}

上述代码,会在 组件初始化 之前,请求'/drivers/banks'接口,接口返回的数据会 融合在 data 中,一并返回模版显示。在浏览器中,使用Vue DevTools可以清晰的查看到 banksData, accesstoken 都在data中。
在调试中发现,刷新页面时,该请求是在服务端发送的,由其他页面回退到该页面时,请求是在客户端发送的。

7)fecth方法

asyncData 方法类似,不同的是它不会设置组件的数据,作用是设置 store 数据。

五、总结

本项目在开发中,使用的是 1.0.0-rc9 版本,我们正在积极尝试迁移到 1.0.0 正式版本。但是,1.0.0-rc9 版本,未见明显问题,比较稳定,足以投入到生产中。
本文主要介绍 Nuxt.js 的特性,后面还会和大家分享踩的坑。文中有任何表述不清或不当的地方,欢迎大家批评指正。
此外,推荐我们的公众号 前端新视野 ,一个很认真的日刊公众号,欢迎扫描下方二维码关注!

clipboard.png

查看原文

xiaopengji 收藏了问题 · 2019-04-10

为什么提交表单不受同源政策限制

为什么浏览器规定提交表单不受同源政策限制呢?

xiaopengji 赞了问题 · 2019-04-10

解决为什么提交表单不受同源政策限制

为什么浏览器规定提交表单不受同源政策限制呢?

关注 6 回答 4

xiaopengji 收藏了文章 · 2019-04-04

微信,支付宝小程序实现原理概述

微信小程序

项目结构

项目结构

上图为微信小程序的项目结构,pages下面包含了小程序中的每一个页面,每一个页面由页面结构,页面样式,页面配置和逻辑代码四部分组成。

  • 页面结构

页面结构文件为index.wxml,通过微信自定义的标签来写。

  • 页面逻辑

页面逻辑通过JavaScript来书写。

  • 页面样式表

类似CSS文件,来定义页面内元素的样式。

  • 页面配置

页面内的权限等配置信息。

微信小程序的技术选型

小程序的定位特点是轻,快,针对这两个特点,在技术选型上,微信进行了一些考量。

渲染界面的技术
  • 用纯客户端原生技术来渲染

缺点:无法动态打包,动态下发。

  • 用纯 Web 技术来渲染

缺点:如果我们用纯 Web 技术来渲染小程序,在一些有复杂交互的页面上可能会面临一些性能问题,这是因为在 Web 技术中,UI渲染跟 JavaScript 的脚本执行都在一个单线程中执行,这就容易导致一些逻辑任务抢占UI渲染的资源。

  • 介于客户端原生技术与 Web 技术之间的,互相结合各自特点的技术来渲染

从渲染底层来看,PhoneGap与微信 JS-SDK 是类似的,它们最终都还是使用浏览器内核来渲染界面。而 RN 则不同,虽然是用 Web 相关技术来编写,同样是利用了 JavaScript 解释执行的特性,但 RN 在渲染底层是用客户端原生渲染的。我们选择类似于微信 JSSDK 这样的 Hybrid 技术,即界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力。同时,每个小程序页面都是用不同的WebView去渲染,这样可以提供更好的交互体验,更贴近原生体验,也避免了单个WebView的任务过于繁重。

微信没有选择RN的原因
  1. RN 所支持的样式是 CSS 的子集,会满足不了 Web 开发者日渐增长的需求,而对 RN 的改造具有不小的成本和风险。
  2. RN 现有能力下还存在的一些不稳定问题,比如性能、Bug等。RN 是把渲染工作全都交由客户端原生渲染,实际上一些简单的界面元素使用 Web 技术渲染完全能胜任,并且非常稳定。
  3. RN 存在一些不可预期的因素,比如之前出现的许可协议问题
原生组件的渲染方式

在安卓则是往 WebView 的 window 对象注入一个原生方法,最终会封装成 WeiXinJSBridge 这样一个兼容层,主要提供了调用(invoke)和监听(on)这两种方法。开发者插入一个原生组件,一般而言,组件运行的时候被插入到 DOM 树中,会调用客户端接口,通知客户端在哪个位置渲染一块原生界面。在后续开发者更新组件属性时,同样地,也会调用客户端提供的更新接口来更新原生界面的某些部分。

Web渲染带来的问题与解决
  • 提供干净纯粹的JavaScript执行环境

由于JavaScript的灵活性和浏览器的功能丰富,会导致很多不可控的隐私,因此,微信提供了一个单纯的JS执行环境,通过对于其中的控件也进行了自定义。因此完全采用这个沙箱环境不能有任何浏览器相关接口,只提供纯JavaScript 的解释执行环境,那么像HTML5中的ServiceWorker、WebWorker特性就符合这样的条件,这两者都是启用另一线程来执行 JavaScript。但是考虑到小程序是一个多 WebView 的架构,每一个小程序页面都是不同的WebView 渲染后显示的,在这个架构下我们不好去用某个WebView中的ServiceWorker去管理所有的小程序页面。得益于客户端系统有JavaScript 的解释引擎(在iOS下是用内置的 JavaScriptCore框架,在安卓则是用腾讯x5内核提供的JsCore环境),我们可以创建一个单独的线程去执行 JavaScript,在这个环境下执行的都是有关小程序业务逻辑的代码,也就是我们前面一直提到的逻辑层。而界面渲染相关的任务全都在WebView线程里执行,通过逻辑层代码去控制渲染哪些界面,那么这一层当然就是所谓的渲染层。这就是小程序双线程模型的由来。

  • 标签自定义

为了防止标签定义带来的一些问题,微信自定义了一套标签语言,WXML,这套标签语言经过编译之后,最终会生成Html。

渲染与逻辑的分离

上面是小程序的渲染技术的选型,在选型之后,由于渲染和逻辑不再同一个浏览器执行,一个在纯JS环境中,一个通过WebView渲染,因此小程序的运行环境分成渲染层和逻辑层,WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。

小程序的渲染层和逻辑层分别由2个线程管理:渲染层的界面使用了WebView 进行渲染;逻辑层采用JsCore线程运行JS脚本。一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端做中转,逻辑层发送网络请求也经由Native转发,小程序的通信模型如图所示。

逻辑渲染分离

数据驱动视图变化

在开发UI界面过程中,程序需要维护很多变量状态,同时要操作对应的UI元素。随着界面越来越复杂,我们需要维护很多变量状态,同时要处理很多界面上的交互事件,整个程序变得越来越复杂。通常界面视图和变量状态是相关联的,如果有某种“方法”可以让状态和视图绑定在一起(状态变更时,视图也能自动变更),那我们就可以省去手动修改视图的工作。

小程序的逻辑层和渲染层是分开的两个线程。在渲染层,宿主环境会把WXML转化成对应的JS对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的setData方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的Dom树上,渲染出正确的UI界面。

image.png

通过setData把msg数据从“Hello World”变成“Goodbye”,产生的JS对象对应的节点就会发生变化,此时可以对比前后两个JS对象得到变化的部分,然后把这个差异应用到原来的Dom树上,从而达到更新UI的目的,这就是“数据驱动”的原理。

数据驱动变化

数据驱动变化原理

事件的处理

UI界面的程序需要和用户互动,例如用户可能会点击你界面上某个按钮,又或者长按某个区域,这类反馈应该通知给开发者的逻辑层,需要将对应的处理状态呈现给用户。由于WebView现在具备的功能只是进行渲染,因此对于事件的分发处理,微信进行了特殊的处理,将所有的事件拦截后,丢到逻辑层交给JavaScript进行处理。

时间拦截与派发

事件的派发处理,具备事件捕获和冒泡两种机制。通过native传递给JSCore,通过JS来响应响应的事件之后,对Dom进行修改,改动会体现在虚拟Dom上,然后再进行真实的渲染。

事件冒泡

数据通信

小程序是基于双线程模型,那就意味着任何数据传递都是线程间的通信,也就是都会有一定的延时。这不像传统Web那样,当界面需要更新时,通过调用更新接口UI就会同步地渲染出来。在小程序架构里,这一切都会变成异步。

异步会使得各部分的运行时序变得复杂一些。比如在渲染首屏的时候,逻辑层与渲染层会同时开始初始化工作,但是渲染层需要有逻辑层的数据才能把界面渲染出来,如果渲染层初始化工作较快完成,就要等逻辑层的指令才能进行下一步工作。因此逻辑层与渲染层需要有一定的机制保证时序正确,

在每个小程序页面的生命周期中,存在着若干次页面数据通信。逻辑层向视图层发送页面数据(data和setData的内容),视图层向逻辑层反馈用户事件。

渲染生命周期

通过Json的方式进行数据的传递,提高性能的方式就是减少交互的数据量。

缓存机制

小程序宿主环境会管理不同小程序的数据缓存,不同小程序的本地缓存空间是分开的,每个小程序的缓存空间上限为10MB,如果当前缓存已经达到10MB,再通过wx.setStorage写入缓存会触发fail回调。

小程序的本地缓存不仅仅通过小程序这个维度来隔离空间,考虑到同一个设备可以登录不同微信用户,宿主环境还对不同用户的缓存进行了隔离,避免用户间的数据隐私泄露。

由于本地缓存是存放在当前设备,用户换设备之后无法从另一个设备读取到当前设备数据,因此用户的关键信息不建议只存在本地缓存,应该把数据放到服务器端进行持久化存储。

支付宝小程序

支付宝小程序简介

支付宝小程序的实现和微信小程序的实现方式大致是相同的,因此这里主要针对两者的差异性的地方。

支付宝小程序目录结构
项目目录结构

支付宝小程序业务架构图
小程序业务架构

在渲染引擎上面,支付宝小程序不仅提供 JavaScript+Webview 的方式,还提供 JavaScript+Native 的方式,在对性能要求较高的场景,可以选择 Native 的渲染模式,给用户更好的体验。

运行时架构

小程序编程模型是分为多个页面,每个页面有自己的 template、CSS 和 JS,实际在运行的时候,业务逻辑的 JS 代码是运行在独立的 JavaScript 引擎中,每个页面的 template 和 CSS 是运行在各自独立的 webview 里面,页面之间是通过函数 navigateTo 进行页面的切换。

每个 webview 里面的页面和公共的 JavaScript 引擎里面的逻辑的交互方式是通过消息服务,页面的一些事件都会通过这个消息通道传给 JavaScript 引擎运行环境,这个运行环境会响应这个事件,做一些 API 调用,可调到客户端支付宝小程序提供的一些能力,处理之后会把这个数据再重新发送给对应的页面渲染容器来处理,把数据和模板结合在一起来,在产生最终的用户界面。

小程序运行时

支付宝小程序虚拟机隔离

通常的做法是在 WebView 里面运行 render 的代码,然后另起一个线程运行 serviceworker,当 serviceworker 需要更新 dom 的时候把事件和数据通过 messagechannel 发送给 render 线程来执行,当业务需要传递到 render 层数据量较大,对象较复杂时,交互的性能就会比较差,因此针对这种情况我们提出一个优化的解决方案。

该方案将原始的 JS 虚拟机实例 (即 Isolate) 重新设计成了两个部分:Global Runtime 和 Local Runtime。

  • Global Runtime 部分是存放共享的装置和数据,全局一个实例。
  • Local Runtime 是存放实例自身相关的模块和私有数据,这些不会被共享。

在新的隔离模型下,webview 里面的 v8 实例就是一个 Local Runtime,worker 线程里面的 v8 实例也是一个 Local Runtime,在 worker 层和 render 层交互时,setData 对象的会直接创建在 Shared Heap 里面,因此 render 层的 Local Runtime 可以直接读到该对象,并且用于 render 层的渲染,减少了对象的序列化和网络传输,极大的提升了启动性能和渲染性能。

首屏速度优化

首屏启动流程

由于小程序启动是受到生命周期的控制,从 onLaunch -> onLoad -> onShow -> onReady -> 用户操作 -> 离开首页这个流程,在这个过程中的任意一个环节都有可能被客观或者主观的原因打断,也就有可能导致保存的离线页面不准确,在启动的时候给用户呈现错误的页面。

所以对于首页离线缓存渲染的效果,保存页面的时机很重要,我们提供让开发者可以配置的时机,配置的时机有两个:渲染完成和离开首页前。对于渲染完成就是首页渲染完成,用户还未执行任何的操作前把页面保存下来作为离线缓存的页面。离开首页前就是指用户在首页执行了一系列的操作后,跳转到其他页面前用户看到的页面保存下来作为离线缓存的页面。

首屏启动流程生命周期

对于闪屏问题发生的场景是因为缓存页面和真实渲染的页面是分离的,是两个独立的页面,缓存页面是静态的页面,真实的页面是通过 js 动态创建的页面,所以常规的做法就是当真实页面创建完成后替换缓存的页面,这样的情况下就会发生闪屏。

首页防闪屏直出

针对这个问题,我们是采用虚拟 dom 来解决,在加载缓存页面的时候把缓存页面放入初始的虚拟 dom 里面,真实页面创建后产生的虚拟 dom 跟缓存页面的虚拟 dom 进行 dom diff,把变化的内容通过 patch 传给浏览器内核,渲染对应的页面,这样就可以只更新局部有变化的页面内容,避免了整个页面的更新,也保证内容的准确性和实时性。

小程序下载加载流程

支付宝采用UC浏览器内核优势

1.图片内存:针对低端机,做了更严格的图片缓存限制,在保持性能体验的情况下,进一步限制图片缓存的使用;多个 webview 共用图片缓存池;全面支持 webp、apng 这种更节省内存和 size 的图片格式。

2.渲染内存:Webview 在不可见的状态下,原生的内存管理没有特殊处理,UC 内核会将不可见 webview 的渲染内存释放;渲染内存的合理设置与调优,避免滚动性能的下降和占用过多内存。

3.JS 内存:更合理地处理 v8 内存 gc,在启动时延时执行 full gc,避免影响启动的耗时。

4.峰值内存管理:系统在内存紧张时,会通知内核,UC 内核能够在系统低内存时释放非关键内存占用的模块,避免出现 oom,也避免过度释放带来的渲染黑块;在部分 oom 的情况,规避原生内核主动崩溃的逻辑,在内存极低的情况,部分功能不可用,而不是崩溃。

对我们的启示

  • 小程序存储管理

增加小程序的存储,包括内存和磁盘,可以缓存部分数据,增加页面直出速度。同时对于磁盘的管理,按照小程序账号双重维度进行划分。

  • 第三方业务接入能力限制

在支持第三方的接入之后,按照现有方式将会导致对于安全和第三方的行为完全不可控,可以参考微信,支付宝方式采用自定义标记语言的方式对标记语言做限制,并提供纯净的JS环境来进行JS环境的执行,WebView只负责渲染。

  • 首屏速度

参考支付宝方案,在加载的时候,现将老的页面呈现给用户,然后在新页面完成之后,计算差值,再进行显示。

  • Native绘制结合

Native绘制采用通过JS和Native通信的方式,将Native控价加入到布局的制定区域。

  • 网络请求发送托管

网络请求等全部交由Native托管,更好的控制网络请求,监控网络请求。

参考文章

查看原文

xiaopengji 收藏了文章 · 2019-02-12

CSS粘性定位是怎样工作的

翻译:疯狂的技术宅
原文:https://medium.com/@elad/css-...

本文首发微信公众号:jingchengyideng
欢迎关注,每天都给你推送新鲜的前端技术文章


浏览器对 CSS粘性定位有着非常好的支持,但很多开发者都没有用过它。

究其原因有两个:

第一,受到浏览器的良好支持需要漫长的等待:浏览器的支持往往需要很长的时间才能完成,到时候它的功能已经被人们遗忘了。

第二个原因是很多开发者并不能完全理解其工作原理背后的逻辑,这就是我的切入点。

clipboard.png

我假设你们都知道什么事CSS定位,不过还是先简单回顾一下比较好:

在3年前,有四种 CSS 定位: staticrelativeabsolutefixed

staticrelativeabsolutefixed之间主要的区别在于它们在DOM流中占用的空间。 staticrelative 会保留它们在文档流中的自然空间,而 absolutefixed 则不会 —— 它们的空间被移除而且具有浮动行为。

正如我下面将要解释的那样,新的粘性定位与所有类型都有相似之处。

我的第一个粘性定位

可能很多人都玩过粘性定位。我已经接触过一段时间了,直到我意识到自己并不是完全理解它。

在第一个例子中,大家很容易就能看明白 当视口到达定义的位置时,元素会被粘住。

例:

.some-component{
    position: sticky;
    top: 0;
} 

但问题是,它有时候能用,而有时却不起作用。 当它工作时,元素会粘住,但在滚动到其他部分,它会停止粘贴。 作为一个靠 CSS 混饭吃的人,我完全不能接受自己对这个问题是不理解的,所以我决定把粘性位置彻底搞清楚。

探索粘性定位

在摆弄它的过程中,我很快就注意到了:当一个具有 position:sticky 样式的元素被包装,且它是包装元素中唯一的元素时,这个被定义为 position:sticky 的元素就不会粘住。

<!-- DOESN'T WORK!!! -->
<style>
    .sticky{
        position: sticky;
        top: 0;
    }
</style>
<div class="wrapper">
   <div class="sticky">
        SOME CONTENT
   </div>
</div>

当我在包装元素中添加更多元素时,它就能开始正常工作了。

这是为什么?

这是因为当一个元素被赋予 position: sticky 样式时,粘性项目的容器是它可以粘贴的唯一区域。 粘性元素没有任何要浮动的元素,因为它只能浮动在同级元素上,作为唯一的子元素,它不能浮动。

怎样才能让 CSS 粘性定位起作用

CSS 粘性定位有两个主要部分,粘性元素(sticky item)粘性容器(sticky container)

粘性元素 —— 是我们用位置定义的 position: sticky 样式。 当视口位置与位置定义匹配时,该元素将会浮动,例如: top: 0px

例:

.some-component{
    position: sticky;
    top: 0px;
}

粘性容器 —— 是包装粘性元素的 HTML 元素。 这是粘性元素可以浮动的最大区域。

当你定义一个具有 position:sticky 样式的元素时,父元素会被自动定义为粘性容器!
记住这一点非常重要! 容器是粘性元素的范围,并且该元素无法离开其所在的粘性容器。

这就是为什么在前面的例子中,粘性元素没有被粘住的原因:这个粘性元素是粘性容器中唯一的子元素。

CSS 粘性定位的示意图:

粘性元素与粘性容器

粘性元素与粘性容器

查看在CodePen上的例子:

https://codepen.io/elad2412/p...

理解 CSS 的粘性行为

正如我前面说过的那样,CSS 粘性定位的行为与所有其他 CSS 定位不一样,但从另一个角度来说,它们也有一些相似之处。我来解释一下:

相对(或静态)—— 粘性定位元素类似于相对和静态位置,因为它保留了 DOM 中的自然间隙(留在流中)。
固定 —— 当元素被粘住时,它的行为与 position: fixed 完全相同,浮动在与视口的相同位置,并从流中移除。
绝对 —— 在粘贴区域的末尾,元素会停止,并堆叠在另一个元素的顶部,就像一个绝对定位的元素被放在一个 position: relative 容器内的行为。

粘在底部?

在大多数情况下,您可以使用粘性定位将元素粘贴到顶部,如下所示:

.component{
    position: sticky;
    top: 0;
}

这正是它被设计的初衷,而在此之前,只能使用JavaScript完成这个功能。

但你也可以使用它把元素粘到底部。 这意味着你可以把页脚定义为粘性,并且在向下滚动时使它看起来总是被粘在底部。 当到达粘性容器的末端时,元素会停在它的自然位置。 最好是在以粘性容器底部为自然位置的元素上使用它。

完整示例:

HTML

<main class="main-container">
  <header class="main-header">HEADER</header>
  <div class="main-content">MAIN CONTENT</div>
  <footer class="main-footer">FOOTER</footer>
</main>

CSS

.main-footer{     
     position: sticky; 
     bottom: 0;
}

去CodePen查看演示效果:https://codepen.io/elad2412/p...

我在开发中常用它来粘贴摘要表,也会用在粘性页脚导航上,这种方法非常适合。

浏览器支持

  • 除了老 IE 浏览器外,所有流行的现代浏览器都支持粘性定位。
  • 如果用在 Safari 浏览器上,你需要添加 -webkit 前缀。
position: -webkit-sticky; /* Safari */  
position: sticky;

超过86%的浏览器支持粘性定位

最后的话

本文到此就结束了,我希望你能喜欢这篇文章,并从我的经验中学习到一些东西。
如果你喜欢这篇文章,我会非常感谢你的掌声和分享 :-)


本文首发微信公众号:jingchengyideng

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章



查看原文

认证与成就

  • 获得 22 次点赞
  • 获得 16 枚徽章 获得 1 枚金徽章, 获得 2 枚银徽章, 获得 13 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-08-03
个人主页被 1.3k 人浏览