My Little World

js 知识点 一

1.< script > 标签几个属性

async :立即异步下载,因此不保证能够按指定的先后顺序执行,但会在load事件前,DOMContentLoad前后执行
defer :延迟执行,也不保证按顺序执行,在DOMContentLoad前后执行
src :外部文件链接
charset :src指定代码的字符集,防止出现乱码
language :已废弃,表示脚本语言,
type :值为脚本语言内容类型(MIME),language替代属性,服务器传递过来类型为application/x-javascript需要注意,一个是可能导致脚本被忽略,另一个是可能触发浏览器XHTML标准模式;另外外链文件不是js文件时,一定要设置该值

2.js 代码执行过程

包括两个部分一个是编译,或者叫预处理过程,一个是执行
编译过程 首先会创建一个全局作用域,然后进行词法分析,
词法分析过程会涉及到变量提升和函数声明提前处理,
即词法分析到变量时会先判断当前域到全局域有没有该变量声明,如果有,就会将该声明的变量挂到该作用域的对象上,
如果到全局域都没有则以全局为背景,新建该变量,并为其分配内存,
对于基本类型,变量中会保存它的值,对于对象,变量中会保存值所在内存的内存地址,
在一定场景下,这里就相当于发生了变量提升,把变量从局部提升到了全局,或者将声明提前到了使用之前,
避免变量提升可以使用es6的let 关键字或者使用立即执行实现变量声明使用,但要注意使用let重复声明会报错,var 会同名覆盖;
当遇到函数声明时,会直接按声明顺序,将声明提前到代码顶端,
重复声明 的函数会被后者覆盖,
如果变量名与函数名相同,则无论原来词法位置如何,先将函数声明提前到顶端,不再进行变量声明,变量名在哪赋值,则原函数在哪被覆盖,
覆盖之前调用使用原函数
提前之后会为函数创建作用域,
这时创建的作用域就是函数的 【【scope】】属性,保存着指向父域到全局域的指针
(指针就是指向活动对象的一个地址,全局域浏览器环境就是windows,nodejs环境就是global)
将来执行时会根据此指针建立作用域链,作用域链就是指针列表,指向不同层级作用域的内存,在里面取值
以上就基本上是编译过程,除了一些特定的编译规则,编译过程还会会使用大量技巧进行优化提升编译速度
但在使用js时,需要注意try-catch,with和eval三个语法关键字的使用,因为在使用它们时,会创建块级作用域,
破坏掉编译器原有的创建管理作用域的规则,在编译时,编译器也就不会对其中的代码进行优化,最终导致性能变差

运行过程,基本就是在运行函数,函数在运行时会首先根据【【scope】】属性拿到父域到全局的指针,创建指针列表即作用域链,
然后创建函数执行的局部作用域,将函数中声明的变量挂到局部作用域活动对象上,然后将局部作用域指针推向作用域链前端,
在用到变量值时,会根据作用域链依次查找取值
函数执行结束后,局部活动对象被销毁,作用域链被销毁,进行垃圾回收
但是对于声明在一个函数内部的函数来说,这个声明的函数在声明时,就会包含其父域的活动对象,
因此在外部使用该函数时,可以访问到其父域的变量,实现了父域访问子域变量,延长了变量的使用范围,也就是常见的闭包现象,
缺点就是闭包的函数执行完毕后,因为本身【【scope】】会指向父域的活动对象,所以父域的活动对象始终不会被回收,除非闭包函数被销毁
从而有可能造成内存泄露,为什么说有可能,因为父活动对象占的内存其实是比较小的,造成内存泄露的真正原因是使用闭包易造成循环引用,
尤其是闭包作用域中保存dom结点时,如果是在IE中,dom和bom都是以C++的COM对象保存的,垃圾回收机制是计数策略,
因此循环引用的话,永远都不会被回收

补充,对于未以分号结尾的语句,会采用能合就合的原则,即,会尝试和下一行语句合并执行,如果能合并执行就合并执行,
不能合并执行就在中间加分号再执行,如果再执行不了就会报错
好处就是对于条件判断语句可进行一行一行判断
坏处就是如果下一行以大括号开头,上一行语句就会变成函数,导致出现意想不到的结果
两个例外,
对于return,break,continue语句不会做尝试合并;
对于++,–自增自减符后没有分号,上一行也没分号情况,自增自减会和下一行合并,上一行自行添加分号

以上即是我所了解的js运行机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//补充1
每个值都有内存地址,变量名指向内存地址/保存内存地址
变量名携带内存地址存储在栈中
保存变量值的内存空间在堆中(数据类型:object,function,函数缓存区,函数加载区),相同模样的值,内存地址不同,(即不同变量名,赋值为看似一样的对象时,会不相等;更改变量的值,即更改指向的地址)
常量值保存在池中(数据类型:string,number,boolean),相同值,内存地址相同

变量是个引用,常量是个值
== 比较值
=== 比较地址

//补充2
function func(a,b,c){
/*
* 逻辑代码执行前
* step1 创建AO对象
* AO ={}
* step2 初始化AO对象
* AO = {
* this:undefined,//内置
* arguments:undefined,//默认
* a:undefined,//参数
* b:undefined,//参数
* c:undefined,//参数
* inner:undefined//函数内声明
* }
* step3 赋值
* AO = {
* this:window,//内置
* arguments:[length:2,0:1,1:2],//默认
* a:1,//参数
* b:2,//参数
* c:undefined,//参数
* inner:undefined//仅声明还未执行
* }
* step4 处理函数声明,有相同变量名的函数覆盖赋值已有变量名,新函数名直接挂到AO上
* AO = {
* this:window,//内置
* arguments:[length:2,0:1,1:2],//默认
* a:function a(){},//有同名函数声明进行覆盖
* b:2,//参数
* c:undefined,//参数
* inner:undefined,//仅声明还未执行
* innerFunc:function(){}//添加函数声明
* }
*/
console.log(arguments);
console.log(global)
console.log(inner)
////////////////
var inner = 20;//执行到该步,inner被赋值为20
function inner(){

}
function innerFunc(){

}
console.log(inner)
}

var global=20
func(1,2)

3.关于对象继承

3.0 new 操作符调用构造函数 执行过程
1.创建或者构造一个全新的对象
2.将构造函数的作用域赋给这个新的对象,
3.执行构造函数 (为这个新对象添加属性) ,第二,三步相当于func.call(newobj)
4.如果构造函数没有返回值或返回值非对象,则返回新对象,否则返回构造函数return的对象

3.1 继承的6种方式及优缺点
第一种原型链方式,子类的原型对象指向父类的实例
缺点
给原型添加方法必须要在替换原型的语句之后;
不能实现多继承(原型指向多个父对象);
所有属性共享
无法传递参数到父类进行初始化
第二种借助构造函数,在子类构造函数中使用call或者apply执行父类构造函数
优点
可以实现多继承
可以传递参数
属性不共享
缺点
只能继承构造函数中属性,不能继承原型上方法,不能复用方法
实例仅为子类实例,不是父类实例
第三种组合继承,以上两组结合在一起,两种方式都执行
缺点
要调用两次父类构造函数,影响性能
实例属性和原型属性各占一份,同名覆盖机制,重复占内存,没必要
第四种原型式继承,借助object()或者object.create()函数,缺点同原型链方式

1
2
3
4
5
function object(o){
function F(){}
F.prototype = o;
return new F();
}

第五种寄生式继承,利用原型式继承和增强对象过程的封装函数创建子类,缺点同原型链方式
第六种寄生组合式继承,保留借助构造函数部分,原型部分功能借助中间函数处理,
中间函数借助object拿到父类prototype,给到子类的prototype,从而完美解决组合继承的缺点

1
2
3
4
5
6
function inheritPrototype(subType,superType){
var prototype = object(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}
subType.prototype.__proto__ = superType

3.2 es6 使用class extend实现继承
使用class声明构造函数时,本来挂在this上的属性,放在constructor()函数里面执行初始化,
挂在prototype上的方法直接放在class里面声明

使用extend继承时,需要在constructor里面先执行super函数才能使用this关键字,
增强方法同样直接在class中和constructor并列声明,相当于挂在prototype上

注意super函数只能在consructor中运行;super用作对象在constructor中使用时,相当于this,
添加属性会添加在当前对象上,运行函数时会调用继承的父类的方法

还有就是class extend继承null和 什么也不继承是,相当于继承Function.prototype
继承Object时,子类就相当于object 的复制,子类实例就是object实例

4.关于this 四种使用场景

普通函数执行,指向全局作用域
对象属性方法调用,指向调用对象
call,apply中强制绑定对象,this执行绑定的对象
构造函数中this指向构造的新对象
(箭头函数中this,指向词法作用域的父域)

5.js文件位置

放html底部原因:
下载解析执行过程是阻断式的,会停止页面渲染,造成白屏,影响用户体验;
另外JS中如果有对dom的操作,页面中还没有dom,获取不到,是不符合逻辑的,会报错

哪些js功能文件可以放顶部:
与css相关的js,比如rem单位的换算,需要根据根结点进行设置
使用的框架需要在根结点将浏览器支持的样式罗列出来

6.什么情况下用内部文件,内部文件比外部文件好处在哪?

内部文件相比外部文件最大好处就是性能提升,因为访问外部文件时,不管文件大小都会造成一次网络请求链接,请求服务器,下载文件,
解析文件,除了文件本身代码外,还要处理文件头文件尾的请求,增加了链接数,从而造成性能影响
对于功能短小精悍,不会被到处复用的js代码不适合采用外部文件,应该采用内部文件写法,尤其对手机端页面有性能提升

7.开发过程避免缓存方法

在浏览器开启禁用缓存的模式
手动清缓存 ctrl+shfit+del
引用的文件名添加随机数,浏览器根据文件名不同,就会重新获取资源更新缓存

8.使用严格模式弊端及解决办法

在代码压缩时,’use strict’ 这一行可能不在位于第一行,
或者后续代码不需要在严格模式下执行,被压缩在了严格模式范围内,导致了全部以严格模式执行

解决办法就是,将需要严格模式执行的代码放在匿名函数中,形成代码块,在函数中使用严格模式

9.立即执行函数好处

避免变量污染:有些变量仅在小功能内使用,将小功能封装起来,就可以避免这些变量暴露到全局,
另外函数本身没有函数名,不会增加全局变量
提升性能:功能所需变量全都在函数内时,查找变量快
有利于压缩:一部分代码执行需要用到的变量变量名太长,将这段代码封装成立即执行函数,将长变量名以参数形式传进去
避免全局命名冲突:一段代码需要用到两个代表不同功能但名称相同的变量,可以将该段代码封装成立即执行函数,
将其中一个变量以参数形式传递进来,达到换名的目的
保存闭包状态:循环执行异步代码时,将异步代码用立即执行函数包裹,函数内可保存本次循环的状态
改变代码运行顺序:umd,通用模块规范,function(fn){fn()}()

10.变量类型

根据当前变量的值的类型确定
如果想要进行标记,方法有三种
初始化时指定相应类型值
使用匈牙利标记法,用单字母表示类型,添加到变量命中,例sName 代表string类型
使用注释,变量声明时,在旁边添加注释

11.null vs undefined

转数字时,null –>0;undefined–>NaN
undefined可以当做windows对象上的一个属性,null不行

12. 类型转换

基本类型包括,undefined,null,boolean,string,number

基本类型 —> boolean Boolean(someval) 相当于 !!someval
undefined,null,NaN,’’,+0,-0 —> false
其他 —> true

基本类型 —> string String(someval)相当于 someval+’’
一般情况直接加引号
[] —> ‘’
{} —> ‘[object object]’

基本类型—>number + someval 相当于 Number(someval)
undefined,{} —> NaN
false, null,’’,[],—> 0
true —> 1
‘123’ —> 123
‘abc’ —> NaN
[]—> toString —> ‘’ —> 0
{} —> valueOf —> {} —> toString —> ‘[object object]’ —> NaN

基本类型 —>对象
undefined,null —> {}
true/123 —> {initval:true/123}
‘abc’ —> {initval:’abc’,length:3,0:’a’,1:’b’,2:’c’}

对象 —>基本类型

关于–对象分类:
内部对象(错误对象;常用对象:Boolean,Number,String,Object,Array,Function,Date,Exec;内置对象:Math,global,Json)
宿主对象:windows,document
自定义对象

关于–属性访问
obj.someprop—>检查obj是不是null/undefined—>是|不是—>报错|是不是Object,不是的话转object—>取值
obj[someval]—>检查obj是不是null/undefined—>是|不是—>报错|是不是Object,不是的话转object—>计算someval拿到string—>取值

Boolean —>true
Number —>基本类型转的|{}/{a:1}|Date—>相应值|NaN|时间戳
Array.toString:数组每一项转字符串
Function.toString:原代码字符串形式
Date.toString:日期和时间组合形式
Exec.toString:正则字符串形式

运算转换

Number()参数不能有非数字
parseInt()参数只能为字符串,非字符串会进行强制转化字符串,可含非数字,但不能以非数字开头
一元操作符对任何数据类型有效,得到数字

+二元运算侧重转字符串,然后进行字符串拼接
其中一个为字符串或者两个都是对象时,转字符串拼接
undefined,null,Boolean,数字混合运算+时,会被转数字再计算
{}为第一个运算值时,会被当做函数结束,不参与计算

1
2
3
4
5
6
7
8
9
10
11
12
null + null --> 0
undefined + null --> NaN
[] + [] --> ''
1 + [1,2] --> '11,2'
1 + {a:1} --> '1[object object]'
{a:1} + 1 --> 1
{} + {} --> NaN
({}) + {} --> '[object object][object object]'
[] + {} --> '[object object]'
{} + [] --> 0
var a = {}
a + a -->'[object object][object object]'

*/- 转数字,进行相减,如果不能转数字,返回NaN

>/< 侧重转数字
string < string 字典比较
非字符串<非字符串/字符串 都转数字然后比较

string == number 字符串转数字
boolean == 其他类型 二者转数字然后比较
null == undefined —>true undefined和null只与对方和自己==比较时为true,其他均为false
null === undefined —>false
非对象 == 对象 与数字比较object转数字,与字符串比较二者转数字再比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
'0' == null  //false
'0' == undefined //false
'0' == false //true
'0' == NaN //false
'0' == 0 //true
'0' == '' //false

false == null //false
false == undefined //false
false == NaN //false
false == 0 //true
false == '' //true
false == [] //true
false == {} //false

'' == null //false
'' == undefined //false
'' == NaN //false
'' == 0 //true
'' == [] //true
'' == {} //false

0 == null //false
0 == undefined //false
0 == NaN //false
0 == [] //true
0 == {} //false

[] == ![] //true

&&和|| 计算结果根据短路原则判断到了哪里,返回最后一个进行判断的值

运算符优先级
属性访问.
一元操作符(+,-,++,–)
二元操作符(+-*/)
比较(>,<)
相等(==,===,!=)
与&&,或||
三目运算符
赋值

运算符结合性
除了一元操作符,三目运算符和赋值是右结合,其他都是左结合

减少小数计算误差
尽量不使用小数进行比较或运算
转整数计算,再转回相应位数小数
使用toFixed()四舍五入
两个情境需要重复计算时,保证前后书写顺序

13. 数组相关方法

arrmethod

1
2
let arr = []
let iterator = arr[Symbol.iterator]() //可以获取数组迭代器

遍历对象/数组的方法
for
while(do…while)
forEach
map
reduce
for…of
for…in
iterator
generator

14.字符串相关方法

strmethod

15.日期相关方法

一共 33个
set/get(UTC)FullYear,Month,Date,Hours,Minutes,Seconds,Millseconds
set/get Time
get(UTC) Day
getTimezoneOffset

16. Math三个舍入方法

Math.ceil:比值大的最小整数
Math.round:四舍五入
Math.floor:比值小的最大整数

17. setInterval 注意问题

累积效应
代码执行时间大于间隔时间,后续调用会进行累积,累积会在短时间内连续触发

当用于动画时,因与显示器刷新频率不统一会造成视觉卡顿,解决办法如下:
一种是使用CSS3创建动画,根据显示器刷新执行动画
另一种是使用 requestAnimationframe(function(){})函数,也是在显示器刷新时执行
可以解决CSS3无法实现的,例如滚动控制等效果

18. 关于DOM

document 三个属性
url:地址栏中URL
domain:域名,跨域域名 X;父域名到子域名 X;
referer:上一页URL,可在跳转时,判断与当前页是否在同一域,是的话,就可以back,否则 location.href其他页面
dom.getAttribute(‘style’),得到样式字符串
dom.style 得到到样式对象
dom.getAttribute(‘onclick’) ,得到代码字符串
dom.onclick 得到函数对象

动态合集
使用时可能会造成死循环
使用框架如Jquery获取的合集不具有动态性
使用 queryselect(all)()获取到的也是静态合集

动态合集生成原理:
浏览器通过DOM树预先缓存起来,获取时,浏览器会通过缓存直接注册,创建一个变量返回

静态合集生成原理:
获取标识是一个CSS选择器,浏览器会先去解析字符串,判断是一个CSS选择器,
分析选择器并创建一个选择器结构,浏览器根据DOM树创建一个静态文件,即DOM树的一个快照
然后拿选择器和快照进行对比,符合选择器就放到合集中,知道整个快照对比完,再把合集的快照返回

19. 事件

绑定方式

直接通过HTML属性绑定
缺点
有可能响应函数还未被解析就被触发
事件名和回调1对1,修改繁琐
响应执行时,作用域为全局,不同浏览器解析规则不同,造成某些对象无法访问

使用dom属性绑定
冒泡阶段执行
缺点
只能绑定一个响应,后续赋值会被覆盖

IE attachEvent/detachEvent绑定/解绑
冒泡阶段执行
绑定多个时按按绑定顺序的逆序执行

addEventListener()/removeEventListener()绑定/解绑
第三个参数为对象时,passive属性为true时,会针对touchstart,touchend等特定事件通过开启两个线程,
一个执行浏览器默认行为,一个执行JS进行优化

事件优先级
浏览器在绑定事件而不是JS在绑定事件

1.html标签绑定事件会被dom属性事件覆盖
2.html标签优先执行,即使被属性事件覆盖,则执行被覆盖的属性事件
3.仅有监听事件和属性事件时,按绑定顺序执行,即事件对象的冒泡事件可能会在捕获事件之前执行

event对象属性
curentTarget:响应函数绑定的对象
target:发生事件的对象