Skip to content

JS-数据类型 ​

类型 ​

TIP

基本数据类型:Number,String,Boolean,Undefined,Null

引用数据类型:Object,Array,Function,RegExp,Date,Error

全局数据类型:Math

弱类型语言: javascript 是弱类型语言,在声明时是不需要定义类型的,变量的类型就是其值的类型;

缺点:在不确定的地方就会发生类型转换,无法确定类型,必须要掌握类型转换的原理;

检测数据类型 ​

typeof ​

TIP

对一个值使用了 typeof 操作符,会返回以下某个字符串

  1. 'undefined' //如果这个值未定义
  2. 'boolean' // 如果是布尔值
  3. 'string' // 如果是字符串
  4. 'number' // 如果是数字
  5. 'object' // 如果值是对象或 null
  6. 'function' // 如果是函数;

例子:

js
var message = 'hello'
console.log(typeof message) // 'string'
console.log(typeof 95) // 'number'
var message = 'hello'
console.log(typeof message) // 'string'
console.log(typeof 95) // 'number'

typeof 作符的操作数可以是变量,也可以是字面量

typeof 不能准确判断变量是什么类型,null 会返回 object,因为 null 的定义是空对象指针;

常使用的环境:

判断函数是否有传值:typeof val == 'undefined';val 是形参;

判断函数传值的类型:typeof val == 'number';

因为检测数据类型时,输入的结果是字符串,所以需要加引号;typeof (typeof a) 检测传入的 a 的类型的类型;

instanceof 检测数据类 ​

TIP

  1. 检测前面的变量是不是某个类型,是的话返回 true,不是返回 false 如果是 new 出来的函数,可以使用这个判断。
  2. 检测类型时类型首字母需要大写;
  3. 检测基本数据类型时需要 new 实例化,复杂数据类型不需要 new 实例化;必须要 new Number()
  4. 数组看是不是 Object 对象时,返回值是 true,但是 Array 大类的;

示例:console.log(obj instanceof Object);

js
console.log(123 instanceof Number) // false 必须new实例化
console.log([] instanceof Array) // true
console.log(123 instanceof Number) // false 必须new实例化
console.log([] instanceof Array) // true

toString.call ​

理解:

  1. Object.prototype.toString.call, call 前面的代码就是 this 指向;
  2. 因为 bind 是返回绑定的函数, 因此将 toString 函数放进去作为第一个参数;
  3. 由于 call 需要一个方法来调用, 因此将 Function.prototype 作为前缀;

改造:

js
types('') // [object String];    直接使用types来判断;
const t = (type) => Function.prototype.call.bind(Object.prototype.toString)(type).slice(8, -1)
types('') // [object String];    直接使用types来判断;
const t = (type) => Function.prototype.call.bind(Object.prototype.toString)(type).slice(8, -1)

constructor 判断(扩展) ​

TIP

js
console.log({}.constructor === Object)  // true
通过constructor判断的,Math.constructor === Math       //false,在javascript中,没有Math的构造函数,Math的构造函数在Object上,可以使用toString.call来判断;
console.log({}.constructor === Object)  // true
通过constructor判断的,Math.constructor === Math       //false,在javascript中,没有Math的构造函数,Math的构造函数在Object上,可以使用toString.call来判断;

不推荐此方法,因为

js
var a = [1, 2, 3]
a.constructor === Array //true
var a = [1, 2, 3]
a.constructor === Array //true

如果 a=null,则报错;

使用原型链判断是否是类型的原型 ​

reg.__proto__ === RegExp.prototype
reg.__proto__ === RegExp.prototype

Undefined 类型 ​

Undefined 类型只有一个值,即特殊的 undefined,

在使用 var 声明变量时,没有赋值,就是 undefined;

函数没有明确返回值,如果接受了,结果是 undefined;

注意

即使不声明,使用 typeof 也会返回'undefined';

js
var name
console.log(typeof name) // 'undefined'
console.log(typeof age) // 'undefined'
var name
console.log(typeof name) // 'undefined'
console.log(typeof age) // 'undefined'

Null 类型 ​

Null 类型是第二个只有一个值的数据类型,这个特殊的值是 null;

null 表示一个空对象指针,因此检测 null 的数据类型时,会返回 object;

如果需要保存的值类型为对象,建议赋值为 null;

注意

判断 undefined == null 时,结果为 true,因为 undefined 值是派生自 null 值的,无论在什么情况下都没有必要把一个变量的值显式地设置为 undefined;

面试题

undefined 和 null 的区别

  • null 表示值为空,但是是 object 对象,
  • undefined 表示变量没有初始化,值为空缺;
  • null ==undefined // 因为都为空,不对比类型;
  • null === undefined //false 类型不同

null 是对象吗?

  • null 的返回值是 object,但是 null 不是对象,而是基本数据类型的一种;

Boolean 类型 ​

boolean 类型是使用最多的类型,只有两个值:true(真)和 false(假),在 ECMAScript 中是区分大小写的,True 和 False 都不是 boolean 类型,只是标识符;

boolean 类型的转换规则

数据类型转换 true 的值转换 false 的值
Booleantruefalse
String任何非空字符串空字符串
Number任何非零数值0 和 NaN
Object任何对象null
Undefined不适用转换 trueundefined

Number 类型 ​

  1. 最基本的数值字面量格式是十进制整数
  2. 八进制字面值第一位必须是 0 且后面的值必须小于 8,否则还是十进制
  3. 十六进制以 0x 开头,十六进制一般用在表示颜色上

浮点数值 ​

在表示 0.1 时,通常可以直接.1,去掉 0,浮点数值的最高精度是 17 位小数,0.1 加 0.2 的结果不是 0.3,而是 0.300000000000000004,这是使用基于 IEEE754 数值的浮点计算的通病,所有语言如此,永远不要测试某个特点的浮点数值;

数值范围 ​

Number.MIN_VALUE 最小值,这个值是 5e-324,

Number.MAX_VALUE   最大值,这个值是1.7976931348623157e+308
Number.MAX_VALUE   最大值,这个值是1.7976931348623157e+308

Infinity 无穷大 -Infinity 无穷小,isFinite() 判断是还是无穷数;

e 的表示法:3.125e7 == 31250000,实际是 3.125 乘以 10 的 7 次幂;0.0000003 表示 3e-7;

NaN ​

(1)即非数值,是一个特殊值,任何数值除以非数值会返回 NaN,不会影响其他代码的执行;

(2)NaN 与任何值都不相等,NaN 不等于 NaN;

(3)判断是不是 NaN,使用 isNaN,在判断时会尝试将这个值转换为数值再进行判断;isNaN 在对对象调用时,会首先调用对象的 valueOf 方法,然后再确定该方法返回的值是否可以转换为数值,如果不能,则基于这个返回值再调用 toString()方法;

js
isNaN(NaN) //true
isNaN(10) //false
isNaN('10') //false
isNaN('blue') //true
isNaN(true) // false  (true被转换为1)
isNaN(NaN) //true
isNaN(10) //false
isNaN('10') //false
isNaN('blue') //true
isNaN(true) // false  (true被转换为1)

数值的转换 ​

  1. Number()、parseInt()、parseFloat(),第一个函数可以用于任何数据类型,另外两个函数则专门用于把字符串转换成数值;

  2. Number 转换规则

  • 如果是 boolean 值,true 为 1,false 为 0

  • 如果是数字,直接返回传入的数值;

  • 如果是 null,返回 0

  • 如果是 undefined,返回 NaN,特殊值

  • 如果是字符串,遵循以下规则

    • 如果字符串只包含数字,'1'转换 1,'011'转换为 11;
    • 如果包含有效浮点格式,如:'1.1',则将转换对应的浮点数值;
    • 如果字符串包含有效十六进制,则将其转换相同大小的十进制
    • 如果字符串是空的,转换为 0
    • 如果字符串除以上规则,转换为 NaN;
  • 如果是对象,则调用对象的 valueOf 方法,如果转换结果是 NaN,则调用对象的 toString 方法;

js
Number('hello') // NaN
Number('') // 0
Number('00011') //11
Number(true) // 1
Number('hello') // NaN
Number('') // 0
Number('00011') //11
Number(true) // 1

注意:空的数组转换为 0,空的对象转换为 NaN; 一般在需要转换的值前面带上+符,可以直接转换,+[],转换为 0;

  1. parseInt 转换规则

由于 Number()函数在转换字符串时比较复杂而且不够合理,因此在处理整数的时候更常用的是 parseInt 函数;

  • parseInt 会忽略空格,直到找到第一个非空格字符,如果第一个不是数字或者负号,则返回 NaN;

  • parseInt 在转换查找时,如果第一个是数字,则会继续解析第二个字符,直到后续字符遇到一个非数字字符,如:'12px'转换为 12;

  • 在遇到小数点时,也不会解析,需要使用 parseFloat;

  • parseInt 转换空字符串时,返回 NaN,Number 转换空字符串返回 0;

  • parseInt 有第二个参数,以什么进制来转换;

    • 如:由于 16 进制是以 0x 开头的,如果没有 0x,'af'转换为 NaN,如果是 parseInt('af',16),则转换是 175;
    • '070'是以 0 开头,转换是以 8 进制,则是 56,如果是 parseInt('070',10),则是 70;
  1. parseFloat 转换规则
  • 主要是解决 parseInt 不能识别小数点而诞生的;
  • parseFloat 只解析 10 进制,如果是 0xA,则解析是 0;

额外的方法 ​

  • toFxied(n),小数点后保留多少位;

  • toExponential(n),返回指数表示法(也称 e 表示法);

  • toPrecision(n),根据要处理的数值决定使用 toFxied 还是 toExponential;

js
var num = 99
alert(num.toPrecision(1)) //"1e+2"
alert(num.toPrecision(2)) //"99"
alert(num.toPrecision(3)) //"99.0"
var num = 99
alert(num.toPrecision(1)) //"1e+2"
alert(num.toPrecision(2)) //"99"
alert(num.toPrecision(3)) //"99.0"

``|0 可以直接取整,比如:12.3|0 结果是 12;

String 类型 ​

  1. String 类型用于表示由零或多个 16 位 Unicode 字符组成的字符序列,字符串可以由双引号(""),或单引号('')表示
  2. 字符串的长度可以使用 length 属性来读取
  3. 转换为字符串:
    • 使用 toString()方法转换,可以传入的参数有 2-36 之间,一般传入 2. 8. 10. 16
    • String()转换规则
      • 如果值有 toString()方法,则调用该方法并返回相应的结果
      • 如果值是 null 或 undefined,则返回'null'或'undefined'

注意:

  • null和undefined值没有toString()这个方法,需要使用String()来转换
  • 整数直接.toString()会报错,可以添加小括号转换; (1).toString()
  • 如果是对象.toString(),转换成[object Object]

BigInt 类型 ​

TIP

BigInt 是 JavaScript 中一种内置对象,用于表示大于 2^53 - 1 的整数(即 Number 能安全表示的最大整数)。

  • 创建方式:在整数后面加 n 或调用 BigInt() 函数
  • BigInt 不能与 Number 直接混合运算(需显式转换)
  • 支持 +, -, *, **, /, % 运算符(除法结果会截断小数)
js
// 创建 BigInt
const big1 = 9007199254740993n
const big2 = BigInt(9007199254740993)
const big3 = BigInt('9007199254740993')

// 运算
console.log(big1 + 2n)  // 9007199254740995n
console.log(big1 * 2n)  // 18014398509481986n
console.log(big1 / 3n)  // 3002399751580331n(截断小数)

// 与 Number 比较
console.log(1n == 1)   // true(宽松相等)
console.log(1n === 1)  // false(严格相等)
console.log(1n < 2)    // true(可以比较)

// 混合运算需显式转换
// console.log(big1 + 1)  // TypeError
console.log(big1 + BigInt(1))  // 正确
console.log(Number(big1) + 1)  // 正确,但有精度丢失风险
// 创建 BigInt
const big1 = 9007199254740993n
const big2 = BigInt(9007199254740993)
const big3 = BigInt('9007199254740993')

// 运算
console.log(big1 + 2n)  // 9007199254740995n
console.log(big1 * 2n)  // 18014398509481986n
console.log(big1 / 3n)  // 3002399751580331n(截断小数)

// 与 Number 比较
console.log(1n == 1)   // true(宽松相等)
console.log(1n === 1)  // false(严格相等)
console.log(1n < 2)    // true(可以比较)

// 混合运算需显式转换
// console.log(big1 + 1)  // TypeError
console.log(big1 + BigInt(1))  // 正确
console.log(Number(big1) + 1)  // 正确,但有精度丢失风险

注意

  • BigInt 不支持单目运算符 +
  • BigInt 不能用于 Math 对象的方法
  • 转为 Number 时若超出安全范围会丢失精度

复杂数据类型(引用类型) ​

引用类型的特点

  • 复杂数据类型有Object(对象)、Array(数组)、function(函数)
  • 引用类型任何数据类型都可以储存
  • 引用类型的地址存储在堆里面,当别的值修改了原始值之后,都会被修改
  • 基本类型存储的是值,引用类型存储的是地址(指针),复制引用类型时只会复制内存指针

引用类型的拷贝 ​

浅拷贝 ​

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝:基本类型拷贝的是值,引用类型拷贝的就是内存地址;所以当我们操作新对象中的引用类型时会影响源对象

Object.assign() ​
js
const obj1 = {
  name: 'xiaoming',
  props: { a: 1 },
}

const obj2 = Object.assign({}, obj1)
obj2.name = '小明'
obj2.props.a++

obj1 // { name: 'xiaoming', props: { a: 2 } }
obj2 // { name: '小明', props: { a: 2 } }
const obj1 = {
  name: 'xiaoming',
  props: { a: 1 },
}

const obj2 = Object.assign({}, obj1)
obj2.name = '小明'
obj2.props.a++

obj1 // { name: 'xiaoming', props: { a: 2 } }
obj2 // { name: '小明', props: { a: 2 } }
Array.prototype.concat() ​
js
const arr1 = [1, 2, 3, [4, 5]]

const arr2 = arr1.concat()
arr2[0] = 'arr2'
arr2[3][0] = 'arr2'

arr1 // [1, 2, 3, ['arr2', 5]];
arr2 // ['arr2', 2, 3, ['arr2', 5]];
const arr1 = [1, 2, 3, [4, 5]]

const arr2 = arr1.concat()
arr2[0] = 'arr2'
arr2[3][0] = 'arr2'

arr1 // [1, 2, 3, ['arr2', 5]];
arr2 // ['arr2', 2, 3, ['arr2', 5]];
Array.prototype.slice() ​
js
const arr1 = [1, 2, 3, [4, 5]]

const arr2 = arr1.slice()
arr2[0] = 'arr2'
arr2[3][0] = 'arr2'

arr1 // [1, 2, 3, ['arr2', 5]];
arr2 // ['arr2', 2, 3, ['arr2', 5]];
const arr1 = [1, 2, 3, [4, 5]]

const arr2 = arr1.slice()
arr2[0] = 'arr2'
arr2[3][0] = 'arr2'

arr1 // [1, 2, 3, ['arr2', 5]];
arr2 // ['arr2', 2, 3, ['arr2', 5]];
ES6 扩展运算符 ​
js
/* 对象 */
const obj1 = {
  name: 'xiaoming',
  props: { a: 1 },
}

const obj2 = { ...obj1 }
obj2.name = '小明'
obj2.props.a++

obj1 // { name: 'xiaoming', props: { a: 2 } }
obj2 // { name: '小明', props: { a: 2 } }

/* 数组 */
const arr1 = [1, 2, 3, [4, 5]]

const arr2 = [...arr1]
arr2[0] = 'arr2'
arr2[3][0] = 'arr2'

arr1 // [1, 2, 3, ['arr2', 5]];
arr2 // ['arr2', 2, 3, ['arr2', 5]];
/* 对象 */
const obj1 = {
  name: 'xiaoming',
  props: { a: 1 },
}

const obj2 = { ...obj1 }
obj2.name = '小明'
obj2.props.a++

obj1 // { name: 'xiaoming', props: { a: 2 } }
obj2 // { name: '小明', props: { a: 2 } }

/* 数组 */
const arr1 = [1, 2, 3, [4, 5]]

const arr2 = [...arr1]
arr2[0] = 'arr2'
arr2[3][0] = 'arr2'

arr1 // [1, 2, 3, ['arr2', 5]];
arr2 // ['arr2', 2, 3, ['arr2', 5]];

深拷贝 ​

深拷贝是将一个对象从内存中完整的拷贝一份出来,即从堆内存中开辟一个新的区域存放新对象,所以修改新对象不会影响原对象

JSON.parse(JSON.stringify()) ​
js
const obj1 = {
  name: 'xiaoming',
  props: { a: 1 },
}

const obj2 = JSON.parse(JSON.stringify(obj1))
obj2.name = '小明'
obj2.props.a++

obj1 // { name: 'xiaoming', props: { a: 1 } }
obj2 // { name: '小明', props: { a: 2 } }
const obj1 = {
  name: 'xiaoming',
  props: { a: 1 },
}

const obj2 = JSON.parse(JSON.stringify(obj1))
obj2.name = '小明'
obj2.props.a++

obj1 // { name: 'xiaoming', props: { a: 1 } }
obj2 // { name: '小明', props: { a: 2 } }

JSON.parse(JSON.stringify()) 存在明显的弊端:

  • 只能序列化对象的可枚举的自有属性
  • undefined、Symbol、任意函数将被忽略
  • NaN、Infinity 、-Infinity 将被当成 null 处理
  • RegExp、Error、Set、Map 等特殊对象,仅会序列化可枚举的属性(一般情况下即为空对象)
  • Date 类型,转换后会调用 toJSON 转为字符串类型
  • 循环引用的对象将报错
js
const map = new Map()
map.set(1, 2) // Map: 0: {1 => 2}
const obj1 = {
  a: undefined,
  b: null,
  c: Symbol(),
  d: NaN,
  e: Infinity,
  f: -Infinity,
  g: map,
  h: new Date(),
  i: () => {},
}
Object.defineProperty(obj1, 'j', {
  value: 'string',
})

const obj2 = JSON.parse(JSON.stringify(obj1))

/** 源对象 obj1
{
  a: undefined,
  b: null,
  c: Symbol(),
  d: NaN,
  e: Infinity,
  f: -Infinity,
  g: Map(1) {1 => 2}
  h: Fri Mar 10 2023 22:41:08 GMT+0800 (中国标准时间) {},
  i: () => {},

  j: 'string'
}
**/

/** 新对象 obj2
{
  b: null,
  d: null,
  e: null,
  f: null,
  g: {},
  h: '2023-03-10T14:41:08.110Z'
}
**/
const map = new Map()
map.set(1, 2) // Map: 0: {1 => 2}
const obj1 = {
  a: undefined,
  b: null,
  c: Symbol(),
  d: NaN,
  e: Infinity,
  f: -Infinity,
  g: map,
  h: new Date(),
  i: () => {},
}
Object.defineProperty(obj1, 'j', {
  value: 'string',
})

const obj2 = JSON.parse(JSON.stringify(obj1))

/** 源对象 obj1
{
  a: undefined,
  b: null,
  c: Symbol(),
  d: NaN,
  e: Infinity,
  f: -Infinity,
  g: Map(1) {1 => 2}
  h: Fri Mar 10 2023 22:41:08 GMT+0800 (中国标准时间) {},
  i: () => {},

  j: 'string'
}
**/

/** 新对象 obj2
{
  b: null,
  d: null,
  e: null,
  f: null,
  g: {},
  h: '2023-03-10T14:41:08.110Z'
}
**/
structuredClone ​

HTML 规范标准的 Web API

js
const original = { name: 'MDN' }
original.itself = original

const clone = structuredClone(original)

console.assert(clone !== original) // the objects are not the same (not same identity)
console.assert(clone.name === 'MDN') // they do have the same values
console.assert(clone.itself === clone) // and the circular reference is preserved
const original = { name: 'MDN' }
original.itself = original

const clone = structuredClone(original)

console.assert(clone !== original) // the objects are not the same (not same identity)
console.assert(clone.name === 'MDN') // they do have the same values
console.assert(clone.itself === clone) // and the circular reference is preserved

HTML 规范的标准提案,使用结构化克隆算法将给定的值进行深拷贝,支持循环引用。还可以使用 structuredClone(value, { transfer }) 调用方式使可转移对象仅被传递,不被克隆(直接移动源数据)

注意点

尽管作为规范标准实现的 Web API,但目前兼容性还是个巨大的问题,同时仍有其他不足:

  • 无法拷贝对象的原型链
  • 无法拷贝函数
  • 不支持 Error 数据类型
MessageChannel ​

vue.nextTick 源码曾使用的 Web API,在了解这个 API 时发现可以用于深拷贝

js
function cloneUsingChannel(obj) {
  return new Promise((resolve) => {
    const channel = new MessageChannel()
    channel.port1.onmessage = (e) => resolve(e.data)
    channel.port2.postMessage(obj)
  })
}
function cloneUsingChannel(obj) {
  return new Promise((resolve) => {
    const channel = new MessageChannel()
    channel.port1.onmessage = (e) => resolve(e.data)
    channel.port2.postMessage(obj)
  })
}

但该方法存在一个缺陷,当拷贝对象带有函数属性时,将抛出错误:

js
const obj1 = {
  fn: function () {},
}
const obj2 = cloneUsingChannel(obj1)
// Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'MessagePort': function () {} could not be cloned.
const obj1 = {
  fn: function () {},
}
const obj2 = cloneUsingChannel(obj1)
// Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'MessagePort': function () {} could not be cloned.
JQuery.extend() ​
js
import $ from 'jquery'

const obj2 = $.extend(true, {}, obj1)
import $ from 'jquery'

const obj2 = $.extend(true, {}, obj1)
lodash.cloneDeep ​
js
import { cloneDeep } from 'lodash-es'

const obj2 = cloneDeep(obj1)
import { cloneDeep } from 'lodash-es'

const obj2 = cloneDeep(obj1)

object(对象) ​

TIP

  • 以花括号的形式为对象
  • 万物皆对象,页面中的任何一个元素都是对象,包括 document,window,对象就相当于地球
  • 创建对象的三个方式
    • 实例化方式:var 变量=new Object();输出是一个空的花括号,表示对象
    • 字面量:var 变量 = {}
    • Object.create(null) 也可以创建一个新对象
  • 对象中以键值对表示存储值,键名:键值;也可以叫属性名:属性值;多个键名之间用逗号隔开
  • 设置对象的元素值:对象名.键名 = 键值; 一般称作键值对
  • 获取值的方法:① 变量名.键名;② 变量名[""]

Array(数组) ​

TIP

  • 创建方式
    • 实例化:var 变量 = new Array();以中括号形式为数组,一个值表示数组的长度,多个值表示数组的数据如果是一个引号引起来的,则是单个数组
    • 字面量:var 变量 = [ ]
  • 数组的长度是下标加一
  • 数组的长度可读可写,可以删除数组的数据
  • 数组不能用 typeof 来判断是不是数组,判断的是 object,判断是不是数组可以用 Array.isArray(参数)来判断,返回结果是布尔型;IE9 以上支持判断
  • 数组可以储存所有类型,也可以存储数组和对象
  • 设置数组:数组名[索引] = 值
  • 获取数据:数组名[索引]

面试题

js
var ary = [], ary[0]=1, ary[5]=5
var ary = [], ary[0]=1, ary[5]=5

数组的长度是 6,没有声明的为undefined;

function 函数 ​

  • function (); 事件处理函数;空格后面可以设置函数名
  • 调用函数:函数();加小括号是直接执行,事件不需要加小括号

关于 Javascript 中数字的部分知识总结 ​

  • Javascript 中,由于其变量内容不同,变量被分为基本数据类型变量和引用数据类型变量。基本类型变量用八字节内存,存储基本数据类型(数值、布尔值、null 和未定义)的值,引用类型变量则只保存对对象、数组和函数等引用类型的值的引用(即内存地址)
  • JS 中的数字是不分类型的,也就是没有 byte/int/float/double 等的差异
  • 基本类型:null,undefined,number,boolean;string 比较特殊!!! 引用类型 Obeject,function,array

计算机最小的计量单位是 bit,翻译为位

8bit = 1byte,byte 翻译为字节

所以 32bit = 4 byte

八字节为 64 位

基本包装数据类型 ​

  • javascript 中有三个基本包装数据类型:String、Number、Boolean;

    TIP

    每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据,其实 String、Number、Boolean 三种类型的值在读取的时候由于是基本包装类型(也可以称是引用类型),因此有属性和方法,可以使用点操作符,读取不到的属性返回 undefined,而 undefind 和 null 两种类型会直接报错

  • 基本包装数据类型的特征

    • 可以使用 new 操作符,当使用 new 操作符之后的类型使用 typeof 检测之后是 object,但是可以使用 instanceof 来检测;
  • 使用 new 和不使用 new 的特点

    TIP

    引用类型与基本包装类型的主要区别就是对象的生存期。使用 new 操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁。

堆和栈 ​

  • 基本数据类型,储存在栈里面,两个基本数据类型是相等的
  • 引用数据类型,地址存储在栈里面,数据存在堆里面,调用时先从栈里获取地址,再从堆中获取数据,两个复杂数据类型是不相等的;堆里面反复存数据会导致内存占满

new 操作符 ​

  • new 操作符创建出来的一定是一个空对象

  • 如果在构造函数里面,显式写了一个返回值,会发生的情况?

    如果返回的不是一个对象,相当于没写,会被忽略,也就是说如果在构造函数中显式的返回了一个不是对象的数据类型,最后得到的还是对象,如果是对象,会把设置的对象返回出来

js
function Car(name, age) {
  this.name = name
  return { age: age }
}
let car = new Car('lishi', 18)
car.name //undefined  因为返回没有name;
car // {age:18} 并且不能标识具体谁创建的;

// 第二种情况,返回不是对象;
function Car(name, age) {
  this.name = name
  return age
}
let car = new Car('lishi', 18)
car // Car {name: 'lishi'}   return 没有起作用;
function Car(name, age) {
  this.name = name
  return { age: age }
}
let car = new Car('lishi', 18)
car.name //undefined  因为返回没有name;
car // {age:18} 并且不能标识具体谁创建的;

// 第二种情况,返回不是对象;
function Car(name, age) {
  this.name = name
  return age
}
let car = new Car('lishi', 18)
car // Car {name: 'lishi'}   return 没有起作用;
  • 使用 new 操作符执行了哪些过程
    1. 创建一个空对象
    2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
    3. 执行构造函数中的代码(为这个新对象添加属性)
    4. 返回新对象
js
function create() {
  var obj = new Object()
  var fn = [].shift.call(arguments) //将第一个函数截取;
  obj.__proto__ = fn.prototype
  var result = fn.apply(obj, arguments)
  return result instanceof Object ? result : obj
  //一般new一个函数,函数可以填写return,如果return了就得将函数返回值保存并返回,函数必须是对象,上面第2点已经讲过;
}

// or
function create(fn, ...args) {
  const obj = new Object()
  obj.__proto__ = fn.prototype
  const result = fn.apply(obj, args)
  return result instanceof Object ? result : obj
}

// 使用实例
function fn(age) {
  this.age = age
}
let obj1 = create(fn, 18) // fn{age:18}
let obj2 = new fn(18) //fn{age:18}
console.log(obj1.__proto__ === obj2.__proto__) //true
function create() {
  var obj = new Object()
  var fn = [].shift.call(arguments) //将第一个函数截取;
  obj.__proto__ = fn.prototype
  var result = fn.apply(obj, arguments)
  return result instanceof Object ? result : obj
  //一般new一个函数,函数可以填写return,如果return了就得将函数返回值保存并返回,函数必须是对象,上面第2点已经讲过;
}

// or
function create(fn, ...args) {
  const obj = new Object()
  obj.__proto__ = fn.prototype
  const result = fn.apply(obj, args)
  return result instanceof Object ? result : obj
}

// 使用实例
function fn(age) {
  this.age = age
}
let obj1 = create(fn, 18) // fn{age:18}
let obj2 = new fn(18) //fn{age:18}
console.log(obj1.__proto__ === obj2.__proto__) //true

增进问题

在使用 new 操作符时,如果没有参数,括号是可以省略的

运算符 ​

js 赋值运算符是从右往左的,var a = 1,把 1 的值赋给 a; '.'是优先级最高的运算符

数字运算符 ​

  • + - * / %(取余) ++(自增) --(自减)
  • 号遇到字符串变成拼接
  • ++(--) 在前,先运算,后赋值
  • ++(--) 在后,先赋值,后运算
js
var i=1;
j=i++ + i++;
j=1 + 2++;
j=3;

var a=1;
b=++a + ++a;
b=2 + ++2;
b= 5;
var i=1;
j=i++ + i++;
j=1 + 2++;
j=3;

var a=1;
b=++a + ++a;
b=2 + ++2;
b= 5;

赋值运算符 ​

  • = += -= *= /= %=
  • a+=2 为 a=a+2
  • 一个等号为赋值
  • 赋值运算表达式:由赋值运算符链接起来的叫赋值表达式

比较运算符 ​

  • > >= < <= !=(不等于) !==(绝对不等于) ==(等于) ===(绝对等于)
  • '!=' 不等于只看值是否相同,不看类型
  • '!==' 绝对不等于除了看值不同,还看类型是否不同
  • 比较运算符的输出结果是 boolear 类型
  • "two">"three"//结果为 true "tw"在字母表中的索引大于"th"

逻辑运算符 ​

  • &&(与--并且) ||(或---或者) !(非--取反或取非)
  • && 只要有一个为假,就为假
  • || 只要有一个为真,就为真
  • !表达式: 表达式 1 的结果是 true,整个结果为 false
  • 表达式 1 的结果是 false,整个结果为 true
  • (!"")为 true 空字符串为假,取非就为真

连等运算符 ​

a=b=c 为 b=c 是 a=b;永远取等号右边的表达式结果赋值到等号的左侧;

js
var a = { n: 1 }
var b = a
a.x = a = { n: 2 }
console.log(a.x) // undefined;
var a = { n: 1 }
var b = a
a.x = a = { n: 2 }
console.log(a.x) // undefined;

理解:

js
var a = {n:1}
var b = a
a = {n:2} // 这里 a 指向了新的地址,b 还是指向原来的地址,b 没有重新被赋值;
上题中点的优先级最高,首先 a.x = a,赋值了,a 又被重新赋值,因此 b.x 有值;a.x 为新对象为 undefined;
var a = {n:1}
var b = a
a = {n:2} // 这里 a 指向了新的地址,b 还是指向原来的地址,b 没有重新被赋值;
上题中点的优先级最高,首先 a.x = a,赋值了,a 又被重新赋值,因此 b.x 有值;a.x 为新对象为 undefined;

位运算 ​

位运算适用于 32 位整数,所以精度可能会丢失

用'|'取整 ​

js
let num = 1.5
num = num | 0 // 1
let num = 1.5
num = num | 0 // 1

用>>取半 ​

js
let num = 4
num = num >> 1 //2
let num = 4
num = num >> 1 //2

用<<加倍 ​

js
let num = 2
num = num << 2 //4
let num = 2
num = num << 2 //4

用'^'两值交换 ​

js
let a = 1,
  b = 2
a ^= b
b ^= a
a ^= b
console.log(a, b) // 2 1;
let a = 1,
  b = 2
a ^= b
b ^= a
a ^= b
console.log(a, b) // 2 1;

用&判断奇数 ​

js
let n = 3
let m = 4
n & (1 === 1) // true 奇数
m & (1 === 1) //false 偶数
let n = 3
let m = 4
n & (1 === 1) // true 奇数
m & (1 === 1) //false 偶数

用'~'判断项是否存在 ​

~的特性是:操作数的负值-1;~12 为-13;

js
let firstname = 'ma'
let fullname = 'jack ma'
~fullname.indexOf(firstname) // true
let firstname = 'ma'
let fullname = 'jack ma'
~fullname.indexOf(firstname) // true

由于负数-1,找不到是-1,-1 就是 0 了

用'+'快速转换数字类型; ​

js
console.log(typeof +'11') //number
console.log(typeof +'11') //number

如果+号前面有数字则会成拼接符;

用 ** 表示平方 ​

js
let n = 4 ** 4 // 256
let n = 4 ** 4 // 256

隐式转换 ​

  • 在进行计算时,会隐式转换;10+true 为 11;,10-true 为 9;
  • 当+号遇到字符串的时候,+变成拼接符,将前后拼接起来,多个相加时,碰到引号开始拼接;
  • 隐式转换会首先调用 toString 方法,如果没有则调用 valueOf 方法;
  • undefined 和谁进行运算都是 NaN
  • 两个+会是 NaN: 'a'+ + 'b' aNaN 后面这一个 b 转`换为 NaN

上题相当于 'a'+(+'b')

理解:由于+是一元正号,计算操作数的数值时,会进行 Number 转换:Number('b') 转换为 NaN,剩下的就是字符串和其他类型相加,都会被转化为字符串;

例子:

js
let date = new Date() + date // 将进行Number(date)转换,转换成时间戳
let date = new Date() + date // 将进行Number(date)转换,转换成时间戳
  • {}空对象和谁进行运算都自动转换 toString,为[object Object];
js
function sum(x) {
  console.log('两个数的和为' + x)
}
function sum(x, y) {
  console.log('两个数的和为' + (x + y)) //NaN;
}
sum(10)
//y没有传值,10+undefined; 为NaN undefined是字符串类型,+号遇到字符串时隐式转换了;
// 如果没有(x+y)的小括号,则会是10undefined,成拼接符;
function sum(x) {
  console.log('两个数的和为' + x)
}
function sum(x, y) {
  console.log('两个数的和为' + (x + y)) //NaN;
}
sum(10)
//y没有传值,10+undefined; 为NaN undefined是字符串类型,+号遇到字符串时隐式转换了;
// 如果没有(x+y)的小括号,则会是10undefined,成拼接符;

json ​

json 是一种数据交换格式,从后台请求下来的数据基本上都是 json 类型,

json 数据用{}括起来,里面的数据以 key:value 存储。