JavaScript
ECMAScript(ES):规定了一些基础核心的知识(变量、数据类型、语法规范、操作语句等)
DOM:document object model 文档对象模型,里边提供了一些属性和方法,可以让我们操作页面中的元素 >BOM::browser object model 浏览器对象模型,里边提供了一些属性和方法,可以让我们操作浏览器
浏览器内核
浏览器开发商开发的浏览器,目的就是为了按照W3C的规范,识别出开发者编写的代码,并且在页面中绘制出开发预想的页面和效果(GPU:显卡),我们把浏览器识别代码回执页面的东西称为‘浏览器的内核或者渲染引擎
- 谷歌浏览器:Webkit内核(v8引擎)
- Safari、大部分国产浏览器(360、搜狗、QQ、)、安卓和IOS大部分手机浏览器
- 火狐浏览器(firefox):Gecko内核
- 欧朋浏览器(opera):Presto内核
- IE浏览器:Trident内核
JS导入的三种方式
行内式(不建议使用) 内嵌式 外链式导入
内嵌式导入和外链导入不能合并在一起,如果当前是外链式导入的,那么在script脚本块中编写的所有代码不会执行
<div onclick='alert(1)'></div>
<script>alert(1)</script>
<script src='1.js'></script>
JS的三种输出方式:
alert():在浏览器中弹出一个提示框(提供确定按钮,点击确定弹窗消失)
使用alert提示信息,信息最终都会被字符串输出(调用了toString这个方法)
confirm:在alert基础上增加了让用户选择性操作(提供了确定 和取消按钮)当用户点击确定返回是true 点击取消返回false
prompt:在confirm基础上,增加让用户输入的效果; 如果用户点击取消返回NULL;点击确定返回用户输入内容;如果没输入返回空字符串
控制台:console.log()在控制台输出,不会转换数据类型,输出什么格式都可以
console.dir:比log输出的更加详细一些
console.table:把JSON数据展示成为一个表格
变量和常量
变量:值是可以变的 >常量:值是不可以变的
//=>JS中定义变量的方式
// var 变量名 = 值;(ES6中使用的是let)
var num = 12;
var str = 'xiaoqiang';
console.log(num);//=>12 变量其实只是一个无意义的名字,它所代表的意义都是其存储的那个值(变量只能代表一个值)
//=>任何一个具体的数据值都是常量,例如:12就是一个常量
//=>和变量类似,我们设置一个常量(也就是一个名字),给其存储一个值,但是这个存储的值不能修改
JS中的命名规范
1、JS中严格区分大小写
var test = 'xiaoqiang' var Test = 'xiaopao' test 和 Test 是两个不同的变量
2、遵循国际命名规则驼峰命名法
第一个单词首字母小写,其余每一个有意义的单词首字母大写
// 命名使用英文单词,不要是用拼音
// 不是所有单词都能简写,我们需要保证大家看到名字后知道所代表的意思
3、命名的时候可以使用$、_、数字、字母
,但是数字不能作为名字的第一位
var $xxx; // =>一般都是应用JQ获取到的值
var _xxx;// =>一般这样的情况代表变量是一个全局或者公用的变量
4、JS中很多的词都是有特殊含义的,我们把这些词叫做关键字
;现在没有特殊含义,以后可能会作为关键词的,我们叫做保留字
;而关键字和保留字都不可以用来命名;
JS中的数据类型
基本数据类型(值类型)
- number:数字
- string:字符串
- boolean:布尔
- null:空对象指针
- undefined:未定义
引用数据类型- object对象数据类型
- {}普通对象
- []数组
- /^$/正则
+....- function函数数据类型
12 12.5 -12.5 0
'xiaoqiang' // =>单双引号包裹起来的都是字符串(单双引号没有区别)
true false // =>布尔类型:只有两值
null undefined
{name:'xiaoqiang',age:18}
[12,32,12]
/^-?(\d|([1-9]\d))(\.\d+)?$/
function(){} .....
这么多数据类型JS如何去检测呢?
- typeof:检测数据类型的运算符
- instanceof:检测某个实例是否属于这个类(某个对象是否是函数返回值)
- constructor:获取当前实例的构造器
- Object.prototype.toString.call:获取当前实例的所属类信息
**typeof
**
使用typeof检测,返回的结果是一个字符串,字符串中包含的内容证明了值是属于什么类型的
【局限性】
1、typeof null不是'null'而是'object':因为null虽然是单独的数据类型,但是它本来的意思是空对象指针,浏览器使用typeof检测的时候会把它按照对象来检测
2、使用typeof无法具体细分出到底是数组还是正则,因为返回结果都是'object'
typeof 12 => 'number'
var num=13;
typeof num =>'number'
Boolean()
把其他数据类型的值转为布尔类型
- 只有
0、NaN、空字符串、null、undefined
这五个数据值转换为布尔类型的false,其余都会变为true
!
!=:不等于 >叹号在JS中还有一个作用:取反
,先把值转为布尔类型,然后在取反
!!
在一个叹号取反的基础上再取反,取两次反等于没有做操作,但是却已经把其他类型值转换为布尔类型值了
字符串
在JS中 单引号 和 双引号包起来的都是字符串 常用方法: charAt charCodeAt substr substring slice toUpperCase toLowerCase indexOf lastIndexOf split replace match ....
number数字
0 12-12 12.5 ,JS中多增加了一个number的数据:
NaN
>typeof NaN ->'number'NaN
>not a number:不是一个数,但是属于number类型
NaN == NaN:false ,NaN和任何其他值都不相等
iSNaN()
用来检测当前这个值是否是非有效数字,如果不是有效数字检测的结果是true,反之为false 当我们用isNaN检测值得时候,检测的值不是Number类型,浏览器会默认的把值先转换为number类型,然后再去检测
Number()
把其他数据类型值转化为number类型的值
在使用Number转换的时候只要字符串中出现任何一个非有效数字字符,最后结果都是NaN
Number(true) =>1
Number(null) =>0
Number(undefined) =>NaN
Number([]) // =>把引用数据类型转换为number,首先需要把引用数据类型转为字符串(toString),再把字符串转为number即可,例如:
Number([]) //-> '' ''->0
Number([12]) //=>[12]->'12' '12'->12
Number([12,23]) //=>[12,23]->'12,23' ->NaN Number({}) => NaN
parseInt()
也是把其他数据类型转为Number,和Number方法在处理字符串的时候有所区别
Number('12px') ->NaN
parseInt('12px') ->12
parseInt('12px12') ->12// 提取规则:从左到右依次查找,直到遇到非有效数字字符为止(不管后面是否还有,都不找了),把找到的转换为数字
parseIFloat()
在parseInt的基础上可以识别小数点
parseFloat(12.4)//=>12.4
null和undefined
null:空,没有
undefined:未定义,没有
" ":空字符串,没有
0:也可理解为没有
空字符串和null的区别
都是取空的
空字符串属于挖了个坑,但是没有种任何东西
null是连坑都没挖
空字符串相对于Null来说是开辟了内存,消耗了那么一丢丢的性能
null和undefined的区别
null一般都是暂时没有,预期中以后可能会有(可能以后也没达到预期):在JS中null一般都是手动先赋值为null,后期我们在给其赋值
undefined:完全没在预料之内的
对象数据类型Object
var obj={name:'xiaoqiang',age:8};
每一个对象都是有零到多组属性名(key键):属性值(value值)
组成的,或者说由多组键值对组成的,每一组键值对中间用逗号分隔属性:描述这个对象的特点和特征的
对象的属性名是字符串或者数字格式的,存储的属性值可以是任何的数据类型
对象名.属性名:忽略了属性名的单双引号
对象名[属性名]:不能忽略单双引号
var obj={name:'xiaoqiang',age:8,friend:['习大大','彭麻麻'],0:100};
// => 获取某个属性名对应的属性值 obj.name或obj['name']
//如果属性名是数字如何操作 obj.0? 语法不支持使用。 obj[0]/obj['0']-> 两种都支持
// ->如果操作的属性名在对象中不存在,获取的结果是undefined
// 设置/修改: 一个对象的属性名是不能重复的(唯一性),如果之前存在就是修改属性值的操作,反之不存在就是新设置属性的操作
// =>删除
// -> 假删除:让其属性值赋值为Null,但是属性还在对象中 obj.sex=null
// -> 真删除:把整个属性都在对象中暴力删除 delete obj.sex
// Object.create(prObj) 创建一个新的对象,但是还要把proObj作为这个对象的原型 ,原理:
var obj={ getX:function(){} };
function(o){
function Fn(){}
Fn.prototype = o;
return new Fn; }
var newObj=object(obj);
基本数据类型和引用数据类型的区别
JS是运行在浏览器中的(内核引擎),浏览器会为JS提供赖以生存的环境(提供给JS代码执行的环境)=>
全局作用域window(global)
基本数据类型是按值操作的
:基本数据类型在赋值的时候,是直接把值赋值给变量即可
引用数据类型是按照空间地址(引用地址)来操作的
:
var n={name:'xiaoqiang'}
// 1、先创建一个变量n
// 2、浏览器首先会开辟一个新的存储空间(内存空间),目的是把对象中需要存储的内容(键值对)分别存储在这个空间中,为了方便后期找到这个空间,浏览器给空间设定一个地址(16进制的)
//3、把空间地址赋值给了变量n
函数数据类型
函数数据类型也是按照引用地址来操作的
函数:具备一定功能的方法
function 函数名 () {
//=>函数体:实现某一个功能的具体JS代码 }
// => 执行函数:把函数体中实现功能的代码实现(如果函数只创建不执行,函数没有任何意义) 函数名()
// =>形参:形式参数(变量),函数的入口
// =>当我们创建一个函数想要实现某个功能的时候,发现有一些材料并不清楚,只有当函数运行的时候,别人传递给我我才知道,此时我们就需要设定入口,让用户执行的时候通过入口把值给我们
function fn(n,m){ return n+m } // =>实参:函数执行传递给函数的具体值就是实参
JS中的函数
函数是指在一段在一起的、可以做某一件事的程序。也叫做子程序、(OOP中)方法
函数是实现某一个功能的方法
创建函数
function [函数名](){
// =>[函数体] //实现功能的具体JS代码
//执行函数 函数名(); //=>把创建的函数执行,而且这个函数可以执行多次 .... 每一次执行都相当于把函数体中实现功能的JS代码重复执行了一遍
}
在真实项目中,我们一般都会把实现一个具体功能的代码封装在函数中:
1、如果当前这个功能需要在页面中执行多次,不封装成为函数,每一次想实现这个功能,都需要把代码重新写一遍,浪费时间;而封装在一个函数中,我们就都没有必要再重新写代码了,只需要把函数重新执行即可,提高了开发效率
2、封装在一个函数中,页面中就基本上很难出现重复的代码了,减少了页面中代码的冗余度,提高了代码的重复利用率:低耦合高内聚
我们把以上的特点称为函数封装
(OOP面向对象编程思想,需要我们掌握的就是类的继承、封装、多态)
一个函数存在了多面性:
- "普通函数":它本是就是一个普通的函数,执行的时候形参的私有作用域(闭包),形参赋值,预解释,代码执行,执行完成后栈内存销毁/不销毁
- "类":它有自己的实例,也有一个叫做Prototype属性是自己的原型,它的实例都可以指向自己的原型
- "普通对象":和var obj={}中的obj一样,就是一个普通的对象,它作为对象可以有一些自己的私有属性,也可以通过proto找到Function.prototype
上面三者之间没有必然的关系
Function.prototype是函数类型的值,但是相关操作和之前是一模一样的
function Fn(){ var num=500 this.x=100 }
Fn.prototype.getX=function(){ console.log(this.x) }
Fn.aaa =1000
var f=new Fn;
f.num //undefined
f.aaa //undefined
var res=Fn() //this.window res=unefined
Fn.aaa //1000
JS中函数的核心原理
函数作为JS中引用数据类型中的一种,也是按照引用地址来操作的
创建函数
- 首先会在当前作用域中声明一个函数名(声明的函数名和使用var声明的变量名是一样的操 作:var sum; function sum; 这两个名字算是重复了)
- 浏览器首先会开辟一个新的内存空间(分配一个16进制的地址),把函数体中写好的代码当做普通的字符串存储在这个内存空间中(创建一个函数不执行,函数没有意义)
- 把内存空间的地址赋值给之前声明的那个函数名
函数执行
目的:把之前存储的实现具体功能的JS代码执行
- 函数执行,浏览器首先会为其开辟一个新的
私有作用域
(只能执行函数中之前的JS代码) - 形参赋值
- 变量提升
- 把之前创建时候存储的那些JS代码字符串拿到私有作用域中,然后把它们变为JS表达式从上到下执行
- 私有作用域是否销毁问题
闭包
函数执行会形成一个私有的作用域,让里面的私有变量和外界互补影响(相互不干扰、外面的无法直接获取里边的变量值),此时我们可以理解为私有作用域把私有变量保护起来的,我们把这种保护机制称之为
闭包
栈内存
作用域(全局作用域/私有作用域):提供一个供JS执行的环境
堆内存
所有的引用数据类型,它们需要存储的内容都在堆内存中(相当于一个仓库,目的是存储信息)
- 对象会把键值对存储进来
- 函数会把代码当做字符串存储进来
函数中的形参和实参
形参:相当于生产洗衣机的时候提供的入口,需要用户执行函数的时候把需要的值传递进来,形参是个变量,用来存储和接收这些值
实参:用户执行的时候传递给形参的具体值
函数本身也会有一些自己的属性:
- length:0 形参的个数
- name: 函数名
- prototype 类的原型,在原型上定义的方法都是当前Fn这个类实例的公有方法
- proto 把函数当做一个普通的对象,指向Function这个类的原型
// =>随便求出两个数的和
function sum(num1,num2){
// =>num1/num2就是形参变量,类似于var了一下
var total=num1+num2 total*=10;
total=total.toFixed(2);
console.log(total) }
sum(10,20); //=> 10/20是实参 num1=10 num2=20 sum(10);
//=>num=10 num2=undefined 定义了形参但是执行的时候没有传递实参,默认实参的值是undefined
arguments实参集合
当我们不知道用户具体要传递几个值得时候(传递几个值都行),此时我们无法设置形参的个数;遇到此类需要,需要使用函数内置的实参集合:arguments
1、arguments只有函数才有
2、不管执行函数的时候是否传递实参,arguments天生就存在,没有传递实参ARG是个空的集合,传递了ARG中包含了所有传递的实参值
3、不管是否设置了形参,ARG中始终存储了所有实参信息
arguments是一个类数组集合
- 1、以数字作为索引(属性名),从零开始
- 2、有一个length属性,存储的是当前几个的长度(当前传递实参的个数)
- 以下两个在严格模式下不可用:“use strict” // =>严格模式
- arguments.callee:存储的是当前函数本身
- arguments.callee.caller:存储的是当前函数在哪执行的(宿主函数),在全局下执行时得到的是null
JS中的返回值return
返回值是函提供的一个出口:我们如果想在外面使用函数私有的一些信息,那么就需要通过return,把这些信息返回出来供外面使用
如果函数中没有写return或者return后面什么都没有,默认返回的结果就是undefined,在函数体中如果遇到return后面的代码都不在执行了
JS中的匿名函数
没有名字的函数
- 函数表达式
- 自执行函数
oBox.onclick=function(){
//=>把一个没有名字的函数(有名字也无所谓)作为值赋值给一个变量或者一个元素的某个事件等:·函数表达式·
}
(function(n){
//=>创建函数和执行函数放在一起了,创建完成立马执行:·自执行函数·
})(10)
~function(n){}(10)
-function(n){}(10)
+function(n){}(10)
!function(n){}(10)
数组的基础结构
数组也是对象数据类型的
typeof [] => 'object'
数组也有属性名,只不过属性名是数字,我们把数字属性名称之为它的索引:数组是以数字作为索引,索引从零开始,有一个length属性代表数组的长度
类数组:类似于数组,但是不是数组
- 1、通过getElementByTagName获取的元素集合是类数组
- 2、函数中的实参集合arguments也是类数组
.....循环数组中的每一项
var ary=[12,23,34]
for(var i=0;i<ary,length;i++){
console.log(ary[i])
}
for(var key in ary){
//使用for in 循环可以遍历到自定义的公共属性遍历到
//key:属性名(数组中的属性名是索引)
// =>for in循坏在遍历的时候,默认的话可以把自己私有的和它所属类原型上扩展的属性和方法都可以遍历到,但是一般情况下,
//我们遍历一个对象只需要遍历私有的即可,我们可以使用一下的判断进行处理(可枚举和不可枚举)
if(obj,propertyIsEnumerable(key)){
console.log(key) } console.log(ary[key])
}
数组中的常用方法
数组中的常用方法
console.dir(Array.prototype)
1、方法的意义和作用
2、方法的形参
3、方法的返回值
4、通过此方法,原来的数组是否发生了改变实现数组的增加、修改、删除
var arr=[12,23,34];
//=>增加
//1、push:向数组的末尾增加新内容
//(参数:1到多个,任何数据类型都可以,向要数组末尾追加,直接传到push方法中即可,传递多个用逗号隔开, 返回值:新增后数组的长度,原来的数组改变)
//2、unshift:向数组的开头增加新内容
//(参数:1到多个,任何数据类型都可以,向数组开头追加,直接传到unshift方法中即可,传递多个用逗号隔开, 返回值:新增后数组的长度,原来的数组改变)
//3、arr[arr.length]:当做普通对象,使用对象键值对操作,给其设置新的属性,向数组末尾增加新内容只能传递一个,可以是任何类型,返回值:新增后数组
//=>删除
//1、pop:删除数组最后一项,没有参数,返回被删除的那一项,原数组改变
//2、shift:删除数组第一项,没有参数,返回被删除那一项,原数组改变,后面每一项的索引都要向前进一位(导致后面项的索引发生改变)
//3、delete ary[索引]:当做普通对象,使用对象键值对操作,删除数组某一项,返回布尔值,原数组其他项索引不会变,length不会改变,别用
//4、arr.length--:删除数组最后一项,length改变,返回数组长度
//=>splice:数组中内置的方法,可以实现数组的增加、修改、删除
//splice实现删除:splice(n,m)从索引n开始删除m个(m不写删除到数组末尾,n也不写,删除整个数组),返回被删除内容(以新数组方式保存),原有数组改变
//splice(0):清空数组
//splice():一项都不删除,返回新的空数组
// splice实现修改:splice(n,m,x)在原有的删除的基础上,用X代替删除的内容(先删除在插入),返回被删除的那一项,原有数组改变
//splice实现增加:splice(n,0,m):在修改的基础上,我们一项都不删除,把X插入到索引N的前面,返回空数组,原数组改变
数组的查询
//slice:数组的查询
//参数:slice(n,m)从索引n开始找到索引m处(不含m)
//返回值:把找到的部分以一个新的数组返回
//原来的数组不变
//slice(n)从索引n开始找到末尾
//slice(0) slice()数组克隆,克隆一份和原来数组一模一样的新数组
//支持以负数为索引的,如果传递的索引为负数,浏览器解析的时候,按照总长度+负数索引来处理的
将两个数组进行拼接
//contact:将多个数组拼接在一起
//参数:要拼接的内容(把内容放在原数组的后面),可是一个数组,也可以是一些数据值
//返回:拼接后的新数组,原有数组不变
//contact():什么都没有拼接,相当于把原有的数组克隆一份出来
数组转为字符串
//1、toString:实现把数组转化为字符串(转换后的字符串以逗号分隔每一项)
//参数:无
//返回值:转换的字符串,原有数组不变
//2、join:把数组按照指定的分隔符转换为字符串,和字符串中的split相对应
//参数:指定的连接符
//返回值:转换的字符串,原数组不变
实现数组中每一项的排序和排列
//1、reverse:把数组中的每一项倒过来排列
//无参数,返回排序后的数组,原有数组改变
//2、sort:实现数组的排序
//参数:无或者回调函数,返回排序后的数组,原有数组改变
//不传参数情况下:可以给10以内的数组进行升序排列,但是超过10的就无法处理了
// ary.sort(function(a,b){return a-b}) 升序 b-a :降序
验证数组中是否包含某一项
//indexOf/lastIndexOf:获取当前项在数组中第一次或者最后一次出现位置的索引
// 数组中的这两个方法在IE6-8下不兼容
// 字符串中的这两个方法兼容所有的浏览器
// 如果当前数组中并没有这一项,返回的索引是-1,我们根据这一点可以验证数组中是否包含这一项
Array.prototype.myIndexOf=function myIndexOf(value){
var result = -1;
for(var i=0;i<this.length;i++){
if(this[i]===value){
result=i; break;
}
}
return result; }
遍历数组中每一项的方法
//=>以下方法在IE6-8下都不兼容
//forEach:遍历数组中的每一项
ary.forEach(fucntion(value,index){
//=>数组中有多少项,当前函数执行多少次,每一次传进来的value就是当前遍历数组这一项的值,index就是遍历这一项的索引
})
//map:遍历数组每一项,在forEach的基础上,可以修改每一项的值
ary.map(fucntion(value,index){
//=>数组中有多少项,当前函数执行多少次,每一次传进来的value就是当前遍历数组这一项的值,index就是遍历这一项的索引
return xxx; //+>return XXX; 后面返回的结果就是把当前遍历的这一项改为XXX
})
filter find reduce every ....
数组去重
1、首先遍历数组中的每一项,拿每一项和它后面的项依次比较,如果相同了,则把相同的这一项在原来的位置上删除(性能不好,循环次数过多)
var ary=[1,2,2,2,3,5,4,6,8,54,6,5,7,3];
//=>ary.length-1:最后一项后面没有内容了,我们不需要再比较
for(var i=0;i<ary.length-1;i++){
var cur=ary[i];
//=>当前遍历的这一项[索引i]
//=>把拿出的这一项和后面的每一项比较
//=>i+1:把当前项和他后面项进行比较,当前项是I,后一项索引是i+1
// for(var j=i+1;j<ary.length;j++){
// if(cur===ary[j]){
// ary.splice(j,1)
//=>数组塌陷问题:我们使用splice删除数组中的某一项后,
// 删除这一项后面的每一项索引都要向前进一位(原有的索引上减一),此时如果我们J++;
// 循环操作的值累加了,我们通过最新J获取的元素不是紧挨删除这一项的元素,而是跳过这一项获取的元素
// j--;
//=>先--,在++,相当于没有加减,此时J还是原有的索引
// }
// }
for(var j=i+1;j<ary.length;){
// if(cur===ary[j]){
//ary.splice(j,1)
// }else{
// j++;
// }
cur===ary[j] ? ary.splice(j,1) : j++;
}
}
2、使用indexOf来验证数组中是否包含某一项,包含把当前项删除(IE6-8不支持)
var ary=[1,2,2,2,3,5,4,6,8,54,6,5,7,3];
for(var i=0;i<ary.length;i++){
var cur=ary[i];//=>当前项 //把当前项后面的那些值以一个新数组返回,我们需要比较的就是后面的这些项对应的新数组
var curNextAry=ary.slice(i+1);
if(curNextAry.indexOf(cur)>-1){
ary.splice(i,1); i--; //=>后面项组成的数组中包含当前这一项(当前这一项是重复的),我们把当前这一项删除掉即可
} }
3、遍历数组每一项,把每一项作为新对象的属性名和属性值存储起来,例如当前项是1,对象存储的格式就为{1:1},在每一次向对象中存储之前,首先看一下原有对象中是否包含了这个属性(
typeOf obj[xxx]==='undefined',
说明当前对象中没有这个属性),如果已经存在这个属性,说明数组中的当前项是重复的,在原有数组中删除这一项,不再向对象中存储这个结果,如果不存在,把当前的属性名和属性值存储进去即可
var ary=[1,2,2,2,3,5,4,6,8,54,6,5,7,3];
var obj={};
// for(var i=0;i<ary.length;i++){
// var cur=ary[i];
// if(typeof obj[cur]!=='undefined'){
// 对象中存在该属性:证明是对象中的重复项
// ary.splice(i,1);
// i--;
// continue;
// }
// obj[cur]=cur;
// }
for(var i=0;i<ary.length;i++){
var cur=ary[i];
if(typeof obj[cur]!=='undefined'){
//ary.splice(i,1);
//=>使用splice会导致后面的索引向前进一位,如果后面有很多项,消耗性能很大
//思路:我们把最后一项拿过来替换当前要删除的这一项,然后在把最后一项删除
ary[i]=ary[ary.length-1];
ary.length--;
i--;
continue;
}
obj[cur]=cur;
}
Array.prototype.myUnique=function myUnique() {
var obj={};
for(var i=0;i<this.length;i++){
var item=this[i];
if(typeof obj[item]!=='undefined'){
this[i]=this[this.length-1];
this.length--;
i--;
continue;
}
obj[item]=item
}
obj=null;
return this;
}
数组排序
ary.sort(function (a,b) {
return a-b;
})
1、冒泡排序
// 冒泡排序
//原理:让数组中的当前项和后一项比较,如果当前项大于后一项,我们让两者交换位置(小->大)
/*
* bubble:冒泡排序
* @parameter
* ary:[array]需要实现排序的数组
* @return
* [array]排序后的数组(升序)
* by team on 2017/10/19 *
*/
function bubble(ary) {
//外层循坏控制的是比较的轮数
for(var i=0;i<ary.length-1;i++){
//里层循环控制每一轮比较的次数
for(var j=0;j<ary.length-1-i;j++){
// ary[j]:当前本次拿出来这一项
// ary[j+1]:当前项的后一项
if(ary[j]>ary[j+1]){
var temp=ary[j];
ary[j]=ary[j+1];
ary[j+1]=temp
}
}
}
return ary;
}
2、递归 函数自己调用自己
function fn(num) {
if(num===0){
return
}
fn(num-1);
}
fn(10);
//1-100之间,把所有被3并且能被5整除的获取到,求和
function fn(num) {
if(num>100){
return 0;
}
if(num%15===0){
return num+fn(num+1)
}
fn(num+1)
}
fn(1);
3、快速排序
//快速排序
//先找中间这一项14
//把剩余项中的每一个值和中间项进行比较,比他小的放左边(新数组),比他大的放右边(新数组)
//.....
function quick(ary) {
//如果传进来的数组只有一项或者空的,我们就不在继续进行拆分
if(ary.length<=1){
return ary;
}
//获取中间项索引:把中间项的值获取到,在原有数组中删除中间项
var centerIndex=Math.floor(ary.length/2);
var centerValue=ary.splice(centerIndex,1)[0];
//splice返回的数组
// =>用剩余数组中的每一项和中间项进行比较
var aryLeft=[],aryRight=[];
for(var i=0;i<ary.length;i++){
var cur=ary[i];
cur<centerValue?aryLeft.push(cur):aryRight.push(ary)
}
return quick(aryLeft).concat(centerValue,quick(aryRight))
}
4、插入排序
//先抓一张牌(一般都抓第一张)
var handAry=[];
//存储现有的手里的牌
handAry.push(ary[0]);
//依次循环抓取后面的牌
for(var i=1;i<ary.length;i++){
var item=ary[i];
//本次新抓的这张牌
//拿新抓的牌和手里现有的牌比较
for(var j=handAry.length-1;j>=0;j--){
// handAry[j]:当前比较的手里的这张牌
//新抓的牌比当比较的这张牌大,把新抓的牌放后面
if(item>handAry[j]) {
handAry.splice(j+1,0,item)
break;
}
if(j===0){
//新抓的牌是最小的,放到最开始位置
handAry.unshift(item)
}
}
}
return handAry;
}
判断操作语句
if、else if 、 else
if(条件1){
//条件1成立执行的操作
}else if(条件2){
//条件2成立,执行操作
} .... else{
//以上条件都不成立执行的操作
}
// 如果好几个条件都成立了,只把第一个成立的条件执行,后面成立的条件忽略不管
// 条件:a==b a!=b a>b a<b
if(A){}//先把A转为布尔型,判断真假一次来判断条件是否成立
if(a>b && a<100){}//只有两个小条件都是真,整体条件才为真
if(a>b || a==0){}//只要其中一个小条件成立,整体条件就是真 ...
三元运算符
条件?条件成立执行:条件不成立执行
if(条件){}else{}
三元运算符就是这种简单的if、else的另一种写法
var num=10;
if(num>5 &&num<=10){
num++; //=>num+=1 num=num+1 自身累加1
}else{
num--;
}
//=>改写成为三元运算符
num>5 && num<=10?num++:num--;
// 如果条件成立或者不成立的某一种情况并不需要做什么处理,我们空着语法不符合,我们使用null、undefined、void 0(就是undefined)占位即可
// num>5 && num<=10?num++:null;
// 某一种情况执行多条操作使用小括号包起来,中间用逗号分隔
// num>5 && num<=10?(num++,console.log(num)):null
// 在三元运算符中不能出现break、continue、return 这些关键词
switch case
switch case应用于if、else中一个变量在不同值情况下的不同操作
var num = 10;
switch(num){
//switch后面小括号中存放的是一个值(一般我们都写变量:把变量存储的值拿来用,有时候也可能是一个计算)
case 1: ... break;
case 10:
// =>case后面放入的都是值,目的是验证switch后面的值和哪一种后面的值相等,相等的进行对应的处理 ... break;
// => 每一种case结束后都要加break,结束当前判断
default:
// =>switch后面的值和每一种case情况对应的值都不相等,执行最后的default,类似于else ....
// 在switch case中的比较使用的是 '==='
}
循环操作语句
循环:重复做一件事情
for循环
for(设置循环起始值;设置循环执行条件;步长累加) {
// =>循环体:重复做的事情都在循环体中
continue;
// =>结束本轮循坏,继续执行下一轮,后面的代码都不会执行,他会直接去执行步长,然后进入下一轮 .....
break;
// => 结束整个循环,一旦遇到break,后面代码不执行,步长累加也不执行,循环结束了 .....
}
思考
for(var i= 1;i<10;i+=2){
if(i<5){
i++; continue;
}else{
i+=3;
break;
}
console.log(i)
}
console.log(i) // =>10->为什么只输出一次
for in循环
//=>for in:用来遍历(循环)对象键值对的
var obj={name:'小强',age:8};
//对象中有多少组键值对,我们for in 循环就遍历多少次,不一定,因为可以遍历到手动设置的公有属性
//每一次循环key变量存储都是当前循环的这组键值对的属性名
//key存储的值都是字符串格式的,不管属性名是否为数字
//在for in循环中大部分浏览器会把对象中的键值对的属性名进行排序
// (把数字属性名排在前面,并且排列的时候按照数字有效到大排列,
// 其次在把非数字的属性名按照之前编写的顺序排列循环的时候按照
// 重新排列的顺序依次遍历,小数算作字母不算数字)
for(var key in obj){
//console.log(obj.key)获取的是obj中key这个属性对应的属性值,->undefined <=> obj['key'] console.log(obj[key]);
}
JS中数据类型转换
把其他数据类型转为Number类型
isNaN、Number、parseInt、parseFloat
在进行加减乘除数学运算的时候
true->1
false->0
' '->0
'12'->12
'12px'->NaN
'xiaoqiang'->NaN
null->0
undefined ->NaN
{} ,/^$/, function(){} ->NaN
[]->''->0
[12]->'12'->12
[12,23]->'12,23'->NaN
//=>引用类型转换为数字,先通过toString方法把数组转为字符串,然后在调用Number把字符串转换成数字
JS中的数学运算
+、-、*、/加减乘除
除了加法有特殊性,其余运算符都是数学运算,也就是遇到非数字类型,需要把其转换为number在进行运算
加法的特殊性:
在遇到字符串的时候,加法不是数学运算,而是字符串拼接,只要不遇到字符串就是数学运算
1-'1'->0
10*null->0
10/undefined->NaN
10*[10]->100
1+'1'->'11'
null+'1'->'null1'
//=>字符串拼接:是把其他值转为字符串然后在进行拼接(toString)
//其他数据类型的toString是直接把值用单双引号包起来即可,只有对象有特殊性,对象.toString()==='[object object]'
将其他类型转为布尔类型
>Boolean、!、!!
>在条件判断的时候,也是转为布尔类型,然后验证条件真假
>只有`0、NaN、空字符串、null、undefined`五个转换为false,其余都是转换为true
>`在使用==进行比较的时候`如果左右两边数据类型不相同,浏览器会默认转为相同类型,然后在比较(`===`不会这样操作),两个等号比较,左右两边数据类型不一样,浏览器会把两边的类型都转为数字在比较,但是null、和undefined除外 ,null==0->false null以及undefined和其他任何值都不相等
> `null==undefined ->true` ` null===undefined->false
````javascript
[]==[] ->false
//对象和对象比较的时候,比较的是空间地址,不是同一个空间
//对象和数组比较:把对象转换为数字 []==0->true ({})==NaN->false
// NaN和自己不相等和其他任何值都不相等
//对象和字符串:把两边都转为数字 []==' ' ->true
//对象和布尔比较:把两边都转为数字
[]==true ->false
[]==false ->true
![]==false ->true
Math中的常用方法
数学函数:但是它是对象数据类型的
typeof Math ->Object
Math中给我提供了很多常用的操作数字的方法 console.dir(Math)查看方法
abs
Math.abs:取绝对值
ceil && floor
Math.ceil :向上取整
Math.floor:向下取整
round
Math.round:四舍五入
random
Math.random:生成0-1之间的随机数,包含0不包含1>获取0-10之间的随机整数 Math.round(Math.random()10)
获取3-15之间的整数 Math.round(Math.random()12+3)
获取[n,m]之间的随机整数 Math.round(Math.random()*(m-n)+n)
max && min
Math.max:获取一组值中的最大值
Math.min:获取一组值中的最小值
PI
Math.PI:获取圆周率(π)
pow && sqrt
Math.pow:获取一个值得多少次幂
Math.sqrt:开平方
字符串中的常用方法
在JS中用单双引号包裹起来的都是字符串
var str='welcome to xiaoqiang,good good study,day day up!'
//字符串就是由0到多个字符组成的
//1、第一个字符索引是0,第二个字符索引是1 ...
//以数字作为索引,从零开始 str[0]
//2、有一个叫做length的属性,存储的是当前字符串的个数(字符串长度)
//最后一个字符 str[str.lengtg-1]
//如果指定索引不存在,获取结果是undefined str[100]=undefined
真实项目中,我们经常操作字符串,此时我们需要掌握常用的一些字符串的操作方法
console.dir(String.prototype)
charAt && charCodeAt
str.charAt:返回指定索引位置的字符,和str[索引]的区别在于,当指定的索引不存在的时候,中括号获取的是undefined,而charAt获取的空字符串
str.charCodeAt:在charAt的基础上把获取到的字符变为Unicode编码值(对应ASCII码表)
48-57: 0-9 65-90:A-Z 97-122:a-z (十进制)
str.fromCharCode(十进制的Unicode值),把值按照ASDII码表中的信息,转为原有的字符
substr && substring && slice
实现字符串截取的三个方法
str.substr(n,m):从索引n开始,截取m个字符
str.substring(n,m):从索引n开始,截取到索引m处(不包含m),把找到的部分截取
str.slice(n,m):和substring语法一样,区别在于slice支持以负数做索引
当索引是负数的时候,浏览器在处理的时候,使用字符串总长度加上负数索引,然后按照得到的整数处理操作
细节:如果值传递n(substr(n)/substring(n)),相当于从索引n开始截取到字符串末尾
如果传递的索引超出最大限制,也是截取到末尾,如果一个不传递,相当于把整个字符串截取(字符串克隆)
toUpperCase && toLowerCase
str.toUpperCase:把字母全部大写
str.toLowerCase:把字母全部小写
indexOf && lastIndexOf
str.indexOf:获取当前字符在字符串中第一次出现为止的索引
str.lastIndexOf:获取当前字符在字符串中最后一次出现为止的索引
如果当前字符在字符串中没有出现过,结果是-1;我们可以根据这个规律可以验证一下当前字符串中是否包含某种字符
split
str.split:按照某一个字符把字符串拆分成数组中的某一项,和数组中的join方法对应,支持正则
replace
str.replace:实现字符的替换,执行一次只能替换一次,需要配合正则使用
trim && trimLeft && trimRight
str.trimLeft:去除字符串开始的空格
str.trimRgiht:去除字符串结尾空格
str.trim:去除字符串首尾空格
DOM基础
DOM:document object model 文档对象模型,提供一些属性和方法可以让我们去操作DOM
获取DOM元素的方法
- document.getElementById 一个元素
- [context].getElementsByTagName 元素集合
- [context].getElementsByClassName 元素集合
- document.getElementByName 节点集合
- document.documentElement 获取整个HTML对象
- document.body 获取BODY对象
- [context].querySelector 一个元素对象
- [context].querySelectorAll 获取元素集合
- .....
getElementById
此方法的上下文只能是document,一个HTML页面中元素的ID理论上是不能重复的,如果页面中的ID重复了我们获取的结果是第一个ID对应的元素对象
在IE及更低版本的浏览器中,会把表单元素的name值当做ID来识别使用(项目中尽量不要让表单的name和其他的元素ID相同)
如果我们把JS放在结构的下面,我们可以直接使用ID的值来获取这个元素(不需要通过getElementById获取),而且这种方式会把所有相同ID的元素获取到,可能是元素集合或者单个元素对象 =>不规范、不推荐
getElementsByTagName
上下文是可以自己指定的
获取到的结果是一个元素集合(类数组集合)
1、获取到的结果是集合,哪怕只有一项,我们想要操作这一项,需要从集合中获取出来,然后在进行操作
2、在指定上下文中获取所有子子孙孙元素中标签名叫做这个的(后代查询)
getElementsByClassName
上下文也可以自己指定
获取的结果也是一个元素结合(类数组集合)
- 1、真实项目中我们经常会用过样式类名来获取元素,但是在IE6-8中不兼容
getElementsByName
1、通过元素的NAME属性值来获取元素(类数组:节点集合NodeList)
它的上下文也只能是document
IE浏览器只能识别表单元素的name属性值,所以我们这个方法一般都是来操作表单元素的
document.documentElement / document.body
获取HTML或者BODY(一个元素对象)
经常用来获取浏览器窗口可是区域的宽高
document.documentElement.clientWidth || document.body.clientHeight
querySelector / querySelectorAll
在IE6-8中不兼容,而且也没什么特别好的办法处理兼容,所以这个用于移动端开发- quertSelector:获取一个元素对象
- querySlectorAll:获取一个元素集合,只要是CSS支持的选择器,这里大部分都支持
表格独有的获取属性:- var tHead=oTab.tHead; //表格自带获取头
- var oThs=tHead.rows[0].cells;// row获取所有行 cells获取所有列
- var tBody=oTab.tBodies[0] // 获取tbody
DOM的节点
node:节点,浏览器认为在一个HTML页面中的所有内容都是节点(包括标签、注释、文字文本等都是节点)> - 元素节点:html标签
- 文本节点:文字内容(高版本浏览器会把空格和换行也当做文本节点)
- 注释节点:注释内容
- document:文档节点
- ....
元素节点
nodeType:1
nodeName:大写标签名(在部分浏览器的怪异模式下,有时候获取的是小写)
nodeValue(节点内容):null
[curEle].tagName:获取当前元素的标签名(获取的标签名一般都是大写)
文本节点
nodeType:3
nodeName:#textnodeValue:文本内容
注释节点
nodeType:8
nodeName:#comment
nodeValue:注释内容
文档节点
nodeType:9
nodeName:#document
nodeValue:null
节点是用来描述页面中每一部分之间关系的,只要我可以获取页面中的一个节点,那么我就可以通过相关的属性和方法获取页面中所有的节点
childNodes
获取当前元素所有的子节点(节点集合:类数组)
注:不仅仅是元素子节点,文本、注释等都会包含在内:子节点说明只是在儿子辈分中查找
children
获取当前元素的所有元素子节点(元素集合)
在IE6-8获取结果和标准浏览器有区别(IE6-8中会把注释节点当做元素节点获取到)
parentNode
获取当前元素的父节点,元素对象:父节点只能有一个
previousSibling nextSibling
previousSibling:获取当前节点的上一个哥哥节点(不一定是元素节点,可能是文本或者注释)
nextSibling:获取当前节点的下一个弟弟节点(不一定是元素节点,可能是文本或者注释)
previousElementSibling nextElementsSibling
=>IE6-8下不兼容
previousElementSibling:获取当前节点的上一个哥哥元素节点
nextElementsSibling::获取当前节点的上一个弟弟元素节点
firstChild lastChild
firstChild:当前元素所有子节点的第一个(也不一定是元素节点,可能是注释和文本)
lastChild:当前元素所有子节点的最后一个(也不一定是元素节点,可能是注释和文本)
firstElementChild lastElementChild
=>IE6-8下不兼容
firstElementChild:当前元素所有子节点的第一个元素节点
lastElementChild:当前元素所有子节点的最后一个元素节点
DOM的增删改
真实项目中,我们偶尔会在JS中动态创建一些HTML标签,然后把其增加到页面中
document.creatElement
在JS中动态创建一个HTML标签
appendChild
容器.appendChild(新元素)
把当前创建的新元素添加到容器的末尾位置
insertBefore
容器.insertBefore(新元素,老元素)
在当前容器中,把新创建的元素增加到老元素之前
removeChild
容器:removeChild(元素)
在当前容器中把某一个元素移除掉
replaceChild
容器.replaceChild(新元素,老元素)
在当前容器中,拿新元素替换老元素
cloneNode
元素.cloneNode(false/true)
把原有的元素克隆一份一模一样的,false:只克隆当前元素本身 true:深度克隆,把当前元素本身以及元素的所有后代都进行克隆,只能克隆结构,不能克隆事件
[get/set/remove]Attribute
给当前元素设置/获取/移除自定义
属性的
使用XXX.index 和XXX.setAttribute('index',0)这两种的区别
xxx.index:是把当前元素当做一个普通对象,为其设置一个属性名(和页面的HTML无关)
xxx.setAttribute:把元素当做特殊的元素对象来处理的,设置的自定义属性和页面结构中的DOM元素映射在一起的
JS中获取的元素对象,我们可以把他理解为两种角色:
- 与页面HTML结构无关的普通对象
- 与页面HTML结构存在映射关系的对象
元素独享的内置属性,大部分都和页面的标签存在映射关系:- xxx.style.backgroundColor='xxx' 此时不仅把JS中对象对应的属性值改变了,而且也会映射到页面的HTML标签上(标签中有一个style的行内样式,元素的样式改变了)
- xxx.className='xxx' 此时不仅是把JS对象中的属性值改了,而且页面中的标签增加了class样式类(可以看见的)
- 元素对象中的自定义属性:xxx.index=0
- 仅仅是把JS对象中增加了一个属性名(自定义的),和页面中的HTML没啥关系(在结构上看不见)
- xxx.setAttribute:通过这种方式设置的自定义属性和之前提到的内置属性差不多,都是和HTML结构存在映射关系的(设置的自定义属性可以呈现在结构上)
Date日期操作
Date是日期类,通过它可以对时间进行处理
var time=new Date();
//获取当前客户端本机时间
// (当前获取的时间不能作为重要的参考依据)
//=>获取结果是日期格式的对象 Sun Oct 22 2017 15:58:40 GMT+0800(中国标准时间)
//=>time.getFullYear()获取四位数年
//=>time.getMonth() 获取月(0-11)代表1-12月
//=>time.getDate() 获取日
//=>time.getDay() 获取星期(0-6)代表周日到周六
//=>time.getHours() 获取小时
//=>time.getMinutes() 获取分钟
//=>time.getSeconds() 获取秒
//=>time.getMilliseconds() 获取毫秒
//=>time.getTime() 获取当前日期距离1970年1月1日毫秒差
var time = new Date('2017-10-22')
//当new Date中传递一个时间格式的字符串,相当于把这个字符串转为标准的时间对象格式(转换完成后,就可以调取上面我们讲的那些方法了)
//=>时间格式的字符串 '2017-10-22'(IE下识别不了) '2017/10/22' 1354242423(毫秒差也可以识别转换,只能是数字,不能是字符串)
预解释(变量提升)
在当前的作用域中,JS代码执行之前,浏览器首先会把所有带var和function的进行提前的声明或者定义
- 理解声明和定义
- var num = 12;
- 声明(declare):var num;告诉浏览器在全局作用域中有一个num的变量了,一个变量声明了但是没有赋值,默认值是undefined
- 定义(defined): num=12;给我们声明的变量赋值
- 对于带var和function关键字的在预解释的时候操作是不一样的
- var :在预解释的时候只是提前的声明
- function : 在预解释的时候提前的声明+定义都完成了
- 预解释只发生在当前的作用域下,例如:开始只对window下的进行预解释,函数执行的时候才会对函数中的进行预解释
- 预解释的时候不管你条件是否成立,都要把带var的进行提前声明
- 自执行函数定义的那个function在全局作用域下不进行预解释,当代码执行到那个位置的时候定义和执行一起完成了
- 函数return下面的代码虽然不执行了,但是需要进行预解释,return后面跟着的都是我们返回的值,所以不进行预解释。如果在预解释的时候,如果名字已经声明过了,不需要重新的声明,但是需要重新的赋值
function fn(){
console.log(num)
return function () {
}
var num=12
}
- 在JS中如果变量的名字和函数的名字重复了,也算冲突
JS中内存分类
栈内存:用来提供JS代码执行的环境 ->作用域(全局作用域、私有作用域)
堆内存:用来存储引用数据类型的值,函数存储的是代码字符串,对象存储的是属性名和属性值得键值对
如何区分私有变量和全局变量
在全局作用域下声明(预解释的时候)的变量是全局变量
在私有作用域中声明的变量
和函数的形参
都是私有变量
作用域链:
在私有作用域中,我们代码执行的时候遇到了一个变量,首先我们需要确定它是否为私有变量,如果是私有变量,那么和外面的没有任何关系:如果不是私有的,则往当前作用域的上级作用域进行查找,如果上级作用域也没有则继续向上查找,一直找到window为止,如果window也没有,就会报错,JS在没有特殊处理的情况下,上面代码报错,下面的代码就不执行了- 如何查找当前作用域的上一级作用域?看当前函数在那个作用域下定义的,和它在哪里执行的没有任何关系
var num =12
function fn(){
var num =120
return function () {
console.log(num)
}
var f=fn()
f()// 120
~(function(){
var num=1200 f() //120
})
}
当函数执行的时候(直接目的:让函数中的代码执行),首先会形成一个新的私有作用域,然后按照如下步骤执行
- 如果有形参,先给形参赋值
- 进行私有作用域预解释
- 私有作用域代码从上到下执行
- .....
闭包:
函数形成了一个新的私有作用域保护了里边的私有变量不受外边的干扰(外面修改不了私有的,私有的也修改不了外面的)
in:判断一个属性是否是对象中的一个属性,返回布尔值 num in window
JS内存释放
堆内存释放:对象数据类型或者函数数据类型,在定义的时候首相都会开辟一个堆引用地址,如果外面有变量等知道了这个地址,那我们就说这个内存被占用了,就不能销毁了,我们想让堆内存释放/销毁,只需要把所有引用它的变量值赋值为null,如果当前的堆内存没有任何东西占用,那么浏览器会在它空闲的时候把它销毁(垃圾回收)
栈内存释放(全局作用域/私有作用域(只有函数执行才会产生)):全局作用域只有当页面关闭的时候全局作用域才会销毁。私有作用域:一般情况下,函数执行会形成一个新的私有作用域,当私有作用域中代码执行完成后,我们当前作用域都会主动的释放和销毁,但是还是存在特殊情况的:当前私有作用域中的部分内容被作用外的东西占用了,那么当前的作用域就不能销毁了
1、函数执行返回了一个引用数据类型值,并且在函数的外面被一个其他的东西接收了,这种情况下一般形成的私有作用域不会被销毁
function fn(){
var num=100
return function(){
num++
}
var f=fn()//fn执行的这个私有作用域不能再销毁了
下述情况不立即销毁
->fn返回的函数没有被其他的东西占用,但是还需要在执行一次,所以暂时不销毁,当返回的值执行完成后,浏览器会在空闲的时候把它销毁了
//++i和i++:都是自身加1,在和其他的值进行运算的时候是有区别的
//i++:先拿i的值进行运算,运算完成本身在+1 先运算在执行 1+i++ ->6 i->6
//++i:先本身累加1,然后拿累加完成的结果去运算 先执行在运算 i+(++1) ->7 i->6
function fn(){
var num=100
return function() {
} }
fn()();//首先执行fn,返回一个小函数对应的内存地址,然后紧接着让返回的小函数在执行
this
我们在JS中主要研究的都是函数中的this
JS中的this代表的是当前行为(方法)执行的主体;JS中的context(上下文)代表的是当前行为执行的环境(区域(window、对象里、方法里都是环境)):
this是谁和函数在哪里定义的和在哪里执行的都没有关系,只和执行主体有关系
如何区分this:
1、函数执行,首先看函数名前面是否有点,有的话前面是谁this就是谁,没有的话this就是window
自执行函数中的this永远都是window
给元素的某一个事件绑定方法,当事件触发的时候,执行对应的方法,方法中的this就是当前的元素
var count = 0;//在项目中为了防止全局变量之间的冲突,我们一般是禁止或减少使用全局变量 obtn.onclick=function(){
count++
sanNum.innerHTML=count
}
//解决:
//1、自己形成一个不销毁的私有作用域来保护我们需要的累加的数字(弊端:有一个不销毁的私有作用域,所以占了那么一丢丢内存)
~function () {
var count = 0
obtn.onclick=function(){
count++
spanNum.innerHTML=count
} }()
oBtn.onclick=(function(){
var count = 0;
return function(){
count++;
spanNum.innerHTNL = count
}
//2、利用innerHTML的方式处理:每次点击的时候先去页面中获取最新的值然后累加在把累加的值重新放回去(弊端:每一次都需要把页面中的内容转为字符串然后在累加在重新添加回去,当重新添加的时候,浏览器都要重新渲染一次)
oBtn.onclick=function(){ spanNum.innerHTML ++; } })()
//3、利用自定义属性存储(推荐)
oBtn.count=0 oBtn.onclick=function(){ spanNum.innerHTML=++this.count }
单例模式
把描述同一个事物(同一个对象)的属性和方法放在一个内存空间下,起到了分组作用,这样不同事物之间的属性即使属性名相同,相互也不会发生冲突,我们把这种分组编写代码的模式叫做
单例模式
,在单例模式中我们把person1和person2叫做命名空间
var person1 = { name:'xiaoqiang', age: 18 }
var person2 = { name:'xiaopao', age: 20 }
工厂模式
单例模式虽然解决了分组的作用,但是不能批量生产,属于手工作业模式->
工厂模式
把实现同一件事情的代码放到一个函数中,以后想实现这个功能,不需要重新的编写这些代码了,只需要执行当前这个函数即可 -->*函数的封装(低耦合(相同)高内聚(重复利用率)),减少页面中的冗余代码,提高代码的重复利用率
function createPerson(name,age){
var obj={}
obj.name=name;
obj.age=age;
obj.writeJs=function(){
console.log('my name is ' + this.name+'haha')
}
return obj
}
var p1=createPerson('小强',18)
JS是一门轻量级的脚本"编程语言(html+css)不属于编程语言,属于标记语言",所有的编程语言都是面向对象开发的->需要掌握类的继承、封装、多态
- 继承:子类继承父类中的属性和方法
- 多态:当前方法的多种形态,在后台语言中多态包含重载和重写
1、重载:两个方法名相同,但是参数不同。JS中不存在重载,方法名一样的话,后面的会把前面的覆掉,最后只保留一个
2、重写:子类重写父类的方法
//后台重载:
public void sum(int num1, int num2){}
public void sum(String num1, int2){}
function sum(num1,num2){}
//JS中有一个操作类似重载,但是不是重载:我们可以根据传递参数不同,实现不同的功能
function sum(num){
if(typeof num==='undefined'){
return 0 }
return num;
}
构造函数
构造函数的目的就是为了创建一个自定义类,并且创建这个类的实例
- 构造函数模式和工厂模式的区别:
1、执行的时候:普通函数执行—>createJsPerson(),构造函数执行—>new createJsPerson(),通过new
执行,我们的createJsPerson就是一个类
了,一般第一个首字母单词大写,而函数执行的返回值(p1)
就是createJsPerson这个类的实例
,JS中所有的类都是函数数据类型
的,它通过new执行变成了一个类,但是它本身也就是一个普通的函数,JS中所有的实例都是对象数据类型
的。
2、在函数代码的时候:相同点:都是形成一个私有的作用域,定义形参赋值,然后预解释,代码从上到下执行(类和普通函数一样,也有普通函数的一面)。不同点:在代码执行之前,不用手动创建obj对象,浏览器会默认创建一个数据类型
值(这个对象其实就是我们当前类的实例),代码从上到下执行,以当前实例为执行的主体(this代表的就是当前的主体
),然后分别把属性名和属性值赋值给当前的实例,最后浏览器会默认把创建的实例返回> - p1和p2都是createJsPerson这个类的实例,所以都拥有write这个方法,但是不同实例之间的方法是不一样的,在这种给实例增加的属性(this.xxx=xxx)属于当前实例的私有属性,实例和实例之间是单独的个体,所以私有属性之间是不相等的
//工厂模式
function createPerson(name,age){
var obj={}
obj.name=name;
obj.age=age;
obj.writeJs=function(){
console.log('my name is ' + this.name+'haha') }
return obj
}
var p1=createPerson('小强',18)
p1.writeJs()
//构造函数模式
function CreatePerson(name,age){
//浏览器默认创建的对象就是我们的实例p1->this
var num=1
this.name=name;
this.age=age;
this.writeJs=function(){
console.log('my name is ' + this.name+'haha')
}
//浏览器在把创建的实例返回
}
var p1=new CreatePerson('小强',18)
var p2=new CreatePerson('小炮',18)
p1.writeJs();
//var ary=[];
//字面量方式
//var ary=new Array();
//实例创建的方式->构造函数模式执行的方式
//不管哪一种方式都是ary都是Array这个类的一个实例
在构造函数模式中,类(函数体中)出现的this.xxx中的this是当前类的实例,而某一个属性值(方法),方法中的this需要看方法执行的时候,前面是否有“.”,new Fn执行,如果不需要传递参数,后面的小括号可以省略
类有普通函数的一面,当函数执行的时候,var num 其实只是当前形成的私有作用域中的私有变量而已,他和我们的f1这个实例没有任何关系,只有this.xxx=xxx才相当于给f1这个实例增加私有的属性和方法,才和我们的f1有关系... ,在构造函数模式中,浏览器会默认的把我们的实例返回(对象数据类型的值);如果我们自己手动写了return返回:如果返回的是一个基本数据类型的值,当前实例是不变的,例如:return 100,我们当前的f1还是当前FN类的实例,如果我们返回的是一个引用数据类型的值,当前的实例会被自己返回的的值替换掉,例如:return {name:'haha'} 我们的f1就不再是FN的实例了,而是返回的对象
检测某一个实例是否属于这个类(检测某个对象是否属于这个方法返回值)->instanceof f1.instanceof Fn f1.instanceof Object 所有的实例都是对象数据类型,而每一个对象数据类型都是object这个类的一个实例,所以f1是它的一个实例
对于检测数据类型来说,typeof有自己的局限型,不能细分object下的对象、数组、正则....
检测某一个属性是否属于这个对象 attr in object 但是In不管是私有属性还是公有属性,只要存在,in都是true, hasOwnProperty:用来检测某一个属性是否为这个对象‘私有属性‘,这个方法只能检测私有属性 f1.hasOwnProperty('getX')
基于构造函数模式的原型模式解决了方法或者属性公有问题->把实例之间相同的属性和方法提取成公有的属性和方法->想让谁公有就把他放到prototype上即可
1、每一个函数数据类型(普通函数、类)都有一个天生自带的属性:prototype(原型),并且这个属性是一个对象数据类型的值,并且在prototype上浏览器天生给它加了一个属性contructor(构造函数),属性值是当前函数(类)本身,每一个对象数据类型(普通对象、实例、prototype、数组)也天生自带一个属性:__proto__,属性值是当前实例所属类(必须是new出来的)的原型(prototype),Object是JS所有对象类型的基类(最顶层的类),所以他没有__proto__
function Fn(){
this.x=100
}
Fn.prototype.getX=function(){
console.log(this.x)
}
var f1=new Fn;
var f2=new Fn ;
console.log(Fn.prototype.constructor===Fn) //true
console.log(f1.__proto__=Fn.prototype) //true
console.log(Fn.prototype.__proto__===Object.prototype) //true
//1、f1 instanceof Object ->true 因为__proto__可以向上查找,不管多少级,总能找到Object
//原型链模式: f1.hasOwnProperty;->hansProperty是f1的一个属性,但是我们发现在f1的私有属性上并没有这个方法,通过对象名。属性名的方式获取属性值的时候,首先在对象的私有属性里边查找,如果私有属性没有,则通过__proto__找到所属类的原型(类的原型上定义的属性和方法都是当前实例公有的属性和方法),原型上存在的话,获取的是公有的属性值,如果原型上也没有,则继续通过原型上的__proto__继续向上查找,一直找到Object.__proto__为止...
// 在IE浏览器中,我们原型模式也是同样的原理,但是IE浏览器怕我们通过__proto__把公有的修改,禁止我们使用__proto__
f1.sum=function(){
//修改我们自己私有的sum
}
f1.__proto__.sum=function(){
//修改所属类原型上的sum
}
Fn.prototype.sum=function(){
//修改公有的sum
}
批量设置公有属性上的公有属性和方法
// 1、起别名 function Fn(){
this.x=100
}
var pro=Fn.prototype;
//把原来原型指向的地址赋值给我们的pro,现在他们操作的是同一个内存空间
pro.getX=function(){}
pro.getY=function(){}
pro.getZ=function(){}
// 2、重构原型对象的方式->自己新开辟一个堆内存,存储我们公有的属性和方法,把浏览器原来给Fn.prototype开辟的那个替换掉
function Fn(){ this.x=100 }
Fn.prototype={ constructor:Fn, a:funvtion(){}, b:funvtion(){}, c:funvtion(){}, d:funvtion(){} }
var f=new Fn
f.a()
f.b()
//只有浏览器天生给Fn.prototype开辟的堆内存离面才有constrastor,而我们自己开辟的这个堆内存没有这个属性,这样constractor指向就不再是FN而是Object,为了和原来的保持一致,我们需要手动的增加constructor指向 console.log(f.constructor) //->Object
//用这种方式给内置类增加公有的属性
//给内置类增加数组去重的方法
Array.prototype.unique=function(){}
//下面这种方式会把之前已经存在于原型上的属性和方法替换掉,所以我们用这种方法修改内置类的话,浏览器是会给屏蔽掉的
Array.prototype={ constructor:Array, unique:function(){} }
//可以用下面的方法改变原型上的方法,可以一个个的修改内置的方法->我们以后再内置类的原型上增加方法,命名都需要加特殊的前缀
Array.prototype.sort=function(){ console.log(1) }
继承
原型继承::(#div1.proto->HTMLDivElement.prototype->HTMLElement->Element.prototype->Node.prototype->EventTarget.prototype->Object.prototype),原型继承是我们JS中最常用的一种方式,子类B想要继承父类A中的所有的属性和方法(私有+公有),只需要B.prototype=new A;即可。特点:他是把父类私有和公有的都集成到了子类的原型上(子类公有的)
核心:原型继承并不是把父类中的属性和方法克隆一份一模一样的给B,而是让B和A之间增加了原型链的链接,以后B的实例N想要A中的getX方法,需要一级级的向上查找来使用(缺点 :不能传参)
function A(){
this.x=1000
}
A.prototype.getX=function(){
console.log(this.x)
}
function B(){
this.y=200;
}
B.prototype=new A;
B.prototype.constructor = B
var n =new B;
call继承:把父类私有的属性和方法,克隆一份一模一样的作为子类私有的(缺点:不能拿到父类公有)
function A(){
this.x=1000
}
A.prototype.getX=function(){
console.log(this.x)
}
function B(){
A.call(this)
// A.call(n)=>把A执行,让A中的this变成n
}
var n =new B;
console.log(n.x)
冒充对象继承:把父类私有的和公有的克隆一份一模一样的给子类私有的
function A(){
this.x=1000
}
A.prototype.getX=function(){
console.log(this.x)
}
function B(){
var temp =new A;
for(var key in temp){
this[key]=temp[key]
}
temp = null;
}
var n =new B;
console.log(n.x)
混合模式继承:原型继承+call继承
function A(){
this.x=1000
}
A.prototype.getX=function(){
console.log(this.x)
}
function B(){
A.call(this) // n.x=100
}
B.prototype=new A;
x=100,
x.getX
B.prototype.constructor = B;
var n =new B;
console.log(n.x)
寄生组合式继承
function A(){
this.x=1000
}
A.prototype.getX=function(){
console.log(this.x)
}
function B(){
A.call(this) // n.x=100
}
B.prototype=Object.create(A.prototype);//把父类公有给子类
B.prototype.constructor = B;
var n =new B;
console.log(n.x)
call、bind、apply
//Array.prototype.slice=function(){}
var ary=[12,23,34]
//ary.slice ->ary这个实例通过原型链的查找机制找到Array.prototype上的slice方法
//ary.slice() ->让找到的slice方法执行,在执行slice方法的过程中,才把ary数组进行了截取
call:让原型上的call方法执行,在执行call方法的时候,我们让fn方法中的tihs变为第一个参数值obj,然后在把FN这个函数执行
//Function.prototype.call=function(obj){}
var obj={name:'xiaoqiang'}
function(){ console.log(this) }
fn();//this ->window
obj.fn()//obj.fn is not a function
fn.call(obj)
//自己模拟一个内置的call方法,写一个myCall方法
Function.prototype.myCall=function(context){
// this 是点前面的那个 fn 或者 this,this就是当前要操作和改变其this关键字的那个函数名
//1、让fn中的this关键字变为context的值->obj // 让this这个函数中的this关键字变为context eval(this.toString().replace('this','context'))
// 2、让fn执行 this() fn1.call.call(fn2)
//->fn1.call首先fn1通过原型链找到Function.prototype上的call方法,然后再让call方法通过原型在找到funtion原型上的call(因为call本身的值也是一个函数,所以同样可以找到funtion.prototype),在第二次在找到call的时候让方法执行,方法中的this是fn1.call,首相让这个方法中的方法变为fn2,然后再让fn1.call执行,
//=>2
}
fn.call(100,200)
//this->100
num1=200
num2=undefined
fn.call(obj,100,200)//this->obj
num1=100
num2=200
fn.call()//this->window 在严格模式下this->undefined fn.call(null)
//this.window
//在严格模式下this->null fn.call(undefined)
//this->window 在严格模式下this->undefined
apply:和call方法作用是一模一样的,都是用来改变方法的this关键字并且把方法执行,而且在严格模式下和和非严格模式下对于第一个参数是Null/undefined这种情况的规律是一样的,区别在于:call传递参数是一个一个的传递的,而apply不是一个个传,而是把要给fn传递的参数值统一放在一个数组中进行操作 call(obj,1,2,3) apply(obj,[1,2,3]);但是其实也相当于一个个给fn的形参赋值
bind:这个方法在IE6-8下不兼容->和call、apply类似都是改变this关键字的,但是区别在于bind只是改变了fn中的this,给函数传递了参数值,但是此时并没有把fn这个函数执行 var temp=fn.bind(obj,1,2) temp() bind会有一个返回值,这个返回值就是我们把fn的this改变后的那个结果。
预处理:事先把fn的this改变为我们想要的结果,并且把对应的参数值也传递了,以后要用到了,直接执行即可
//求数组的最大值和最小值
var ary=[12,32,32,432,321,534,23,13,445,7568,78,56,43,45,3];
var newAry=ary.mySlice();
function avgFn() {
var ary=Array.prototype.slice.call(arguments);
ary=[].slice.call(arguments);
}
// slice 原理
Array.prototype.mySlice=function () {
var ary=[];
//slice 原理就是循环类数组然后组成新的数组返回
//借用slice目的就是为了把当前的arguments转为this,然后执行循环
for(var i=0;i<arguments.length;i++){
ary[ary.length]=arguments[i]
}
ary.sort(function (a,b) {
return a-b
});
ary.shift();
ary.pop();
return (eval(ary.join('+'))/ary.length).toFixed(2)
};
浏览器异常信息捕获
try catch 在JS中本行报错,下面代码都不执行
try {
//如果下面这行代码报错,如果用try/catch捕获了异常信息,不影响下面的代码执行,如果try中的代码出错了,会默认去执行catch中的代码
// <js code>
console.log(num)
}catch(e){
//形参必须要写,我们一般起名为e
// 如果代码报错执行的代码
console.log(e.message)//会收集当前代码报错的原因
console.log('出错了')
//下面代码处理终止代码执行
throw new Error('当前网络繁忙,请稍后再试')
// new ReferenceError ->引用错误
// new TypeError ->类型错误
// new RangeError ->范围错误
}finally{
//一般不用:不管try中的代码是否报错,都要执行
}
//有时候既想捕获到错误的信息,又不想让下面的代码执行,只需要在catch中手动抛出一条错误信息,终止代码执行
回调函数
把一个方法当做一个参数值传到另一个方法中,然后在另一个方法中执行的过程中,我们随时根据需要让A方法执行
function fn(callback){ callback() }
fn(function(){}) //callback等于这里边的方法
JSON
JSON不是一个单独的数据类型的,它只是一种特殊的数据格式->它是对象数据类型的
var obj={name:'xiaoqiang',age:8}//->普通格式对象
var jsonObj={"name":"小强","age":7}//JSON格式的对象(相对于普通格式来说,只是把属性名用双引号包起来了,必须是双引号)
// 在window浏览器中,提供了一个叫做JSON的实例,它里边提供了两个方法
//JSON.parse:把JSON格式的字符串转换为JSON格式的对象
//JSON.stringify:把JSON格式的对象转换为JSON格式的字符串
//在IE6-7浏览器中,我们的window没有JSON对象
eval("("+str+")")//一定要手动加一个小括号
DOM回流和重绘
回流(重排 reflow):当页面中的html结构发生改变(增加、删除元素、位置发生改变...),浏览器都要重新计算一遍最新的DOM结构,重新的对当前页面进行渲染
重绘:摸一个元素的部分样式发生改变了(背景颜色),浏览器只需要重新渲染当前元素即可
- 1、利用动态创建元素节点和把它追加到页面中的方式是向数据绑定
for(var i=0;i<ary.length;i++){
var cur=ary[i];
var oli=document.createElement('li');
oLi.innerHTML='<span>'+(i+4)+'</span>'+cur.title
oUl.appendChild(oLi)
}
//优势:把需要动态绑定的内容一个个的追加到页面中,对原来的元素没有任何影响,之前元素的事件依然可用
//弊端:浏览器每当创建一个li我们就添加到页面中,引发一次DOM回流,最后引发回流次数过多影响性能
- 2、字符串拼接:首先循坏需要绑定的数据,然后把需要动态绑定的标签以字符串的方式拼接到一起,拼接完成后,最后统一添加到页面中,字符串拼接绑定数据是我们以后最常用的绑定数据的方式
var str='' for(var i=0;i<ary.length;i++){
var cur=ary[i];
str+='<li>';
str+='<span>'+(i+4)+'</span>'
str+='</li>'
}
oul.innerHTML+=str
//把之前的三个Li以字符串的方式获取到和新的str拼接,拼接完成整体还是字符串,最后再把字符串统一添加到页面中,浏览器还需要把字符串渲染成为对应的标签
//弊端:我们把新拼接的字符串添加到Ul中,原有的三个Li的绑定事件都没用了
//优势:事先把内容拼接好,最后统一添加到页面中,只引发一次回流
- 3、文档碎片
var frg=document.createDocumentFragment();//创建一个文档碎片,相当于临时创建了一个容器
for(var i=0;i<ary.length;i++){
var cur=ary[i];
var oli=document.createElement('li');
oli.innerHTML='<span>'+(i+4)+"</span>"+cur.title;
frg.appendChild(oli); }
oUl.appendChild(frg)
frg=null
DOM的映射机制:页面中的标签和JS中获取到的元素对象(元素集合)是紧紧的绑定在一起的,页面中的HTML结构改变了,JS中不需要重新获取,集合里边的内容也会跟着自动改变
var oul=document.getElementById('UL1');
var olis=oul.getElementsByTagName('li');
console.log(olis.length)//=>5
var oli=document.createElement('li')
oul.appendChild(oli)
console.log(olis.length)//=>6 没有重新的获取元素,但是Olis这个集合中的长度和内容会自动发生改变
AJAX
//1、首先创建一个Ajax对象:
var xhr=new XMLHttpRequest
//2、打开我们需要请求数据的那个文件地址:
xhr.open('get','data.txt',true)
//3、监听请求的状态:
xhr.onreadystatechange=function(){
if(xhr.readyState===4 && /^2\d{2}$/.test(xhr.status)){
var val=xhr.responeseText;
console.log(val)
}}
//4、发送请求:
xhr.send(null)
正则
定义:他就是一个规则,用来处理字符串的一个规则(正则就是用来处理字符串的,处理包含判断一个字符串是否
匹配
我们制定的规则(test:reg.test(str)),把字符串中符合我们正则规则的内容捕获
到(exec:reg.exec(str)))
var reg=/\d/; //包含一个0-9之间的数字
console.log(reg.test('朱'))// false
console.log(reg.test('1')) //true
console.log(reg.test('xiaoqiang2015')); //true
console.log(reg.exec('朱')); // null
console.log(reg.exec('1') // ['1',index:0,input:'1']
创建:
1、字面量方式:var reg=/\d/;
2、实例创建方式:var reg =new RegExp("");
正则的两种创建方式是有区别的,在字面量方式中,我们两个斜杠之间包起来的所有内容都是元字符,有的具有特殊的意义,大部分都是代表本身含义的普通的元字符
var name='xiaoqiang'
var reg=/^\d+"+name+"\d+$/; //错误
//对于需要字符串拼接只能用实例
var reg=new RegExp("^\\d+"+name+"\\d+$","g")
学习:console.dir(RegExp.prototype)
- 元字符:每一个正则表达式都是由元字符(在两个斜杠之间具有意义的一些字符)和修饰符组成的,具有特殊意义的元字符。
-1、\:转义字符,转译后面字符所代表的含义- 2、^:以某一个元字符开始
- 3、$:以某一个元字符结尾
- 4、.:除了\n(匹配一个换行符)以外的任意字符
- 5、():分组,1.改变x|y的默认的优先级/^18|19/,2.分组引用
- 6、代表出现次数的两次元字符,*(出现零到多次),+(出现一到多次),?(出现零次或者一次),{n}:出现n次,{n,}:出现n到多次,{n,m}:出现N到M次,x|y:x或者y中一个,[xyx]:x或者y或者z中的一个,[xyz]:除了三个以为的任何一个字符,[a-z]:a-z之间的任何一个字符,[a-z]:除了a-z之间的任何一个字符,\d:一个0-9之间的数字,\D:除了0-9之间的数字以外的任何字符,\b:匹配一个边界符('w1 w2 w3'),\w:数字、字母、下划线中 的任意一个字符[0-9a-z-A-Z_],\s:匹配一个空白字符 空格、一个制表符、换页符..,在[]中出现的所有字符都是代表本身意义的字符(没有特殊含义[+-]) ,[]中不识别两位数[12]->1或者2中的一个,[12-68]->1、2-6中一个、8三个中的一个,[\w-]数字、字母、下划线、-中的一个
var reg=/^(\d+)xiaoqiang(\d+)$/;
var reg=/^0.2$/;//以0开头,以2结尾,中间可以是除了\n外的任意字符
var reg=/^\d$/;//以数字开头,以数字结尾,只能是一个0-9之间的数字
var reg2=/^\d+$/;
var reg3=/^1\d{10}$/;
console.log(reg.test('9')) // true
console.log(reg.test('012')) //false
var reg=/^(1[8-9]|[2-5]\d|6[0-6])$/ //介于18-65之间
- 正则的捕获
//捕获到的内容是一个数组
//每一次捕获都先进行默认的匹配,如果没有匹配成功的,默认的都是Null,只有匹配的内容我们才能捕获到
//正则的懒惰性->每一次执行exec只捕获第一个匹配的内容,在不做任何处理的情况下,在执行多次捕获,捕获的还是第一次匹配的内容
//解决办法,在正则的末尾加一个修饰符“g”,
//在正则中三个修饰符:'g'(global全局)、'i'(忽略大小写),'m(多行匹配)'
//reg中有一个lastIndex:是正则每一次捕获在字符串中开始查找的位置,默认值是0,每次捕获都是从0开始查找的
//正则的贪婪性:正则每一次捕获都是按照匹配最长的结果捕获的,例如2符合正则 2015也符合正则,我们默认捕获的是2015
//如何解决正则的贪婪性:->在量词元字符后面添加一个?即可
// ?在正则中有很多的作用,放在一个普通的元字符后面代表0-1次 /\d?/,放在一个量词的后面是取消捕获时候的贪婪性
//字符串中的math方法->把所有和正则匹配的字符都获取 str.match,虽然match简便一些,但是match存在一些自己处理不了的问题,在分组捕获的情况下,match只能捕获到大正则匹配到的,而对于小正则是捕获不到的
//数组中的第一项是当前大正则捕获到的内容
// index:捕获内容在字符串中开始的索引位置
// input:捕获的原始字符串
var reg=/\d+/g;
var reg=/\d+?/g;
var str='haha2015heiehei2016'
var res=reg.exec(str)
var reg=/^(\w)\1(\w)\2$/ //->\2代表和第二个分组出现一模一样的内容;\1和第一个分组出现一模一样的内容
console.log(res) //->['2015',index:7,input:'haha2015heiehei2016']
分组捕获
->正则在捕获的时候,不仅仅把大正则匹配的内容捕获到,而且还把小分组匹配的内容捕获到
(?:)在分组找那个?:的意思是只匹配,不捕获
var reg= /^(\d{2})(\d{4})(\d{4})(\d{2})(\d{2})(?:\d{2})(\d)(?:\d|X)$/;
var str='513425199307271511'
console.log(reg.exec(str));
// ary=['513425199307271511','51','3425','1991',....]
//arr[0]->大正则匹配的内容
//arr[1]->第一个分组捕获的内容
//arr[2]->第二个分组捕获的内容...
replace:把原有的字符串替换成新的字符串,在不使用正则的情况下,每当执行一次只能替换一个字符
var str='xiaoqiang2016xiaoqiang2016'
str.replace('xiaoqiang','xiaopao') // =>xiaopao2016xiaoqiang2016 str=str.replace(/xiaoqiang/g,'xiaoqiang');//=>xiaopao2016xiaopao2016
client系列(当前元素的私有属性)
clientWidth/clientHeight:内容的宽度/高度+左右/上下填充(和内容溢出没有任何关系)
clientLeft:左边框的宽度 clientTop:上边框的高度
offset系列
offsetWidth/offsetHeight:clentwidth/clentHeight+左右、上下边框(和内容是否溢出没有任何关系)
offsetParent:当前元素的父级参照物
offsetLeft/offsetTop:当前元素的外边框距离父级参照物的内边框的偏移量
scroll系列
scrollWidth/scrollHeight:和我们clientWidth/clientHeight一模一样(前提是:容器中的内容没有溢出)
- 如果内容有溢出,我们获取的结果是如下规则:
- scrollWidth:真实内容的宽度(包含溢出)+ 左填充
- scrollHeight:真实内容的高度(包含溢出)+ 上填充(padding)
获取到的结果都是约等于的值,因为:同一个浏览器,我们是否设置overflow:hidden对于最终结果是有影响的;在不同的浏览器中我们获取到的结果也是不相同的;
scrollLeft/scrollTop:滚动条卷曲的宽度和高度
JS盒子模型属性取值问题
我们通过上面13个属性值获取的结果永远不可能出现小数,都是整数;浏览器在获取结果的时候,还在原来真实结果的基础上进行四舍五入;
关于操作浏览器本身的盒子模型信息
- clientWidth/clientHeight是当前浏览器可视宽度和高度(一屏幕的宽度和高度)
-scrollWidth/scrollHeight是当前页面的真实宽度和高度(所有屏加起来的宽度和高度,一般都是约等于的值)- 我们不管那些属性,也不管是什么浏览器,也不管是获取还是设置,想要都兼容,需要些两套(document.documentElement[attr] || document.body[attr]);必须document.documentElemen在前;设置也需要写两套
document.documentElement.clientWidth || document.body.clentWidth
document.documentElement.clientWidth=0; document.body.clentWidth=0;
// wind:编写一个有关于操作浏览器盒子模型的方法
function wind(attr,value) {
//如果传入value表示设置,否则是获取 ->类似于重载
if(typeof value === 'undefined') {
return document.documentElement[attr] || document.body[attr]
} else {
document.documentElement[attr]=value || document.body[attr]=value
}
}
offset
1.offsetParent:父级参照物,在同一个平面中,最外层的元素是里边所有元素的父级参照物,和HTML的层级结构没有必然联系,一般来说,一个页面中所有的元素的父级参照物都是BODY;但是body的父级参照物是null;body是最顶级的;如果想改变父级参照物,需要脱离当前平面,需要设置position定位来改变(absolute,relative,fixed),任意一个值都可以把父级参照物进行修改;
2.offsetLeft/offsetTop:当前元素(外边框)距离父级偏移物(内边框)的距离
JS盒子模型中:client系列/offset系列/scrollWidth/scrollHeight都是只读属性->只能通过属性获取值,不能通过属性修改元素的样式
crollTop/scrollLeft:滚动条卷去的高度和宽度(这两个是唯一的可读可写(设置)的),设置的时候,最小值只能为0,如果为负的时候也是回到顶部的,再次获取依然还是0.最大值=真实的高度(scrollHeight)-当前容器一屏幕的高度(clientHeight)
事件
1.行为本身:浏览器天生就赋予其的行为 onclick、onmouseover(onmouseenter)、onmouseout(onmouseleave)、onmousemove、onmousedown、onmouseup、onmousewheel(鼠标滚动行为)、onscroll、onresize(window.onresize:浏览器窗口大小改变事件)、onload、onunload(浏览器关闭)、onfocus(文本框获取焦点行为)、onblur(文本框失去焦点行为)、onkeydown、onkeyup(键盘的按下和抬起行为).....
- 哪怕我们没有给上述的行为绑定方法,事件也是存在的,当我们点击这个盒子的时候,同样会触发他的onclick行为,只是什么事情都没有做而已
2.事件绑定:给元素的某一个行为绑定方法 oDiv.onclick = function(){} 或者 oDiv.addEventListener('click',function(e){})- onclick这个行为是定义到当前元素的私有属性上的,DOM零级事件绑定,把匿名函数定义的部分当做一个值赋值给oDiv的点击行为(函数表达式),不仅仅把绑定的方法执行了,而且浏览器还默认的给这个方法传递了一个参数值 (mouseEvent->e:鼠标事件对象,他是一个对象数据类型值,里边包含了很多属性名和属性值,这些都是用来记录当前鼠标的相关信息的)
- MouseEvent->UIEvent->Event->Object MouseEvent记录的是页面中唯一一个鼠标每次触发时候的相关信息,和到底是在那个元素上触发的没有关系,IE6-8里边的e是存储在window.enent 标准浏览器中使用e就行 e=e || window.event
var oDiv=document.getElementById('box');
oDiv.onclick=function(e){
e=e || window.event
e.target = e.target || e.srcElement
e.type: 存储的是当前鼠标触发的行为类型 'click'
e.target:事件源,当前鼠标触发的哪个元素,那么他存储的就是哪个元素,但是在IE6-8中不存在,值为undefined,使用e.srcElement来获取事件源
e.clientX/e.clientY:当前鼠标触发点距离屏幕左上角的X/Y轴的坐标值,
e.pageX/e.pageY:当前鼠标出发点距离BODY左上角(页面第一屏幕最左上端)的X/Y轴坐标,但是在IE6-8没有这个属性,我们通过clientY+滚动条卷曲的高度也可以,
e.pageX = e.pageX || (e.clientX+(document.documentElement.scrollLeft || document.body.scrollLeft))
e.pageY = e.pageY || (e.clientY+(document.documentElement.scrollTop || document.body.scrollTop))
e.preventDefault():阻止浏览器默认行为,在IE6-8中没有这个方法,e.returnValue = false ,
e.preventDefault()?e.preventDefault():e.returnValue = false; 也可以直接 return false;一样的效果 都是阻止默认行为,
e.stopPropagation():阻止时间冒泡,在IE6-8中不兼容,使用e.cancelBubble= true 来代替,
e.keyCode;当前键盘上每一个按键对应的值 space->32 back->8 enter->13 del->46 左上右下 37 38 39 40
onmouseenter和onmouseover都是鼠标滑上去的行为,但是onmouseenter浏览器默认阻止了他的冒泡传播(mark的onmouserenter行为触发,不会传播到box);而onmouseover是存在冒泡传播的,想要阻止的话只能自己写代码阻止;
}
- addEventListener这个行为是定义在当前元素所属eventTarget这个类的原型上的,DOM二级事件绑定,我们通过使用DOM2级事件绑定,其实是让BOX通过原型链一直找到EventTarget这个内置类原型上的addEventListener方法实现的,但是DOM零级事件绑定,只能给一个元素的某一个行为绑定一次方法,第二次绑定的会把前面的覆盖掉,但是DOM2级事件可以给某个元素的同一个行为绑定多个不同的方法,DOM零级事件中的行为类型,我们用DOM2一样可以绑定,而且DOM2中还提供了一些DOM零级没有的行为类型->DOMContentLoaded:当页面中的DOM结构(HTML结构加载完成)触发的行为
function fn1 (e){
console.log(this)
}
box.addEventListener('click',fn1.bind(window),false)
//移除必须保证三个参数:行为、方法、那个阶段发生,一个都不能差,在使用DOM2绑定事件的时候,我们一般都是给他绑定的是实名函数
// box.removeEventListener('click',fn1,false)
//DOM2级事件中的this时当前被绑定事件的本身
//事件池:浏览器天生自带的,用来存储当前元素行为绑定的方法的
//在IE6-8中,不支持addEventListener,需要使用attachEvent/detachEvent,它们只有两个参数,
//不能像addEventListener那样控制在那个阶段发生,默认只能在冒泡阶段发生,行为需要添加'on',而且执行顺序是混乱的,标准的是按照绑定顺序一次执行
//重复问题:IE6-8中,同一个行为可以绑定多个相同方法,但是标准浏览器会覆盖 box.addEventListener('click',fn1,false);box.addEventListener('click',fn1,false)->只执行一次
//this问题:IE6-8中当方法执行的时候,方法中的this不是当前元素box,而是window
事件的默认传播机制
- 捕获阶段:从外向内一次查找元素 目标阶段:当前时间源本身的操作 冒泡阶段:从内向外一次触发相关的行为
- 用DOM零级事件绑定给元素的某一个行为绑定的方法,都是在行为触发后的冒泡阶段把方法执行的,每一个浏览器传播到最顶层是不一样的,谷歌可以传播到document,但是IE只到HTML
- 用DOM二级事件绑定第一个参数是行为的类型 第二个参数是给当前的行为绑定的方法,第三个才是控制在那个阶段发生:true 在捕获阶段发生 false 在冒泡阶段发生
事件委托/事件代理
利用事件的冒泡传播机制(触发当前元素的某个行为,它父级所有元素的相关行为都会被触发),如果一个容器中有很多元素都要绑定点击事件,我们没有必要一个个的绑定了,只需要给最外层容器绑定一个点击事件即可,在这个方法执行的时候,通过事件源的区分来进行不同的操作
响应式布局
viewport:视口(width=device-width 设置视口的宽度等于设备的宽度,如果不设置的话,默认的视口的宽度是980px->通俗理解:我们这个操作其实就是告诉当前的浏览器按照多少宽度来渲染页面,换句话说就是展示当前这个页面的区域一共有多宽(浏览器的宽度))
高清屏:苹果手机是2倍高清屏幕的,也就是我们在手机上看到的那张100100图片,其实苹果手机是按照200200的尺寸给我们渲染的,这样的话,如果我们真是图片本身才100*100,最后呈现出来的就是被拉伸后变模糊的效果
- 苹果手机上需要的素材图片都需要比看到的尺寸大一倍才可以
- DPI适配思想:我们在做页面的时候,最好每一张素材图片都准备两套或者三套
- 媒体查询:@media ->all所有设备 screen 所有屏幕设备PC+移动端 print 打印机设备...
- 媒体条件:指定在什么样的条件下执行对应的样式 @media all and (max-width:319px)
响应式布局的解决方案:- 1)流式布局法:容器或者盒子的宽度一般都不写固定值,而是使用百分比(相对于视口区域的百分比),其余的样式:字体、高度、MARGIN、PADDING等都按照设计稿上的标注尺寸的一般来设置,对于有些屏幕尺寸下,我们设置的固定值看起来不是特别的好看,使用@media进行微调整
- 2)REM响应式布局:H5页面只在移动端访问(REM不兼容低版本浏览器)
-REM:当前页面元素的REM单位的样式值都是针对HTML元素的fontSize的值进行动态运算的 ,从UI设计师拿到设计稿,在样式中给HTML设定一个fontSize值,我们一般都设置一个方便后面计算的值,例如(100px),然后完全按照设计稿写样式,此时不用管任何的事情,设计稿给你的宽度、高度、字体大小等值是多少,我们就写多少,但是我们在写这些样式值得时候,需要把像素值除以100,计算出对应的rem值,我们设定的也就都是rem值,值得注意的是:真实项目中外层盒子的宽度我们一般还是不写固定值,沿用流式布局的思想,我们用百分比的方式布局,根据当前屏幕的宽度和设计稿的宽度来计算我们HTML的fontSize的值(根据当前屏幕宽度和设计稿宽度的比例,动态计算当前宽度下的fontSize值,如果font-size改变了,之前设定的所有REM单位的值自动回跟着放大和缩小 ~function(){var winW=document.documentElement.clientWidth ;var desW=640; var oMain=document.getElementById('main'); var ratio=winW/desW; if(winW>desW){oMain.style.width = desW+'px';return} document.documentElement.style.fontSize = ratio*100+'px;'})())
html{
font-size: 100px; // 1rem是100px
}
.box {
width:2rem;
height:2rem;
}
// 单击和双击(300MS)
// 单击和长按(750MS)
// 点击和滑动(X/Y轴偏移的距离是否在30PX以内)
//左右滑动和上下滑动(X轴和Y轴偏移距离谁大)
(function(){
let textW = 640;
let winW = document.documentElement.clientWidth;
let radio = winW/textW;
document.documentElement.style.fontSize = radio*100 + 'px'
})()
let box=document.getElementById('box');
function on(ele, type, func) {
ele.addEventListener(type,func, false)
}
on(box,'touchstart', function(ev){
// ev:TouchEvent =>type/target/preventDefault/stopPropagation/changedTouches/changedTouches
//->changedTouches和touches都是手指信息集合(touchList),touches获取到值必备条件只有手指还在屏幕上才会获取
//如果想获取离开的瞬间的坐标使用changedTouches获取
console.log(ev.touches)
console.log(ev)
let point=ev.touches[0]
this['strX']=point.clientX
this['strY']=point.clientY
this['isMove'] = false //是否移动
})
on(box,'touchmove', function(ev){
//判断是否是滑动
var point = ev.touches[0]
var newX = point.clientX;
var newY = point.clientY;
// if(newX!== this['strX'] || newY!==this['strY']) {
// this['isMove'] = true
// }
//一般偏移距离不超过30PX 都是点击
if(Math.abs(newX-this['strX'])>30 || Math.abs(newY-this['strY'])>30) {
this['isMove'] = true
}
console.log(ev.touches)
})
on(box,'touchend', function(ev){
console.log(this['isMove'])
//离开时只能通过changedTouches获取离开坐标
if(!this['isMove']) {
//如果还是false 手指没有移动 就是点击
this.style.transform='rotate(360deg)'
var delayTimer=window.setTimeout(function(){
this.style.transform='rotate(360deg)'
}.bind(this),1000)
} else {
this.style.background = 'red'
}
console.log(ev.changedTouches)
})