一般来说,项目的TS编译器配置全部存储在项目根目录下的tsconfig.json文件中。

编译器启动时,首先会读取tsconfig.json,以获取有关如何编译项目的说明(例如,要编译哪些源文件,在哪里存储输出等)。 不过,如果编译器配置文档保存在其他特殊位置,我们也可以通过使用-p选项告诉编译器配置文档的具体位置:

rn8VAJ.png

上图出现的比较常见的编译器/tsconfig选项:

1. “target”: “es5”

这个属性定义了你编译后的目标javascript版本,一般来说,我们需要让他编译为es5,这样就可以被主流浏览器解读了。当然,你说我的react代码不是给浏览器看的,比如说,你使用的是react-native做作手机app,那么这里的选项可以选择es6。除了es5和es6,我们还有很多其他常见的选项,ES5, ES6/ES2015, ES2016, ES2017, ES2018, ES2019, ES2020, ESNext,等等等等

2. “lib”: [“dom”, “dom.iterable”, “esnext”]

这个属性列出了编译期间需要被包括进来的库文件,通过这些库文件,告诉typescript编译器可以使用哪些功能。比如说,我们这里有一个dom的库文件,这个文件会告诉编译器dom api的接口,所以当我们在ts代码中使用dom的时候,比如说执行“document.getElementById("root")”这句话的时候,编译器就会知道该如何进行检查。如果我们不设置这个选项,那么编译器也有自己默认的库文件列表,一般来说是["dom", "es6","DOM.Iterable"]等等

3. “allowJs”: true

允许混合编译JavaScript文件

4. “esModuleInterop”: true

这个选项允许我们使用ES6的方式import默认文件。比如说,在没有开启这个选项时,我们需要这样写才能引用react:

import * as React from 'react'

但是当我们开启了这个选项以后,import方式就与普通的JavaScript没有区别了,可以写为:

import React from 'react'

这样处理项目引入会更自然。

5. “module”: “esnext”

这里配置的是我们代码的模块系统,比较常见的有Node.js的CommonJS系统,ES6标准的esnext系统,以及requirejs的AMD系统。我们这里使用的是ES6标准的esnext系统,不过如果把这里换成CommonJS也是可以的。

6. “moduleResolution”: “node”

这个选项决定了我们编译器的工作方式,也决定了我们各个文件之间调用、import的工作流程。这里曾经有两个选项,"node" and "classic",但是"classic"这个选项在2019年12月就已经废弃了。

7. “moduleResolution”: “node”

开启这个选项以后,编译器会将每个文件作为单独的模块来使用。

9. “jsx”: “react”,

显而易见,这个选项允许编译器支持编译react代码

10. “include”: [“src/*/”]

使用此选项列出我们需要编译的文件, “文件路径”选项需要文件的相对或绝对路径,例如:

"**" - 任意子目录
"*" - 任意文件名
"?" - 只要字符跟随“?”,这个字符就会被视为可忽略字符 (e.g., "src/.tsx?"则同时指代"src/.tsx"与"src/*.ts")

11. “files”: ["./file1.ts", “./file2.d.ts”, …]

使用此选项列出编译器应始终包含在编译中的文件。无论是否使用“exclude”选项,都将会编译使用此选项内包括的所有文件。

12. “exclude”: [“node_modules”, “**//.test.ts”]

此选项将会列出从编译中排除的文件。它与“include”选项采取相同的模式,我们以使用此选项来过滤使用“include”选项指定的文件。 但是, “exclude”选项不会影响“files”选项。

通常,我们会排除node_modules、测试文件、和编译输出目录。

如果省略此选项,编译器将使用“outDir”选项指定的文件夹。

如果没有同时指定“files”和“include”这两个选项,则编译器将编译根目录和任何子目录中的所有TS文件,但不包括使用“exclude”选项指定的文件。

13. "baseUrl": "./src"

指定项目的绝对路径,在import xxx from "module" 时,module路径默认从baseUrl开始

参考链接:Intro to the TSConfig Reference

今天开始准备重拾Vue,学习第一步打开官网文档,第二步照着撸#(赞一个)......
先上个文档,对着看效果更佳。

文档中说:当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。

这里我用官网上的例子撸了一遍

<div id="app">
  {{ message }}
 </div>
var data = {message: 'Hello Vue!', data: '我是data'};
var app = new Vue({
  el: '#app',
  data: data
});

结合文档我有以下的疑问,并做了一一验证

1. 我们自定义的data后加入属性是否也在Vue的响应式系统中?

不能。data需要在实例化Vue对象时就定义好,如果有后续用到的property,需要预先赋初值。

data.num = 1; // 在实例化后再添加属性,不ok,会报错

app.massage得到的是data.massage的值,那对Vue的实例app添加属性,是否有用呢?

app.num = 1; // 会和上面报一样的错误
//"[Vue warn]: Property or method 'b' is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties. (found in <Root>)"

2.app.$data.message能访问到属性么?

可以。

文档中说:除了数据 property,Vue 实例还暴露了一些有用的实例 property 与方法。它们都有前缀 $,以便与用户定义的 property 区分开来。

我对其做了一下验证

console.log(app.message === data.message && data.message === app.$data.message); // true

3.如果定义的data中有属性$data,那么app.$data访问的是Vue实例appdata还是自定义data中的属性$data

这么定义直接会报错,所以在自定义的data中属性名不要以$开头(为了避免属性名与Vue中定义的相同,经过测试如果$+在Vue实例中不存在的property不会报错)

var data = {message: 'Hello Vue!', data: '我是data', $data: '我是data中的$data'};
// Uncaught SyntaxError: Invalid or unexpected token 

4.app.messageapp.$data.messagedata.message 都能访问数据、修改数据使得视图产生"响应"么?

这三种写法都可以对数据进行访问、修改来使得视图发生变化

点击这里可在线编辑调试
1607151536GZ.png

JS判断数据类型和常用的标准内置对象

在了解如何判断JS的数据类型前我们必须要知道目前JS到底有几种数据类型。

JS的数据类型(8种)

  • 7种原始类型

    • Boolean

    ​ 布尔类型,只有两个值true和false

    • Null

    ​ Null类型,表示尚未创建的对象,空引用

    • Undefined

    ​ 未定义变量的初始值

    ​ 数字类型,它基于 IEEE 754 标准的双精度 64 位二进制格式

    • BigInt(ES10新增,V8引擎v6.7 默认启用对 BigInt 的支持)

    ​ 可以表示大于 253 - 1 的整数,即任意大的整数

    • String

    ​ 字符串类型,用于表示文本类型

    ​ 在ES5中,对象属性名都是字符串容易造成属性名冲突。为了避免这种情况的发生,ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。

  • Object

​ 对象,一组属性的集合。

JS数据类型的判断方法

  1. 使用typeof来判断:

typeof 操作符返回一个字符串,表示未经计算的操作数的类型。

typeof true //boolean
typeof null //object
typeof undefined //undefined
typeof 1 //number
typeof 1n //bigint
typeof '1' //string
typeof Symbol() //symbol
typeof Object() //object

由上面的结果可见,typeof不能判断Null类型和Object类型。因为Null类型本身也就是表示空对象,因此不能判断到底是Null类型还是Object类型倒也无伤大雅。完整的判断JS数据类型代码如下:

function type(o) {
    let s = typeof o;
    if(s === 'object') {
        return o == null ? 'null' : 'object'; 
    }else {
        return s;
    }
}

JS标准内置对象及判断方法

标准的内置对象例如 Array, Boolean, Date, Error, Function, JSON, Math, Number, Object, RegExp, String, Map, Set, WeakMap , WeakSet 以及其他对象。那么标准的内置对象和Object是什么关系呢?请看下方的验证

Set.prototype.__proto__ === Object.prototype // ture

这就整明白了,原来他们是在原型链上的,Set继承了Object,由此可以推断出,内置标准对象都是继承自Object(这里不做一一验证)。既然都在原型链上,那么判断一下子就简单了,这里需要用到instanceof

1.instanceof判断内置对象

instanceofinstanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上)。

if(typeof o === 'object' && o instanceof Array) {
    //o是数组
}else if(o instanceof Date) {
   //...
} // ...

通过这样的方法我们就能轻松判断了。

2.使用Object.prototype.toString方法来判断

除了instanceof,作为另一种方法我们也可以用Object.prototype.toString。其中 toString() 来获取每个对象的类型。为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为 thisArg。注:Object.toString不一定调用的原型上的,可以被对象自定义方法所覆盖

不同数据类型的Object.prototype.toString方法返回值如下。

  • 数值:返回[object Number]
  • 字符串:返回[object String]
  • 布尔值:返回[object Boolean]
  • undefined:返回[object Undefined]
  • null:返回[object Null]
  • 数组:返回[object Array]
  • arguments 对象:返回[object Arguments]
  • 函数:返回[object Function]
  • Error 对象:返回[object Error]
  • Date 对象:返回[object Date]
  • RegExp 对象:返回[object RegExp]
  • ...
Object.prototype.toString.call(2) // "[object Number]"
Object.prototype.toString.call('') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(Math) // "[object Math]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call([]) // "[object Array]"

利用这个特性我们可以写一个方法来专门判断JS的标准内置对象:

var type = function (o){
  var s = Object.prototype.toString.call(o);
  return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};

type({}); // "object"
type([]); // "array"
type(1); // "number"
type(null); // "null"
type(); // "undefined"
type(/abc/); // "regex"
type(new Date()); // "date"

用JS的二进制位运算符来取整

二进制位运算符介绍

  • 二进制或运算符(or):符号为|,表示若两个二进制位都为0,则结果为0,否则为1
  • 二进制与运算符(and):符号为&,表示若两个二进制位都为1,则结果为1,否则为0。
  • 二进制否运算符(not):符号为~,表示对一个二进制位取反。
  • 异或运算符(xor):符号为^,表示若两个二进制位不相同,则结果为1,否则为0。
  • 左移运算符(left shift):符号为<<,表示将一个数的二进制值连着符号位向左移,末尾补0
  • 右移运算符(right shift):符号为>>,表示将一个数的二进制值连着符号位向右移,正数头部补0,负数头部补1
  • 头部补零的右移运算符(zero filled right shift):符号为>>>,表示将一个数的二进制值连着符号位向右移,不管是正数负数都补0

这些位运算符直接处理每一个比特位(bit),所以是非常底层的运算,好处是速度极快,缺点是很不直观,许多场合不能使用它们,否则会使代码难以理解和查错。

为什么二进制运算符能取整

位运算符只对整数起作用,如果一个运算子不是整数,会自动转为整数后再执行。另外,虽然在 JavaScript 内部,数值都是以64位浮点数的形式储存,但是做位运算的时候,是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数。

二进制运算符取整的具体写法

// 二进制或运算符
1.2 | 0 //1

// 二进制与运算符
1.2 & 1 //1

// 二进制否运算符
~~1.2 //1

// 异或运算符
1.2 ^ 0 //1

// 左移运算符
1.2 >> 0 //1

// 右移运算符
1.2 << 0 //1

其他小技巧

  • 不借助中间变量交换两变量值
var a = 1;
var b = 2;
a ^= b, b ^= a, a ^= b;
a // 2
b // 1
  • 右移运算符可以模拟整除2的运算
12.1212 >> 1 //6
-12.1212 >> 1 //-6

层叠、优先级和继承

层叠

层叠的冲突解决需要考虑三个方面:

  • 样式表的来源
  • 选择器的优先级
  • 源码顺序

层叠的高级流程图.png

选择器优先级的顺序:

  • 如果ID选择器数量一样那么看类选择器数量
  • 如果类选择器数量一样那么看标签选择器数量
  • 如果前面两次比较都一样,那么拥有最多标签选择器数量的优先级最高

选择器优先级的量化计算:

可以将选择器的优先级量化为3位数的数字,例如A选择器有1个ID选择器、2个类选择器、2个标签选择器;B选择器有2个ID选择器、2个类选择器、2个标签选择器。那么A为122,B为222,显然B的优先级大于A。

所以如果要覆盖一个ID选择器的样式必须要使用另一个ID选择器

注:伪类选择器[:hover]和属性选择器[type="input"]的优先级相同

提升选择器的优先级比简单的使用!important更加有效,不推荐简单的使用!important去改变优先级