目录
关于指针:https://blog.csdn.net/weixin_41311515/article/details/80298228
var a = { n: 1 };
var b = a;
a.x = a = { n: 2 };
console.log(a.x); //undefined
console.log(b.x); //{n:2}
首先是
var a = {n:1};
var b = a;
在这里a指向了一个对象{n:1}(我们姑且称它为对象A),b指向了a所指向的对象,也就是说,在这时候a和b都是指向对象A的:
这一步很好理解,接着继续看下一行非常重要的代码:
a.x = a = {n:2};
我们知道js的赋值运算顺序永远都是从右往左的,不过由于“.”是优先级最高的运算符,所以这行代码先“计算”了a.x。
这时候发生了这个事情——a指向的对象{n:1}新增了属性x(虽然这个x是undefined的):
从图上可以看到,由于b跟a一样是指向对象A的,要表示A的x属性除了用a.x,自然也可以使用b.x来表示了。
接着,依循“从右往左”的赋值运算顺序先执行 a={n:2} ,这时候,a指向的对象发生了改变,变成了新对象{n:2}(我们称为对象B):
接着继续执行 a.x=a,很多人会认为这里是“对象B也新增了一个属性x,并指向对象B自己”
但实际上并非如此,由于一开始js已经先计算了a.x,便已经解析了这个a.x是对象A的x,所以在同一条公式的情况下再回来给a.x赋值,也不会说重新解析这个a.x为对象B的x。
所以 a.x=a 应理解为对象A的属性x指向了对象B:
那么这时候结果就显而易见了。当console.log(a.x)的 时候,a是指向对象B的,但对象B没有属性x。没关系,当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止。但当查找到达原型链的顶部 - 也就是 Object.prototype - 仍然没有找到指定的属性B.prototype.x,自然也就输出undefined;(感觉是废话)
---------------------------------------------------------------------------------------------
闭包的和this相关问题https://www.jianshu.com/p/80b273701ab2
预编译,递归
var foo = function () {} 和 function foo()区别
var a = {}
这种方式, 编译后变量声明a 会“被提前”了,但是他的赋值(也就是a)并不会被提前。
也就是,匿名函数只有在被调用时才被初始化。
如果使用
function a () {};
这种方式, 编译后函数声明和他的赋值都会被提前。
词法作用域,动态作用域,作用域链
JS只有词法作用域,没有真正的动态作用域。
Javascript中的函数“在定义它们的作用域里运行,而不是在执行它们的作用域里运行”,这是权威指南里抽象而精辟的总结。
其实本质仍然是作用域链,叫法不同罢了。
还有什么上下文,也都是这一块的概念。
闭包
先说一下[[scope]],也就是作用域链,函数定义时,保存的是父级环境的AO,函数运行时,会创建一个自身AO并保存到作用域链顶端。
查找变量时,是从作用域链顶端开始的,也就是由内往外
AO:(Actived Object)活动对象: 保存函数调用时的局部变量(也就是function内部包裹的)
GO:
闭包:内部函数B被return保存到到外部函数A的外部,就形成闭包。本质是外部函数A结束销毁A的执行上下文,而并没有真正销毁,B仍指向这块区域,因此不会被回收,因此可以从A的外部访问A里的变量
闭包作用:
- 实现公有变量
- 可以做缓存
- 可以实现封装, 属性私有化
- 模块化开发,防止污染全局变量
属性私有化例子:构造函数里 abc:function(){}这样定义,new一个对象a出来后在外部由a调用这个方法是可以访问内部变量的,但是直接访问不可以,就像a的私有变量
function Person(){
//var this={}
var name='123';
this.sayname=function(){
console.log(name)
}
//return this。函数就被返回到外部了(构造函数原理)
}
var person=new Person();
person.sayname();//123
console.log(person.name);//undefine
不污染全局变量的用法也类似
var name="123";//全局变量
var foo=(function(){
//这里定义变量
var name="234"
//这里写函数
function callName(){
console.log(name);
}
return function(){
//这里写要返回的函数
callName();
}
}())
foo();//外部调用,只会取内部变量234
js中函数的作用域是定义时的作用域而不是执行时的作用域
在方法内部改变this指向 https://www.cnblogs.com/pangbo1213/p/7774589.html
既然对象中的say方法中this是指向obj的,那么我们就使用that代替this,在闭包函数中写成that.name 那么结果就是obj
来看闭包的一个例子
function test(){
var arr=[];
for(var i=0;i<10;i++){
arr[i]=function(){
console.log(i);
}//这一步仅仅是将函数引用存入到数组,函数里面不执行,i在函数里没有定义,往上一级找,找到了
//因此下面真正执行时,因为闭包,i成了一个共有变量,i已经变成了10
}
return arr;}
var myarr=test();//这一步将console.log(i)这10个函数存入myarr中;
for(var j=0;j<10;j++){
myarr[j]();//这时经过上一步,i已经变成了10,数组内的每个函数去访问的i都是同一个,值为10
}
解决办法(立即执行函数)
function test(){
var arr=[];
for(var i=0;i<10;i++){
(function(j){
arr[j]=function(){
console.log(j);
}
}(i))
}
return arr;
}
var myarr=test();
for(var j=0;j<10;j++){
myarr[j]();
}
第一次立即执行函数放入j=0,然后立即执行函数消失。这个属于立即执行函数的值就消失了,但是arr[0]中保存了这个j。
第二次立即执行函数会重新生成一个文档,放入一个全新的j=1.同理这个j会被保存到相应的arr[1]中。
也就是说每一个arr[i]中关于立即执行函数所给的值不是同一值。
原因是立即执行函数执行完毕就消失了。再次运行,就是一个新的函数。
立即执行函数
在了解立即执行函数之前先明确一下函数声明、函数表达式及匿名函数的形式
function test(){} 函数声明
var test=function(){}; 函数表达式,
function(){} 匿名函数
(function (){}());建议这一种
(function (){})();
只有函数表达式(函数声明不可以)才能被执行符号执行,执行完函数立即销毁,
除了使用()运算符之外,!,+,-,=等运算符都能起到立即执行的作用。这些运算符的作用就是将匿名函数或函数声明转换为函数表达式。
来看一种特殊情况
function test(a,b,c,d){
//略
}()
会报错,而
function test(a,b,c,d){
//略
}(1,2.3.4)
不会报错,原因是(1,2.3.4)被当成一个独立的表达式,但是也不执行
对象
创建对象的方法
1.字面量:var obj={} 。和var obj=new Object()没有区别,不要用后者
2.构造函数:
- 系统自带的. Object(),Array(),Number()等
- 自定义构造函数(命名需要遵守大驼峰命名法,用来区分)
function Person(){
//var this={};
this.name=" ";
//return this;
}
var person=new Person();
构造函数内部原理(前提是new):1在函数体顶端隐式的加上var this={} (大致这么理解,等到了解原型知识以后会有更深入的解释)2.执行this.xxx=xxx 3隐式的返回this
一个冷门知识:若使用new,则函数返回的必须是一个对象,否则报错。
3.Object.create(原型) 详细介绍请看原型与原型链
构造函数原理的一道题
var a=5;
function test(){
a=0;//变量声明提升
alert(a);
alert(this.a);
var a;
alert(a);
}
test();//0 5 0 this指向window
new test();//0 undefine 0 函数顶部隐式生成this,这个this对象没有a
属性的两种表示方法
obj.name或obj['name']
方括号表示法优点是可以用变量表示。
如果key是一个变量,obj.key--->obj['key'],达不到我们的目的(例:for in循环)
包装类
理论上原始类型绝对不可以有属性和方法,但是有包装类的存在,像下面这段代码不会报错。
总结就是包装类会导致原始类型增加属性不会报错,但是无效
var num=4;
num.len=2;//隐式进行new Number(4).len=2;然后销毁
//隐式进行new Number(4).len 先后生成两个对象,后面一个对象并没有len属性
console.log(num.len);//输出undefine
var str="abcd";
str.length=2;
//new String("abcd").length=2;delete
console.log(str);//abcd
//new String("abcd").length
console.log(str.length);//4,隐式创建的对象是有length属性的,和上个例子的num不太一样
来看道题:
var str="abc";
str+=1;
var test=typeof(str);//test=="string"
if(test.length){//可以进入循环
test.sign="typeof的返回值是string"//也确实可以执行,但是随后销毁隐式生成的对象
}
console.log(test.sign);//undefine
原型
1、定义:原型是function对象的一个属性,它代表了通过构造函数制造出的对象的公共祖先,通过该构造函数产生的对象,可以继承该原型的属性和方法。原型也是对象。
原型的使用:
Person.prototype.name="hehe";
function Person(){
}
var person1=new Person();//person1会继承name这个属性,但是不在person1对象内部
原型的增删改查:必须通过prototype属性,否则都认为是操作对象自己的属性,查可以不用。
因为原型也是一个对象,所以也可以这么写,更加方便
Person.prototype={
//
}
2.利用原型概念和特点,可以提取共有属性
当对象和原型具有同一个属性时,取对象自己的属性。
将对象的共有属性提取至原型,更加方便。
3.对象如何查看原型:隐式属性 __proto__(左右两个_)
__proto__可以手动更改,但是不要这么做
__proto__有什么用?还记得之前构造函数的原理吗,现在可以解释了
函数顶部隐式的var this={}的时候,这个对象并不是空的。
__proto__:Person.prototype 这个属性是个引用,指向Person.prototype
person.name就是通过__proto__来访问原型中的属性的
来看这一段代码:
Person.prototype.name ='sunny';
function Person() {
//var this={__proto__:Person.prototype}
}
var person = new Person();
//person.prototype.name='cherry';
Person.prototype = {
name :'cherry'
}
console.log(person.name)//sunny
//原理就是下面这个过程,Person.prototype变了,但是__proto__保存的引用没变,对象通过__proto__访问原型的时候仍是原来的
//var obj = {name : "a"};
//var obj1 = obj;
//obj = {name : "b"};
Person.prototype = {name : "a"};
__proto__ = Person.prototype;
Person.prototype = {name:"b"};
这体现了Person.prototype = {name : "a"};这种写法与Person.prototype.name="a"这种写法的不同。
前者直接就把原型换了,后者是换了属性的值。如果用Person.prototype.name="a"这种写法,输出的就是cherry了
如果改变一下位置呢?来看
Person.prototype.name ='sunny';
function Person() {
//var this={__proto__:Person.prototype}
}
Person.prototype = {
name :'cherry'
}
var person = new Person();
console.log(person.name)//cherry
因为隐式的var this={__proto__:Person.prototype}是在new阶段才会进行的,这时候已经Person.prototype已经改变了
更加印证了之前那句话:构造函数内部原理(前提是new)
所以对原型的操作,都要注意new的位置
4.对象如何查看构造函数:隐式属性 constructor
person.constructor可以访问构造函数,这是保存在原型对象里的一个隐式属性,如下图所示
这个constructor属性是可以手动更改指向的。(但是一般没什么用?)
原型链
原型是一个对象,他也有__proto__属性,也就是原型也有原型。
原型链的终点,所有对象的祖先:Object.prototype。这个对象没有__proto__属性
child.name++;类似这种操作,都是先从原型链取值,然后赋给自己,也就是执行完之后自己会多一条属性,而原型的属性没有变。
来说一些Object.create(原型)方法,括号里需要传入一个对象或者null。
如果传的是null,那生成的对象就没有原型(对象里为空,没有__proto__)。可以人为赋予__proto__,但是没有什么用。
一道判断题:绝大多数(并非所有)的对象都继承自Object.prototype。
undefined和null不是对象,故也没有选型,不可以调用toString方法(原始类型可以通过包装类调用)
123.toString()不可行,原型是因为被识别成了小数点
冷知识:document.write()隐式调用了toString方法
额外:一个bug':JS存在精度不准问题 0.14*100=14.0000000002.
可正常计算范围:小数点前后16位(不考虑bug)
prototype
和 __proto__
的关系是什么?
prototype
是只有函数才会有的属性;而__proto__
是所有对象(并不)都有的属性。
几乎所有的函数都有一个prototype
属性,prototype
上挂载的所有属性和方法都可以被这个函数的实例继承。
call 与 apply
正常方法执行:test()----->test.call()一致
都可以改变this的指向,区别是接收参数形式不同
function sum(x, y){
var sum = x + y;
alert(sum);
}
sum.apply(this, [1, 2]); //3
sum.call(this, 1, 2); //3
bind返回的是一个函数,供以后调用,call 与 apply 都是立即调用的
用apply模拟实现bind
Function.prototype.bind=function(context){
var self=this; //保存调用函数的引用,没有这一步下面直接用this指向window,
//因为形成闭包,函数被保存到外部了
return function{ //返回一个新函数
return self.apply(context,arguments)
}
}
var fun=xxx.bind(……);
fun();//这里实际调用,this指向window
//还是那句话,谁调用执行this就指向谁,xxx.bind调用函数,此时this指向xxx,这时候保存下来。然后函数结果返回一个函数,在全局调用,这时候this就指向window了
继承
继承的发展史
- 1传统形式->原型链
- 过多的继承了没用的属性
- 2.借用构造函数(构造函数里call)
- 不能继承借用构造函数的原型
- 每次构造函数都要多走一个函数
- 3.共享原型(A.prototype=B.prototype)
- 不能随便改动自己的原型
- 4.圣杯模式
圣杯模式
function inherit(Target,Origin){
Fuction F(){};
F.prototype=Origin.prototype;
Target.prototype=new F();//new一定要在后面,否则无效
Target.prototype.constructor=Target;//不改constructor是Origin函数
Target.prototype.uber=Origin;//记录超类信息
}
高级一点的写法 (闭包,推荐用这种,F是一个私有变量)
var inherit=(function(){
var F=function(){};
return function (Target,Origin){
F.prototype=Origin.prototype;
Target.prototype=new F();
Target.prototype.constructor=Target;
Target.prototype.uber=Origin;
}
}());
对象的枚举
For in循环
这种遍历是会连原型一起遍历的,但只有自己手动设置的属性才会打印,系统自带的不会。
var obj={
name:'12',
__proto__:{
lastname:'deng'
}
}
Object.prototype.abc="123";
for (var i in obj) {
if (!obj.hasOwnProperty(i)) {
console.log(obj[i]);//deng 123
}
}
hasOwnProperty(PropertyName)可以判别是否是自己的属性
in只能判断包括原型链在内有没有这个属性,很少用。in也可以直接用:‘name’ in obj,返回true or false
instance of
A instance of B 代表看A的原型链上有没有B的原型(官方解释是A对象是不是B函数构造出来的,不太准,原型链上的也算)
区别对象还是数组的三种方法(typeof不好使)
1.unknown.constructor
2.unknown instanceof Array
3.Object.prototype.tostring.call(unknown)
深拷贝与浅拷贝
深拷贝:拷贝之后两个个体互不影响
深拷贝步骤:
- 遍历对象
- 判断属性是不是原始值
- 判断属性值是对象还是数组
- 建立相应的数组或对象
- 递归遍历这个属性
function deepClone(origin,target){
var target=target||{},//容错处理
toStr=Object.prototype.tostring,
arrStr="[Object,Array]";
for(var prop in origin){
if (origin.hasOwnerProperty(prop)) {
if (origin[prop]!=="null" && typeof(origin[prop]) =='object') {
target[prop]=toStr.call(origin[prop])==arrStr?[]:{};//创建
deepClone(origin[prop],target[prop]);
}else{
target[prop]=origin[prop];//原始值
}
}
}
return target;
}
数组
两种定义方式
- 字面量 var arr=[]
- var arr=new Array();//若只传一个数字,会认为是数组长度
JS中数组是基于对象的,可以越界访问,返回undefined,不会报错,赋值也可以,数组长度会自动改变
数组常用方法(ES3.0)
改变原数组:
返回值去看W3School文档
push,pop(尾部增减),shift,unshift(头部增减,unshift是增),sort,reverse
splice:参数(从第几位开始,截取几位,在切口处添加新的数据),返回的是截取部分
arr.splice(1,1,2,3)从第1位截取1位,然后加入2,3
第二位为0,就能实现添加:var arr=[1,2,3,5]; arr.splice(3,0,4)
sort()
默认是按字符编码顺序排序的
也可以传入一个函数,这个函数需要两个形参,用冒泡排序的顺序传入两个数组元素,若返回值为正,则交换这两个元素的值
var arr=[10,20,45,1,6,7];
arr.sort(function(a,b){
return a-b;//升序,a比b大时交换,小的往前
或return b-a//降序,a比b小时交换,大的往前
})//1,6,7,10,20,45
sort也可以实现有序数组乱序重组:return Math.Random()-0.5;
不改变原数组
concat拼接,join ----->string的spilt,toString ,
slice参数(从第几位开始,截取到该位)
slice(2)从第2位开始全部截取
slice()全部截取
提一下这里第几位的问题[0,1,2,3,4,5]从第一位截取到第三位返回的是[1,2]
join():参数最好写成字符串形式
[1,2,3].join('-')返回一个字符串"1-2-3"
不传值按','连接
这个过程与String.spilt('char')互逆,返回一个数组
数组去重:(哈希的方式)
还有很多方法,不再一一介绍
将数组元素作为对象属性名存入,如果该属性存在则该数组元素重复,将未定义过的属性名存入一个数组即可
但是存在一个问题,若含有[,,]这样的数组去重结果里是会有undefined的,暂时保留这个问题。
Array.prototype.unique=function(){
var temp={};//用来标记已有的元素
var arr=[];//用于存储已有的元素
var len=this.length;
for(var i=0;i<len;i++){
if(!temp[this[i]]){
temp[this[i]]="abc";//标记占位
arr.push(this[i]);
}
}
return arr;
}
reduce方法实现
Array.prototype.myReduce=function (fn, init){
var len = this.length;
var pre = init;
var i = 0;
if (init = undefined){
pre = this[0];
i = 1;
}
for(i;i<len;i++){
pre = fn(pre, this[i], i);
}
return pre;
}
Array.map
值得提的一点是不会对空数组检测,不用自己做检测处理,但是还是要检测是不是数组的
类数组(具备对象和数组的双重性质)
属性要为索引(数字),一定要有length属性,最好加上push
注意一下,类数组的下标就是属性的实际名字,不是第一个就是obj[0]
看下面例子,push操作是在obj[length]处插入,然后length++
var obj{
2:2,
3:3,
length:2,
push:Array.prototype.push
splice:Array.prototype.splice
}
obj.push(1);
console.log(obj);
//打印出来obj长这样
Object(3) [empty × 2, 1, 3: 3, push: ƒ, splice: ƒ]
2: 1
3: 3
length: 3
push: ƒ push()
splice: ƒ splice()
__proto__: Object
this
https://www.jianshu.com/p/4792dbddfc81
事件
传统句柄方式div.οnclick=function(){} 兼容性好,但是同一对象同一事件只能绑定一个函数
div.addEventListener(事件类型,处理函数,false);可以同一对象同一事件绑定多个函数,同一函数只能绑定一次
IE独用:attachEvent(on+事件类型,处理函数)同一个函数可以绑定多次,程序中this指向window
绑定事件如果是在循环里,要考虑是否生成闭包,看一个例子
//情景:如果想点击一个列表项就打印他的索引号
var list=document.getElementsByTagName('li'),
len=list.length;
// for(var i=0;i<len;i++){
// list[i].addEventListener('click',function(){
// console.log(i);//闭包,输出全为len
// },false);
// }
for(var i=0;i<len;i++){
(function(i){
list[i].addEventListener('click',function(){
console.log(i);
},false);
}(i))
}
给一个js原生绑定事件
function addEvent(obj,type,handle){
try{ // Chrome、FireFox、Opera、Safari、IE9.0及其以上版本
obj.addEventListener(type,handle,false);
}catch(e){
try{ // IE8.0及其以下版本
//obj.attachEvent('on' + type,handle);
obj.attachEvent('on' + type,function(){handle.call(obj)});
}catch(e){ // 早期浏览器
obj['on' + type] = handle;
}
}
}
解除事件处理程序
ele.onclick = false/"/null;
ele.removeEventListener(type, fn, false);
ele.detachEvent('on' + type,fn);//ie
注:若绑定匿名函数,则无法解除
事件处理模型(冒泡,捕获)
事件冒泡:
结构上(非视觉上)嵌套关系的元素,会存在事件冒泡的功能,即同一 事件,自子元素冒泡向父元素。(自底向上)
下面这个例子点击黄色区域仍有三个事件发生
事件捕获:
将addEventListener()中的false改为true就可以
结构上(非视觉上)嵌套关系的元素,会存在事件捕获的功能,即同一事件,自父元素捕获至子元素(事件源元素)。(自顶向下)
IE没有捕获事件,部分浏览器如chrome才有
触发顺序,先捕获,后冒泡,但是对于嵌套结构最里面的那个元素,它属于正常执行事件,谁先绑定就谁先执行
也就是 外捕获-中捕获-内执行-内执行(这两个看绑定顺序)-中冒泡-外冒泡
focus, blur, change, submit, reset, select等事件不冒泡
取消冒泡和阻止默认事件
取消冒泡:
- W3C标准event.stopPropagation0;但不支持ie9以下版本
- IE独有event.cancelBubble = true;(目前chrome也可以)
- 封装取消冒泡的函数stopBubble(event)
阻止默认事件:
默认事件:表单提交,a标签跳转,右键菜单(oncontextmenu)等
- returm false;以对象属性的方式注册的事件才生效
- event.preventDefault(); W3C标注,IE9以下不兼容
- event.returnValue = false; 兼容IE
- 封装阻止默认事件的函数cancelHandler(event)
事件对象
浏览器会把发生事件时一些参数打包成一个对象,会穿给事件处理函数的形参(我们可以给事件处理函数加一个形参)
IE的事件对象是window.event
var event=e||window.event;//兼容IE
事件源对象,是事件对象里的一个属性
event.target火狐只有这个
event.srcElement IE只有这个
chrome都有
兼容性写法:
var target=event.target||event.srcElement;
补充一个event.currentTarget属性,就是在捕获冒泡过程中当前触发事件的那个对象(简单来说就是代表现在冒泡冒到哪了)。
事件委托
利用事件冒泡和数据源对象进行处理(给父对象绑定事件函数就可以,然后获取事件源对象)
优点:1.不需要循环所有元素一个个绑定事件2.灵活,当有新的子元素不需要重新绑定事件
//情景:有很多很多很多的li,想实现点击就打印他的内容
ul.onclick=function(e){
var event=e||window.event;//兼容处理
var target=event.target||event.srcElement;//获取事件源对象,兼容处理
console.log(target.innerText);
}
事件分类
鼠标事件
DOM3规定:click只能监听左键。只有mouseup和mousedown可以区分鼠标左右键 e.button:0左键1滚轮2右键
click=mousedown+mouseup
键盘事件
keydown>keypress>keyup 按下键盘之后连续触发
keydown和keypress区别:keydown可以响应任何键盘事件,keypress只能响应字符类键盘按键
keypress返回ASCII码,可以转换成相应字符
文本操作事件
input(内容更改就会触发),focus,blur,change
窗体操作类(window上的事件)
scroll滚动条触发 ,可以获取当前滚动条位置
load 整个页面全部就绪时
异步加载js
javascript 异步加载的三种方案
defer异步加载,但要等到dom文档全部解析完才会被执行。只有IE能用,也可以将代码写到内部。
<script type="text/javascript" defer="defer"></script>
async异步加载,加载完就执行,async只能加载外部脚本,不能把js写在script标签里。
<script type="text/javascript" async="async"></script>
执行时也不阻塞页面
创建script,插入到DOM中,加载完毕后callBack
callback是我们想要执行的函数
只有appendChild后,js才会被执行。
一个封装好的函数:
function loadScript(url,callback){
var script=document.createElement('script');
script.type="text/javascript";
if (script.readyState) {//IE
script.onreadystatechange=function(){
if (script.readyState=="complete"||script.readyState=="loaded") {
callback();
}
}
}else{
script.onload=function(){
callback();
}
}
script.src=url;//为什么这步放在后面,主要是防止在绑定时间前js文件就下载好了,这样onreadystatechange监听就没用了
document.head.appendChild(script);
}
这个函数直接在html script标签里定义完了直接loadScript('test.js',test)会报错test未定义,因为load函数执行才会去调用function下载js文件。解决方法:loadScript('test.js',function(){test();})这样就可以了。(预编译的问题)
关于异步加载js要说一下,异步之后程序仍然继续执行,且速度是很快的,基本可以认为程序完了异步执行的都还没好,所以报错原因一般就是是方面。不只是加载js,其他异步使用也是适用的。
JS时间线
1、创建Document对象,开始解析web页面。解析HTML元素和他们的文本内容后添加Element对象和Text节点到文档中。这个阶段document.readyState = 'loading'。
2、遇到link外部css,创建线程加载,并继续解析文档。
3、遇到script外部js,并且没有设置async、defer,浏览器加载,并阻塞,等待js加载完成并执行该脚本,然后继续解析文档。
4、遇到script外部js,并且设置有async、defer,浏览器创建线程加载,并继续解析文档。 对于async属性的脚本,脚本加载完
成后立即执行。(异步禁止使用document.write())
5、遇到img等,先正常解析dom结构,然后浏览器异步加载src,并继续解析文档。
6、当文档解析完成,document.readyState = 'interactive'。
7、文档解析完成后,所有设置有defer的脚本会按照顺序执行。(注意与async的不同,但同样禁止使用document.write());
8、document对象触发DOMContentLoaded事件(只能通过addEventListener绑定),这也标志着程序执行从同步脚本执行阶段,转化为事件驱动阶段。
9、当所有async的脚本加载完成并执行后、img等加载完成后,document.readyState = 'complete',window对象触发load事件。
10、从此,以异步响应方式处理用户输入、网络事件等。
生成document-解析-执行并加载完成
命名规范
基本小驼峰,事件全小写,构造函数大驼峰
正则表达式
匹配原则是贪婪的 在表示数字的符号后加?表示非贪婪 如*?,+?,??,{n,}?,{m,n}?
\可以表示特殊的字符,也可以取消特殊含义
var reg=/abc/g;
var reg=new RegExp(“abc”,"g")第一个参数为规则,第二个参数为属性(修饰符)
三个属性( 修饰符)
- i 执行大小写不敏感的匹配
- g 全局匹配(不仅仅是找到第一个符合的就停止)
- m 多行匹配
多行匹配主要是用来匹配开头和结尾
var reg=/^a/gm;
var str="abcd\na";
str.match(reg);
(|) 查找任何指定选项
[]方括号用于查找某个范围内的字符
^在方括号内表示非,方括号外表示首部
. 查找单个字符,除了换行和结束符 \r和\n
\W===[^\w]\w===[0-9A-z_]
\d===[0-9] 数字
\D===[^\d] 非\d
\s===[\r\t\n\v\f ]表示空白字符,包含空格
\S===[^\s]
\b===单词边界
\B===非单词边界
var reg=/\bbc\b/;
var str="a bc da";
str.match(reg);//bc
var reg=/\bbc\b/;
var str="a bcda";
str.match(reg);//null
\t 制表符,只能匹配字符串里有\t 俩字 ,按TAB键不行
量词
关于*,*遇到g模式,string.match方法为空的地方也算(除去匹配的,游标有几个位置就会有几个"")reg.exec方法没有这个问题
/^abc$/ 只有abc可以,abcabc不可以
reg.exec()
如果是全局匹配,这个方法与reg.lastIndex属性有关,每次都从lastIndex索引开始的地方去匹配一个,然后返回匹配完的游标位置(匹配文本的最后一个字符的下一个位置。这就是说,您可以通过反复调用 exec() 方法来遍历字符串中的所有匹配文本。当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0)。
如果不是全局匹配,lastIndex一直是0,与match方法大致相同
重要事项:如果在一个字符串中完成了一次模式匹配之后要开始检索新的字符串,就必须手动地把 lastIndex 属性重置为 0
如果想匹配aaaa型的字符串呢
var reg=/(\w)\1\1\1/ 其中\1表示反向引用第一个子表达式
aabb型就是var reg=/(\w)\1(\w)\2/ 其中\2表示反向引用第一个子表达式
但是这样如果用match会额外匹配子表达式,比如aabb,返回的数组是aabb,a,b
加了g后就不会出现,而exec不管是否有g都会出现
正向预查,正向断言:?=和?!
//如何把一个整数变为三位一个逗号的计数方式
var str="10000000";
var reg=/(?=(\B)(\d{3})+$)/g;//匹配后面有三的倍数位数字的非字符边界的空串
//var reg=/\B(?=(\d{3})+$)/g;这样也是可以的
console.log(str.replace(reg,","));
String的方法
replace只能返回一个,非全局的正则表达式也只有一个,如果是全局模式的正则表达式,var reg=/a/g;var str="aa";str.replace(reg,"a")返回bb
replace方法可以使用$来引用子表达式,也可以传入函数实现更灵活的功能
var reg=/-(\w)/g;
var str="the-first-name";
console.log(str.replace(reg,function($,$1){
return $1.toUpperCase();
}));//theFirstName
一些碎片知识
内存分为堆和栈,简单来说:栈保存变量和对象的引用,堆保存对象
JS传参方式
function changeStuff(num, obj1, obj2)
{
num = num * 10;
obj1.item = "changed";
obj2 = {item: "changed"};
}
var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num); // 10
console.log(obj1.item); // changed
console.log(obj2.item); // unchanged
应该还是按值传递,如果是引用类型传入,形参保存的是实参指向内存的地址。
如果是传引用,obj2重新赋值也是影响到实参的。
主要需要搞懂引用类型和值类型的区别吧,这个有点底层了,网上也没好点的解释。