V8引擎中的堆栈内存及前端必备的数据结构
1、ECMAScript262中数据类型的标准定义
-
ECMAScript is object-based: basic language and host facilities are provided by objects, and an ECMAScript program is a cluster of communicating objects. In ECMAScript, an object is a collection of zero or more properties each with attributes that determine how each property can be used—for example, when the Writable attribute for a property is set to false, any attempt by executed ECMAScript code to assign a different value to the property fails. Properties are containers that hold other objects, primitive values, or functions. A primitive value is a member of one of the following built-in types: Undefined, Null, Boolean, Number, String, and Symbol; an object is a member of the built-in type Object; and a function is a callable object. A function that is associated with an object via a property is called a method.
ECMAScript是基于对象的:基本语言和主机设施是由对象提供的,而ECMAScript程序是通信对象的集群。在ECMAScript中,对象是0个或多个属性的集合,每个属性都具有决定如何使用每个属性的属性——例如,当属性的可写属性被设置为false时,执行ECMAScript代码为属性分配不同值的任何尝试都将失败。属性是容纳其他对象、基本只或函数的容器。原始值是以下内置类型之一的成员:未定义、Null、布尔、数字、字符串、和符号;对象是内置类型对象的成员;函数是可调用对象。通过属性与对象关联的函数成为方法。
-
ECMAScript defines a collection of built-in objects that round out the definition of ECMAScript entities. These built-in objects include the global object; objects that are fundamental to the runtime semantics of the language including Object, Function, Boolean, Symbol, and various Error objects; objects that represent and manipulate numeric values including Math, Number, and Date; the text processing objects String and RegExp; objects that are indexed collections of values including Array and nine different kinds of Typed Arrays whose elements all have a specific numeric data representation; keyed collections including Map and Set objects; objects supporting structured data including the JSON object, ArrayBuffer, and DataView; objects supporting control abstractions including generator functions and Promise objects; and, reflection objects including Proxy and Reflect.
ECMAScript定义了一个内置对象集合,它完善了ECMAScript实体的定义。这些内置对象包括全局对象;对象是语言运行时语义的基础,包括对象、函数、布尔值、符号和各种错误对象;表示和操作数值(包括数学、数字和日期)的对象;文本处理对象字符串和RegExp;对象,该对象是值的索引集合,包括数组和9种不用类型的数组,其他元素都具有特定的数值数据表示;键控集合,包括Map和Set对象;支持结构化数据的对象,包括JSON对象、ArrayBuffer和DataView;支持控制抽象的对象,包括生成器函数和承诺对象;以及反射对象,包括代理和反射。
原始值类型【值类型/基本数据类型】
- number 数字
- string 字符串
- boolean 布尔
- null 空对象指针
- undefined 未定义
- symbol 唯一值
- bigint 大数
对象类型【引用数据类型】
- 标准普通对象 object
- 标准特殊对象 Array、RegExp、Date、Math、Error...
- 非标准特殊对象 Number、String、Boolean...
- 可调用/执行函数 [函数] function
原始值类型详情
-
number数字类型:NaN非有效数字、Infinity无穷大的值
-
数值中有最大安全数和最小安全数
Number.MAX_SAFE_INTEGER; //9007199254740991 最大安全数字
Number.MIN_SAFE_INTEGER;//-9007199254740991 最小安全数字
-
NaN!==NaN(NaN不等于任何数,包括自己)isNaN([value]);检测[value]是否为有效数字
-
Object.is(NaN,NaN);结果为true
-
-
string字符串类型:' '," ",``包起来的都是字符串
-
boolean布尔类型:true、false
-
null 后期会赋值,初始值就为null
-
undefined 后期并没有打算赋值,初始值就为undefined
-
symbol唯一值类型
console.log(Symbol() === Symbol()); //false 创建了两个唯一值
console.log(Symbol('AA') === Symbol('AA')); //false
@作用1:可以给对象设置唯一值类型的属性「对象“属性名”的类型:string、symbol」
let obj = { name: 'zhufeng', age: 12, [Symbol()]: 100 }; console.log(obj[Symbol()]); //undefined 获取的时候,它会创建一个新的唯一值,所以它获取的是一个新的唯一值的属性值 ,唯一值是不想等的,所以会返回undefined;[ ]的作用:为了让语法符合规范
let a = [10, 20]; let sym = Symbol('BB'); let obj = { name: 'zhufeng', age: 13, 0: 100, [{ xxx: 'xxx' }]: 200, // "[object Object]":200 [a]: 300, // "10,20":300 [Symbol('AA')]: 400, [sym]: 500 };
Object.getOwnPropertySymbols(obj) 获取当前对象所有Symbol类型的私有属性,结果为数组
Reflect.ownKeys(obj);获取所有私有属性,不论是啥类型
let obj = { name: 'zhufeng', age: 12, [Symbol()]: 100 }; let symbolKeys = Object.getOwnPropertySymbols(obj); //获取当前对象所有Symbol类型的私有属性,结果为数组 symbolKeys.forEach(key => { console.log(obj[key]) } // for (let key in obj) { //问题:for in循环是无法迭代symbol类型的私有属性 // console.log(key); // } // ---- // let keys = Object.keys(obj); //获取非symbol类型的私有属性(返回包含属性名的数组) // keys = keys.concat(Object.getOwnPropertySymbols(obj)); //获取symbol类型的私有属性 // console.log(keys); // ---- // let keys = Reflect.ownKeys(obj); //获取所有私有属性,不论是啥类型 // console.log(keys);-
面试题:
let a = { name: 'zhufeng' }; let b = { name: 'web' }; let obj = {}; obj[a] = 100; //obj["[object Object]"]=100 obj[b] = 200; //obj["[object Object]"]=200 console.log(obj[a]); //200对象不能作为属性,它会转换成字符串,会调用toString()
a.toString(); //"[object Object]"
@作用2: vuex/redux中我们需要派发很多行为标识,我们可以把这些行为标识统一管理,为了保证行为标识的唯一性,所以可以基于symbol处理
@作用3:
把Symbol做为对象,提供的很多静态属性方法,是JS很多的知识的底层实现原理,Symbol.hasInstance\Symbol.toStringTag\Symbol.toPrimitive\Symbol.iterator...很多JS底层的处理机制,就是基于这些属性方法实现的
-
-
BigInt大数类型:数字后面加个n即是大数类型
-
10 Number类型
-
10n BigInt类型
-
!在js中进行数学运算时,如果计算的数值超过最大/最小安全数字,计算出来的结果很可能就不准确[如果遇到这样的需求,则一定会出问题:服务器端数据存储,是有longInt类型,服务器返回给客户端的值也是超过安全数字的{一般会以字符串的方式返回,这样保证返回途中数字的安全性},此时我们去进行数学运算,结果可能会错误]
解决方法:把服务器返回的值变为BigInt格式,然后进行运算[保证了运算的准确性{进行运算的另外一个值也应该是BigInt类型}];把运算的结果变为字符串,再发送给服务器即可
BigInt(“45431513451245134251245”);//45431513451245134251245n
(45431513451245134251245n).toString();//"45431513451245134251245"
-
对象类型
- 标准普通 {num:100}
- 标准特殊 [10,20] /\d+/ new Date() new Error() ...
- 非标准特殊 new Number(10)... 原始值类型的对象类型实例
- 函数对象 function fn(){}
2、typeof数据类型检测的底层机制
-
UnaryExpression : typeof UnaryExpression Let val be the result of evaluating UnaryExpression. If Type(val) is Reference, then If IsUnresolvableReference(val) is true, return “undefined”. Let val be GetValue(val). ReturnIfAbrupt(val). Return a String according to Table 35.
UnaryExpression: UnaryExpression的类型 设val为对UnaryExpression求值的结果。 如果类型(val)是引用,则 如果IsUnresolvableReference(val)为真,则返回“undefined”。 让val为GetValue(val)。 ReturnIfAbrupt (val)。 根据表35返回一个字符串。
特点1:返回的结果是字符串,字符串中含有了对应的数据类型
-
typeof typeof typeof[1,2,3];
typeof[1,2,3] //"object"
typeof "object" //"string"
typeof "string" //"string"
多个typeof返回的都是string
特点2:按照计算机底层存储的二进制进行检测【效率高】
- 计算机科学:计算机原理、进制转化、计算机网路、数据结构和算法...
- 000 对象
- 1 整数
- 010 浮点数
- 110 布尔
- 000000... null
- -2^30 undefined
- ...
特点3:typeof null -> "object"
特点4:typeof 对象 -> “object” && typeof 函数 -> "function"
- 验证是否是对象的判断
特点5:typeof 未被声明的变量 -> "undefined"
- 插件封装中的暴露API
检测数据类型:
-
typeof
- 返回结果是一个字符串,字符串中包含了对应的数据类型
- typeof检测未被声明的变量,不会报错,结果是”undefined“
- typeof null -> "object" ?
- typeof 不能细分是啥对象「排除函数对象」,返回结果是 ”object/function“ ?
-
所有的数据类型值,在计算机底层中都是以2进制形式存储的{64位}
-
而typeof就是基于二进制检测的:如果是以“000”开始的二进制,则被识别为对象(null存储的二进制值都是0,符合以000开始);然后再去看对象是否实现了[[call]],实现了则为函数(返回“function”),没实现就是对象(返回“object”);
好处:检测处理的性能是很高的
-
instacnceof
-
constructor
-
Object.prototype.toString.call([value])
-
Array.isArray([value])//特殊
需求:验证val是否为一个对象
if (val !== null && (typeof val === "object" || typeof val === "function")) { }
if(val !== null && /^(object|function)$/i.test(typeof val)){
//...
}
支持浏览器导入 && 支持NODE端运行{CommonJS规范}
(function () {
let utils = {
// ...
};
/!* 暴露API *!/ typeof不会报错,只会返回undefined。
if (typeof module === "object" && typeof module.exports === "object") module.exports = utils;(符合CommonJS规范)
if (typeof window !== "undefined") window.utils = utils; (浏览器中有window这个对象,所以不会报错)
})();
课后思考题:0.1 + 0.2 ?== 0.3
原因:计算机计算精准的问题。
0.6 * 2 = 1.2 => 1
0.2 * 2 = 0.4 => 0
0.4 * 2 = 0.8 => 0
0.8 * 2 = 1.6 => 1
0.6 * 2 = 1.2 => 1
........
所有值在计算机底层都是按照二进制存储的,运算也是基于二进制来的...
整数运算一般不会有问题
小数运算一般会出现问题
计算机底层都是按照二进制值来存储数据的 【typeof】
- 64位的二进制值
- 对于无限循环的情况,会自动裁切掉多余的部分 => 十进制的小数,在计算机底层存储的时候,就已经”失真“了
0.1 + 0.2 在进行运算的时候,是按照二进制来进行计算,把计算的结果变为十进制,交给客户端呈现,浏览器呈现的数值是有长度限制的,超过长度限制的也会截取掉【从最后一位向前数,全是零的干掉,遇到非零的就要保留了】
浮点数转为二进制会出现“无限循环”的情况,计算机底层存储的时候按照可以识别的最长位数存储,其余的直接干掉... 所以浮点数存储的二进制值本身就失去了精准度,所以最后运算的结果也是缺乏精准度的,而且小数最后面的零会省略,但凡不是0,都不会省略 => 0.1+0.2=0.30000000000000004 0.1+0.6=0.7(0.70000000000....)
@1 十进制转换为二进制的计算 n.toString(2)
+ 整数部分
+ 小数部分
@2 JS使用Number类型表示数字(整数和浮点数),遵循IEEE-754标准 通过64位二进制值来表示一个数字
babbage.cs.qc.cuny.edu/IEEE-754.ol…
第0位:符号位,0表示正数,1表示负数 S
第1位到第11位「11位指数」:储存指数部分 E
第12位到第63位「52位尾数」:储存小数部分(即有效数字)F
注:尾数部分在规约形式下第一位默认为1(省略不写)
@3 最大安全数字「16位」 Number.MAX_SAFE_INTEGER === Math.pow(2,53)-1
@4 怎么解决精度问题?
+ 将数字转成整数「扩大系数」
+ 三方库:Math.js 、decimal.js、big.js ...
console.log((0.1x10 + 0.2x10)/10);//扩大系数,变为整数进行运算
编写plus和minus方法,可以解决浮点数加减运算中的精准度问题
const plus = function plus(n,m){ ... }; const minus = function minus(n,m){ ... }; -------------------------------------- const coefficient = function coefficient(num) { num = num + ''; let [, char = ''] = num.split("."), len = char.length; //pow() 方法返回 x 的 y 次幂。 return Math.pow(10, len); } const plus = function plus(n, m) { n = +n; m = +m; if (isNaN(n) || isNaN(m)) return NaN; let coeffic = Math.max(coefficient(n), coefficient(m)); return ((n * coeffic + m * coeffic) / coeffic); }; const minus = function minus(n, m) { n = +n; m = +m; if (isNaN(n) || isNaN(m)) return NaN; let coeffic = Math.max(coefficient(n), coefficient(m)); return ((n * coeffic - m * coeffic) / coeffic); };
专题:JS中检测数据类型的方法
typeof[value]:检测数据类型的运算符
@1 返回结果是一个字符串,其次字符串中包含对应的数据类型,例如:"number","object","function"....
typeof typeof typeof[1,2,3] => "string"
@2 弊端
- typeof null -> "object"
- typeof检测对象,除函数对象会返回“function”,其余对象返回的都是“object”「不能细分对象」
- typeof 未被声明的变量 -> 不会报错,而是"undefined"
除了这些以外,用typeof监视原始值类型【或者函数类型】还是非常方便、准确的
@3 检测的原理
所有数据类型在计算机底层都是按照二进制的值来进行存储的,而typeof就是按照二进制的值进行检测的
- 性能好
- 对象的二进制值开头都是“000”,而null的二进制值都是零,所以typeof检测null的时候,识别其实一个对象(这样是不对的);如果识别为对象,再看对象是否实现了call方法,实现了call方法的返回“function”,没有实现的统一返回“object”;
@4 应用
- 检测除null之外的原始值类型可以使用他
- 检测是否为对象 if(obj!==null && /^(object|function)$/.test(typeof obj)){...}
- 检测某个东西是否兼容 if(typeof Symbol!=="undefined"){...}
- ...
instanceof:检测当前实例是否属于这个类【临时拉来做数据类型检测】
@1 [对象] instanceof [构造函数] 检测对象是否为这个类的实例,基于这个特点可以“临时”拿来检测数据类型,返回true/false
@2 可以做一些数据类型的检测【对typeof做了一个补充,可以适当的细分一下对象】
@3 弊端:
无法基于instanceof检测是否为标准普通对象【纯粹对象:直接是Object类的实例】;因为所有对象都是Object类的实例,基于“xxx instanceof Object”检测的时候,返回的结果都是true;
对原始值类型无效:instanceof左侧只要是原始值类型,结果就是false,默认不会进行“装箱”
例如:10 instanceof Number -> false
检测的结果不一定严谨;因为可以修改原型链的指向
.....
@4 当我们基于“【value】instanceof [Ctor]”运算符进行数据类型的检测的时候
传统版本:如果不存在这个函数,浏览器会按照当前【value】原型链一层一层向上找,直到找到Object.prototype为止;查看[Ctor].prototype是否出现在它的原型链中,如果出现了,则结果是true,说明[value]是[Ctor]的实例,反之则为false...
新版本:首先调用【Ctor】【Symbol.hasInstance】(value)这个函数,如果存在这个函数,则直接基于这个函数处理即可当代浏览器基本都有,因为Symbol.hasInstance在Function.prototype中,每一个构造函数都是Function的实例,都可以调用Function.prototype[Symbol.hasInstance]这个方法,当我们基于 “[对象] instanceof [构造函数]”检测处理的时候,内部是这样处理的:
[构造函数][Symbol.hasInstance]([对象])
相关代码
let obj = {}; let arr = []; let reg = /^$/; let num = new Number(10); console.log(arr instanceof Array); //true console.log(obj instanceof Array); //false console.log(reg instanceof Array); //false console.log(typeof num); //"object" console.log(num instanceof Number); //true 说明num是Number类的一个实例「原始值对应的对象类型结果」 console.log(arr instanceof Array); //true console.log(arr instanceof Object); //true console.log(reg instanceof Object); //true console.log(obj instanceof Object); //true console.log(num instanceof Object); //true
//可以修改原型链的指向 const Fn = function Fn() {}; Fn.prototype = Array.prototype; let f = new Fn; console.log(f); //从结构来看,f一定不是数组「数组的结构:数字索引、逐级递增、length属性...」 console.log(f instanceof Array); //true
const Fn = function Fn() { this.name = 'zhufeng'; }; Fn.prototype.sayName = function() {}; Fn.xxx = 'xxx'; Fn[Symbol.hasInstance] = function() { //这样设置是无效的 console.log(1); return false; }; Function.prototype[Symbol.hasInstance] = function() { //这样设置也是无效的 console.log(1); return false; }; let f = new Fn; console.log(f instanceof Fn); //------------------------------------ class Fn { name = 'zhufeng'; sayName() {} // 当做对象,设置静态私有的属性方法「这样设置是有用的,所以重构“构造函数”的Symbol.hasInstance,只支持ES6中class创建的类,ES5中创建的构造函数不支持这样重构」 static xxx = 'xxx'; static[Symbol.hasInstance](obj) { console.log(obj); return false; } } let f = new Fn; console.log(f instanceof Fn); //falseclass Fn { name = 'Fn'; x = 10; y = 20; sum() { if (!this.name) throw new TypeError('this.name is not defined'); // ... } // 只要基于ES6中的class创建类,基于“static xxx”这种语法重写Symbol.hasInstance才有用;如果是ES5创建的类,基于 Fn[Symbol.hasInstance]=xxx 这样重写是无效的!! static[Symbol.hasInstance](obj) { return (obj.name && Object.getPrototypeOf(obj) === Fn.prototype) ? true : false; } } let f1 = new Fn; let f2 = new Fn; console.log(f1 instanceof Fn); //true f1.sum(); let obj = {}; Object.setPrototypeOf(obj, Fn.prototype); console.log(obj instanceof Fn); //Fn[Symbol.hasInstance](obj) false // console.log(obj instanceof Fn); //true // obj.sum(); //报错
@5 分析instanceof的优缺点和底层实现机制,并且重写instancof
/* instance_of:检测value是否为Ctor的实例 value:要检测的实例 Ctor:要检测的构造函数 */ const instance_of = function instance_of(value, Ctor) { // 保证Ctor的格式也是正确的 要是一个函数,并且有prototype属性 if (typeof Ctor !== "function") throw new TypeError('Right-hand side of instanceof is not callable'); if (!Ctor.prototype) throw new TypeError('Function has non-object prototype in instanceof check'); // 不支持原始值类型值的检测 if (value === null) return false; if (!/^(object|function)$/.test(typeof value)) return false; // 支持Symbol.hasInstance方法的直接按照这个处理 //if (typeof Ctor[Symbol.hasInstance] === "function") return Ctor[Symbol.hasInstance](value); if (typeof Symbol !== "undefined") return Ctor[Symbol.hasInstance](obj); // 不支持的则按照原型链一层层的查找即可 Object.getPrototypeOf(value):获取value所属构造函数的原型对象 let proto = Object.getPrototypeOf(value); while (proto) { // Ctor.prototype出现在了value的原型链上「value是Ctor的实例对象」:直接返回true & 结束查找 if (proto === Ctor.prototype) return true; proto = Object.getPrototypeOf(proto); } return false; };Object.getPrototypeOf(value):获取value所属构造函数的原型对象
Error 类型
除了通用的Error构造函数外,JavaScript还有6个其他类型的错误构造函数。更多客户端异常,详见 Exception Handling Statements。
EvalError
创建一个error实例,表示错误的原因:与 eval()有关。
eval():函数会将传入的字符串当做 JavaScript 代码进行执行。
InternalError
创建一个代表Javascript引擎内部错误的异常抛出的实例。 如: "递归太多".
RangeError
创建一个error实例,表示错误的原因:数值变量或参数超出其有效范围。
ReferenceError
创建一个error实例,表示错误的原因:无效引用。
SyntaxError
创建一个error实例,表示错误的原因:
eval()在解析代码的过程中发生的语法错误。TypeError
创建一个error实例,表示错误的原因:变量或参数不属于有效类型。
URIError
创建一个error实例,表示错误的原因:给
encodeURI()或decodeURI()传递的参数无效。
constructor:获取当前实例所属的构造函数【临时】
@1 [value].contructor 获取其构造函数,验证是否为我们想检测的类
例如:[value].constructor===Array
@2 相比较于instanceof来讲
- 可以检测原始值类型的值【排除null/undefined】
- 检测是否为纯粹对象(标准普通对象) [value].constructor===Object
- 和instanceof一样,检测的结果仅供参考【constructor这个值是可以被肆意修改的】
相关代码
//--------------constructor
let arr = [];
let reg = /^$/;
let num = 10;
console.log(arr.constructor === Array); //true
console.log(arr.constructor === Object); //false
console.log(reg.constructor === Array); //false
console.log(num.constructor === Number); //true
Object.prototype.toString.call([value]):专门检测数据类型的方法
@1 调用Object.prototype.toString方法,让方法中的this指向检测的值,就是检测当前值的数据类型
返回的结果是一个字符串”[object ?]“
例如:Object.prototype.toString.call(10) -> "[object Number]"
Object.prototype.toString.call(new Number(10)) -> "[object Number]"
它是所有检测数据类型的办法中,最强大、最稳定...的方式{除了写起来麻烦一些}
@2 返回的结果是”[object ?]“,"?"会是啥呢?
首先获取【value】[Symbol.toStringTag]属性值。如果存在这个属性,则这个属性值是什么,”?“就是什么
如果没有这个属性,一般“?”是当前实例所属的构造函数
Symbol.prototype & BigInt.prototype & Math & GeneratorFunction.prototype & Promise.prototype & Set.prototype & Map.prototype ... 这些类的原型上,都有Symbol.toStringTag这个属性
Object.prototype.toString这个方法是用来检测数据类型的,而且方法内部规定:方法中的this是谁,我们就检测谁的类型,所以我们基于call方法去改变this指向
除Object.prototype.toString之外,其余构造函数原型上的toString一般都是用来“转换字符串的”,只有它是用来检测数据类型的,返回的结果 “[object ?]”
相关代码
//-------------Object.prototype.toString.call
let obj = {},
toString = obj.toString; //->Object.prototype.toString 基本上所有的数据类型,所属构造函数的原型上都有toString方法,一般都是用来转换为字符串的,只有Object.prototype.toString是用来检测数据类型的
console.log(toString.call(10)); //"[object Number]"
console.log(toString.call(new Number(10))); //"[object Number]"
console.log(toString.call("zhufeng")); //"[object String]"
console.log(toString.call(true)); //"[object Boolean]"
console.log(toString.call(null)); //"[object Null]"
console.log(toString.call(undefined)); //"[object Undefined]"
console.log(toString.call(Symbol())); //"[object Symbol]"
console.log(toString.call(10 n)); //"[object BigInt]"
console.log(toString.call({})); //"[object Object]"
console.log(toString.call([])); //"[object Array]"
console.log(toString.call(/^$/)); //"[object RegExp]"
console.log(toString.call(function() {})); //"[object Function]"
console.log(toString.call(new Date())); //"[object Date]"
console.log(toString.call(new Error())); //"[object Error]"
console.log(toString.call(Math)); //"[object Math]"
console.log(toString.call(function*() {})); //"[object GeneratorFunction]"
console.log(toString.call(Promise.resolve())); //"[object Promise]"
// 即使constructor值被修改 或者 基于Object.setPrototypeOf重定向实例的原型指向,结果也是不变的!!所以 toString.call 这种办法检测的结果是非常可靠的!!
[object ?] 返回自定义值
//自定义的构造函数一般返回的结果都是Object
// const Fn = function Fn() {};
// let f = new Fn;
// console.log(toString.call(f)); //“[object Object]”
// 需求:期望自己写自定义构造函数,所创建出来的实例在检测数据类型的时候,可以返回的是“[object 自己的构造函数]”
const Fn = function Fn() {};
Fn.prototype[Symbol.toStringTag] = "Fn";
let f = new Fn;
console.log(toString.call(f)); //“[object Fn]”
class Fn {
[Symbol.toStringTag] = "Fn";
}
let f = new Fn;
console.log(Object.prototype.toString.call(f));//"[object Fn]"
编写工具类utils能实现类型检测
(function() {
"use strict";
/* 检测数据类型的 */
const getProto = Object.getPrototypeOf,
class2type = {},
toString = class2type.toString, //Object.prototype.toString
hasOwn = class2type.hasOwnProperty; //Object.prototype.hasOwnProperty
//fnToString = hasOwn.toString,//Funtcion.prototype.toStirng
//ObjectFunctionString = fnToString.call(Object);//'function Object(){native code}'
// 检测是否为函数
const isFunction = function isFunction(obj) {
return typeof obj === "function" && typeof obj.nodeType !== "number" &&
typeof obj.item !== "function";
};
// 检测是否为window对象
const isWindow = function isWindow(obj) {
return obj != null && obj === obj.window;
};
// 通用检测数据类型的办法,返回结果:字符串、含小写的数据类型
const toType = function toType(obj) {
let reg = /^\[object (.+)\]$/;
if (obj == null) return obj + "";
return typeof obj === "object" || typeof obj === "function" ?
reg.exec(toString.call(obj))[1].toLowerCase() :
typeof obj;
};
// 检测是否为标准普通对象(纯粹对象)
const isPlainObject = function isPlainObject(obj) {
let proto, Ctor;
if (!obj || toString.call(obj) !== "[object Object]") return false;
proto = getProto(obj);
if (!proto) return true;
//proto.hasOwnProperty('constructor') => hasOwn.call(proto, "constructor")
Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
//fnToString.call(Ctor) === ObjectFunctionString
//fnToString.call(Ctor) === fnToString.call(Object)
return typeof Ctor === "function" && Ctor === Object;
};
// 检测是否为空对象
const isEmptyObject = function isEmptyObject(obj) {
if (obj == null || !/^(object|function)$/.test(typeof obj)) return false;
let keys = Object.getOwnPropertyNames(obj);
if (typeof Symbol !== "undefined") keys = keys.concat(Object.getOwnPropertySymbols(obj));
return keys.length === 0;
};
// 检测是否为数组或者类数组
const isArrayLike = function isArrayLike(obj) {
let length = !!obj && "length" in obj && obj.length,
type = toType(obj);
if (isFunction(obj) || isWindow(obj)) return false;
return type === "array" || length === 0 ||
typeof length === "number" && length > 0 && (length - 1) in obj;
};
/* 暴露API */
const utils = {
version: '1.0.0',
isFunction,
isWindow,
toType,
isPlainObject,
isEmptyObject,
isArrayLike
};
if (typeof module === "object" && typeof module.exports === "object") module.exports = utils;
if (typeof window !== "undefined") window.utils = utils;
})();
Array.isArray:检测是否为数组
isNaN:检测是否为有效数字
面试题
alert({
name: 'xxx'
}); //=>“[object Object]” alert会把编写的值转换为字符串宰输出,而对象toString的时候,调用的是Object.prototype.toString这个方法,而这个方法是检测数据类型的
// 真正转换标准普通对象为字符串
// + JSON.stringify 把对象变为 JSON 格式字符串 ->JSON.parse
// + Qs.stringify 依托Qs第三方库,我们把对象变为 urlencoded 格式字符串
// 前后端数据通信的时候,我们经常需要把对象变为指定的字符串格式,传递给服务器;或者把服务器返回的指定格式字符串,变为对象!!
let obj = {
name: 'zhufeng',
age: 12,
teacher: 'zhou'
};
// console.log(JSON.stringify(obj)); //'{"name":"zhufeng","age":12,"teacher":"zhou"}'
console.log(Qs.stringify(obj)); //'name=zhufeng&age=12&teacher=zhou' */
3、JS底层存储机制:堆(Heap)、栈(Stack)内存
- 堆(Heap)、栈(Stack)
- ECStack(Execution [ˌeksɪˈkjuːʃn] Context Stack)和 EC(Execution Context )
- GO(Global Object) 和 VO(Varbale Object)
面试题:var a = {n: 1}; var b = a; a.x = a = {n: 2}; console.log(a.x);//undefined console.log(b);//Object
运算符优先级: developer.mozilla.org/zh-CN/docs/…
var a = {n: 1}; var b = a; a.x = a = {n: 2}; console.log(a.x);//undefined console.log(b);//Object
知识点:在全局上下文中,首先看VO(G)有没有存在这个变量,如果没有再找GO有没有存在这个对象,如果没有则报错;如果遇到window.xxx,则直接找GO中是否存在,如果不存在则返回undefined,不会报错!
a是对象的某个属性,不论a的值是什么类型,a连同他的值都在堆中。
a是一个变量
a的值是原始值,原始值是存储在栈中的 a和它关联
a的值是一个对象,先开辟堆内存,把堆内存的地址放在栈中存储 a和它关联
4、前端必备数据结构:栈结构 & 队列结构(优先级队列)
组织结构 Array
JS中的数组结构非常的简单(已经是浏览器帮助我们进行封装处理好的)
可以存储不同的数据类型值
数组容量伴随存储的内容自动缩放
Array.prototype上提供很多供数组操作的方法
优势:基于索引直接进行查找和获取,效率很高
弊端:进行中间插入和删除操作时,性能非常低(数组塌陷和删除中间项的优化)
数组塌陷问题:删除数组中的某一项后,后面每一项的索引都要往前提一位【性能问题】
案例:(删除指定的元素)
let arr = [10, 20, 30, 40, 50];
@1
// let res = arr.splice(1, 1);
// console.log(arr, res);
@2
// arr[1] = arr[arr.length - 1];
// arr.pop();
// console.log(arr);
@3
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
console.log(item);
if (item === 20) {
arr.splice(i, 1);
i--;
}
}
栈结构 Stack
后进先出 LIFO(last in first out)
只能在一段操作(顶端front):包括增加(进栈)和删除(出栈)
递归算法中的无限递归会出现栈溢出
代码案例:(后进先出)
class Stack {
container = [];
//进栈
enter(item) {
return this.container.push(item);
}
//出栈
leave() {
return this.container.pop();
}
//长度
size() {
return this.container.length;
}
//内容
value() {
return this.container.slice(0);
}
}
let sk1 = new Stack;
sk1.enter(10);
sk1.enter(20);
sk1.enter(30);
console.log(sk1.leave());//30
进制
在浏览器中都是以十进制显示
计算机计算是以二进制数进行计算
需求:把十进制数转成二进制数(整数情况下:需要转成二进制的十进制数除以2,将得到的余数拼接起来)
class Stack {
container = [];
//进栈
enter(item) {
return this.container.push(item);
}
//出栈
leave() {
return this.container.pop();
}
//长度
size() {
return this.container.length;
}
//内容
value() {
return this.container.slice(0);
}
}
Number.prototype.decimal2binary = function decimal2binary() {
let decimal = +this;
//判断是不是整数
if (/\./.test(decimal)) {
//包含小数
throw new RangeError('处理的数字必须是整数');
}
//整数
let sk = new Stack;
if (decimal === 0) return "0";
while (decimal > 0) {
sk.enter(decimal % 2);
decimal = Math.floor(decimal / 2);
}
return sk.value().reverse().join('');
}
let num = 28;
console.log(num.toString(2));
console.log(num.decimal2binary());
补充:
-
new Number(10); 返回的结果为 Number {10},是一个对象。
-
+new Number(10); 返回的结果为 10,才是一个数字。
-
如何判断一个数是否为整数?
先转为字符串,在判断是否含有小数点(可使用正则/\.test(number)/)
队列结构 Queue
先进先出 FIFO (first in first out)
允许在前端(front)删除,允许在后端(rear)插入
特殊:优先级队列
面试题:击鼓传花(N个人一起玩游戏,围成一圈,从1开始数数,数到M的人自动淘汰;最后剩下的人会取得胜利,问最后剩下的是原来的哪一位?)
//队列结构
class Queue {
container = [];
// 从顶端进入队列
enter(item) {
this.container.unshift(item);
}
// 从底端出队列
leave() {
return this.container.pop();
}
// 长度
size() {
return this.container.length;
}
// 内容
value() {
return this.container.slice(0);
}
}
//击鼓传花
// + n参与的人数 >= 1
// + m被淘汰的数字
const game = function game(n, m) {
let qe = new Queue;
//让参与的人一次进入这个队列
for (let i = 1; i <= n; i++) {
qe.enter(i);
}
while (qe.size() > 1) {
//只要的队列中还存在两个及两个以上的人,则重复这个循环
for (let i = 1; i < m; i++) {
qe.enter(qe.leave());
}
//数到m的人直接出队列被淘汰
qe.leave();
}
return qe.value()[0];
}
console.log(game(8, 5));
补充:
- 击鼓传花的循环要从1开始。