赞
踩
2021前端开发最全vuejs面试题(持续更新)_勤动手多动脑少说多做厚积薄发-CSDN博客1、什么是MVVM?答:MVVM是是Model-View-ViewModel的缩写,Model代表数据模型,定义数据操作的业务逻辑,View代表视图层,负责将数据模型渲染到页面上,ViewModel通过双向绑定把View和Model进行同步交互,不需要手动操作DOM的一种设计思想。2、怎么定义vue-router的动态路由?怎么获取传过来的动态参数?答:在router目录下的index.js文件中,对path属性加上/:id。 使用router对象的params.id3、vue-router有哪https://blog.csdn.net/qq_22182989/article/details/106795502JS必看面试题_勤动手多动脑少说多做厚积薄发-CSDN博客2019JS必看面试题1. javascript的typeof返回哪些数据类型.答案:string,boolean,number,undefined,function,object2. 例举3种强制类型转换和2种隐式类型转换?答案:强制(parseInt,parseFloat,number)隐式(== ===)3. split() join() 的区别答案:前者是将字符串切割成数组的形式,后者是将数组转换成字符串4. 数组方法pop() push() unshift() shttps://blog.csdn.net/qq_22182989/article/details/106792847
前端面试题汇总资料:
答:push、pop、shift、unshift、join、sort、concat、reverse、splice、slice、indexOf等
详细资料:
1. 相同点
if 判断语句中,两者都会被转换为false
2. 不同点
Number转换的值不同,Number(null)输出为0, Number(undefined)输出为NaN
null表示一个值被定义了,但是这个值是空值
作为函数的参数,表示函数的参数不是对象
作为对象原型链的终点 (Object.getPrototypeOf(Object.prototype))
定义一个值为null是合理的,但定义为undefined不合理(var name = null)
undefined表示缺少值,即此处应该有值,但是还没有定义
变量被声明了还没有赋值,就为undefined
调用函数时应该提供的参数还没有提供,该参数就等于undefined
对象没有赋值的属性,该属性的值就等于undefined
函数没有返回值,默认返回undefined
题目解析
先看代码
var Func=function(){
};
var func=new Func ();
new共经过了4几个阶段
1、创建一个空对象
varobj=new Object();
2、设置原型链
obj.__proto__= Func.prototype;
3、让Func中的this指向obj,并执行Func的函数体。
var result =Func.call(obj);
4、判断Func的返回值类型:
如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象。
if (typeof(result) == "object"){
func=result;
}
else{
func=obj;;
}
1、call,apply都属于Function.prototype的一个方法,它是JavaScript引擎内在实现的,因为属于Function.prototype,所以每个Function对象实例(就是每个方法)都有call,apply属性。既然作为方法的属性,那它们的使用就当然是针对方法的了,这两个方法是容易混淆的,因为它们的作用一样,只是使用方式不同。
2、语法:foo.call(this, arg1,arg2,arg3) == foo.apply(this, arguments) == this.foo(arg1, arg2, arg3);
3、相同点:两个方法产生的作用是完全一样的。
4、不同点:方法传递的参数不同。
包括:
页面级优化和代码级优化。然后细分
页面级优化:比如:
1.减少 HTTP请求数
2.将外部脚本置底(将脚本内容在页面信息内容加载后再加载)
3.异步执行 inline脚本(其实原理和上面是一样,保证脚本在页面内容后面加载。)
4. Lazy Load Javascript(只有在需要加载的时候加载,在一般情况下并不加载信息内容。)
5. 将 CSS放在 HEAD中
6. 异步请求 Callback(就是将一些行为样式提取出来,慢慢的加载信息的内容)
7. 减少不必要的 HTTP跳转
8. 避免重复的资源请求
代码级优化:比如:
一般有以下几种方式:
defer 属性
async 属性
动态创建DOM方式
使用jQuery的getScript方法
使用setTimeout延迟方法
让JS最后加载
参考:
浏览器渲染的整个流程
浏览器的整个流程如上图所示。
1、 首先当用户输入一个URL的时候,浏览器就会发送一个请求,请求URL对应的资源。
2、 然后浏览器的HTML解析器会将这个文件解析,并且构建成一棵DOM树。
3、 在构建DOM树的时候,遇到JS和CSS元素,HTML解析器就换将控制权转让给JS解析器或者是CSS解析器。
4、 JS解析器或者是CSS解析器解析完这个元素时候,HTML又继续解析下个元素,直到整棵DOM树构建完成。
5、 DOM树构建完之后,浏览器把DOM树中的一些不可视元素去掉,然后与CSSOM合成一棵render树。
6、 接着浏览器根据这棵render树,计算出各个节点(元素)在屏幕的位置。这个过程叫做layout,输出的是一棵layout树。
7、 最后浏览器根据这棵layout树,将页面渲染到屏幕上去。
资料:
我们来看看ES6都做了哪些扩展。
变量的解构赋值
字符串,数值的扩展:
数组,对象,函数的扩展:
资料:
JavaScript ES6 (w3schools.com)https://www.w3schools.com/js/js_es6.asp
资料:
CSS垂直居中,你会多少种写法?_携手天下-CSDN博客https://blog.csdn.net/u010419337/article/details/89529541
通过Object.definerProperty来劫持各个数据的属性的setter和getter,在数据变化时,发布消息给依赖收集器,通知观察者去执行回调函数,达到视图更新的效果。(但是使用Object.definerProperty实现监听时是有一些痛点的,比如,①无法监测数组下标变化,导致数组删除或者插入元素时,数组的变化无法实时响应;②只能对对象的属性进行监测,当对象深度比较深时,只能遍历每个属性来实现监听。vue3.0采用的Proxy,就完全避开了Object.definerProperty方法的这些痛点)
props
、emit
、vuex
、路由传参、通过本地存储传参、vue-bus
(事件巴士)、$refs
、$children
、$parent
在严格模式下vuex中的state对象中的属性是不能随意更改的,但是在表单处理时使用v-model时用户可以随意更改数据,如果vuex中的state中的属性直接绑在v-model中时会抛出一个错误。解决办法
beforeCreate:初始化事件,进行数据观测。
created:data数据进行绑定。
beforeMount:虚拟DOM替换真实DOM。
mounted:将DOM元素挂载到页面。
beforeUpdate: data数据更新之前。
updated: data数据更新完成之后。
beforeDestroy:在实例销毁之前调用,所有实例仍可以调用。
destroyed:在实例销毁之后调用,所有实例被销毁。
懒加载即在需要的时候才进行加载,随用随载。在单页面应用中,如果没有应用懒加载,webpack打包后的文件会非常大,导致第一次进入首页时,加载时间过长,不利于用户体验。而运用懒加载可以将页面进行划分,需要的时候才加载页面,可以有效的分担首页所承受的加载压力,有效减少了加载用时。
给style标签添加scoped属性
Object.definerProperty是无法对数组和对象进行劫持监听的,所以vue3.0以下(不包括3.0)版本,对数组的监听是直接重写了push、shift……等Array常用的数组操作。
var存在变量声明提升,var在{}中是没有作用域的,var定义的变量名可以重复命名。而这些let和const都做了限制
map和forEach都能达到遍历数组的目的。不过map会返回一个新的和原来数组长度一样的数组,而forEech没有返回值
JavaScript共有八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。
其中 Symbol 和 BigInt 是ES6 中新增的数据类型:
这些数据可以分为原始数据类型和引用数据类型:
两种类型的区别在于存储位置的不同:
堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:
在操作系统中,内存被分为栈区和堆区:
(1)typeof
- console.log(typeof 2); // number
- console.log(typeof true); // boolean
- console.log(typeof 'str'); // string
- console.log(typeof []); // object
- console.log(typeof function(){}); // function
- console.log(typeof {}); // object
- console.log(typeof undefined); // undefined
- console.log(typeof null); // object
- 复制代码
其中数组、对象、null都会被判断为object,其他判断都正确。
(2)instanceof
instanceof
可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。
- console.log(2 instanceof Number); // false
- console.log(true instanceof Boolean); // false
- console.log('str' instanceof String); // false
-
- console.log([] instanceof Array); // true
- console.log(function(){} instanceof Function); // true
- console.log({} instanceof Object); // true
- 复制代码
可以看到,instanceof
只能正确判断引用数据类型,而不能判断基本数据类型。instanceof
运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype
属性。
(3) constructor
- console.log((2).constructor === Number); // true
- console.log((true).constructor === Boolean); // true
- console.log(('str').constructor === String); // true
- console.log(([]).constructor === Array); // true
- console.log((function() {}).constructor === Function); // true
- console.log(({}).constructor === Object); // true
- 复制代码
constructor
有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor
对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor
就不能用来判断数据类型了:
- function Fn(){};
-
- Fn.prototype = new Array();
-
- var f = new Fn();
-
- console.log(f.constructor===Fn); // false
- console.log(f.constructor===Array); // true
- 复制代码
(4)Object.prototype.toString.call()
Object.prototype.toString.call()
使用 Object 对象的原型方法 toString 来判断数据类型:
- var a = Object.prototype.toString;
-
- console.log(a.call(2));
- console.log(a.call(true));
- console.log(a.call('str'));
- console.log(a.call([]));
- console.log(a.call(function(){}));
- console.log(a.call({}));
- console.log(a.call(undefined));
- console.log(a.call(null));
- 复制代码
同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
这是因为toString是Object的原型方法,而Array、function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object原型上的toString方法。
- Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
- 复制代码
- obj.__proto__ === Array.prototype;
- 复制代码
- Array.isArrray(obj);
- 复制代码
- obj instanceof Array
- 复制代码
- Array.prototype.isPrototypeOf(obj)
- 复制代码
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。
undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
typeof null 的结果是Object。
在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:
- 000: object - 当前存储的数据指向一个对象。
- 1: int - 当前存储的数据是一个 31 位的有符号整数。
- 010: double - 当前存储的数据指向一个双精度的浮点数。
- 100: string - 当前存储的数据指向一个字符串。
- 110: boolean - 当前存储的数据是布尔值。
- 复制代码
如果最低位是 1,则类型标签标志位的长度只有一位;如果最低位是 0,则类型标签标志位的长度占三位,为存储其他四种数据类型提供了额外两个 bit 的长度。
有两种特殊数据类型:
那也就是说null的类型标签也是000,和Object的类型标签一样,所以会被判定为Object。
instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。
- function myInstanceof(left, right) {
- // 获取对象的原型
- let proto = Object.getPrototypeOf(left)
- // 获取构造函数的 prototype 对象
- let prototype = right.prototype;
-
- // 判断构造函数的 prototype 对象是否在对象的原型链上
- while (true) {
- if (!proto) return false;
- if (proto === prototype) return true;
- // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
- proto = Object.getPrototypeOf(proto);
- }
- }
- 复制代码
在开发过程中遇到类似这样的问题:
- let n1 = 0.1, n2 = 0.2
- console.log(n1 + n2) // 0.30000000000000004
- 复制代码
这里得到的不是想要的结果,要想等于0.3,就要把它进行转化:
- (n1 + n2).toFixed(2) // 注意,toFixed为四舍五入
- 复制代码
toFixed(num)
方法可把 Number 四舍五入为指定小数位数的数字。那为什么会出现这样的结果呢?
计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和。0.1的二进制是0.0001100110011001100...
(1100循环),0.2的二进制是:0.00110011001100...
(1100循环),这两个数的二进制都是无限循环的数。那JavaScript是如何处理无限循环的二进制小数呢?
一般我们认为数字包括整数和小数,但是在 JavaScript 中只有一种数字类型:Number,它的实现遵循IEEE 754标准,使用64位固定长度来表示,也就是标准的double双精度浮点数。在二进制科学表示法中,双精度浮点数的小数部分最多只能保留52位,再加上前面的1,其实就是保留53位有效数字,剩余的需要舍去,遵从“0舍1入”的原则。
根据这个原则,0.1和0.2的二进制数相加,再转化为十进制数就是:0.30000000000000004
。
下面看一下双精度数是如何保存的:
对于0.1,它的二进制为:
- 0.00011001100110011001100110011001100110011001100110011001 10011...
- 复制代码
转为科学计数法(科学计数法的结果就是浮点数):
- 1.1001100110011001100110011001100110011001100110011001*2^-4
- 复制代码
可以看出0.1的符号位为0,指数位为-4,小数位为:
- 1001100110011001100110011001100110011001100110011001
- 复制代码
那么问题又来了,指数位是负数,该如何保存呢?
IEEE标准规定了一个偏移量,对于指数部分,每次都加这个偏移量进行保存,这样即使指数是负数,那么加上这个偏移量也就是正数了。由于JavaScript的数字是双精度数,这里就以双精度数为例,它的指数部分为11位,能表示的范围就是0~2047,IEEE固定双精度数的偏移量为1023。
-1022~1013
。对于上面的0.1的指数位为-4,-4+1023 = 1019 转化为二进制就是:1111111011
.
所以,0.1表示为:
- 0 1111111011 1001100110011001100110011001100110011001100110011001
- 复制代码
说了这么多,是时候该最开始的问题了,如何实现0.1+0.2=0.3呢?
对于这个问题,一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON
属性,而它的值就是2-52,只要判断0.1+0.2-0.3
是否小于Number.EPSILON
,如果小于,就可以判断为0.1+0.2 ===0.3
- function numberepsilon(arg1,arg2){
- return Math.abs(arg1 - arg2) < Number.EPSILON;
- }
-
- console.log(numberepsilon(0.1 + 0.2, 0.3)); // true
- 复制代码
因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。表达式 void ___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0 来获得 undefined。
NaN 指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。
- typeof NaN; // "number"
- 复制代码
NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN !== NaN 为 true。
对于 ==
来说,如果对比双方的类型不一样,就会进行类型转换。假如对比 x
和 y
是否相同,就会进行如下判断流程:
null
和 undefined
,是的话就会返回 true
string
和 number
,是的话就会将字符串转换为 number
- 1 == '1'
- ↓
- 1 == 1
- 复制代码
boolean
,是的话就会把 boolean
转为 number
再进行判断- '1' == true
- ↓
- '1' == 1
- ↓
- 1 == 1
- 复制代码
object
且另一方为 string
、number
或者 symbol
,是的话就会把 object
转为原始类型再进行判断- '1' == { name: 'js' } ↓'1' == '[object Object]'
- 复制代码
其流程图如下:
为了将值转换为相应的基本类型值,抽象操作 ToPrimitive 会首先(通过内部操作 DefaultValue)检查该值是否有valueOf()方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用 toString() 的返回值(如果存在)来进行强制类型转换。
如果 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
以下这些是假值: • undefined • null • false • +0、-0 和 NaN • ""
假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。
|| 和 && 首先会对第一个操作数执行条件判断,如果其不是布尔值就先强制转换为布尔类型,然后再执行条件判断。
|| 和 && 返回它们其中一个操作数的值,而非条件判断的结果
在 JavaScript 中,基本类型是没有属性和方法的,但是为了便于操作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在后台隐式地将基本类型的值转换为对象,如:
- const a = "abc";
- a.length; // 3
- a.toUpperCase(); // "ABC"
- 复制代码
在访问'abc'.length
时,JavaScript 将'abc'
在后台转换成String('abc')
,然后再访问其length
属性。
JavaScript也可以使用Object
函数显式地将基本类型转换为包装类型:
- var a = 'abc'
- Object(a) // String {"abc"}
- 复制代码
也可以使用valueOf
方法将包装类型倒转成基本类型:
- var a = 'abc'
- var b = Object(a)
- var c = b.valueOf() // 'abc'
- 复制代码
看看如下代码会打印出什么:
- var a = new Boolean( false );
- if (!a) {
- console.log( "Oops" ); // never runs
- }
- 复制代码
答案是什么都不会打印,因为虽然包裹的基本类型是false
,但是false
被包裹成包装类型后就成了对象,所以其非值为false
,所以循环体中的内容不会运行。
首先要介绍ToPrimitive
方法,这是 JavaScript 中每个值隐含的自带的方法,用来将值 (无论是基本类型值还是对象)转换为基本类型值。如果值为基本类型,则直接返回值本身;如果值为对象,其看起来大概是这样:
- /**
- * @obj 需要转换的对象
- * @type 期望的结果类型
- */
- ToPrimitive(obj,type)
- 复制代码
type
的值为number
或者string
。
(1)当type
为number
时规则如下:
obj
的valueOf
方法,如果为原始值,则返回,否则下一步;obj
的toString
方法,后续同上;TypeError
异常。(2)当type
为string
时规则如下:
obj
的toString
方法,如果为原始值,则返回,否则下一步;obj
的valueOf
方法,后续同上;TypeError
异常。可以看出两者的主要区别在于调用toString
和valueOf
的先后顺序。默认情况下:
type
默认为string
;type
默认为number
。总结上面的规则,对于 Date 以外的对象,转换为基本类型的大概规则可以概括为一个函数:
- var objToNumber = value => Number(value.valueOf().toString())
- objToNumber([]) === 0
- objToNumber({}) === NaN
- 复制代码
而 JavaScript 中的隐式类型转换主要发生在+、-、*、/
以及==、>、<
这些运算符之间。而这些运算符只能操作基本类型值,所以在进行这些运算前的第一步就是将两边的值用ToPrimitive
转换成基本类型,再进行操作。
以下是基本类型的值在不同操作符的情况下隐式转换的规则 (对于对象,其会被ToPrimitive
转换成基本类型,所以最终还是要应用基本类型转换规则):
+
操作符+
操作符的两边有至少一个string
类型变量时,两边的变量都会被隐式转换为字符串;其他情况下两边的变量都会被转换为数字。
- 1 + '23' // '123'
- 1 + false // 1
- 1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number
- '1' + false // '1false'
- false + true // 1
- 复制代码
-
、*
、\
操作符NaN
也是一个数字
- 1 * '23' // 23
- 1 * false // 0
- 1 / 'aa' // NaN
- 复制代码
==
操作符操作符两边的值都尽量转成number
:
- 3 == true // false, 3 转为number为3,true转为number为1
- '0' == false //true, '0'转为number为0,false转为number为0
- '0' == 0 // '0'转为number为0
- 复制代码
<
和>
比较符如果两边都是字符串,则比较字母表顺序:
- 'ca' < 'bd' // false
- 'a' < 'b' // true
- 复制代码
其他情况下,转换为数字再比较:
- '12' < 13 // true
- false > -1 // true
- 复制代码
以上说的是基本类型的隐式转换,而对象会被ToPrimitive
转换为基本类型再进行转换:
- var a = {}
- a > 2 // false
- 复制代码
其对比过程如下:
- a.valueOf() // {}, 上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
- a.toString() // "[object Object]",现在是一个字符串了
- Number(a.toString()) // NaN,根据上面 < 和 > 操作符的规则,要转换成数字
- NaN > 2 //false,得出比较结果
- 复制代码
又比如:
- var a = {name:'Jack'}
- var b = {age: 18}
- a + b // "[object Object][object Object]"
- 复制代码
运算过程如下:
- a.valueOf() // {},上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
- a.toString() // "[object Object]"
- b.valueOf() // 同理
- b.toString() // "[object Object]"
- a + b // "[object Object][object Object]"
- 复制代码
+
操作符什么时候用于字符串的拼接?根据 ES5 规范,如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+ 将进行拼接操作。如果其中一个操作数是对象(包括数组),则首先对其调用 ToPrimitive 抽象操作,该抽象操作再调用 [[DefaultValue]],以数字作为上下文。如果不能转换为字符串,则会将其转换为数字类型来进行计算。
简单来说就是,如果 + 的其中一个操作数是字符串(或者通过以上步骤最终得到字符串),则执行字符串拼接,否则执行数字加法。
那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字。
JavaScript中Number.MAX_SAFE_INTEGER表示最⼤安全数字,计算结果是9007199254740991,即在这个数范围内不会出现精度丢失(⼩数除外)。但是⼀旦超过这个范围,js就会出现计算不准确的情况,这在⼤数计算的时候不得不依靠⼀些第三⽅库进⾏解决,因此官⽅提出了BigInt来解决此问题。
扩展运算符:
- let outObj = {
- inObj: {a: 1, b: 2}
- }
- let newObj = {...outObj}
- newObj.inObj.a = 2
- console.log(outObj) // {inObj: {a: 2, b: 2}}
- 复制代码
Object.assign():
- let outObj = {
- inObj: {a: 1, b: 2}
- }
- let newObj = Object.assign({}, outObj)
- newObj.inObj.a = 2
- console.log(outObj) // {inObj: {a: 2, b: 2}}
- 复制代码
可以看到,两者都是浅拷贝。
(1)块级作用域: 块作用域由 { }
包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:
(2)变量提升: var存在变量提升,let和const不存在变量提升,即在变量只能在声明之后使用,否在会报错。
(3)给全局添加属性: 浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。
(4)重复声明: var声明变量时,可以重复声明变量,后声明的同名变量会覆盖之前声明的遍历。const和let不允许重复声明变量。
(5)暂时性死区: 在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区。使用var声明的变量不存在暂时性死区。
(6)初始值设置: 在变量声明时,var 和 let 可以不用设置初始值。而const声明变量必须设置初始值。
(7)指针指向: let和const都是ES6新增的用于创建变量的语法。 let创建的变量是可以更改指针指向(可以重新赋值)。但const声明的变量是不允许改变指针的指向。
区别 | var | let | const |
---|---|---|---|
是否有块级作用域 | × | ✔️ | ✔️ |
是否存在变量提升 | ✔️ | × | × |
是否添加全局属性 | ✔️ | × | × |
能否重复声明变量 | ✔️ | × | × |
是否存在暂时性死区 | × | ✔️ | ✔️ |
是否必须设置初始值 | × | × | ✔️ |
能否改变指针指向 | ✔️ | ✔️ | × |
const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量。
但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。
箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。
new操作符的实现步骤如下:
所以,上面的第二、三步,箭头函数都是没有办法执行的。
(1)箭头函数比普通函数更加简洁
- let fn = () => void doesNotReturn();
- 复制代码
(2)箭头函数没有自己的this
箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。
(3)箭头函数继承来的this指向永远不会改变
- var id = 'GLOBAL';
- var obj = {
- id: 'OBJ',
- a: function(){
- console.log(this.id);
- },
- b: () => {
- console.log(this.id);
- }
- };
- obj.a(); // 'OBJ'
- obj.b(); // 'GLOBAL'
- new obj.a() // undefined
- new obj.b() // Uncaught TypeError: obj.b is not a constructor
- 复制代码
对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号{}
是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。
(4)call()、apply()、bind()等方法不能改变箭头函数中this的指向
- var id = 'Global';
- let fun1 = () => {
- console.log(this.id)
- };
- fun1(); // 'Global'
- fun1.call({id: 'Obj'}); // 'Global'
- fun1.apply({id: 'Obj'}); // 'Global'
- fun1.bind({id: 'Obj'})(); // 'Global'
- 复制代码
(5)箭头函数不能作为构造函数使用
构造函数在new的步骤在上面已经说过了,实际上第二步就是将函数中的this指向该对象。 但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。
(6)箭头函数没有自己的arguments
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。
(7)箭头函数没有prototype
(8)箭头函数不能用作Generator函数,不能使用yeild关键字
箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。
可以⽤Babel理解⼀下箭头函数:
- // ES6
- const obj = {
- getArrow() {
- return () => {
- console.log(this === obj);
- };
- }
- }
- 复制代码
转化后:
- // ES5,由 Babel 转译
- var obj = {
- getArrow: function getArrow() {
- var _this = this;
- return function () {
- console.log(_this === obj);
- };
- }
- };
- 复制代码
(1)对象扩展运算符
对象的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。
- let bar = { a: 1, b: 2 };
- let baz = { ...bar }; // { a: 1, b: 2 }
- 复制代码
上述方法实际上等价于:
- let bar = { a: 1, b: 2 };
- let baz = Object.assign({}, bar); // { a: 1, b: 2 }
- 复制代码
Object.assign
方法用于对象的合并,将源对象(source)
的所有可枚举属性,复制到目标对象(target)
。Object.assign
方法的第一个参数是目标对象,后面的参数都是源对象。(如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性)。
同样,如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
- let bar = {a: 1, b: 2};
- let baz = {...bar, ...{a:2, b: 4}}; // {a: 2, b: 4}
- 复制代码
利用上述特性就可以很方便的修改对象的部分属性。在redux
中的reducer
函数规定必须是一个纯函数,reducer
中的state
对象要求不能直接修改,可以通过扩展运算符把修改路径的对象都复制一遍,然后产生一个新的对象返回。
需要注意:扩展运算符对对象实例的拷贝属于浅拷贝。
(2)数组扩展运算符
数组的扩展运算符可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。
- console.log(...[1, 2, 3])
- // 1 2 3
- console.log(...[1, [2, 3, 4], 5])
- // 1 [2, 3, 4] 5
- 复制代码
下面是数组的扩展运算符的应用:
- function add(x, y) {
- return x + y;
- }
- const numbers = [1, 2];
- add(...numbers) // 3
- 复制代码
- const arr1 = [1, 2];
- const arr2 = [...arr1];
- 复制代码
要记住:扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中,这里参数对象是个数组,数组里面的所有对象都是基础数据类型,将所有基础数据类型重新拷贝到新的数组中。
如果想在数组内合并数组,可以这样:
- const arr1 = ['two', 'three'];const arr2 = ['one', ...arr1, 'four', 'five'];// ["one", "two", "three", "four", "five"]
- 复制代码
- const [first, ...rest] = [1, 2, 3, 4, 5];first // 1rest // [2, 3, 4, 5]
- 复制代码
需要注意:如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
- const [...rest, last] = [1, 2, 3, 4, 5]; // 报错const [first, ...rest, last] = [1, 2, 3, 4, 5]; // 报错
- 复制代码
- [...'hello'] // [ "h", "e", "l", "l", "o" ]
- 复制代码
比较常见的应用是可以将某些数据结构转为数组:
- // arguments对象
- function foo() {
- const args = [...arguments];
- }
- 复制代码
用于替换es5
中的Array.prototype.slice.call(arguments)
写法。
Math
函数获取数组中特定的值- const numbers = [9, 4, 7, 1];
- Math.min(...numbers); // 1
- Math.max(...numbers); // 9
- 复制代码
在 Vue3.0 中通过 Proxy
来替换原本的 Object.defineProperty
来实现数据响应式。
Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
- let p = new Proxy(target, handler)
- 复制代码
target
代表需要添加代理的对象,handler
用来自定义对象中的操作,比如可以用来自定义 set
或者 get
函数。
下面来通过 Proxy
来实现一个数据响应式:
- let onWatch = (obj, setBind, getLogger) => {
- let handler = {
- get(target, property, receiver) {
- getLogger(target, property)
- return Reflect.get(target, property, receiver)
- },
- set(target, property, value, receiver) {
- setBind(value, property)
- return Reflect.set(target, property, value)
- }
- }
- return new Proxy(obj, handler)
- }
- let obj = { a: 1 }
- let p = onWatch(
- obj,
- (v, property) => {
- console.log(`监听到属性${property}改变为${v}`)
- },
- (target, property) => {
- console.log(`'${property}' = ${target[property]}`)
- }
- )
- p.a = 2 // 监听到属性a改变
- p.a // 'a' = 2
- 复制代码
在上述代码中,通过自定义 set
和 get
函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。
当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要在 get
中收集依赖,在 set
派发更新,之所以 Vue3.0 要使用 Proxy
替换原本的 API 原因在于 Proxy
无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy
可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。
解构是 ES6 提供的一种新的提取数据的模式,这种模式能够从对象或数组里有针对性地拿到想要的数值。 1)数组的解构 在解构数组时,以元素的位置为匹配条件来提取想要的数据的:
- const [a, b, c] = [1, 2, 3]
- 复制代码
最终,a、b、c分别被赋予了数组第0、1、2个索引位的值: 数组里的0、1、2索引位的元素值,精准地被映射到了左侧的第0、1、2个变量里去,这就是数组解构的工作模式。还可以通过给左侧变量数组设置空占位的方式,实现对数组中某几个元素的精准提取:
- const [a,,c] = [1,2,3]
- 复制代码
通过把中间位留空,可以顺利地把数组第一位和最后一位的值赋给 a、c 两个变量:
2)对象的解构 对象解构比数组结构稍微复杂一些,也更显强大。在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。现在定义一个对象:
- const stu = {
- name: 'Bob',
- age: 24
- }
- 复制代码
假如想要解构它的两个自有属性,可以这样:
- const { name, age } = stu
- 复制代码
这样就得到了 name 和 age 两个和 stu 平级的变量:
注意,对象解构严格以属性名作为定位依据,所以就算调换了 name 和 age 的位置,结果也是一样的:
- const { age, name } = stu
- 复制代码
有时会遇到一些嵌套程度非常深的对象:
- const school = {
- classes: {
- stu: {
- name: 'Bob',
- age: 24,
- }
- }
- }
- 复制代码
像此处的 name 这个变量,嵌套了四层,此时如果仍然尝试老方法来提取它:
- const { name } = school
- 复制代码
显然是不奏效的,因为 school 这个对象本身是没有 name 这个属性的,name 位于 school 对象的“儿子的儿子”对象里面。要想把 name 提取出来,一种比较笨的方法是逐层解构:
- const { classes } = school
- const { stu } = classes
- const { name } = stu
- name // 'Bob'
- 复制代码
但是还有一种更标准的做法,可以用一行代码来解决这个问题:
- const { classes: { stu: { name } }} = school
-
- console.log(name) // 'Bob'
- 复制代码
可以在解构出来的变量名右侧,通过冒号+{目标属性名}这种形式,进一步解构它,一直解构到拿到目标数据为止。
扩展运算符被用在函数形参上时,它还可以把一个分离的参数序列整合成一个数组:
- function mutiple(...args) {
- let result = 1;
- for (var val of args) {
- result *= val;
- }
- return result;
- }
- mutiple(1, 2, 3, 4) // 24
- 复制代码
这里,传入 mutiple 的是四个分离的参数,但是如果在 mutiple 函数里尝试输出 args 的值,会发现它是一个数组:
- function mutiple(...args) {
- console.log(args)
- }
- mutiple(1, 2, 3, 4) // [1, 2, 3, 4]
- 复制代码
这就是 … rest运算符的又一层威力了,它可以把函数的多个入参收敛进一个数组里。这一点经常用于获取函数的多余参数,或者像上面这样处理函数参数个数不确定的情况。
ES6 提出了“模板语法”的概念。在 ES6 以前,拼接字符串是很麻烦的事情:
- var name = 'css'
- var career = 'coder'
- var hobby = ['coding', 'writing']
- var finalString = 'my name is ' + name + ', I work as a ' + career + ', I love ' + hobby[0] + ' and ' + hobby[1]
- 复制代码
仅仅几个变量,写了这么多加号,还要时刻小心里面的空格和标点符号有没有跟错地方。但是有了模板字符串,拼接难度直线下降:
- var name = 'css'
- var career = 'coder'
- var hobby = ['coding', 'writing']
- var finalString = `my name is ${name}, I work as a ${career} I love ${hobby[0]} and ${hobby[1]}`
- 复制代码
字符串不仅更容易拼了,也更易读了,代码整体的质量都变高了。这就是模板字符串的第一个优势——允许用${}的方式嵌入变量。但这还不是问题的关键,模板字符串的关键优势有两个:
基于第一点,可以在模板字符串里无障碍地直接写 html 代码:
- let list = `
- <ul>
- <li>列表项1</li>
- <li>列表项2</li>
- </ul>
- `;
- console.log(message); // 正确输出,不存在报错
- 复制代码
基于第二点,可以把一些简单的计算和调用丢进 ${} 来做:
- function add(a, b) {
- const finalString = `${a} + ${b} = ${a+b}`
- console.log(finalString)
- }
- add(1, 2) // 输出 '1 + 2 = 3'
- 复制代码
除了模板语法外, ES6中还新增了一系列的字符串方法用于提升开发效率:
(1)存在性判定:在过去,当判断一个字符/字符串是否在某字符串中时,只能用 indexOf > -1 来做。现在 ES6 提供了三个方法:includes、startsWith、endsWith,它们都会返回一个布尔值来告诉你是否存在。
- const son = 'haha'
- const father = 'xixi haha hehe'
- father.includes(son) // true
- 复制代码
- const father = 'xixi haha hehe'
- father.startsWith('haha') // false
- father.startsWith('xixi') // true
- 复制代码
- const father = 'xixi haha hehe'
- father.endsWith('hehe') // true
- 复制代码
(2)自动重复:可以使用 repeat 方法来使同一个字符串输出多次(被连续复制多次):
- const sourceCode = 'repeat for 3 times;'
- const repeated = sourceCode.repeat(3)
- console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;
- 复制代码
new操作符的执行过程:
(1)首先创建了一个新的空对象
(2)设置原型,将对象的原型设置为函数的 prototype 对象。
(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)
(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
具体实现:
- function objectFactory() {
- let newObject = null;
- let constructor = Array.prototype.shift.call(arguments);
- let result = null;
- // 判断参数是否是一个函数
- if (typeof constructor !== "function") {
- console.error("type error");
- return;
- }
- // 新建一个空对象,对象的原型为构造函数的 prototype 对象
- newObject = Object.create(constructor.prototype);
- // 将 this 指向新建对象,并执行函数
- result = constructor.apply(newObject, arguments);
- // 判断返回对象
- let flag = result && (typeof result === "object" || typeof result === "function");
- // 判断返回结果
- return flag ? result : newObject;
- }
- // 使用方法
- objectFactory(构造函数, 初始化参数);
- 复制代码
Map | Object | |
---|---|---|
意外的键 | Map默认情况不包含任何键,只包含显式插入的键。 | Object 有一个原型, 原型链上的键名有可能和自己在对象上的设置的键名产生冲突。 |
键的类型 | Map的键可以是任意值,包括函数、对象或任意基本类型。 | Object 的键必须是 String 或是Symbol。 |
键的顺序 | Map 中的 key 是有序的。因此,当迭代的时候, Map 对象以插入的顺序返回键值。 | Object 的键是无序的 |
Size | Map 的键值对个数可以轻易地通过size 属性获取 | Object 的键值对个数只能手动计算 |
迭代 | Map 是 iterable 的,所以可以直接被迭代。 | 迭代Object需要以某种方式获取它的键然后才能迭代。 |
性能 | 在频繁增删键值对的场景下表现更好。 | 在频繁添加和删除键值对的场景下未作出优化。 |
(1)Map map本质上就是键值对的集合,但是普通的Object中的键值对中的键只能是字符串。而ES6提供的Map数据结构类似于对象,但是它的键不限制范围,可以是任意类型,是一种更加完善的Hash结构。如果Map的键是一个原始数据类型,只要两个键严格相同,就视为是同一个键。
实际上Map是一个数组,它的每一个数据也都是一个数组,其形式如下:
- const map = [
- ["name","张三"],
- ["age",18],
- ]
- 复制代码
Map数据结构有以下操作方法:
map.size
返回Map结构的成员总数。Map结构原生提供是三个遍历器生成函数和一个遍历方法
- const map = new Map([
- ["foo",1],
- ["bar",2],
- ])
- for(let key of map.keys()){
- console.log(key); // foo bar
- }
- for(let value of map.values()){
- console.log(value); // 1 2
- }
- for(let items of map.entries()){
- console.log(items); // ["foo",1] ["bar",2]
- }
- map.forEach( (value,key,map) => {
- console.log(key,value); // foo 1 bar 2
- })
- 复制代码
(2)WeakMap WeakMap 对象也是一组键值对的集合,其中的键是弱引用的。其键必须是对象,原始数据类型不能作为key值,而值可以是任意的。
该对象也有以下几种方法:
其clear()方法已经被弃用,所以可以通过创建一个空的WeakMap并替换原对象来实现清除。
WeakMap的设计目的在于,有时想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存。
而WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
总结:
全局的对象( global objects )或称标准内置对象,不要和 "全局对象(global object)" 混淆。这里说的全局的对象是说在 全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。
标准内置对象的分类:
(1)值属性,这些全局属性返回一个简单值,这些值没有自己的属性和方法。例如 Infinity、NaN、undefined、null 字面量
(2)函数属性,全局函数可以直接调用,不需要在调用时指定所属对象,执行结束后会将结果直接返回给调用者。例如 eval()、parseFloat()、parseInt() 等
(3)基本对象,基本对象是定义或使用其他对象的基础。基本对象包括一般对象、函数对象和错误对象。例如 Object、Function、Boolean、Symbol、Error 等
(4)数字和日期对象,用来表示数字、日期和执行数学计算的对象。例如 Number、Math、Date
(5)字符串,用来表示和操作字符串的对象。例如 String、RegExp
(6)可索引的集合对象,这些对象表示按照索引值来排序的数据集合,包括数组和类型数组,以及类数组结构的对象。例如 Array
(7)使用键的集合对象,这些集合对象在存储数据时会使用到键,支持按照插入顺序来迭代元素。 例如 Map、Set、WeakMap、WeakSet
(8)矢量集合,SIMD 矢量集合中的数据会被组织为一个数据序列。 例如 SIMD 等
(9)结构化数据,这些对象用来表示和操作结构化的缓冲区数据,或使用 JSON 编码的数据。例如 JSON 等
(10)控制抽象对象 例如 Promise、Generator 等
(11)反射。例如 Reflect、Proxy
(12)国际化,为了支持多语言处理而加入 ECMAScript 的对象。例如 Intl、Intl.Collator 等
(13)WebAssembly
(14)其他。例如 arguments
总结: js 中的内置对象主要指的是在程序执行前存在全局作用域里的由 js 定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。一般经常用到的如全局变量值 NaN、undefined,全局函数如 parseInt()、parseFloat() 用来实例化对象的构造函数如 Date、Object 等,还有提供数学计算的单体内置对象如 Math 对象。
- // (1)匹配 16 进制颜色值
- var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;
-
- // (2)匹配日期,如 yyyy-mm-dd 格式
- var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
-
- // (3)匹配 qq 号
- var regex = /^[1-9][0-9]{4,10}$/g;
-
- // (4)手机号码正则
- var regex = /^1[34578]\d{9}$/g;
-
- // (5)用户名正则
- var regex = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;
- 复制代码
JSON 是一种基于文本的轻量级的数据交换格式。它可以被任何的编程语言读取和作为数据格式来传递。
在项目开发中,使用 JSON 作为前后端数据交换的方式。在前端通过将一个符合 JSON 格式的数据结构序列化为 JSON 字符串,然后将它传递到后端,后端通过 JSON 格式的字符串解析后生成对应的数据结构,以此来实现前后端数据的一个传递。
因为 JSON 的语法是基于 js 的,因此很容易将 JSON 和 js 中的对象弄混,但是应该注意的是 JSON 和 js 中的对象不是一回事,JSON 中对象格式更加严格,比如说在 JSON 中属性值不能为函数,不能出现 NaN 这样的属性值等,因此大多数的 js 对象是不符合 JSON 对象的格式的。
在 js 中提供了两个函数来实现 js 数据结构和 JSON 格式的转换处理,
延迟加载就是等页面加载完成之后再加载 JavaScript 文件。 js 延迟加载有助于提高页面加载速度。
一般有以下几种方式:
一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length 属性值,代表可接收的参数个数。
常见的类数组转换为数组的方法有这样几种:
(1)通过 call 调用数组的 slice 方法来实现转换
- Array.prototype.slice.call(arrayLike);
- 复制代码
(2)通过 call 调用数组的 splice 方法来实现转换
- Array.prototype.splice.call(arrayLike, 0);
- 复制代码
(3)通过 apply 调用数组的 concat 方法来实现转换
- Array.prototype.concat.apply([], arrayLike);
- 复制代码
(4)通过 Array.from 方法来实现转换
- Array.from(arrayLike);
- 复制代码
(1)Unicode
在说Unicode
之前需要先了解一下ASCII
码:ASCII 码(American Standard Code for Information Interchange
)称为美国标准信息交换码。
ASCII
码可以表示的编码有限,要想表示其他语言的编码,还是要使用Unicode
来表示,可以说Unicode
是ASCII
的超集。
Unicode
全称 Unicode Translation Format
,又叫做统一码、万国码、单一码。Unicode
是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
Unicode
的实现方式(也就是编码方式)有很多种,常见的是UTF-8、UTF-16、UTF-32和USC-2。
(2)UTF-8
UTF-8
是使用最广泛的Unicode
编码方式,它是一种可变长的编码方式,可以是1—4个字节不等,它可以完全兼容ASCII
码的128个字符。
注意: UTF-8
是一种编码方式,Unicode
是一个字符集合。
UTF-8
的编码规则:
Unicode
编码,因此对于英文字母,它的Unicode
编码和ACSII
编码一样。Unicode
码 。来看一下具体的Unicode
编号范围与对应的UTF-8
二进制格式 :
编码范围(编号对应的十进制数) | 二进制格式 |
---|---|
0x00—0x7F (0-127) | 0xxxxxxx |
0x80—0x7FF (128-2047) | 110xxxxx 10xxxxxx |
0x800—0xFFFF (2048-65535) | 1110xxxx 10xxxxxx 10xxxxxx |
0x10000—0x10FFFF (65536以上) | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
那该如何通过具体的Unicode
编码,进行具体的UTF-8
编码呢?步骤如下:
Unicode
编码的所在的编号范围,进而找到与之对应的二进制格式Unicode
编码转换为二进制数(去掉最高位的0)X
中,如果有X
未填,就设为0来看一个实际的例子: “马” 字的Unicode
编码是:0x9A6C
,整数编号是39532
(1)首选确定了该字符在第三个范围内,它的格式是 1110xxxx 10xxxxxx 10xxxxxx
(2)39532对应的二进制数为1001 1010 0110 1100
(3)将二进制数填入X中,结果是:11101001 10101001 10101100
(3)UTF-16
1. 平面的概念
在了解UTF-16
之前,先看一下平面的概念: Unicode
编码中有很多很多的字符,它并不是一次性定义的,而是分区进行定义的,每个区存放65536(216)个字符,这称为一个平面,目前总共有17 个平面。
最前面的一个平面称为基本平面,它的码点从0 — 216-1,写成16进制就是U+0000 — U+FFFF
,那剩下的16个平面就是辅助平面,码点范围是 U+10000—U+10FFFF
。
2. UTF-16 概念:
UTF-16
也是Unicode
编码集的一种编码形式,把Unicode
字符集的抽象码位映射为16位长的整数(即码元)的序列,用于数据存储或传递。Unicode
字符的码位需要1个或者2个16位长的码元来表示,因此UTF-16
也是用变长字节表示的。
3. UTF-16 编码规则:
U+0000—U+FFFF
的字符(常用字符集),直接用两个字节表示。U+10000—U+10FFFF
之间的字符,需要用四个字节表示。4. 编码识别
那么问题来了,当遇到两个字节时,怎么知道是把它当做一个字符还是和后面的两个字节一起当做一个字符呢?
UTF-16
编码肯定也考虑到了这个问题,在基本平面内,从 U+D800 — U+DFFF
是一个空段,也就是说这个区间的码点不对应任何的字符,因此这些空段就可以用来映射辅助平面的字符。
辅助平面共有 220 个字符位,因此表示这些字符至少需要 20 个二进制位。UTF-16
将这 20 个二进制位分成两半,前 10 位映射在 U+D800 — U+DBFF
,称为高位(H),后 10 位映射在 U+DC00 — U+DFFF
,称为低位(L)。这就相当于,将一个辅助平面的字符拆成了两个基本平面的字符来表示。
因此,当遇到两个字节时,发现它的码点在 U+D800 —U+DBFF
之间,就可以知道,它后面的两个字节的码点应该在 U+DC00 — U+DFFF
之间,这四个字节必须放在一起进行解读。
5. 举例说明
以 " 本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。