#前端基础 #
## JavaScript基础 ##
###渲染机制与变量 ###
script代码为什么放到body的后边?是否可以放到head里边,如果放到head里边是否会实现放到body后边的效果?script的两个属性,一个是defer和async分别代表什么意思?
**案例:简易的随机点名系统?
数组的常用方法10个
字符串常用方法10个
### js注意的基础知识###
**JavaScript有两种情况为false**:
null,“”,undefined,NaN ,false ,0
####栈和堆 ####
var的3个步骤:
[if !supportLists]1,[endif]声明一个变量,默认值是undefined(声明)
[if !supportLists]2,[endif]存储一个值
[if !supportLists]3,[endif]变量与值关联到一起(定义)
基本类型的内存操作:
在栈内开辟作用域。window在加载页面后需要允许所以提供了一个全局作用域。
栈内存中是基础类型的操作,基础类型的操作是直接操作值。
引用类型的内存操作:
引用类型的数据都是比较大所以是存储在堆内存中的。
而引用变量的值只是堆内存中的地址。
函数体的存储方式:
函数体的内容以字符串的方式存储到堆中。
栈内存的作用:(全局作用域和局部作用域)
[if !supportLists]1,[endif]提供一个js自上而下的执行环境(所有的代码都是在这个里边执行的)
[if !supportLists]2,[endif]存储基础类型的值
=>当栈内存被销毁,存储的那些基本值也跟着销毁了。
堆内存:(引用值对应的空间)
1,存储引用值的(对象:键值对 函数:是函数体)
=>当前堆内存的销毁和释放,那么这个引用值就彻底没了。
堆内存的消失:
当堆内存没有任何变量或者是其他的东西占用的时候,浏览器会在空闲的时候自动进行回收,也就是销毁掉(webkit内核浏览器)。
IE内核对内存的回收机制,每一个占用都会计数。如果计数为0则会销毁掉。
销毁的方式:xxx=null 堆内存的释放。
IE内核与webkit内核的区别:
IE内核是计数器,webkit内核是空闲自动回收。
####堆栈的销毁 ####
全局作用域的销毁主要是页面关闭的时候销毁了。
堆内存的销毁:是所有指向它的变量是null的时候就可以销毁。也就是没有指向了。
js中的内存是分成堆内存和栈内存,堆内存存储引用数据类型,包括键值对和函数块。栈内存是存储原始数据类型和提供js代码执行环境。
堆内存销毁:所有引用堆内存空间地址的变量赋值为null即可。浏览器会在空闲的时候会释放掉(浏览器不同销毁方式不同)。
栈内存销毁:一般情况下函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉,在栈内存中存储的值也都会释放掉。但是也有特殊不销毁的情况,一种情况是函数执行完成,当前执行的栈内存过程中某些内容被栈内存以外的引用占用(如果释放了,就找不到了,所以不会被释放,只要是有地址指向,而指向并没有执行完,就不能销毁)。第二种情况是:全局作用域只有页面关闭时才会被释放。
如果栈内存没有被释放,则栈内存中的基本数据也都会保存下来,不会被释放。
return一个函数就是return的第一个地址:
return function(){}创建一个函数,然后把函数的地址返回出去。
栈内存销毁的另一种情况:
如果栈内存返回一个引用给外边,但是这个引用并不是自己栈内开辟的,是外边传递过来的引用,则该栈内存一样会被销毁,栈内存是否被销毁,看自己是否开辟了一个堆内存。
####堆和栈的关系 ####
重点:我们堆的创建是来自于栈内存的,如果栈内存毁掉,则在当前栈内存中产生的堆内存也会消失。
堆内存存储两种:
一种是函数,存储函数块
一种是对象,存储键值对,在存储键值对的时候如果值是一个函数则会产生堆,如果是一个自执行函数则会产生栈。
**对象在堆内存的生成**
对象的变量名是一个值,且是一个引用地址,只有当引用地址里边所有的操作都完成后才会把这个引用地址给对象的变量名,在此之前对象的变量名是undefined.
如下实例:代码会报错
因为obj里边的操作fn并没有操作完毕(obj的堆内存还没有操作完,只有操作完了,最后才会把引用地址赋值给obj,obj才能调用堆内存中的属性),所以obj此时的值是undefined,undefined.n是会报错的。
```
var obj={
n:3,
fn:(function(n){
console.log(2);
})(obj.n)
}
```
属性是属性,变量是变量,不能因为同名就混起来。
自执行函数的this是window。
栈内存销毁的另一种情况:
如果栈内存返回一个引用给外边,但是这个引用并不是自己栈内开辟的,是外边传递过来的引用,则该栈内存一样会被销毁。
####变量提升 ####
概念:当栈内存或者是作用域形成,js代码自上而下执行之前,浏览器首先会把所有带“var”和“function”关键字进行提前的声明或者定义。这种预先处理的机制称之为“变量提升(预解析)”。
declare =>声明 var a;
defined=>定义 a=12
带var关键字是只声明未定义。
function关键字的是声明又定义。
####栈的执行流程 ####
[if !supportLists]1,[endif]首先是形参赋值。
[if !supportLists]2,[endif]变量提升。(形参赋值后,如果再有定义的同名的变量会被省略掉)
[if !supportLists]3,[endif]执行当前代码
[if !supportLists]4,[endif]栈内存的释放问题
####闭包 ####
概念:市面上开发者认为的闭包是形成一个不销毁的私有作用域(私有栈内存)才是闭包。
例如:(柯里化函数)
function fn(){
return function(){
}
}
var f=fn();
这样形成的fn不会被销毁。fn执行后不会被销毁。同时这种也叫做柯里化函数。
柯里化函数:(下面这种形式也叫做柯里化函数)
function fn(){
return function(){
}
}
var f=fn();
第二种闭包:(惰性函数)
var utils=(function(){
return {}
})();
也是形成了不销毁的栈内存,function()执行后返回一个对象被外边的变量占用了。
闭包的两个作用:闭包的保护机制,保护私有变量,不被外界干扰。保存机制,形成一个作用域,只要作用域不被销毁,则变量也保存了。
闭包就是保护和保存两个作用。
####闭包的项目实战应用 ####
真实项目中为了保证js的性能,堆栈内存的性能优化,应该尽可能减少闭包的使用。(不销毁的内存会耗掉性能的)
真实项目中:
在真实的项目中,尤其是团队协作开发的时候,应当尽可能的减少全局变量的使用,以防止相互冲突(全局变量污染)。那么此时,我们无安全可以自己这一部分内容封装到一个闭包中,让全局变量转化为私有变量。
这个是最简单的方式处理团队协作:
(function(){
...
})();
我们封装内库插件的时候,我们会把自己的插件放到闭包中保护起来,同时又要暴露一些方法给客户使用。
保护作用:
常用的方式两种:
jq的方式,把需要用到的方法抛到全局。jq用的给window添加属性的方式来暴露出去的。
zepto方式:基于return,把需要暴露的方法直接暴露出去。var Zepto=(function(){...return $;})() 执行的方式 Zepto.xxx();
保存作用:
保存的作用是作用域不被销毁。
事件绑定,点击事件执行的是返回的函数。自执行函数从一开始就执行了。但是执行后并不会销毁作用域,因为返回的变量地址和外界的变量存在指向。
下边aa.onclick执行的是返回的函数。
例如:
aa.onclick=(function(){
return function(){
}
})();
如果aa.onclikc=function(){},则aa如果是外边的变量也会形成闭包,因为引用也是被外界占用的。
####同步和异步的简单介绍 ####
如果页面显示出来了说明页面已经执行完了,也就是操作都已经完成了。
同步:就是一件事一件事的做。
异步:同时做多件事情。
所有的事件绑定都是异步操作,因为我们给你绑定事件的时候不会等你事件执行才会执行下一步操作,而是绑定后就执行下一步操作了。
### 编程思维和编程习惯 ###:
null为什么会存在,那是因为语义化的原因,这么写就代表是null。
总结:
1,并且变量提升只发生在当前作用域。
2,函数在变量提升的时候就已经开辟一个堆内存,产生了引用地址赋值给变量了。
3,全局作用域下的变量是全局变量,私有作用域是私有变量。
4,浏览器很懒,做过的事情不会重复执行第二遍。(当代码执行遇到创建函数这部分代码的时候会直接跳过,因为在提升阶段就已经完成了函数的赋值操作了。)
5,私有作用域是先是形参赋值,然后是变量提升,再才是代码执行。在es3和es5版本中只有函数作用域和全局作用域会在栈内存中开辟作用域。
6,在全局作用域下声明一个变量其实就是给window设置了一个属性(私有作用域下声明的变量和window就没有任何的关系了)。
7, 在名字相同的情况下是不会重新声明的,但是会重新进行赋值。
in的作用:
in是判断属性是否是一个对象的。是返回true,反之false。
“属性” in 对象 =>返回是false还是true。
变量加不加var的区别:
如果加var是全局变量。如果不加var是window的属性。window的属性是在定义的时候存在的。
例如:
var a; //这个是全局作用域的变量,默认是undefined,同时给window也设置了window.a=undefined;
a=12;//这个操作是分两步,一步是给变量a定义一个值12,还有一个是window.a=12;(全局作用域与window属性有映射关系)
如果不加var,直接 a=12;那么就只是window.a=12;全局作用域并不存在全局作用域a,用的都是window.a的属性。
私有作用域里边的变量是否带var,如果带var则是私有变量与外界没有关系。如果不带var则会往外边寻找。
带不带var的几种写法:
var a=12,b=12 //这种方式b是带var 的。
var a=b=12 //这种方式b是不带var的。
**不管条件是否成立,在当前的作用域下都是需要进行变量提升的,为了迎合es6中的块级作用域,新版本浏览器对于条件判断的函数,都只是先声明没有定义。
**注意问题:
parseInt(“AA”) // 是NaN
var a;//同时也给window设置了a属性。
逻辑或与逻辑与在赋值当中的运算:
var a= A || B 如果A为真赋值A,否则赋值B。
var b=A && B 如果A为真赋值B,A为假赋值A;
逻辑与和逻辑或在赋值的时候只需要判断第一个值就可以。
但是这种方式赋值不是很严禁。
### ES6语法规范 ###
#### let和 const ####
let和const创建的变量不存在变量提升。
创建函数的方法:let fn=function(){}
let的解析:
1,并且切断了全局变量和window的映射机制。
2,在相同的作用域中不能有相同的变量,只要let的变量,不管是函数名还是var都不能够与let的重名,也包括a=100,不加关键字的这种。但是不包括window.属性名,window.属性名这种方式是可以的。
3,虽然没有变量提升机制,但是在当前作用域下浏览器会做执行代码之前一个重复性检测(浏览器会记住let的变量名)。重复性检测(@1:自上而下查找当前作用域下所有的变量,发现重复性变量立马抛出异常,代码是一行都不会执行的,从一开始就会报错。@2:但是除了a=100,这种没有关键字的,如果与let有重复性变量名是会执行的,直到遇到与报错相关的变量名。)
let的总结:
1,let重名中不包括window.属性名。
2,let重名抛出异常时是一个代码都不会执行。除了不带关键字的的变量,抛出异常也是与不带关键字的异常开始抛出。
例如:
console.log(100);
let b=1000;
console.log(b);
let fn1=function(){
console.log("fn1");
}
fn1();
let fn2=function(){
let c=6;//这里存在重名
let c=100;//这里存在重名
}
fun2();
//一个都不会输出,会直接报错。
Uncaught SyntaxError: Identifier 'c' has already been declared
[if !supportLists]3,[endif]浏览器在同一个作用域下会记住let的变量名,如果发现let的变量在该作用域下提前使用也会报错。
[if !supportLists]4,[endif]浏览器是执行es6的语法还是执行es5的语法,主要是看声明的关键字是var还是let。
[if !supportLists]5,[endif]let中的全局变量与window不存在映射关系了。
[if !supportLists]6,[endif]
let中会有一个变量的检测:如果有let变量,说明这个变量就是这个作用域里边的。如果在声明前使用这个变量,那么就会报错。会提示变量 is defined。
#### es6的块级作用域 ####
js是有3中作用域:
[if !supportLists]1,[endif]全局2,私有 3,块级
es6中块级作用域一般有{} 就是一个块级作用域,但是也有特殊情况,例如 let obj={} 这个不是块级作用域。
判断和循环以及直接写{}和with都是块级作用域,除了obj={}
代码:会报错
{
{
{
console.log(a);
}
let a=100;
}
}
因为定义的时候在输出的后边。
####解构赋值 ####
解构指的是左边与右边的解构一样即可。
1,解构赋值中的不需要赋值的值,可以用空加逗号解决。let [a,,b]=[];
[if !supportLists]2,[endif]写多少个解构赋值就解构多少个。只要有值就会解构给左边的变量,如果没值则左边的变量是undefined;
[if !supportLists]3,[endif]剩余运算符号,...arg 这个是剩余运算符号,只能放到最后,否则会报错。例如:let [a,...b]=array; b是一个剩余运算符解构出来是一个数组。
[if !supportLists]4,[endif]解构赋值还可以赋值默认值,如果当前的变量没有解构出来值则用默认值。let [a,b=0]=array;
5,展开运算符号...array 展开是需要有容器接收的。展开运算符用的是克隆的方式。
案例变量交换位置
let a=0;b=1;
let [b,a]=[a,b] //构造一个数组出来
**对象解构赋值**
对象解构赋值左侧的变量名和右边的属性名一致才可以。
**变量名不想是属性名的情况下的实现**:
let {变量名:别名}=对象;//给解构的属性名起别名作为我们的变量。
一旦有了别名,就只能使用别名。
对象的默认值赋值:
let {变量=默认值}=对象。//给不存在的属性赋值默认值。
**解构赋值形参赋值**
1,解构赋值进行传值
function fn({
name:”huminggui”
}={}) //表示传入过来的值进行解构,如果不传值就是空对象。如果传递过来是有对象的,但是对象里边的某个属性是不没有值的,所以赋值了默认值。
如果不传值则默认值为{},不能只写{},例如:function({}={})这种方式是会报语法错误。
2,解构赋值参数赋值时剩余运算符取值
function(obj,...arg){
}
第一个参数是obj,剩余的参数都在arg这个数组里边。
**案例求平均数**
####类数组转化成数组 ####
类数组变成数组是通过克隆的方法实现的。
数组的slice是可以进行克隆的。
类数组转换成数组的方式与数组的slice()克隆一个数组的方式非常类似,唯一不一样的是slice里边执行的this。所以我们把this指向类数组,接用slice方法来实现类数组变成数组的过程。
把字符串转变成数组的方法有两种:
第一种是:[].slice.call(“字符串”);这样也是可以实现的
第二种是:”字符串”.split();
注意:类数组和字符串都有length和索引所以通过过数组的slice方法变成数组。
**类数组转化成数组的方法**:
类数组能够像数组那样操作数组的方法。主要是类数组有length和索引,所以才能实现这些操作。因为数组底层实现的原理就是操作数组本身。而类数组比较类似,所以操作类数组了。
[].sort.call(argument,function(a,b){
return a-b;
})
[].slice.call(argument,2);等等方法。
案例分析:去掉最大和最小然后算出平均值,小数点保留两位。可以应用在评委打分算出平均值的。
arguments 是一个类数组。类数组不是一个数组。
es5的方法实现:
第一步:排序然后去掉首尾,就是干掉最大值和最小值。
先变成数组,然后排序。(接用slice方法)
第二步:把剩下的值求和然后除以平均数。数组.split(“+”)最快
#### promise ####
重点:async 如果没有await就只是一个普通的函数。
####箭头函数 ####
(x,y)=>x+y;
接下来可以这么理解:let fn=x=>y=>x+y;
是function fn(x){
return function (y){
return x+y;
}
}
总结:
1,箭头函数里边没有arguments
2,箭头函数没有this,箭头函数的this是上下文的this。所以如果要去改变箭头函数的this是不生效的,因为箭头函数本身没有this。
###变量提升与作用域总结 ###
####暂时性死区 ####
console.log(typeof a);//undefined;
在原有的浏览器的渲染基础下,基于typeof等逻辑元素符检测未声明的变量不会报错,而是”undefined”。
但是你要是直接输出就会报错。console.log(a);
es6解决了暂时性死区的问题:
typeof a;
let a;
如果代码是上边的两行则会直接报错,解决了浏览器暂时性死区的问题。
####实参 ####
函数的第一步执行是形参赋值。
fn(a)形参赋值只是 a=值 如果是引用则值是一个地址。注意:形参是私有变量。
形参赋值后,如果再有定义的同名的私有变量会被省略掉
####上级作用域 ####
函数的上级作用域与在哪里执行是没有任何关系的。只与它在哪里创建有关系。
如果别人赋值给你一个函数名(其实是地址),就是当前变量指向了函数创建的堆位置。
函数创建:指的是在开辟一个堆空间,储存函数代码块。
arguments
arguments是实参的集合。
arguments.callee:函数本身
arguments.callee.caller :函数在哪里执行,就是哪个函数。如果是在全局作用下执行,就是null。
#### arguments ####
arguments与形参是有映射关系的。
形参如果不赋值则是undefined。
在非严格模式下:
arguments是一个类数组,它的内容取决于形参是否赋值,如果赋值则aruments与形参就有映射关系。如果实参没有赋值给形参,而是形参之后赋值的,与arguments没有关系。
arguments和形参的映射机制建立在函数执行后形参赋值的时候。之后就无法再建立映射关系了。
**非严格模式下**,形参和arguments有映射机制。记住:映射关系argument实在实参赋值的一瞬间建立。如果没有实参指传递的形参也就无法建立映射关系。只有传递了实参的形参才建立成功映射关系。
例如下代码:
```
var y=1;
function fn(x,y,z){
arguments[0]=1000;
console.log(x);//1000
arguments[1]=100;
console.log(y); //undefined
}
fn(1);
```
####严格模式 ####
在当前作用域下的最顶端写上“use strict”
严格模式和非严格模式的区别:
1,严格模式下不支持使用arguments.callee
2,在严格模式下arguments和形参没有映射机制
3,在严格模式下,不允许给一个对象设置重复属性的。
4,在严格模式下,函数执行,如果没有明确指定函数主体不会向非严格模式下统一交给window。而是让this指向undefined.
###面向对象编程(oop) ###
####单例模式 ####
(singleton pattern)单列设模式
表现形式:
```
var obj1={
name:xxx,
age:xxx
}
var obj2={
name:xxx,
age:xxx
}
这里边的name不是同一个name。
```
>作用:把模式同一件事物的属性和特征进行“分组”和“归类”(存储在同一个空间)避免了全局变量之间的冲突和污染。
在单列模式中对象名不能叫做变量而是叫做命名空间(namespace),例如上述案例的obj1和obj2。把描述事物的属性储存到命名空间里边,多个命名空间是独立分开的,互相是不相冲突的。
单列设计模式的命名由来:每一个命名空间都是js中object这个内置基类的实例,而实例之间是相互独立互相不干扰的,所以我们称它为单利。
高级单例模式:
```
var namespace=(function(){
function fn(){
}
return{
fn:fn
}
})();
```
**高级单例模式核心理论模型**:在给命名空间赋值的时候不是直接赋值一个对象,而是先执行匿名函数形成一个不销毁的作用域,然后在作用域中创建一个堆内存,把堆内存的地址赋值给命名空间。
**好处**:我们可以在不销毁的作用域中创建很多的函数,哪些需要是外界使用的则暴露出去就可以。(这也是模块实现的一种思想)。
**单例模式可以用来做模块化的开发**:
1,团队协作,功能板块划分。(一般是一个业务体系是一个板块)
2,把各个板块之间共用的部分进行提取和封装,后期再想实现这些功能调取即可。
####工厂模式 ####
factory pattern
就是通过一个函数返回一个对象。
<br>
**概念**:实现相同功能的代码进行封装,实现批量生产(后期想要实现这个功能只要执行这个函数就可以)。
**作用**:
[if !supportLists]1,[endif]实现批量生成
[if !supportLists]2,[endif]减少页面中的臃肿的代码,提高代码的重复使用率。
#### oop ####
句有编程思想的语言才是编程语言。js是一门编程语言。
面向对象:
js/java/php/c#/python
面向过程:
c
面向对象编程需要我们掌握对象,类,实例的概念:
整个js就是基于面向对象设计和开发的语言,我们学习和实战的时候也是需要按照面向对象的思想和实战去体会。
**ojbect(基类)分别有**:【Number,NUll,String,Array,RegExp,Date,Function,Boolean】,【eventTarget】,【HTMLCollection】,【nodeList】这些类都是自己自带的。
####构造函数模式 ####
constructor构造函数,基于构造函数创建自定义类。
[if !supportLists]1,[endif]在普通函数的基础上new 执行,这样不就不是普通函数执行了,而是构造函数执行,当前的函数名,当前的函数名称之为类名。接收的返回的结果是当前类的一个实例。
[if !supportLists]2,[endif]自己创建的类名,最好类名的首字母进行大写。
[if !supportLists]3,[endif]这种构造函数模式的执行,主要用于组件、类库、插件、框架等封装。平时编写业务逻辑以一般都不这样操作。
[if !supportLists]4,[endif]js中创建指有两种模式:一种是字面量模式,还有一种是构造函数模式。不管是哪一种方式创建出来的都是Object这个类的实例。而实例之间是独立分开的。字面量的模式是单列模式,构造函数是构造函数模式。
基类的两种创建方式,传不传参数没有本质的区别:只是第二种是需要obj2.name这样的方式添加属性。
let obj1={};
let obj2=Object();
**基本数据类型**:的两种创建方式的区别:
@1:基于字面量创建的是基本数据typeof 的结果是基本数据类型。var num=2;
@2:如果是基于构造函数创建,例如:var num2=new Number(2),那么typeof就是一个对象”object”是一个引用。
num1和 num2都是数字的实例,都可以使用数组类提供的属性和方法。但是这两种创建方式有本质的区别,一个是基础类型,一个是引用类型。
**new实现的机制**:
构造函数的机制:会向普通函数那样,在堆存储函数体。
new 构造函数会执行2大块:
**第一块**:和普通函数类似
[if !supportLists]1,[endif]创建一个私有作用域
[if !supportLists]2,[endif]形参赋值
[if !supportLists]3,[endif]变量提升
**第二块**:
4,【构造函数独有】在当前形成的作用域中创建一个堆内存(暂时不存储任何东西),并且让函数中的执行主体(this)指向新的堆内存。
5,代码开始自上而下执行。(执行的是new的这个构造函数里边的内容)
6,【构造函数独有】把之前创建的堆内存地址返回,浏览器默认返回,不需要自己写return。
####构造函数中写return的问题 ####:
我们尽量在构造函数中少写return。
1,如果在构造函数中写return,如果return的是原始类型的值则返回的还是实例,return不受影响。
2,如果在构造函数中return的是一个引用,则new 构造函数返回的是我们写的引用。返回的就不是实例了。
3,如果我们在构造函数中只写一个return,不返回任何东西则只是结束后变的执行,不会影响返回的实例。
new Fn()与 new Fn,没有太大的区别,在之后会有一些小的问题。
####检测对象的属性 ####
in 是不管是私有的还是公有的属性,都成立。
hasOwnProperty,必须是私有属性。
f.hasOwnProperty(“属性”);
**暂时理解的公有属性和私有属性**:公有属性就是父亲的,私有属性就是自己的。
####原型和原型链 ####
prototype原型
__proto__原型链
【函数类型】
所有的类都是函数类型的。包含内置类和自己创建的类都是函数类型。
【对象】
普通对象,数组,正则,Math,实例(除了基本类型创建的字面量创建的值),prototype的值也是对象类型,arguments,函数也是对象类型的。万物皆是对象。
【规定】
[if !supportLists]1,[endif]所有的函数数据类型都自带一个属性叫做prototype,我们叫做原型,这个属性的值是一个对象。浏览器会默认给它开辟一个堆内存。
[if !supportLists]2,[endif]在浏览器给prototype开辟的堆内存当中有一个自带的属性叫constructor,这个属性存储的值是当前函数本身,而这个属性存储的值是当前函数本身。
[if !supportLists]3,[endif]每一个对象都有一个__proto__的属性,这个属性指向当前实例所属类的prototype(如果不确定它是谁的实例都是Object的实例);
重点:每一个类把公共的属性和方法都存储到原型上,给实例进行调用。
重点:Object也是一个函数,所有的函数都是有原型的,原型是一个对象。每个对象里边有一个属性__proto__指向当前实例的所属的原型,如果不知道原型就指向Object。
可以通过截图去理解:
原型链的概念:基于向上查找的机制,当我们操作实例的某个属性或方法的时候,首先找自己的空间中的私有属性和方法,找到了则结束查找,使用自己私有的即可。没找到通过基于__proto__所属类的原型prototype,如果找到就用这个公有的,如果没找到,基于原型链的__proto__继续向上寻找,直到Object原型,如果还是找不到则操作的属性和方法就不存在了。
如下截图:
给原型加公有属性的两种方法:
Fn.prototype.name=xxxx;
f1.__proto__.name=xxx;
总结:
1,类才有原型,对象是没有原型的。对象有原型链,类没有原型链。每个对象都有原型链,并且指向这个对象所属类的原型上,又因为原型的值也是一个对象,所以原型的值也有一个原型链,指向所属的类,如果找不到类则指向Object的原型上。Object是有原型的,它的原型里边原型链的值为null,因为原型链到顶了。
[if !supportLists]3,[endif]内置类的原型是无法会被改变的,即使我们改了,浏览器的自我保护机制会被处罚。
[if !supportLists]4,[endif]如果自定义构造函数中的prototype改了后,constructor会被丢失。我们会通过原型链去寻找上级的constructor,直到Object的constructor。记住,内置函数的prototype直接改是不会生效的,但是它下边的方法是生效的。
[if !supportLists]5,[endif]
####逻辑与逻辑或 ####
逻辑或与逻辑与在赋值当中的运算:
var a= A || B 如果A为真赋值A,否则赋值B。
var b=A && B 如果A为真赋值B,A为假赋值A;
逻辑与和逻辑或在赋值的时候只需要判断第一个值就可以。
但是这种方式赋值不是很严禁。
**优先级**:
如果逻辑与与逻辑或同时存在,优先级是逻辑与大于逻辑或。
在es6中是而已直接给形参设置默认值的。一旦传递值不管是什么值都是按照传递值进行处理。
如果形参传递的值是undefined,那么浏览器会按照没有传递的值进行处理。因为本身形参的默认值就是undefined。
#### this的使用 ####
重点1:判断this,如果前边有点,则就是前边点的都是this。不管是否是通过原型链还是本身自己的调用,如果有this的存在首先要判断this是谁。
例如:f1.prototype.getx()这个的this是f1.prototype 是this。
**总结**:
[if !supportLists]1,[endif]元素绑定事件,方法中的this是当前操作元素
[if !supportLists]2,[endif]方法名前边是否有点,有点则this就是谁,如果没有点,严格模式是undefined,非严格模式是window
[if !supportLists]3,[endif]构造函数中的this是当前构造函数的实例
4,在非严格模式下,第一个参数不传递或者传递的是null或者是undefined,thid都是window。在严格模式下,第一个参数传递的是谁就是谁包括undefined和null,如果不传递是undefined。
#### apply ####
和call基本上一模一样,只有一个区别:
区别在与传参的方式不同,fn.call(obj,10,20)这样的传输的方式,apply是 fn.apply(obj,[10,20]).
apply是把传递的参数放入到一个数组或者是一个类数组里边。其实apply也是一个一个传递参数的,只是语法上是写个数组。
底层实现原理与call一样。
注意:applay的传参方式是一个一个传参,虽然语法上写的是数组,但是传参到里边是一个一个传参的。
#### bind ####
bind的语法与call一样。唯一的区别是,在于立即执行还是等待执行。
fn.call(obj,10,20)改变this并且立即执行。
fn.bind(obj,10,20);改变了this,但是fn并没有执行。兼容行(不兼容ie6~8);
#### eval和括号表达式 ####
应用场景:
1,例如我们点击事件,
document.onclick=fn.call(obj);//我们点击时虽然改变了this,但是这个fn的函数早就执行完毕了,函数的返回值是绑定给document的事件。所以点击事件并不会执行fn函数。
document.onclick=fn.apply(obj)//这个绑定才算成功,改变了this同时不会执行函数,要等到点击才会执行函数。
获取数组中的最大值或者最小值:
第一种方法:
先排序,小到大排序,最后一值就是最大的值。
第二种方法(假设法):
假设第一个值是最大的,然后从二个开始比较,更改假设最大的值。所以加设置最后肯定是最大的。
第三种方法:
Math.max(一堆数); 获取一堆数的最大值,并不是获取数组的最大值。所以需要一个的传递值。如何变成一个一个数,数组.toString()
eval(“字符串”);//能够把字符出转变成js表达式。如果里边有多项则只会显示最后一项。例如:eval(“1,2,3”)输出3.
括号表达式:括号里边的每一项用逗号分隔,最后只会获取最后一项。但是其他的项也会过一遍,最终结果只会显示最后的一项。例如:(1,2,3)则会输出
不建议大家使用括号表达式,容易改变this。
例如:
(fn,obj.fn)(); 输出this 发现是window,正常应该是obj才对,毕竟执行的是obj.fn();因为这样执行只会执行函数里边的内容,不会去判断否有调用它的对象存在。
var obj={
fn:function(){
console.log(this,1);//只会执行这一部分内容
}
}
function fn(){
console.log(this);
}
(fn,obj.fn)();//window 1
如果括号表达式只有一项,则this是正常的,不会被括号所改变。例如:(fn,obj.fn)()//this是obj。
eval(“1,2,3,4”)确实是把内容变成js的模式,确实是变成了1,2,3,4但是会在外边加上一个小括号(1,2,3,4)小括号是以最后一个为准的,所以输出的是4.
如何实现第三种数组获取最大的值的方法呢?
eval(“Math.max(“+array.toString()+”)”); //输出最大值
第四种方法:
Math.max.apply(null,array); //这种是最简单的方法,利用了apply的特征,虽然放的是一个数组,但是执行的时候也是一个一个参数传递的。其实Max.max(x,x,x,x)这样的方式进行接收的。
第五种方法,是es6的方法,最优秀的方法:
Math.max(...array);
###函数 ###
####函数重点分析 ####
函数的3种形式
普通函数、类、普通对象
万物皆对象
Function类的prototype是一个空的匿名函数,但是它的原型链是指向Object的原型的。
函数的在new的时候的顺序:
遵循的是js运算符的优先级
new Foo.getName() //这个是先把Foo.getName当做整体,所以是new 这个整体();
new Foo().getName() //这个是new Foo() 是一个整体然后才执行getName
new new Foo().getName() //
运算符号的优先级别:
不带参数的new是低于 . 的优先级别的。带参数的new 的优先级别是与 . 的优先级是一样的。这时是从左到右进行执行。
如果有多个new的关键字的时候,一定要分清楚new 的是哪个函数。
只要你是个函数,不管你是什么类,永远都是内置Function这个类的实例。
每个函数都有一个原型链__prototype__ ,函数的原型链的最基类是Function ,对象的原型链是Object,Object作为函数时它的原型链是指向Function的。
万物皆对象。
谨记:万物皆对象,所以都有__proto__。但是prototype只有类有。
#### call、apply、bind ####
call:
都是改变this的指向。
fn.call(x,...)第一个参数是把this指向x。x以后的参数都是实参,传给操作的函数。然后再执行操作call的这个函数(注意这个函数其实是在call方法里边执行的)。
机制:也可以分3步进行理解
首先是通过原型链找到call方法,call方法里边实现fn函数里边的this指向第一个参数。然后把参数传入给fn参数做实参,并且执行fn();这个就是call方法的原理。
例如:
Fn.call.call(obj)
//Fn.call找到的是Function.prototype.call方法,在这里简写称F,然后F.call(obj)。这里已经到了括号部分进行执行了,改变F的this指向,实参传递,并且执行。这里的结果是会报错的,但是执行的顺序是这样的。
内置方法则是会受到保护,不会让我们查看到,所以在输出内置方法的时候显示f fname{[native code]}。
###正则表大式 ###
####基础 ####
正则:是用来处理字符串的规则。正则是一个对象。
正则只能用来处理字符串。
**正则处理包含两方面**:验证当前字符串是否复合某一个规则,把当前的字符串当中的复合规则的字符串获取到。我把第一个叫做“正则匹配”,另一个叫做,“正则的捕获”。
创建正则的两种方式:
let reg=new RegExp(元子符,修饰符);
正则是由:元字符和修饰符组成。
正则两个斜杠之间包起来的是元字符,斜杠后边的是修饰符。
常用的修饰符就只有3个:
i(忽略大小写匹配),m(多行匹配),g(全局匹配)
常用的元字符:
>特殊元字符:
\d 0到9次以上 例如:\d+ 出现1到多个0到9的数字
\D非0到9之间的任意数
\w数字、字母、下划线中的任意一个
\W刚好与\w 相反
\s任意一个空白字符(包括\t的制表符,TAB健一般是四个空格符)
\b匹配边界符 (边界符:一个单词的左边和右边。例如: “zhu-niu”z的左边,u的右边,f的左边,u的右边 都是边界。)
\n匹配一个换行符号
\转义字符 ,就是把普通字符转义成特殊字符。有特殊含义的转化成普通意思。(来回转)
.代表的是除换行符以外的任意字符。
^以元字符开头
$以元字符结尾
|或者的意思,两个中选择一个。
[]中括号中的任意一个
[^]除了中括号中的任意一个 (读:非)
[start-end]可以是start和end的中任意一个字符
()正则的分组
(?:)当前分组只匹配不捕获
(?=)正向预查
(?!)负向预查
>量词元字符:让其左边的元字符出现多少次。
-- * 0次以上
--+ 1次以上
-? 出现一次或者不出现
- {n}出现n次
- {n,} n到次
- {n,m} n到m次
>普通元字符:
除了特殊和量词元字符以外,其他的都是普通的元字符。
**[]中括号的一些细节**:
[if !supportLists]1,[endif]中括号中出现的元字符一般都是代表本身含义的。一般的特殊元字符会出现消磁的作用。例如:.就只是点,不是除\n以外的所有字符。 但是 \d 却不消磁。这个是特殊的。
[if !supportLists]2,[endif]中括号出现一个两位数,并不是两位数,而是两个不同的数字中其中一个。
例如:/^[12-65]$/,它的意思是:1或者2到6之间的数或者5。
正则表达式:匹配18到65岁之间
let reg=/^((1[89])|([2-5][0-9])|(6[0-5]))$/
[]中括号,里边只有一个字符位。多个字符一起需要切成多个来处理。
最外边为什么会有一个分组符号,是表示只能符合分组里边的内容。而并不是只要包含就可以。
实例:正则表达式是匹配一个规则“[object这里可以是任意]”
/^\[object .+\]$/
####分组的作用 ####
[if !supportLists]1,[endif]改变默认的优先级
- /^18|19$/这个会特别的乱,这个可以理解是18开头或者是以19结尾。
/^(18|19)$/这个时候就是以18和19作为一个整体了,与外界其他的字符就没有任何的关系了。
[if !supportLists]2,[endif]分组捕获
- exec()实现的是正则的捕获。获取的结果是一个数组。如果不匹配获取的结果是null。这个是正则的方法。能够捕获到大正则和各个小正则。
如果只匹配不捕获,则可以在分组内加上(?:)
例如:
let reg2=/^(a|b|c)(0|1|2)(a|b|c)$/
reg2.exec("a1b");
["a1b", "a", "1", "b", index: 0, input: "a1b", groups: undefined]
如果对某一个分组只想匹配不想捕获,例如对第一组不想捕获:
let reg2=/^(?:a|b|c)(0|1|2)(a|b|c)$/
[if !supportLists]3,[endif]分组引用
- \1就是把第一个分组一模一样的内容,\2表示的是第二个分组一模一样的内容。
**注意**:不仅仅只是符合这个正则的条件,还要值也是相对应的。例如:
let reg2=/^([a-z])([0-9])\2\1$/
reg2.test("a22a"); //值也要对应,第一个分组是a,\1的值也是a 等等
###常用正则 ###
[if !supportLists]1,[endif]有效数字
-整数、负数、0、正数、小数
分析规则:
[if !supportLists]1,[endif]可以出现正负号,可以有也可以没有
[if !supportLists]2,[endif]整数位,可以一位或者多位,多位不以0开头
[if !supportLists]3,[endif]小数,可能有可能没有,如果有后边至少跟一个数字
答案先拿过来
[if !supportLists]2,[endif]电话号码的正则:
[if !supportLists]3,[endif]中文姓名
中文汉字的第一个字道最后一个字的编码是:
[if !supportLists]4,[endif]邮箱
邮箱:
[if !supportLists]1,[endif]可以有-和点,但是不能以点开头或者是 - 开头,而且也不能出现连续两个以上的点或者是 -
[if !supportLists]2,[endif]域名的名字可以是:
参考答案:
####正则捕获 ####
exec()捕获的方法,如果捕获不到就返回null。
test()也是可以进行捕获的
字符串的方法:
replace
split
match
总结:
[if !supportLists]1,[endif]执行一次exec只能匹配到一个,但是执行多次还是捕获到第一次,并没有起到执行整个字符串的作用。正则的捕获有懒惰性,只能捕获到第一个,剩余的默认捕获不到。是因为正则里边有一个捕获的属性lastIndex,这个属性表示的是索引,表示从字符串中的某个索引开始进行捕获。但是这个lastIndex的索引值每次都为0,所以每次都是重新开始捕获。即使手动修改lastIndex的值,然后并没有用,捕获的时候还是从头开始捕获。
解决正则懒惰性:我们需要加全局修饰符g,这个是唯一的办法。
有两个条件可以确定正则是否执行完毕:
如果捕获不到了,则为null。
lastIndex的值大于字符串的长度说明全局全部执行了。
2,正则reg有一个属性global,如果加g了则这个属性为true,否则为false。
字符串的match()方法实现了捕获功能。
使用:str.match(reg);
match的局限性:但是字符串的match方法,只捕获大正则并没有分组捕获,正则不设置g的情况下,与exec是一样的。
3,在正则捕获的是时候,不仅仅只捕获到大正则,而且把小分组匹配到的内容也单独捕获出来了。我们把这个叫做分组捕获。
正则捕获具备贪婪性:每次捕获的时候总是捕获到正则匹配到最高或者是最长的内容。例如:2和 2018 两个都符合\d+ ,正则则捕获的是2018。
解决正则表达式的贪婪性:把?号放到量词符号的后边并不是代表出现0次或者是1次,而是取消捕获的贪婪性。
问号在正则当中的作用:
[if !supportLists]1,[endif]本身是量词元字符,出现0次或者是1次。
[if !supportLists]2,[endif]取消贪婪性,在量词后边。
[if !supportLists]3,[endif]?:取消捕获
[if !supportLists]4,[endif]?=正向预查
[if !supportLists]5,[endif]?!负向预查
test()的使用:当正则表达式有g的时候也会改变lastIndex的值。
正则捕获的原理都是和lastIndex相关的:
如果是同一个正则,则不同的正则的函数都是可以更改lastIndex的。
exec和test这两个正则表达式都会改变lastIndex的值。
test与 exec的关系:
test是没有捕获能力的,只能是改变lastIndex并且生效,如果exec如果操作的话会改变exec的捕获位置。
exec可以循环捕获,如果捕获到最后结尾了,再进行捕获则又是从头开始捕获。
test与exec的捕获都不是全局的捕获。
exec是有捕获功能的,并且具备大捕获和分组捕获的功能。
而match的捕获是大捕获而没有分组捕获。
split方法是用正则的方式进行拆分。
RegExp.$1表示的是一个分组匹配到的内容。
replace方法是实现正则捕获的方法:
字符串方法,本身是字符串的替换的一个方法。
真实项目中很多的替换如果不基于正则则是无法替换的。
str.replace(正则,替换的内容);把符合正则要求的内容,被第二个参数进行替换。
str.replace(正则,$1) //$1代表的是第一个分组的匹配到的内容,相当于RegExp.$1 。
str.replace(正则,function(...arg){
})
原理:
[if !supportLists]1,[endif]只要每次正则匹配成功,就会执行一次这个函数,并且输出每一次匹配成功的信息。这个成功的信息格式是一个数组,第一项是大匹配成功的值内容(总体匹配),第二项是分组匹配成功的内容,可能有多个分组所以会有多个分组匹配的值当作第三项第四项等。然后是lastIndex索引值,然后是整个str原始值。
[if !supportLists]2,[endif]每一次replace返回是什么则匹配成功的就被替换成是什么。
[if !supportLists]3,[endif]replace捕获的流程:replace有两种用法:第一种是replace的第一个参数是正则表达式,第二个参数是字符串,实现的功能是第二个参数替换满足条件第一个参数的正则表达式的条件的内容。- 第二种用法也是最重要的一种用法:第一个参数是一个正则,第二个参数是一个函数,当正则在捕获成功后把捕获的结果作为参数传递给第二个参数为函数的表达式,并且作为函数的参数,并且执行这个函数,函数的返回值是每一论捕获成功后替换的内容。
例如代码:
```
let str="nihaowoshi@11122anihaowo@44442a##nihao";
let reg=/@(\d+)(a)/g;
str.replace(reg,function(...arg){
console.log(arg);
})
```
显示的结果是:
####练习 ####
题目一:let str=”2018/5/5 18:55:40”拆分成5-5 18:55”
有两种办法,一种是用split,还有一种是简单粗暴的,直接得到数字即可。
题目2:替换模板字符串中的内容。
let str=”{0}年{1}月{2}日 {3}时{4}时{5}秒”;
let datax=2019/7/31 17:43:30
用datax中的数字替换str中需要表达日期的部分。
并且,如果模板的长度变化,则显示的日期也跟着变化。
实现一个方法,可以实现万能的日期格式。输入不同的日期格式,显示出不同的日期效果。
###商场排序 ###
#### ajax的4个操作步骤 ####
创建ajax的四个步骤:
let xhr=new XMLHttpRequest();
xhr.open(“GET”,””,false);//请求方式,地址,是否异步,一般这个地址是服务器端提供好的。
xhr.onreadstatechange=()=>{
if(xhr.readstate===4 && xhr.states===200){
let product=xhr.responseText;
}
}
xhr.send(null);
####排序 ####
对页面中的li进行排序。
得到的是li的元素集合。
对于li里边的代码进行排序:
则需要获取li里边的内容的数字进行排序。例如:给当前li设定一个自定义属性,存储数字的值(存价格/日期/销量)。还有一种方法是:获取子元素里边的数据进行排序。
总结什么时候用自定义属性:自定义属性编程思想(最伟大的编程思想之一)。
后边的编程用到前边的值,把前边的值用自定义属性的方式存储。
自定义属性最好叫做data-xxx
sort的原理:
sort的原理:是通过返回值,如果返回值是大于0则交换位置,否则不变。
例如:return 1;则数组的每个值都交换位置,这样就是把数组进行了倒序的操作。
sort原理的算法实现是:当前和下一个比较,如果当前的减去下一个是大于0的,那么交换位置。交换位置后需要对前边排序好的再逐一比较直到小于0才确定位置。
ary.sort(funciton(a,b){
//a是数组中的当前项,b是下一项
return a-b;//当前项减去下一项大于0则a往后移一位,小于等于0不动,这个是升序。如果是b-a,b大于0则b往前移a往后移,则大的前边。是降序。
});
#### dom的映射机制 ####
概念:页面中的html元素,通过js获取的集合或者是元素的对象,存在映射关系。当一个改变,另外一个也会跟着改变。
由于dom的映射关系,页面中的标签和xxx元素对象是绑定在一起的。
我们修改元素对象空间中的值,页面中的元素会按照最新的值进行修改,这个机制是浏览器完成的。
dom映射机制是浏览器自带的映射机制。
appendChild在追加的时候,之前在容器当中存在,此时不是克隆一份新的,而是把原有的元素进行移动。
第二种映射机制:
页面绑定页面的空元素,我们把内容绑定到这个空元素里边,我们无需再次获取这个元素,浏览器会自动更新这个空元素,让添加进去的元素生效。
例如我们获取li是一个空的数组,然后我们通过js添加一些内容到li里边,浏览器会自动更新,那么li就不是一个空数组而是有内容的数组。这种是另外一种dom的映射机制。
注意:如果我们用querySelectAll获取元素是掐断了映射机制。也就是获取的集合和页面是没有关系的。
####专业排序 ####
这样的一种排序方式比较是适合真正的项目开发,比较专业。
项目业务功能的实现的四个步骤:
第一步:一个高级单利模式给变量起到保存和保护的作用。
第二步:写好要执行的函数
第三步:在init里边按照顺序写好要执行的函数(命令模式);
第四步:执行init
(function(){
function getData(){}
function inBind(){}
return{
init:function(){
getData();
inBind();
}
}
})();
#### dom回流和重绘 ####
浏览器渲染的步骤是:
[if !supportLists]1,[endif]dom结构的计算(哪些是父dom节点,哪些是子dom节点),形成DOM TREE
[if !supportLists]2,[endif]加载css
[if !supportLists]3,[endif]样式渲染树的形成,STYLETREE
[if !supportLists]4,[endif]浏览器通过GPU(显卡)根据样式渲染树进行渲染。
重绘:
就是当页面的结构不变时,页面的样式发生改变,浏览器会重新渲染这个样式。例如:
xxx.style.color=”xxx”
如果有多个这样的样式,并且多个之间并不是连续的,那么浏览器会重绘多次,从而影响性能。如下:
xxx.style.color=”xxx”
...其他代码
xxx.style.color=”xxx”
提高这方面的性能使用类名的更改:
所以我们更多的希望是更改类名,这样的话就一次性改了这些样式。box.className=”xxx”,这样的性能会好一些。
回流:
dom的元素发生了改变(增加,删除,位置变化)
如果产生回流性能消耗会比较大。因为浏览器渲染的步骤需要重新走一遍(有可能css有缓存)。渲染的第一步dom的节点需要重新计算。
基于文档碎片可以解决回流问题:
文档碎片是在虚拟内存中开辟的一个容器。
每当创建一个元素,首先把它存到文档碎片当中,千万不要放到页面当中,避免回流,当我们把需要的元素都创建完,并且都添加到文档碎片中,再统一把文档碎片放到页面中。这样只会触发一次回流。
创建文档碎片:
let frg=document.createDocumentFragment();
frg.appendChild(dom节点);
然后一次放入到页面中。
这样的话就只有一次回流。
frg=null;//释放这个内存
**字符串拼接的性能**:
字符串的拼接也只引发一次dom回流,因为拼接并没有放入到dom页面上,而是最后才放入到页面,也只只有一次回流。
与文档碎片相比,文档碎片性能稍微好一点。虽然字符串也只影响了一次回流,但是字符串会转化成dom节点才能插入到页面。
**分离读写**:
box.style.top=”100px”;
console.log(22222);
box.style.left=”100px”;
上边引发两次回流。
box.style.top=”100px”;
box.style.left=”100px”;
console.log(22222);
上边只引发一次回流。
分离:是指写的操作写在一起,读的操作写在一起。
现在的浏览器机制是,如果发现都是写的操作,那么会放入到待办事项里边,但是如果发现有读的操作,则会先执行待办事项的操作。才能去操纵读的操作。所以为什么上边的代码一个是两次回流,一个是一次回流。
####总结规律 ####
总结:
1,我们在文件中获取到的文件内容是一个字符串是一个json格式的字符串。
json不是一种数据类型而是一种数据格式。只要把对象的属性用双引号套起来是一个json格式对象。操作起来和普通对象没有什么太大的区别。
json格式的字符串和json格式对象。
window里边给我们JSON对象。
2,数据绑定,就是依托于页面的数据解构显示出来。数据的绑定以下3个方式:
第一种方式:
->传统的字符串凭借
->模板字符串拼接
->模板引擎
第二种:
动态创建dom
->createElment
->appendChild
动态创建dom的弊端:
[if !supportLists]1,[endif]操作起来麻烦
[if !supportLists]2,[endif]影响性能
3,dom回流
3,日期比较大小,可以通过Date变成时间戳。如果变成时间戳比较麻烦。把日期中间的-去掉一样是可以比较大小的。
### DOM ###
####基础 ####
IE6-7下,表达元素的name会当作id来使用。
getElementByClassName
不兼容ie6到8
getElementByName
只对ie6-8里边的表单元素起作用
querySelector没有映射
querySelectorAll
document.documentElement
document.body
document.head
描述节点与节点之间的关系:
nodeType NodeName Nodevalue
元素节点:1【nodeName :大写标签名】 null
文本节点:3【#text】 文本内容
注释节点:8【#comment】 文本内容
文档节点:9【#document】 null
childNodes :所有子节点
children:所有元素子节点(ie6到8中会把注释当中节点)
parentNode:父节点
firstChild
lastChild
动态操作dom的:
createElement
createElementFragment
appendChild
insertBefore
cloneNode
removeChild
####盒子模型属性 ####
js盒子模型属性:
在js中通过相关的属性获取或者是设置元素的样式的信息。这些属性就是盒子模型属性。
分成3大系列:
client
(
width和height:
>可视区的宽度和高度:内容+padding。与内容无关与overflow的设置也无关。
>但是如果没有设置高度,则以内容+padding;
)
let,right,width,height
document.documentElement (获取HTML节点)
获取当前屏幕的可视区(表示一屏的,不算隐藏的):因为兼容行不好,所以写两套
document.documentElement.clientWidth || document.body.clientWidth
document.documentElement.clientHeight || document.body.clientHeight
clientLeft和 clientTop:是获取边框的宽度。
offset
let,right,width,height
offsetWidth和 offsetHeight:是在clientWidth和ClientHeight的原有基础上加上border的值。(与内容益处也是没有关系的)
parent
scroll
scrollWidth和 scrollHeight:真实内容的宽高+padding(不一定是自己设定的值,可能会存在内容溢出。有内容溢出的情况下,需要把溢出的部分算上。是一个约等于值。设置overflow.hidden,影响值的大小,不同浏览器的值还不同。)
分溢出和不溢出两种情况:
不溢出:和client一样
溢出:内容+padding的左或者下,因为溢出部分会覆盖掉其中一个padding的值
页面的所有长度,而不是一屏幕的宽和高度:
document.documentElement.scrollWidth || document.body.scrollWidth
document.documentElement.scrollHeight|| document.body.scrollHeight
let,right,width,height
#### dom获取值的特点 ####:
1,获取的值都是不带单位的
2,获取的值都是整数不是小数(一般都是四舍五入,但是浏览器的一样也会有不一样的效果)
3,获取的结果都是复合值。(如果只想获取单一的值,例如只想获取padding的值,我们的盒子模型就操作不了了。)
####获取样式值 ####
>元素.style.xxx 获取样式的值
这种方式只能获取所有写在元素行内上的样式。不写在行内上的样式不管你写没写都是获取不到的。真实项目中我们很少把样式写在行内上。
>获取当前元素所有经过浏览器计算的样式
浏览器渲染果的样式或者是显示的样式,那么它的样式都是被计算出来的。
=》不管当前的样式写在哪都能获取
=》不管你写没写,浏览器会给默认样式,都能够获取。
标注浏览器(ie9+):
window.getComputedStyle(“元素”,[伪类,一般写null]); 获得一个对象,对象里边键值对,就是样式键值对。这个获取样式是带单位的。
非标注你浏览器:
元素.currentStyle()获取被计算的样式。
pathFloat(..px)可以去掉单位。
####设置css样式的方法的实现 ####
- 1,在js中设置css有两种方法,一种是用类名设置,一种是xxx.style.attr进行设置。
- 2,为了方便于我们处理样式,所以我们封装一个方法。
- 3,如果需要考虑6——8兼容,透明度这个样式在低版本浏览器中用的是filter(alpha(opacity=100)),高版本浏览器中用的opacity。
- 4,如果传递进来的value是没有带单位的,我该如何给属性加单位。
>某些样式属性才会加单位。width,height,padding,margin,font-size,top,left,bottom,right。
>用户自己传递的value值是没有单位的。
>首先确定用户输入进来的数据是否是带有单位的。用isNaN()进行判断。如果为true说明不是有效数字,即是带有单位的。如果为false说明是有效数字需要添加单位。
批量更改样式的操作:
obj.hasOwnProperty(attr)判断属性是公有的还是私有的。
我们批量更改样式我们要循环的是对象私有的属性,并且进行更改。这里的对象指的是options;
function fun(obj,options={let:xx,width:xxx}){
}
循环使用的是for-in循环。
实例:写一个方法实现上边所有的操作,这个方法明叫做css()
#### offset ####
offsetParent
offsetLeft/offsetTop获取当前和盒子左偏移和上偏移,参照物是父盒子为准。
总结:
1,如何没有position的定位,那么大家的都在一个层面,都所以body作为参照物。如父亲以上有定位那么就是父亲有定位的层面作为参照物。
body的父级参照物是null。
设置position的relatvie和absolute和fixed 。
所以:改变元素的定位,可以改变其父级的参照物。
offset的大小:当前盒子的外边框到父亲盒子的内边框。
[if !supportLists]3,[endif]需求:不管元素的父亲参照物是谁,如何获取它到body的举例。
tagName获取当前的标签名,并且是大写的。
4,scrollTop 和 scrollLeft 滚动条滚动的高度或者宽度。
操作浏览器的盒子模型属性:document.documentElement和 document.body 并不是兼容ie6到8,而是兼容浏览器下的其他模式。
页面高度:scrollHeight和 一屏幕是: clientHeight
在js的13个属性中,只有scrollTop和scrollLeft是可读写属性。
#### @@@跑马灯实例 ####
在以前都是用这个标签实现的
标签,文字跑马灯的效果,这个是以前使用的。消耗性能比较大。好像不能够实现无缝衔接。这个标签基本上被撤销了。
现如今的跑马灯都是基于js动画和css动画实现的。
这个实例自己实现就可以了。不需要更这个老师走。自己想办法实现把。一个小时的时间应该差不多。我想。
**遇到问题1**:
在求运动的模块的长度等于小模块之和,但是模块会掉下来。
解决办法:因为我模块用的是行内模块,但是行内模块之间会有默认的距离。所以我用浮动了。浮动就解决这个问题了。并且需要在大运动模块的长度上+1;
**问题2**:
dom的映射关系。
query获取的元素在元素改变的时候,需要重新获取,因为没有映射关系。
document获取的则是有映射关系。
**问题**:
js中通过offsetWidth获取宽度不准的问题。
#### @@@瀑布流 ####
瀑布流的原理:
效果:多列不规则排列,每一项内容不固定,3列之间不能相差太大。
数据:首先获取需要展示的数据(假设有50条,共3列);把50条数据的前3条依次插入到3列当中。目前有的列高,有的列低,再拿3条,先排最矮的。以此类推把50条数据都插入即可。
使用的是jquery这个类库实现的。
真实项目中:
@1:数据的获取一般都是通过分页获取的,会提交页数过去。
###事件 ###
####什么是事件 ####
**事件**:就是一个件事情或者一个行为,元素天生自带的事件行为。
我们在这里学的事件都是dom事件。
对于页面中的元素来说很多的事件都是天生自带的。只要我们去操作这个元素就会触发这些行为。
事件对于元素来说就是天生自带的行为,只是没有绑定方法,所以事件被触发的时候没有任何操作而已。
事件绑定:
box.onclick=function(){
...
}
事件本身本来就存在的,我们做的只是在事件上绑定一个方法。
元素天生自带事件?:
首先是鼠标事件:
- click(pc端是点击,移动端是单击。移动端使用click会有300ms的延迟。因为移动端的click是单击并不是点击。单击就是指在规定的时间内只能点击一次。)。
- dblclick:(双击,在pc端快速点击两次会触发两个单击和一个双击)
- mouseover鼠标经过
- mouseout鼠标移出
- mouseenter鼠标进入
- mouseleave鼠标离开
- mousemove鼠标移动
- mousedown鼠标按下(鼠标的左键和右键都是会触发的,而click是按下之后抬起的时候才会触发)
- mouseup(鼠标抬起,会先触发up事件,再触发click事件)
- mousewheel(鼠标滚轮滚动)
键盘事件:
- keydown:键盘按下,返回的是键盘码
- keyup: 键盘抬起
- keypress: 也是键盘按下的意思,只是返回的是一个ascii码
- input:移动端内容改变事件;由于pc端有物理键盘,可以监听到键盘的按下和抬起的事件。但是移动端是虚拟的键盘,所以keydown和keyup 在大部分手机上,都没有。我们使用input事件统一代替他们。移动端用input(内容改变事件)。
表单元素常用事件:
- focus:获取焦点
- blur:失去焦点
- change:内容改变
...
其他常用事件:
- load :加载完成
- unload:页面关闭
- beforeunload:关闭之前
- scroll:滚动条,滚动事件
- resize:大小改变事件
...
移动端手指事件:
touch事件模型,分为:touchstart touchmove touchend,touchcancel(意外情况手指结束,例如手机关机了) 。
gesture多手指事件:
gesturestart:多手指按下
gesturechange:多手指改变
gestureend:多手指离开
音频和视频的事件:
- canplay:可以播放(只要加载一丢丢就可以播放)
- canplaythrough: 表示资源加载完了,可以正常无障碍播放
查看这个元素到底有多少事件的方法是:
控制台输入:
dir(元素节点),然后打开元素节点里边的详细内容,既可以看到具体的事件是哪一些。
这个是ajax的事件
dir(new XMLHttpRequest)
####事件绑定 ####
事件绑定:就是给当前元素绑定事件的方法。
**第一种绑定事件的方法**:
dom 0级事件绑定:元素.onxxxx=function(){}
**第二种绑定事件的方法**:
demo 1级绑定事件:
元素.addListenerEvent(“事件”,function(){},boolean)
【ie6~8】 元素.attachEvent(“on事件”,function(){})
【目的】:就是给当前元素的某个事件绑定方法,都是为触发元素的相关行为时能做点事(也就是把绑定的方法执行)。
不仅把方法执行了,还给方法传递了一个实参信息值。
传递过来的实参值,就是事件对象。
事件对象:分为mouseEvent鼠标事件对象,keyboardEvent键盘事件对象,普通事件对象event等等(既不是鼠标也不是键盘,即使普通的事件对象)。
事件对象常用的属性:
【mouseEvent常用的】:
- target事件源
- clientX /clientY鼠标距离当前窗口的距离,就是可视区域左上角。
- pageX /pageY当前鼠标距离body左上角 ,指的是document页面的左上角,包括隐藏的区域。
- preventDefault阻止默认行为
- stopPropagation阻止事件冒泡
- type事件的类型
【keyboardEvent的常用事件】:
- keyCode:当前案件的键盘码。
-keyWhich:获取档期那的键盘码。
-常用键盘码:
左上右下:37 38 39 40
backspace:8,
enter 13,
Space 32,
shift 16,
delete:46,
alt:18,
ctrl:17,
esc 27,
keyCode和 keyWhich 两个的区别:
兼容的问题:
keyCode是ie
火狐和其他的keyWhich
谷歌两者都有。
- type当前事件的类型
####事件的兼容性 ####
在低版本ie浏览器中,事件对象ev,并没有值,所以值是undefined。
所以要基于window.event进行获取。
由于是全局属性,鼠标每次操作都会把上一次操作的值给替换掉。
兼容低版本ie:
-事件对象:window.event 并不是直接ev=window.event;
- ie低版本浏览器是srcElement,并没有target。所以设置ev.target=srcEvent;
- pageX/Y在ie下也是没有的。
所以也是给他设置。
ev.pageX/Y=ev.clientX/Y+document.documentElement.scrollLeft || document.body.scrol.......
也可以在低版本中添加which。
因为在低版本中有很多的东西都是没有的。所以我们做判断,如果是低版本我们把这些东西都添加进去。那么我们在操作的时候就不存在没有的内容了。
**阻止默认行为的操作**:
ev.preventDefault();
-低版本中阻止默认行为:
ev.preventDefault=function(){
ev.returnValue=false; //这个方法是阻止默认行为,只是我们统一放到了一个方法里。
}
-阻止冒泡传播的:
- ev.stopPropagation()
-低版本浏览器中阻止冒泡行为:
ev.stopPropagation=function(){
ev.cancelBubble=true;//阻止冒泡传播,我们统一放到一个方法里边。
}
把低版本的兼容的方法进行封装以后,以后就使用高版本的进行操作就可以。
####事件的默认行为 ####
A标签的默认行为就有两个:
>页面的跳转
>锚点(hash值跳转)【电商网站的楼层导航可以用这个来实现,基于hash值我们可以实现spa单页面应用,广告语的定位例如打开另外一个网站要跳到另外一个网站的距离的位置才可以的实现。】
input标签的默认行为:
>输入的内容可以呈现在文本框里
>输入内容会把直接输入的一些信息呈现出来(不是所有的浏览器支持)
> submit点击后页面会刷新。
**如何阻止这些默认行为**:
>有的时候我们只是把A标签当作一个普通的按钮并不想要它是一个跳转的页面。第一种方法:href的值是 “javascript:;” ; “javascript:void 0/undefined/null” ;
在js中如何阻止默认事件:
>先触发事件(在先触发的事件里边绑定一个方法,写上阻止默认行为的操作。)
- return false;
- ev.preventDefault();
>然后触发默认行为
阻止默认行为的案例:
例如:输入字符的长度不能大于6。阻止默认行为就是除了回车,空格,上下左右键外,其他的键都给它阻止默认行为。这样其他的键就无法输入了。
####事件的传播机制 ####
冒泡传播:触发当前元素的某一个事件(点击)行为,不仅是当前元素的触发,而是祖先元素的触发。这种机制就是事件的冒泡传播机制。
冒泡机制:就是触发一个事件,如果它的父亲的事件也绑定了方法,则父亲的相同事件也会触发。
事件传播的大小:
window>document>documentElement
事件的传播机制中的捕获:
当我们在点击事件的时候,就是按照这个顺序进行执行的:
第一个阶段:捕获阶段,就是最外边往里边捕获事件源,直到找到被触发的事件源,其实它是给冒泡规划路线。
第三阶段:目标阶段;把事件源的相关操作进行执行。
第三阶段:冒泡阶段:按照捕获阶段,自内而外的根据祖先元素进行执行。如绑定方法执行方法,如果没有绑定方法只是触发行为。直到window。
各类时间方法的执行:
xxx.onxxx=function(){
....
}
这个方法是在冒泡阶段或者是在目标阶段完成的。
xxx.addEventListener(“”,function(){},false)//第三个参数为false,是在冒泡阶段或者目标阶段完成。只有第三个参数为true时,才是在捕获阶段执行事件的绑定。
不同浏览器对于祖先元素不同:谷歌:window-document-html-boy
ie高:window-html-body
ie低:window-body
我们一般是操作到body就可以了。浏览器都支持。
总结:
>1,事件分成事件行为和事件绑定;只要操作了就有事件行为,但是有没有操作,要看是否绑定了方法。
>2,事件对象的一些理解;事件对象是用来存储当前的一些信息,与操作有关,与元素本身是没有必然关联的;当我们基于鼠标或者键盘等操作以后,浏览器会把本次操作的信息存储起来(标注浏览器存储到内存当中,非标准浏览器存储到window上,存储的值也是一个对象);操作肯定会触发元素的某个行为,此时标准浏览器会把之前存储的对象当作实参传递给每一个执行的方法。(冒泡阶段所有的祖先元素的ev都是事件源的ev对象。因为ev对象只是对操作的记录,与元素本身无关。)
3,标准浏览器的ev是传进来的。非标准是window上获取的。
4,如果阻止冒泡传播,冒泡就不会往上传播了。
#### mouseover和mouseout的问题 ####
1,因为冒泡的机制,当进入子元素的时候,会触发父元素的同样的事件,这样触发的事件会特别的多。导致事件很混乱。
[if !supportLists]2,[endif]所以我们用enter和leave ,因为它阻止了冒泡机制。同时enter和leave在从父元素进入子元素的时候不会出现离开父元素的情况。也就是不会触发父元素的leave事件。真实的项目中使用enter的情况会比较多。
总结:
mouseover和mouseenter的区别:
> over属于经过划过的事件,从父元素进入到子元素,属于离开了父元素,会触发父元素的out,触发子元素的over
>enter属于进入,从父元素到子元素,并没有离开父元素。不会触发父元素的leave,触发的是子元素的enter。
所以,对于父元素嵌套子元素的这种情况使用over会出现各种想不到的问题,此时我们使用enter。
#### @@@放大镜 ####
第一种实现方法:左边和右边的盒子是相等的实现原理:
1,左侧盒子和mark盒子的对比等于右边盒子和大图的比例必须是相等的。
2,移动,左侧mark盒子移动,与右侧大图的移动呈相反的方向。
3,mark盒子和右边的盒子一开始都是隐藏的。
4,mark盒子是不能出去左边的盒子的。
第二种实现的方法:宽高都是不比例的原理:
1,这种情况下右边盒子的大图是需要求出来的。左边mark盒子/左边盒子等于右边盒子/大图。(mark盒子的内容在右边盒子里边显示内容是一样的,只是倍数放大,所以可以把右边的盒子看作是左边mark盒子,而把大图看作是左边的盒子。所以他们的比例相等。而大图的宽高通过比例求出来。)
总结:
1,是不是只要移动的marnk盒子和左边盒子的比例与右边盒子与大图的比例相等就可以,并不需要对图片最开始的样式进行设置?
不是的,必须是左边与右边的盒子之间的比例是相等的才可以。如果不相等就无法显示。
#### @@@鼠标跟随 ####
>实现这个案例有很多的方法。
>可以之前把显示的大图写好在li的样式里边。也可以鼠标移入li时自动进行创建。
>步骤:
1,首先布局排列
2,鼠标移入li时创建大图div,移出时移出掉大div(jq移出元素,只是元素移出掉了,js的变量还是引用地址。所以需要对js变量做进一步处理)。
3,通过当前移入的img标签来获取当前大div需要显示的图片地址。(图片地址是有规则的。正则在这里有一个经典的使用方法,很帅。)
4,大图div是整个大容器的子div。相对于大容器定位。
5,通过鼠标距离,大容器距离,以及鼠标与大图之间的距离显示
####事件委托 ####
利用事件冒泡的传播机制。(但是,mouseenter和mouseleave没有冒泡的机制的)。
事件冒泡的机制,事件委托的原理:
因为有冒泡机制,所以只要把方法父类就可以,不管点到哪个子类,冒泡机制都会去执行父类的方法。
使用事件委托,可以提高百分之五十的性能。
我们绑定的事件越多消耗的性能就越大。
#### @@@分类导航 ####
>使用html5和css3实现就可以。要显示的导航,放入到鼠标移入的元素里边。
能够用css处理的问题一定不要用js去实现。
**事件委托实现左侧分类菜单(京东)**:
>左边是一个导航,右边是就是一个大的div,通过左边选择不同的导航,右边显示的内容也是不相同的。
把事件绑定在父亲的元素上,后代的列表不管操作的是谁,最终都会去操作父亲元素。
jq的使用方法:
parents()获取所有的祖先元素。
filter是大的集合中找到的内容以一个新的集合返回。
判断自己的样式类是:hasClass(“样式类”);
**右边的div显示的原理**:如果事件源是右边div或者是右边div的后代元素,则不做任何处理,也就是不隐藏。如果不符合以左边的导航栏和右边的div的所有的条件。则右边div会进行隐藏。
后代元素的判断:就是后代元素的祖先元素包含了右边div的类。
body的高度只有内容的高度,添加margin和padding,也和body是隔开的。
事件委托在工作当中是非常重要的。
这个demo的关键点,节约性能的点是:阻止了右边大div的冒泡事件,不会在传播到祖先body上,这样也是什么都不做,就一直在显示。而直接阻止了就简单很多。
事件委托和中端事件委托利用好可以提高性能。
**总结:事件委托实现分类导航类案例**:
>使用filter方法可以获取祖先元素的集合里边是否包含某一个元素。如果包含会返回一个类数组。可以通过这个类数组的长度来进行判断。是否有。
#### @@@无限菜单 ####
```事件委托和事件中断是事件操作里边非常重要的一个知识点,也是经常用到的一个知识点。
引用场景:之所以叫无限菜单,以后只要有其他的菜单,想怎么加菜单都是可以的,都会实现。我们不需要去改变代码。
```
[if !supportLists]1,[endif]>只给容器绑定一个方法。然后通过冒泡机制来进行事件的委托。通过时间源来判断到底是哪个做的一个操作。
[if !supportLists]2,[endif]>容器中很多的后代元素要进行操作,委托给祖先元素来出来。
[if !supportLists]3,[endif]>还有就是元素是动态绑定的(就是后面创建的元素,因为后面创建的元素我还要重新获取,重新进行事件的绑定。而事件委托是,我不管你什么时候创建的,只要是我的子元素,那么就可以用事件委托可以使用到我的事件)。
[if !supportLists]4,[endif]>除了某某剩下的操作都是干同样的事情(此时我们把点击行为的操纵委托给body,事件源是某某作什么,不是统一做什么,获取事件源然后进行分类操作)。
[if !supportLists]5,[endif]>事件源合并,两个标签操作的同样的内容时,我们以其中的一个事件源为参照。就是判断某一个标签,把这个标签的操纵的源让另一个源覆盖掉(其实就是引用的覆盖)。
[if !supportLists]6,[endif]>jq的next(标签)获取下一个节点,如果没参数就直接是下一个参数。jq的prev()的上一个兄弟。
[if !supportLists]7,[endif]>基于jq获取的元素,获取的都是jq对象。如果没有获取到返回的也是一个对象。如果没有则它的length是0。
[if !supportLists]8,[endif]>jq的收起和展开的动画:$xx.stop() 表示的是结束当前动画。$xxx.stop().slideDown(时间)展开下边的。$xxx.stop().slideUp(时间);结束当前的动画,收缩内容。还有更简单的方法是:$xxxx.slideToggle(时间);当前是展开则收缩否则展开。
[if !supportLists]9,[endif]>如果我们把外层收起来,则里边分类也是跟着收起来的实现。
[if !supportLists]10,[endif]>在我们进行外层收起来,里层也收起来的时候我们用的是display设置为none,其实隐藏。但是我们在前面用的是$xxxx.stop.slideDown()这样的方式进行展开和收缩的,这个效果是定时器的原理实现的,是异步的。而里边的是隐藏的方式,是同步的操作。所以在定时器收缩的时候,可能有部分内容已经隐藏了。这样的动画效果会很难看。解决办法是:使用promise进行操作。
####事件之间的区别 ####
-元素的存在本身,浏览器本身就给它设置了很多的公有属性和私有属性,这些属性的默认值是null。其中onclick就是它的私有属性。
- DOM0:就是给默认的私有属性进行赋值,浏览器会建立监听机制。当我们触发这个默认的行为的时候就会触发其操作。
- DOM0:多此绑定方法时后面绑定的内容会替换前边的内容,以最后一次绑定的方法为主。
- DOM2:使用的方法是EventTarget的prototype下定义的,上到window下到每一个元素都是EventTarget的实例。
- DOM2中新增加了一种机制,就是事件池的机制。浏览器有一个统一的事件池,然后元素自己的事件池在里边。事件池是用于存储监听的方法的。一个事件可以添加多个方法,但是同一个事件是不同的方法。因为存储的地址,而事件池中地址相同则会被覆盖。
-行为被触发后,浏览器会根据当前事件池中存储的顺序进行执行。每一个被执行的方法浏览器都会把事件对象传递给他,this是当前操作的元素。
-在执行的方法中不会出现重复,在向事件池中增加的时候就去重了。
-执行是按照存放的顺序进行执行的。
- dom的兼容问题:
谷歌:ie高版本:谷歌在移出事件绑定的时候,如果移出操作发生在正要执行的方法之前,那么下一轮操作才会执行。(当前执行的事件池不会有变化)
```
如果是ie低版本的区别是:
谷歌:
addEventListener和 removeEventListener
ie低版本:attachEvent 和 detachEvent (并且执行的顺序是乱序的,并且同一个事件可以绑定同一个方法并且还可以绑定多次)
```
- ie低版本的问题:1,首先是事件方法重复问题,ie低版本并没有那么完善,同一个事件可以绑定相同的方法多次。2,顺序问题:标注浏览器是按照事件存放的顺序依次执行,而ie低版本是没有规律的执行。3,ie低版本出现的问题,是浏览器本身自带的事件池机制不完成。