You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
var obj = {};
var a = Symbol('a');
var b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols); // [Symbol(a), Symbol(b)]
如果使用Symbol.for可以获取同一个的Symbol值,有则返回,没有就创建
var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');
console.log(s1 === s2); // true
Symbol.keyFor方法返回一个已登记的Symbol类型值的key
var s1 = Symbol.for("foo");
console.log(Symbol.keyFor(s1)); // "foo"
var s2 = Symbol("foo");
console.log(Symbol.keyFor(s2) ); // undefined
JS 数据类型
基本(原始Primitive)类型:undefined, null, String, Number, Boolean, Symbol
引用(对象)类型:Object
基本类型是没有方法可以调用的。比如undefined.toString()是会报错的,'1'.toString()可以是因为'1'已经不是原始类型了。
基本包装类型(装箱拆箱)
'1'.toString()有方法调用,这主要是因为Boolean,Number,String有相应的包装类型,是为了方便操作基本数据类型设置的。
包装类型和引用类型的区别主要是对象生存期:
引用类型:使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都保存在内存中
包装类型:只存在这一行代码执行的瞬间,然后立即销毁,所以不能在执行那一瞬间添加方法和属性。
每当读取一个有包装类型的基本类型时,会自动创建一个对应的基本包装类型对象,从而让我们能够调用一些方法操作这些数据。
因为第二行创建的对象其实在第二行执行完已经销毁了。第三行读取的时候自己又创建了String对象,然后新的对象没有color属性。
使用new String()调用和String调用是不同的,前者typeof 返回object并且可以添加属性和方法,但是容易让人不清楚是在操作基本类型还是引用类型,所以不推荐使用,后者则还是string。
拆箱:将引用类型转换成对应值的基本类型,是通过引用类型的valueOf方法或者toString方法来实现的,也就是JS的隐式转换
类型转换和隐式转换
转换类型只有三种情况:
如下图:
对象转原始值就是通过调用内置的ToPrimitive
ToPrimitive(obj, PreferredType) 函数转换的结果一定是基础类型
Symbol.toPrimitive方法在转原始类型的时候优先级最高
例:
隐式转换
1、四则运算符
加法运算符:
除了加法运算符,其中一方是数字则另一方转换为数字
例:
2、比较运算符
** 3、==运算符
规则如下
类型判断
1、typeof
优点:可以快速检测除了null的基本类型
缺点:无法检测null、object、Array等类型
2、instanceof
优点:只能可以检测array,function,object等引用类型,不能检测基本类型
缺点:由于instanceof是判断是否在原型链上,不能判断对象具体的类型,只能判断两个对象是否是实例关系。多全局对象(跨窗口或者多iframe)
3、Object.prototype.toString
内部实现步骤:
为什么不是toString(),而要Object.prototype.toString(),是因为其他类型作为Object实例可能重写了toString方法
优点:除了自定义对象基本都能检测,即使是多全局环境
缺点:无法区分自定义对象类型,自定义类型可以采用instanceof区分
4、constructor
缺点:undefined和null 没有constructor,而且constructor指向可以改变
typeof原理,instanceof原理
typeof null = 'object'
由于null的也是0,所以返回的是object。
undefined和null的区别
null 表示没有对象,此处不应该有值
undefined表示缺少值,此处应该有值但是还没有定义
Number精度问题
JS采用了IEEE754格式来表示。IEEE754规定,双精度浮点数尾数部分的有效数字是52位。如图:
IEEE754规定,在计算机内部保存有效数字时,默认第一位总是1,所以舍去,只保留后面的部分。比如保存1.01时,只保存01,等读取的时候再把第一位加上,所以52位有效数字可以存储到53位。
指数位第一位为1而不是0是怕被当做无效片段舍弃,所以将指数转化为10进制时要减去1023
所以能提供的安全数字是(-253, 253),注意两边都是开区间。
安全整数:“安全”意思是说能够one-by-one表示的整数,也就是说在(-2^53, 2^53)范围内,双精度浮点数和整数是一对一的,反过来说,在这个范围以内,所有的整数都有唯一的浮点数表示,这叫做安全整数。比如1 === 1.00是true。
不安全整数:而超过这个范围,会有两个或更多整数的双精度表示是相同的;反过来说,超过这个范围,有的整数是无法精确表示的,只能round到与它相近的浮点数(说到底就是科学计数法)表示,这种情况下叫做不安全整数。比如253 === 253 + 1是true。和253是一样的尾数。
最大安全整数:2*53-1 = 9007199254740991
最大数字:则是全填满
再来看:
精度损失主要出现在进制转换和对阶计算中。在运算的过程中,先将0.1和0.2转换成二进制,但是他俩的二进制会无限循环
为什么0.1=0.1?
IEEE754针对0.1这种无限循环这种默认舍入模式舍入到最接近且可以表示的值,当存在两个一样接近的时,取偶数值。所以会有
js尾数是52位,再加上省略一位是53位,所以2**52 = 9007199254740992。长度是16位,由于第一位默认是1,所以JS最大精度范围是17位?有疑问。
由于精度范围是17位,所以0.1000000000000000 = 0.1。
0.1+0.2 = 0.30000000000000004
所以0.1 + 0.2 != 0.3
解决方法:
超大整数:
1.使用bigInt处理:在整数后面加n的形式定义
2.将数字作为字符串来处理
浮点数:
1、乘最小数倍数转为整数处理
2、字符串处理
变量在内存中的存储形式
基本类型:存储在栈内存中,值有固定大小,按值访问
引用类型:存储在堆内存中,值大小不固定,按址访问,操作对象时操作的是对象的引用。
栈的运算速度比堆快,所以将变量都放在栈中,然后引用类型的指针也放在栈中,由于引用类型结构复杂且可扩展,放在栈里影响栈性能,所以放在堆里。
所以先从栈里查找再去堆里查找
JS对象的底层存储
Object存储和查找一个属性,大概有以下几种方式
属性不超过128个的时候则使用Search Cache
属性都是连续数字的时候使用数组,此方法最快
其他情况使用哈希表,并且数字和字符串哈希不一样
数组和链表
严格意义上的数组,数组是一串连续的内存位置,然后通过位置偏移来获取数据。
JS的数组是哈希映射,可以用不同的数据结构实现,比如链表。所以JS数组获取数据则是通过遍历寻址。效率更低。
其实在解析的时候有优化,将同类型数据的数组JIT会转换为连续的内存地址存储,但是一旦有不同类型的数据时,则恢复到以前的哈希映射形式,效率降低。
ES6有ArrayBuffer,则是通过创建连续的内存地址,在处理大量数据时提升明显。是JS操作二进制数据的一个接口,由于直接操作二进制数据,所以可以直接和硬件进行数据交换传递,不用再进行格式转换等,所以效率高。
Symbol实现原理及应用
Symbo几个特点:
Symbol值通过Symbol函数生成,使用typeof,结果为"symbol"。由于typeof的原理可知,这个没法实现接受一个字符串作为参数,表示对Symbol实例的描述。Symbol值不能和其他类型值进行运算Symbol值可以显示转化为字符串规范中当调用 Symbol 的时候,会采用以下步骤:
实现
应用
运算符
题一
这题主要考的就是操作符优先级和赋值运算符
操作符优先级简单点的如下
赋值运算符:
像A=B这个操作规范是如何解释的:
从以上步骤可以看出,赋值是从右向左,但是读取数据是从左向右的。
再来看A=B=C,可以看成A=(B=C)
所以刚才那道题计算过程如下
此时的关系如下图:
题二
再看一题
这里除了this外,还考察了赋值运算符和,运算符
上一题讲了赋值运算符在赋值完之后还会返回对应的值,所以(d.fn = obj.fn)实际是上返回了obj.fn的值,所以实际上是在window环境下执行的,所以this指向window
,运算符则是返回最后一个值,所以var e = (1,2,3,4,5)时,e的值是5。所以(b, obj.fn)实际上返回的是obj.fn的值,所以也是在window环境下执行的,所以this指向window
跨域
跨域是由于浏览器的同源策略导致的。同源策略是为了防CSRF攻击
分为:
DOM同源策略:不同域的iframe限制互相访问
XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求
没有XmlHttpRequest同源策略时的CSRF:
没有DOM同源策略时的CSRF:
跨域的解决方式:
客户端设置,如果要携带cookie则需要添加
服务端设置 response header中设置
基本原理就是通过动态创建script标签,然后利用src属性进行跨域。
浏览器有同源策略,但是服务器没有,可以通过服务器代理所要域的请求
适用于iframe 通过document.domain将两个iframe设置相同的域名,然后通过Window['iframename']获取
在一个window的生命周期内, window载入的所有的页面都是共享一个window.name的,即使页面甚至域名都不同。每个页面对window.name都有读写的权限,并且name长度可以达到2MB
子框架可以修改父框架的src的hash值,且修改hash值父框架不会刷新。传递数值有限
The text was updated successfully, but these errors were encountered: