javascript 基础学习笔记
前端开发常用浏览器
-
谷歌浏览器(Chrome):webkit内核(v8引擎)
safari、大部分国产浏览器(360、QQ、UC……)、安卓和iOS大部分手机浏览器……
-
火狐浏览器(Firefox):gecko内核
-
欧朋浏览器(Opera):presto内核
-
IE浏览器:trident内核
浏览器内核
作为前端开发,我们会使用HTML/CSS/JS编写代码,编写代码的时候要遵循一些规范(W3C)
浏览器
浏览器开发商开发的浏览器,目的就是为了按照W3C的规范,识别出开发者编写的代码,并且在页面中绘制出开发者预想的页面和效果(GPU:显卡),我们把浏览中识别代码绘制页面的东西称为‘浏览器的内核或者渲染引擎’浏览器兼容:
1、W3C发布的规范都是开发者们不断尝试总结下来的产物,例如:谷歌浏览器开发了一个新的CSS属性(border-radius)可以让开发者快速实现盒子圆角
-webkit-border-radius
火狐浏览器发现这个功能很好用,也实现了这个属性
-moz-border-radius
……
W3C把他融入到规范的时候
border-radius2、每个浏览器为了彰显自己的不一样,不按照标准来,但是吧标准中规定的效果用另外一种方式实现了
window.getComputedStyle
currentStyle
这样我们写的时候需要写两套代码
link与@import的区别
1、link是XHTML标签,除了加载css外,还可以定义RSS等其他事物;@import属于CSS范畴,只能加载CSS。
2、link引用css时,在页面载入是同时加载;@import需要页面完全载入以后加载。
3、link是XHTML标签,没有兼容问题;@import是在css2.1提出的,低版本的浏览器不支持。
4、link支持使用JavaScript控制DOM去改变样式;而@import不支持。
JS中提供的浏览器弹框
1、alert:在浏览器中弹出一个提示框(提供确定按钮,点击确定弹框消失)
alert({name:’meng’}); //=>”[object object]”
alert([12, 13]); //=>”12,23”
使用alert弹框提示信息,提示的内容最后都会被转换为字符串输出(调用了toString方法)
alert(1+1);//=>”2”
2、confirm:在alert基础上增加了让用户选择性的操作(提供了两个按钮:确定和取消)
var meng = confirm(‘are you sure delete this!’);
当用户点击确定的时候,我们接收到的结果是true,点击的是取消按钮我们接收到的结果是false,此后我们可以根据接收的结果做不同的处理即可
3、prompt:在confirm基础上增加让用户输入的效果
var flag = prompt(‘are you sure delete this!’);
用户点击取消按钮,我们获取到的结果是null,如果用户点击的是确定按钮,我们将会获取到用户输入的内容(如果用户没有输入任何内容,获取的结果是空字符串)
真实项目中,有确实需要样式精美的网站中,我们的提示框一般都是自己封装插件和组件来实现的,不会用内置的(使用原生js封装模态框组件)
控制台输出
方便开发人员进行调试的
F12:打开浏览器的控制台
console.log:在控制台输出,优势不会转换数据类型,输出什么格式的数据都可以
例: console.log({name:’mengxianjun’}); =>{name:’mengxianjun’}
console.dir:比log输出的更加详细一些
console.table:把json数据展示成为一个表格
JavaScript
ECMAScript(ES):规定了JS的一些基础核心的知识(变量、数据类型、语法规范、操作语句等)3/5==6/7
DOM:document object model文档对象模型,里面提供了一些属性和方法,可以让我们操作页面中的元素
BOM:browser object model 浏览器对象模型,里面提供了一些属性和方法,可以让我们操作浏览器
变量和常量
变量:值是可以变的
常量:值是不可变的
//=>JS中定义变量的方法
//var 变量名 = 值; (ES6中定义变量使用let)
var num = 12;
var str = "meng";
//=>JAVA等后台语言定义变量比较严谨,js比较松散
int num = 12;
float num = 12.5;
double num = 3.1415926;
console.log(num); //=>12 变量其实只是一个无意义的名字,他所代表的意义都是其存储的那个值
num = 13; //=>让原有的num变量存储的值修改为13(变量只能代表一个值)
//=>任何一个具体的数据值都是常量,例如:12就是一个常量
//=>和变量类似,我们设置一个常量(也就是一个名字),给其存储一个值,但是这个存储的值不能修改
const num = 12; //=>定义一个常量num,给他存储了12
num = 13; //=> Uncaught TypeError: Assignment to constant variable. 常量存储的值是不能修改的
JS中的命名规范
1、JS中严格区分大小写
var test = 'meng';
var Test = 'xianjun';
console.log(test); //=>'meng' test和Test是两个不同的变量
2、遵循国际命名规则‘驼峰命名法’
第一个单词首字母小写,其余每一个有意义单词首字母大写
var studentInfo;
命名使用英文单词,不要使用拼音
不是所有单词都能简写,我们需要保证大家看到名字后知道所代表的意思
add / insert / create : 增加 创建 插入
remove / rm / clear / del / delete : 删除
update : 修改
get / query / select : 查询获取
……
3、命名的时候可以使用$ 、_ 、数字 、字母,但是数字不能作为名字的第一位
var student_info;
var $xxx; //=> 一般都是应用JQ获取到的值
var _xxx; //=>一般这样的情况代表变量是一个全局或者通用的变量
4、JS中很多的词都是有特殊含义的,我们这些词叫作“关键字”;现在没有特殊含义,以后可能会作为关键词的,我们叫作“保留字”;而关键字和保留字都不可以随便用来命名;
ECMA-262描述了一组具有特定用途的关键字。这些关键字可用于表示控制语句的开始或结束,或者用于特定操作等。按照规则,关键词也是语言保留的,不能用作标识符。以下就是ECMAScript的全部关键字(带*号上标的是第5版新增的关键字):
break | do | instanceof | typeof |
case | else | new | var |
catch | finally | return | void |
continue | for | switch | while |
debuger* | function | this | with |
default | if | throw | delete |
in | try |
ECMA-262还描述了另外一组不能用作标识符的保留字。尽管保留字在这门语言中还没有任何特定的用途。但它们有可能在将来被用作关键字。以下是ECMA-262第3版定义的全部保留字:
abstract | enum | int | short |
boolean | export | interface | static |
byte | extends | long | super |
char | final | native | synchronized |
class | float | package | throws |
const | goto | private | transient |
debugger | implements | protected | volatile |
double | import | public |
第5版包在非严格模式下运行时的保留字缩减为下列这些:
class | enum | extends | super |
const | export | import |
在严格模式下,第5版还对以下保留字施加了限制:
implements | package | public | interface |
private | static | let | protected |
yield |
JS中的数据类型
基本数据类型(值类型)
number:数字
+NaN
+infinitystring:字符串
boolean:布尔
null:空对象指针
undefined:未定义
symbol 唯一值
+static symbol
+symbol.prototype
+bigint引用数据类型
object对象数类型
{}普通对象
[]数组
/^$/ 正则
日期对象
JSON对象
Set
Map
……
function函数数据类型
普通函数
构造函数
箭头函数
生成器函数
……
number:数字
12 12.5 -12
string:字符串
'mengxianjun' "孟献军" =>单双引号包裹起来的都是字符串(单双引号没有区别)
Boolean:布尔
true false => 布尔类型:只有两个值
null:空对象指针
undefined:未定义
{name:'meng',age:26}
[12,13,14]
function fn(){}
这么多数据类型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'
腾讯的面试题:
console.log(typeof typeof []);
typeof [] =>'object';
typeof 'object' =>'string';
Boolean()
把其他数据类型的值转换为布尔类型
只有
0、NaN、空字符串、null、undefined
这五个数据值转换为布尔类型的false,其余的都会变为true
!
单感叹号
!= :不等于
感叹号在JS中还有一个作用:取反
,先把值转换为布尔类型,然后再去取反
!!
两个
在一个感叹号取反的基础上取反,取两次反相当于没有做操作,但是却已经把其他类型值转换为布尔类型了,和Boolean是相同的效果
字符串
在JS中单、双引号抱起来的都是字符串
12 ->number
'12' ->string
'[12,13]' ->string
常用方法:
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(0) ->false
isNaN(NaN) ->true
isNaN("12") ->false 当我们使用isNaN检测值的时候,检测的值不是number类型的,浏览器会默认的把值先转换为number类型,然后再去检测
Number()
把其他数据类型值转化为number类型的值
Number('12') ->12
Number('12px') ->NaN 在使用Number转换的时候只要字符串中出现任何一个非有效数字字符,最后的结果都是NaN
Number(true) ->1
Number(false) ->0
Number(null) - >0
Number(undefined) ->NaN
Number([]) ->把引用数据类型转换为Number,首先需要吧引用数据类型转换为字符串(tostring),再把字符串转换为number即可 例如:[] ->'' ''->0
Number([12]) =>[12]->'12'->12
Number([12,13]) =>[12,13]->'12,13'->NaN
Number({name:'meng'} =>NaN
Number({}) =>NaN
parseInt()
也是把其他数据类型值转换为number,和number方法在处理字符串的时候有所区别
Number('12px') ->NaN
parseInt('12px') ->12
parseInt('12px13') ->12 提取规则:从左到右依次查找有效数字字符,直到遇见非有效数字字符为止(不管后面是否还有,都不找了),把找到的转换为数字
parseInt('px12') ->NaN
parseInt(12.5px) ->12
parseFloat()
在parseInt的基础上可以识别小数点
parseInt('12.5px') ->12
parseFloat('12.5px') ->12.5
课后思考:parseInt常用的只需要传递一个值做参数即可,但是它支持多个参数,回去后扩展其他参数的意思 例如:parseInt(‘12.5’,10)=>12
null和undefined
null:空,没有
undefined:未定义,没有”:空字符串,没有
0:也可以理解为没有
空字符串和null的区别
都是去种树
空字符串是挖了个坑,但是什么都还没有种下去
null是连坑都没有挖空字符串相对于null来说开辟了内存,消耗了那么一丢丢的性能
null和undefined的区别
null一般都是暂时没有,预期中以后是会有的(可能以后也没达到预期):在JS中null一般都是手动先赋值为null,后期我们再给他附具体的值
undefined:完全没在预料之内的
小明是个帅气的男孩子
她现在的女朋友是null
他的男朋友是undefined
symbol
给对象设置唯一的属性
在vuex / redux中做行为派发的时候,统一管理派发的行为标识,标识的值可以是唯一值
……
console.log(Symbol() === Symbol()); //=>false
let symb = Symbol('aa');
console.log(symb === symb); //=> true
Symbol.hasInstance 用于判断某对象是否为某构造器的实例
Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数
Symbol.toStringTag
Symbol.iterator 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of 循环使用
Symbol.isConcatSpreadable 用于配置某对象作为Array.prototype.concat()方法的参数时是否展开其数组元素。
Symbol.match 指定了匹配的是正则表达式而不是字符串
bigint
BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值。在对大整数执行数学运算时,以任意精度表示整数的能力尤为重要。使用BigInt,整数溢出将不再是问题。(-9007199254740991 (-(2^53-1)) 和9007199254740991(2^53-1)之间的整数)
要创建BigInt,只需在整数的末尾追加n即可 (9007199254740991n+1n =9007199254740992n )
对象数据类型 object
var obj = {name:’meng’,age:26,sex:’男’};
每一个对象都是由一到多组属性名(key键):属性值(value值)
组成的,或者说有多组键值对组成的,每一组键值对中间用逗号分隔属性:描述这个对象特点特征的
对象的属性名是字符串或者数字格式的,存储的属性值可以使任何的数据类型
对象名.属性名:忽略了
对象名[属性名]
:不能忽略单双引号
var obj = {name:'meng',age:26,sex:'男',friend:['哈哈', '呵呵'],0:100};
//=>获取某个属性名对应的属性值
obj.name
obj[name] obj['name']
//=>如果属性名是数字如何操作
obj.0 语法不支持
obj[0] / obj['0'] 两种都可以支持
//->如果操作的属性名在对象中不存在,获取的结果是undefined
obj.abc ->undefiend
//=>设置/修改:一个对象的属性名是不能重复的(唯一性),如果之前存在就是修改属性值的操作,反之不存在就是新设置属性的操作
obj.abc = 'come on';
obj['age'] = 27;
//=>删除
//->假删除:让其属性值赋值为null,但是属性还在对象中 obj.abc=null;
//->真删除:把整个属性都在对象中完全移除
delete obj.abc;
思考:obj[age]和obj[‘age’]有什么样的区别?
age:变量名,代表的是它存储的值。
‘age’:常量,字符串的具体值。
var age = 'name';
var obj ={
name:'mengxj',
age:8
};
console.log(obj.age); //=> 8
console.log(obj['age']);// =>8
console.log(obj[age]); //=>obj[age变量] =>obj['name'] =>获取name属性名的属性值 =>"mengxj"
obj[age]操作的是对象中的属性,并不是操作的变量,是操作的变量所存储的值,也就相当于obj[‘name’],而obj[‘age’]就是操作的是obj对象中age属性名,所以值就是age本身。
基本数据类型和引用数据类型的区别
JS是运行在浏览器种的(内核引擎),浏览器会为JS提供赖以生存的环境(提供给JS代码执行的环境)=> ‘全局作用域 window(global)’
var a = 12;
var b = a;
b = 13;
console.log(a);
var n = {name:'孟献军'};
var m = n;
m.name = mengxj;
console.log(n.name);
‘基本数据类型是按照值来操作的’:基本数据类型在赋值的时候,是直接吧值赋值给变量即可
‘引用数据类型是按照空间地址(引用地址)来操作’:
var n = {name:’孟献军’};
1、先创建一个变量n
2、浏览器首先会开辟一个新的存储空间(内存空间),目的是把对象中需要存储的内容(键值对)分别存储在这个空间中,为了方便后期找到这个空间,浏览器会给空间设定一个地址(16进制的)
3、把空间的地址赋值给了变量
函数数据类型
函数数据类型也是按照引用地址来操作的
函数:具备一定功能的方法
//=>创建函数:相当于生产了一台洗衣机
function 函数名(){
//=>函数体:实现某一功能的具体JS代码
}
//=>执行函数:相当于使用洗衣机洗衣服(如果函数只创建了,但是没有去执行,函数没有任何的意义)
函数名();--->执行函数
function fn(){
console.log(1+1);
}
fn;=>输出函数本身
fn();=>执行函数(把函数体中实现功能的代码执行)
//=>形参:形式参数(变量),函数的入口
//当我们创建一个函数想要实现某个功能的时候,发现有一些材料并不清楚,只有当函数运行的时候,别人传递给我,我才知道,此时我们就需要设定入口,让用户执行的时候通过入口把值给我们
function fn(num1,num2){
console.log(num1+num2);
}
//=>实参:函数执行传递给函数的具体值就是实参
fn(10,20);
判断操作语句
if,else if,else
if(条件1){
//=>条件1成立执行的操作
}else if(条件2){
//=>条件1不成立,条件2成立,执行的操作
}
else{
//=>以上条件都不成立执行的操作
}
如果好几个条件都成立了,只把第一个成立的条件执行,后面成立的条件忽略不管
条件:
A==B,A!=B,A>=B,A<B
if(A){} 先把A转换为布尔类型,判断真假以此来决定条件是否成立
if(A>B && A<10){} 只有两个小条件都是真,整体条件才为真
if(A>B || A==0){} 只要其中一个小条件成立,整体条件就是真
BAT面试题:
var num = parseFloat('width:12.5px'); //=>NaN
if(num==12.5){ //=>开头说明一切
alert(12.5);
}else if(num==NaN){ //=>NaN不等于NaN
alert(NaN);
}else if(typeof num=='number'){
alert(0); //=>alert输出的内容是字符串
}else{
alert('啥也不是!');
}
//=>最终结果是'0'(字符串),因为alert是以字符串输出数据
三元运算符
条件?条件成立执行:条件不成立执行
if(条件){}else{}:三元运算符就是这种简单if、else的另外一种写法
var num = 10;
if(num>5 && num<=10){
num++; //=>num+=1 num=num+1 自身累加1
}else{
num--;
}
//=>改写成为三元运算符:如果条件成立或者不成立的某一种情况并不需要做什么处理,我们空着语法不符合,可以使用null、undefined、void 0(undefined)占位即可
num>5 && num<=10?num++:num--;
num>5 && num<=10?num++:;
var num = 10;
if(num>5 && num<=10){
num++;
console.log(num);
}
//=>改写成为三元运算符:某一种情况执行多条操作,使用下括号抱起来,中间用逗号分隔
num>5 && num<=10?(num++,console.log(num)):null;
var num = 10;
if(num>5 && num<=10){
num++;
break;/continue;/return;
}
//=>改写成为三元运算符:在三院运算符的操作中不能出现break,continue,return这些关键词,我们无法用三元运算符代替if,else
num>5 && num<=10?(num++,return):null;
var num = 10;
num>0?(num<10?num++:num--):(num>-10?num+=2:num-=2);
//=>改写成if else
if(num>0){
if(num<10){
num++;
}else{
num--;
}
}else{
if(num>-10){
num+=2;
}else{
num-=2;
}
}
switch case
switch case应用于if、else中一个变量在不同值情况下的不同操作
var num = 10;
switch(num){ //=>switch后面小括号中存放的事一个值(一般我们都写变量:把变量存储的值拿来用,有时候也可能是一个计算)
case 1:
……
break;
case 10: //=>case后面放入的都是值,目的是为了验证switch后面的值和哪一种case后面的值相等,相等的进行对应的处理
……
break; //=>每一种case结束后都要加break来结束当前的判断
default://=>switch后面的值和每一种case情况对应的值都不相等,执行最后的default,类似于else
……
}
案例剖析
var num = 10;
switch(num%2){ //=>先把取余操作进行运算,拿运算结果和case比较
case 0:
num++;
break;
case 2-1: //=>case后面也应该是值,此处先把2-1进行计算,把计算的结果和switch值比较
num--;
//=>最后一项可以不加break,不加也能跳出判断
default:
……
}
num%2:让num存储的值除以2取余数(0或1)
var num = 10;
switch(num%3){
case 0:
num++;//=>不加break,不管后面的条件是否成立,都会继续向下执行,直到遇到break为止
case 1:
num--;
break;
default:
num=0;
}
//=>小应用:把符合的值都去做同一件事,使用不加break实现
switch(num%3){
case 0:
case 1://=>余数是0或者1都执行减减的操作
num--;
break;
default:
num=0;
}
var num = 6;
switch(num%3){
case 0:
num++;
break;
case 1:
num--;
break;
default:
num=0;
}
num=>7
switch case中的比较使用的是:===
=:赋值,等号左边是变量,右边是值
==:比较,如果左右两边值的类型不一样,浏览器会默认转换为一样的然后再进行比较
‘6’==6 ‘6’->6 6=6=>true
===:绝对相等,不仅要求值一样,并且类型也要完全一样 ‘6’===6 =>false
循环操作语句
循环:重复做一件事情
for循环
for(设置循环起始值;设置循环执行的条件;步长累加){
//=>循环体:重复做的事情都在循环体中
}
//1、设置初始值
//2、验证条件
//3、条件成立,执行循环体,条件不成立,循环结束
//4、步长累加
for(var i=0;i<5;i++){
console.log(i);//=>0 1 2 3 4
}
console.log(i);//=>5
var i=0
for(;i<5;){
console.log(i);//=>没有步长累加,i永远都是0,循环条件永远成立“死循环”:项目中不能出现死循环,一旦出现,循环里面的事情都做不了
}
console.log(i);//=>5
for(var i=0;i<5;i+=2){
console.log(i);
continue;//=>结束本轮循环,继续执行下一轮,循环体中continue后面的代码都不会再执行,他会直接去执行步长,然后进入下一轮循环
console.log(i);//=>0
for(var i=0;i<5;i+=2){
console.log(i);
break;//=>结束整个循环:循 环体中一旦遇到break,首先后面的代码不执行了,而且步长累加也不执行了,循环都结束了
console.log(i);//=>0
BAT面试题
for(var i=1;i<10;i+=2){
if(i<5){
i++;
continue;
}else{
i+=3;
break;
}
console.log(i);
}
console.log(i);=>10
2 5 10
自定义属性思想:
当我们在某一个阶段需要用到一些信息,但是此时不好获取这些信息,我们可以在之前好获取的时候,把这些后续需要用到的信息存储在元素对象的某一个自定义属性上,以后想用的时候直接在自定义属性上获取这些信息即可。
JS函数
函数是指一段在一起的、可以做某一件事儿的程序。也叫作子程序、(面向对象OOP中)方法。
函数是实现某一个功能的方法
创建函数
function 函数名(形参){ //形参:当我们使用函数实现某个功能的时候,发现一些材料我们并不清楚,只有当函数运行的时候,别人传递给我,我才知道,此时我们就需要设定一个入口,让用户执行的时候通过入口把值给我们
//=>【函数体】
//实现功能的具体js代码
}
执行函数
函数名(); //=》把创建的函数执行,而且这个函数可以执行很多次
函数名();
...
每一次执行都相当于把函数体中实现功能的JS代码重复执行了一遍
在真实项目中,我们一般都会把实现一个具体功能的代码封装在函数中:
1、如果当前这个功能在页面中执行多次,不封装成为函数,每一次想实现这个功能,都需要重新把代码写一遍,浪费时间;而封装在一个函数中,以后想实现多次这个功能,我们就没有必要再重新写代码了,只需要把函数重新的执行即可,提高了开发效率;
2、封装在一个函数中,页面中就基本上很难出现重复一样的代码了,减少了页面中代码的冗余度,提高了代码的重复利用率:‘低耦合高内聚’我们把以上的特点称为
函数封装
(OOP面向对象编程思想,需要我们明白的就是类的继承、封装、多态)
JS中函数的核心原理
函数作为JS中引用数据类型中的一种,也是按照引用地址来操作的
function sum(){
var qiuhe = 1+1;
qiuhe*=20;
console.log(qiuhe.toFixed(2));
}
sum();
创建函数
-
首先会在当前作用域中声明一个函数名(声明的函数名和使用var声明的变量名是一样的操作 var sum; function sum;这两个名字算重复了)
-
浏览器首先会开辟一个新的内存空间(分配一个16进制地址),把函数体中写好的代码当做普通字符串存储在这个内存空间中(创建一个函数如果不执行,函数就没有意义)
-
把内存空间的地址赋值给之前声明的那个函数名
函数执行
目的:把之前存储的实现具体功能的JS代码执行
函数执行的步骤
1、形成一个私有的作用域(AO私有变量对象等也都有了:存储当前作用域中声明的变量),然后进栈执行
2、代码执行之前:
+初始化作用域链 scope-chain <当前自己的私有作用域,函数的作用域(创建函数时候所在的作用域)> =>链的右侧时当前作用域的“上级作用域”
+初始化this
+初始化arguments
+形参赋值:在当前作用域中声明一个形参变量,并且把传递的实参值赋值给它
+变量提升
3、代码执行
4、出栈释放
私有变量
私有作用域下声明的变量
形参是私有变量
代码执行时候声明的变量
作用域链查找机制
私有作用域中代码执行,如果遇到一个变量,首先看是否为自己的“私有变量”,如果是“私有”的,则操作自己的,和外界没有必然的关系;如果不是自己私有的,则给予作用域链向其作用域中查找,看是否为上级作用域中私有的,如果也不是,则继续向上查找……一直找到全局作用域为止,我们把这种查找的过程,称为“作用域链查找机制”
闭包
函数执行会形成一个私有的作用域,让里面的私有变量和外界互不影响(相互不干扰,外面的无法直接获取里面的变量值),此时我们可以理解为私有作用域把私有变量保护起来了,我们把这种保护机制称为
闭包
栈内存
作用域(全局作用域/私有作用域):提供一个供JS代码执行的环境
堆内存
所有的引用数据类型,他们需要存储的内容都在堆内存中(相当于一个仓库,目的是存储信息)
对象会把键值对存储起来
函数会把代码当做字符串存储
函数中的形参和实参
形参:相当于生产洗衣机的时候提供的入口,需要用户执行函数的时候把需要的值传递进来,形参是个变量,用来存储和接收这些值
实参:用户执行的时候传递给形参的具体值
//=>随便求出两个数的和
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);//->num1=10 num2=undefined =>NaN 定义了形参但是执行的时候没有传递实参,默认实参的值是undefined
//=>如果有一个值没有传递的话,我们为了保证结果不是NaN,我们为其设置一个默认的值:0
if(num1 === undefined){
num=0;
};
if(typeof num2 === "undefined"){ //=>项目中常用的吧
num2 = 0;
}
//=>容错处理
arguments 实参集合
当我们不知道用户具体要传递几个值的时候(传递几个值都行),此时我们无法设置形参的个数;遇到此类需求,需要使用函数内置的实参集合:arguments
1、arguments只有函数才有
2、不管执行函数的时候是否传递实参,arguments天生就存在,没有传递实参ARG是个空的集合,传递了ARG中包含了所有传递的实参值
3、不管是否设置了形参,ARG中始终存储了所有的实参信息
arguments是一个类数组集合
1、以数字作为索引(属性名),从零开始
arguments[0]第一个实参信息
arguments[1]第二个实参信息
arguments[n]第n+1个实参信息
2、有一个length的属性,存储的是当前几个的长度(当前传递实参的个数)
arguments.length
arguments[‘length’]arguments.callee:存储的是当前函数本身
arguments.callee.caller:存储的是当前函数在哪执行的(宿主函数),在全局作用域下执行,结果是null
use strict:
在JS代码执行前加入这句话:开启js的严格模式
//=>arguments.call或arguments.callee .caller一般在真正项目中很少使用;因为在严的JS模式下不允许我们使用这两个属性,然后现有项目大部分都是基于严格模式来的
任意数求和(解析)
function sum(){
var total = null;
for(var i = 0; i<arguments.lenght; i++){
var cur = arguments[i];//=>每一轮循环获取当前传递的那个实数值
cur = Number(cur); //=>为了防止字符串+数字是字符串拼接不是数学的累加,我们最好把其他数据类型
//首先转换为Number类型
//=>为了防止传递的是非有效数字(数字+NaN=NaN),我们最好做一下非有效数字的检测:有效数字才进行累加
if(isNaN(cur) === flase){
total+=cur;
}
}
}
sum(10,20,30);
sum(10,20,'30');
任意数求和优化
function sum(){
var total = null;
for(var i = 0; i<arguments.length; i++){
var cur = Number(arguments[i]);
if(!isNan){
total+=cur;
}
// !isNaN?total+=cur:null;
}
}
sum(10,20,30,'40','meng')
JS中的返回值 return
返回值是函数提供的一个出口:如果我们想在外面使用函数私有的一些信息,那么就需要通过return,把这些信息返回出来供外面使用。
function sum(){
var total = null;
for(var i=0;i<arguments.length;i++){
var cur = Number(arguments[i]);
if(isNaN(cur)=== false){
total+=cur;
}
}
return total;//=>return后面跟着的都是值(返回的都是值):此处不是吧total变量返回,而是把total存储的值返回而已<=>return 60
}
console.log(sum(10,20,30));
//=>sum:代表的是函数本身
//=>sum():让函数先执行,代表的事当前函数返回的结果(return后面的是啥,相当于函数返回的是啥)
function sum(){
var total = 0;
return;
}
console.log(sum());//=>如果函数中没有写return或者return后面啥也没有,默认返回的结果就是undefined
function sum(){
var total = 0;
return;
console.log(total);//=>在函数体中遇到return后,return后面的代码都不再执行了
}
console.log(sum());
JS中的匿名函数
没有名字的函数
函数表达式
自执行函数
oBox.onclick = function(){
//=>把一个没有名字的函数(有名字也无所谓)作为值赋值给一个变量或者一个元素的某个事件等:函数表达式
}
自执行函数
;(function(n){
//=>创建函数和执行函数放在一起了,创建完成立马执行:自执行函数
//n形参 n=10;
})(10)
//=>以下都是自执行函数,符号只是控制语法规范
~function(n){}(10);
-function(n){}(10);
+function(n){}(10);
!function(n){}(10);
for in 循环
for in:用来遍历(循环)对象键值对的
var obj = {name:'孟',age:26,1:'献',3:'军',2:'哈哈'};
//=>var key; var attr;
//=>对象中有多少组键值对,我们的for in循环就遍历多少次(不一定)
//=>每一次循环key这个变量存储的都是循环这组键值对的属性名
//=>1、key存储的值都是字符串格式的(不管属性名是否为数字)
//=>2、在for in循环遍历的时候,大部分浏览器都是先把对象中的键值对进行排序(把数字属性名的按照数字大小排在前面),其次再把非数字的属性名按照之前编写的顺序排列,循环的时候按照重新排列的顺序依次遍历(小数算作字母不算作数字)
for(var key in obj){
console.log(typeof key); //->"string"
//key->属性名
console.log(obj.key);//->获取obj中key这个属性名对应的属性值 ->undefined
console.log(obj[key]);//->每一次循环把key变量存储的值(当前遍历的属性名)获取到放在中括号中,获取obj中对应属性的属性值
}
//'key':字符串key
//key:变量key,代表的是存储的值
//obj['key'] obj.key 一样的属性名都是key
//obj[key] 属性名不是key而是key变量存储的值
数据类型转换
把其他数据类型转换为number类型
isNaN、Number、parseInt、parseFloat
在进行加减乘除数学运算的时候
true ->1 false ->0
'' ->0 '12' ->12 '12px' -> NaN/12 'MENG'->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
[] ->true
-1 ->true
if(box){
//=>首先把box变量存储的值获取到,然后转化为布尔类型,如果为true条件成立,反之不成立
}
if(3 + '3px'){
//=>条件成立:3+'3px'='33px'
}
if(3-'3px'){
//=>条件不成立:3-'3px'=NaN
}
在使用==进行比较的时候
在使用==进行比较的时候,如果左右两边数据类型不相同,浏览器会默认转换为相同的类型,然后再比较(
===
不会这样操作)
//=>对象和对象:比较的是空间地址,不是相同的空间,结果肯定是false
[] == [] ->false
var a = [];
var b = a;
a==b ->true;
//=>对象和数字:把对象转换为数字
[]==0 ->true
({})==NaN ->false NaN和自己不相等和其它任何值都不相等
//=>对象和字符串:把两边都转换为数字比较的
[]=='' ->true
//=>对象和布尔:把两边都转换为数字
[]==true ->false
[]==false ->0==0 ->true
![]==false ->![]把数组变为布尔再取反=false ->false==false ->true
//=>字符串和数字:字符串转换为数字
//=>字符串和布尔:都转为数字
//=>布尔和数字:布尔转换为数字
//=>规律:两个等于号比较,左右两边数据值的类型不一样,浏览器会把两边的类型都转换为数字然后再比较,但是null和undefined除外
null==undefined ->true
null===undefined ->false
null==0 ->false null以及undefined和其他任何值都不相等
数组的基础结构
数组也是对象数据类型的
typeof[] ->'object'
数组也有属性名,只不过属性名是数字,我们把数字属性名称之为它的索引:数组是以数字作为索引,索引从零开始,有一个length属性代表数组的长度
类数组:类似于数组,但是不是数组
1、通过getElementsByTagName获取的元素集合是类数组
2、函数中的实参集合arguments也是类数组
循环数组中的每一项
var ary = [12,23,24];
Array.prototype.aa =100;
//=>for循环(项目使用更多)只能遍历到数组私有的一些属性,而for in循环可以把一些自定义的公共属性也能遍历到
//for 循环操作
for(var i=0;i<ary.lengtj;i++){
console.log(ary[i]);
}
//for in循环操作
for(key in ary){
console.log(ary[key]);
}
数组中的常用方法
数组中有很多常用的方法,
console.dir(Array.prototype)
(查看数组中的方法)
1、方法的意义和作用
2、方法的形参
3、方法的返回值
4、通过此方法,原来的数组是否发生了改变
实现数组的增加,修改,删除
var ary = [12,23,24];
//=>增加
//1、push:向数组的末尾追加新内容
//参数:一到多个,任何数据类型都可以,想要给数据末尾追加什么,直接传递到push方法中即可,传递多个用逗号隔开
//返回值:新增后数组的长度
//原有数组改变了
//2、unshift:向数组开头追加新内容
//参数:需要追加的内容(可以是多个任何数据类型的值)
//返回值:新增后数组的长度
//原来数组改变了
//3、把数组当做一个普通的对象,使用对象键值对的操作,给其设置新的属性(索引)
//ary[ary.length]==xxx 向数组的末尾追加了新的内容
//=>删除
//1、pop:删除数组最后一项
//参数:无
//返回值:被删除的那一项内容
//原有数组改变了
//2、shift:删除数组第一项
//参数:无
//返回值:被删除那一项的内容
//原有数组改变了
//使用shift删除第一项之后,后面的每一项的索引都要向前进一位(导致后面项的索引发生改变)
//3、把数组当做普通的对象操作
//delete删除:`delete ary[索引]`删除指定索引这一项(当前项被删除后,原有数组其他项的索引不会改变;当前数组的length也不会改变)
// ary.length--:删除数组最后一项
//=>splice:数组中内置的方法,可以实现数组的增加、修改、删除
//1、splice实现删除
//splice(n,m):从索引n开始删除m个(m不写是删除到数组的末尾)
//返回值:被删除的内容(以一个新数组保存)
//原有数组改变了
//->splice(0) 清空数组
//->splice() 一项都不删除,返回一个新的空数组,原数组不变
//->splice(0,1)删除第一项
//->splice(ary.length-1) 删除最后一项
//=>splice 实现修改
// splice(n,m,x):在原有删除的基础上,用x代替删除的内容
//splice实现增加
//splice(n,0,x):在修改的基础上,我们一项都不删除,把x插入到索引n的前面
//splice(0,0,x):向数组开头追加新的内容
//splice(ary.length,0,x)向数组末尾追加一个元素
数组的查询
1、slice:数组的查询
参数:slice(n,m) 从索引n开始找到索引为m处(不包含m)
返回值:把找到的部分以一个新数组返回
原来的数组不变->slice(n) 从索引n开始找到末尾
->slice(0) / slice() 数组克隆,克隆一份和原来数组一模一样的新数组
->slice(-4,,-1) 支持负数索引,如果传递的索引为负数,浏览器解析的时候是按照 总长度+负数索引 来处理2、
将两个数组进行拼接
concat:将多个数组拼接在一起
参数:要拼接的内容(把内容放在原数组的后面),可以使一个数组,也可以是一些数据值
返回值:拼接后的新数组
原有数组不变
->concat() 什么都没有拼接,相当于把原有数组克隆一份一模一样的新数组
把数组转换为字符串
1、toString:实现把数组转化为字符串(转换后的字符串以逗号分隔每一项)
参数:无
返回值:转换的字符串
原有数组不变2、join:把数组按照指定的分隔符转换为字符串,和字符串中的split相对应
参数:指定的连接符
返回值:转换后的字符串
原有数组不变
//=>已知数组中的每一项都是数字,想实现数组求和,我们如何实现?
// 1、循环实现
var total=null;
var ary = [12,23,24,25,26];
for(var i=0;i<ary.length;i++){
total+=ary[i];
}
// 2、利用join
var total = eval(ary.join('+')); //->eval:把字符串变为JS表达式执行
实现数组中每一项的排序和排列
//1、reverse:把数组中的每一项倒过来排列
//参数:无
//返回值:排列后的新数组
//原有数组改变
//2、sort:实现数组的排序
//参数:无或者回调函数
//返回值:排序后的新数组
//原有数组改变
//不传递参数的情况下:可以给10以内的数字进行升序排列,但是超过10的就无法处理了(多位数只识别第一位)
ary.sort(function(a,b){
return a-b; //=>升序
return b-a; //=>降序
})
验证数组中是否包含某一项
// indexOf / lastIndexOf:获取当前项在数组中第一次或者最后一次出现位置的索引
// 数组中的这两个方法在IE6~8下不兼容
// 字符串中的这两个方法兼容所有的浏览器
// 如果当前数组中并没有这一项,返回的索引是-1,我们根据这一点可以验证数组中是否包含这一项
if(ary.indexOf(12)>-1){
//->数组中包含12
}
`indexof兼容写法`
Array.prototype.myIndexOf = function myIndexOf(value){
var result = -1;
for(var i=0;this.length;i++){
if(value===this[i]){
result=i;
break;
}
}
return result;
}
ary.myIndexOf(12);
遍历数组中每一项的方法
//=>以下方法在IE6~8下都不兼容
// forEach:遍历数组中的每一项
ary.forEach(function(value,index){
//=>数组中有多少项,当前回调函数执行多少次;每一次传递进来的value就是当前遍历数组这一项的值,index就是遍历这一项的索引
});
// map:遍历数组中的每一项,在forEach的基础上,可以修改每一项的值
ary.forEach(function(value,index){
//=>数组中有多少项,当前回调函数执行多少次;每一次传递进来的value就是当前遍历数组这一项的值,index就是遍历这一项的索引
return xxx; //=>return后面返回的结果就是把当前遍历的这一项修改为xxx
});
数组去重
方案一:
遍历数组中的每一项,拿每一项和他后面的项依次比较,如果相同了,则把相同的这一项在原来数组中删除即可
var ary = [1,2,2,2,3,1,3,3,4,4,5,2,3,6,5];
//=>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++){
//ary[j]:作比较的那一项
if(cur === ary[j]){
//=>本次作比较的这一项和当前项相同,我们需要在原有数组中把作比较的这一项删除掉(作比较这一项的索引是j)
ary.splice(j,1); //=>数组塌陷问题:我们使用splice删除数组中的某一项后,删除这一项后面的每一项索引都要向前进一位(在原有索引上减一),此时如果我们j++,循环操作的值累加了,我们通过最新j获取的元素不是紧挨删除这一项的元素,而是跳过一项获取的元素
j--; //=>先让j--,然后再j++,相当于没加没减,此时j还是原有索引
}
}
}
console.log(ary);
var ary = [1,2,2,2,3,1,3,3,4,4,5];
for(var i=0;i<ary.length-1;i++){
var cur;
cur = ary[i];
for(var j=i+1;j<ary.length;){
cur===ary[j]?ary.splice(j,1):j++;
}
}
console.log(ary);
方案二:
利用indexof来验证当前数组中是否包含某一项,包含把当前项删除掉(不兼容IE6~8)
var ary = [1,2,2,2,3,1,3,3,4,4,5];
for(var i=0;i<ary.length-1;i++){
var cry = ary[i]; //=>当前项
var curNextAry = ary.slice(i+1);//=>把当前项后面的那些值以一个新数组返回,我们需要比较的就是后面的这些项对应的新数组
if(curNextAry.indexOf(cry)>-1){
//=>后面项组成的数组中包含当前这一项(当前这一项是重复的),我们把当前这一项删除掉即可
ary.splice(i,1);
i--;
}
}
console.log(ary);
方案三:
遍历数组中的每一项,把每一项作为新对象的属性名和属性值存储起来,例如:当前项1,对象中存储的{1:1}
{1:1,2:2,2:2}(对象中不允许存在属性名相同键值对)
在每一次向对象中存储之前,首先看一下原有对象中是否包含了这个属性(’typeof obj[xxx]===’undefined’说明当前对象中没有xxx这个属性’),如果已经存在这个属性说明数组中的当前项是重复的(1、在原有数组中删除这一项 2、不再向对象中存储这个结果),如果不存在(把当前项作为对象的属性名和属性值存储进去即可)
var ary = [1,2,2,2,3,3,4,2,3,5,4,2,5];
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; //=>obj[1]=1 {1:1} 存储
}
//=>使用splice会导致后面的索引项前进一位,如果后面有很多项,消耗的性能很大
//=>思路:我们把最后一项拿过来替换当前要删除的这一项,然后再把最后一项删除
for(var i=0;i<ary.length;i++){
var cur = ary[i];
if(typeof obj[cur] !=== 'undefined'){
ary[i] = ary[ary.length-1]; //把最后一项存储在当前项
ary.length--; //数组长度发生改变
i--; //数组索引发生改变
continue;
}
obj[cur] = cur; //=>obj[1]=1 {1:1} 存储
}
//=>最终优化版:
var ary = [1,2,2,2,3,3,4,2,3,5,4,2,5];
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;
}
console.log(ary.myUnique().sort(function (a,b){
return a-b;
}))
代码注释习惯
// bubble:冒泡排序
// @parameter (参数)
// ary:[array] 需要实现排序的数组
// @return
// [array] 排序后的数组(升序)
// by team on 2017/10/19
冒泡排序
原理:让数组中的当前项和后一项进行比较,如果当前项大于后一项,我们让两者交换位置(小->大)
每一轮从前到后两两比较,虽然不一定实现最后的排序效果,但是可以把当前最大的放在末尾
具体比较的轮数:ary.length-1 数组有多长,我们只需要把总长度-1个数分别放在末尾,即可实现最后的排序第一轮比较5次:一共留个,不需要和自己比较
第二轮比较4次:一共六个,不用和自己比,也不用和第一轮放在末尾的那个最大值比
第三轮比较3次
……
每一轮比较的次数 ary.length-1(不用和自己比) 当前已经执行的轮数(执行一轮向末尾放一个最大值,这些值不需要再比较)a=12 b=13 a和b交换值
c=a a=b b=c
a=a+b; =>25 b=a-b; =>12 a=a-b =>13
var ary = [1,2,5,4,9,7,6,3,11,15,18,13];
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;
}
递归
函数自己调用自己
面试题:1~100之间,把所有能被3并且能被5整除的获取到,然后累加求和
方案一
var total = null;
for(var i = 0;i<=100;i++){
if(i % 3 === 0 && i % 5 === 0){
total+=i;
}
}
console.log(total);
方案二 递归
function fn(){
if(num > 100){
return 0;
}
if(num%15=0){
retrun num + fn(num + 1);
}
return fn(num + 1);
}
console.log(fn(1));
1-> fn(2)
2-> fn(3)
...
15-> 15+fn(16)
16-> 15+fn(17)
....
30-> 15+fn(30)
-> 15+30+fn(31)
....
15+30+45+60+75+90+0
需求:1~10以内的所有偶数乘积
function fn(num){
if(num<=1){
return 1;
};
if(num % 2 === 0){
return num*fn(num - 1);
}
return fn(num - 1);
}
var result = fn(10);
快速排序
原理
var ary = [11,13,15,16,12,14];
function quick(ary){
//-> 如果传递进来的数组只有一项或者是空的,我们则不再继续取中间项拆分
if(ary.length <= 1){
return ary;
}
//-> 获取中间项的索引:把中间项的值获取到,在原有数组中删除中间项
var centerIndex = Math.floor(ary.length / 2),
centerValue = ary.splice(centerIndex,1)[0];//->splice返回的是个数组,数组中包含了删除的那个内容
//->用剩余数组中的每一项和中间项进行比较,比中间项大的放在右边,比他小的放在左边(左右两边都是新数组)
var aryLeft = [],
aryRight = [];
for(var i = 0; i < ary.length; i++){
var cur = ary[i];
if(cur < centerValue){
aryLeft.push(cur);
}else{
aryRight.push(cur);
}
//->cur < centerValue?aryLeft.push(cur) : aryRight.push(cur);
}
return quick(aryLeft).concat(centerValue, quick(aryRight));
}
console.log(ary);
插入排序
var ary = [12,15,14,13,16,11];
function insert(ary){
//->先抓一张牌(一般都抓第一张)
var handAry = [];
handAry.push(ary[0]);
//-> 依次循环抓取后的牌
for(var i = 1; i < ary.length; i++){
var item = ary[i];//->本次新抓的这张牌
//->那新抓的牌和手里现有的牌比较
//->j = handAry.length - 1 从手里的最后一张牌开始比较
for(vr 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);
}
}
}
retrun handAry;
}
console.log(insert(ary));
Math中常用的方法
数学函数:但是他是对象数据类型的 typeof math ->’object’
Math对象中给我们提供了很多常用操作数字的方法console.dir(Math)
可以查看Math的所有方法
abs
:取绝对值
Math.abs(12) ->12
Math.abc(-12) ->12
ceil && floor
Math.ceil:向上取整
Math.floor:向下取整
Math.ceil(12) ->12
Math.ceil(12.1) ->13
Math.ceil(12.9) ->13
Math.ceil(-12.1) ->-12
Math.ceil(-12.9) ->-12
Math.floor(12) ->12
Math.floor(12.1) ->12
Math.floor(12.9) ->12
Math.floor(-12.1) ->13
Math.floor(-12.9) ->13
round
Math.round:四舍五入
Math.round(12.3) ->12
Math.round(12.5) ->13 正数中5包含在向上
Math.round(-12.3) ->-12
Math.round(-12.5) ->-13 负数中5包含在向下
random
Math.random:获取[0,1)之间的随机小数
for(var i=0;i<100;i++){
console.log(Math.random());
}
//=> 需求:获取[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:获取一组值中的最小值
Math.max(12,32,56,16)-> 56
Math.min(12,23,24,26,35)-> 12
PI
Math.PI:获取圆周率(π)
Math.PI ->3.141592653
pow && sqrt
Math.pow:获取一个值的多少次幂
Math.sqrt:开平方
Math.pow(10,2) -> 100
Math.sqrt(100) -> 10
字符串中常用的方法
在JS中用单(双)引号包裹起来的都是字符串
var str = 'good good study,day day up!';
//=>字符串就是由零到多个字符组成的
// 特点一:
// 第一个字符索引0
// 第二个字符索引1
// ……
// 以数字作为索引,从零开始的
// 特点二
// 有一个叫作length的属性,存储的是当前字符串中字符的个数(字符串的长度)
str[0] ->'w' 第一个字符
str.length ->46
str[str.length-1] -> '!' 最后一个字符
str[100] -> undefined 如果指定的索引不存在获取的结果是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
……String.fromCharCode(十进制的Unicode值):把值按照ASCII码表中的信息,转换为原有字符,和charCodeAt正好对应
substr && substring && slice
实现字符串截取的三个办法
str.substr(n,m):从索引n开始,截取m个字符
str.substring(n,m):从索引n开始,截取到索引为m处(不包含m),把找到的部分截取
str.slice(n,m):和substring语法一样,区别在于slice支持以负数做索引
当索引是负数的时候,浏览器在处理的时候,使用字符串的总长度加上负数索引,然后按照正数处理操作细节:
1、如果只传递了n(str.substr(n) / str.substring(n)),相当于从索引n开始一直截取到字符串的末尾
2、如果传递的索引超出最大限制,也是把能截取的部分截取掉即可
3、如果一个参数都不传递:相当于把整个字符串都截取(字符串的克隆)
……
toUpperCase && toLowerCase
toUpperCase:把字母全局大写
toLowerCase:把字母全局小写
indexOf && lastIndexOf
str.indexOf:获取当前字符在字符串中第一次出现位置的索引
str.lastIndexOf:获取的是最后一次出现位置的索引
如果当前字符在字符串中没有出现过,结果是-1;我们根据这个规律可以验证一下当前字符串中是否包含某个字符
if(str.indexOf('?') === -1){
//=>没有出现过
}
if(str.indexOf('?')>=0){
//=>出现过
}
split
str.split:按照某一个字符把字符串拆分成数组中的某一项,和数组中的join方法是对应的
replace
str.replace:实现字符的替换
执行一次replace只能替换一次,如果有好几个都需要替换,在不使用正则的情况下我们需要执行很多次replace
有些需求即时执行很多次replace也实现不了,此时需要使用正则处理,真实项目中replace一般都是和正则搭配使用的
trim && trimLeft && trimRight
str.trim:除去字符串首尾的空格
str.trimLeft:去除字符串开始的空格
str.trimRight:去除字符串结尾的空格
案例:queryURLParameter
获取地址栏中URL地址?好传递的参数值
node_百度搜索目标:把问号传递的参数值分别的解析出来
obj={wd:’javascript’,rsv_spt:1,issp:1}
方案一
var str = 'https://www.baidu.com/s?wd=javascript&rsv_spt=1&issp=1';
//=>{wd:'javascript',rsv_spt:1,issp:1}
var questionIndex = str.indexOf('?'); //获取?出现位置的索引
str = str.substring(questionIndex + 1); //从?后的第一个字符开始截取
//->console.log(str) => 'wd=javascript&rsv_spt=1&issp=1'
var ary = str.split('&');
//console.log(ary) => ['wd=javascript','rsv_spt=1','issp=1']
var obj = {};
for(var i=0;i<ary.length;i++){
var cur = ary[i]; //->['wd=javascript']
var curArry = cur.split('='), //->以=拆分循环出['wd',javascript]
key = curArry[0];
value = curArry[1];
obj[key] = value;
}
console.log(obj);
方案二
function queryURLParameter(url){
//=>URL:传递的参数(我们当前要解析的URL地址)
var quesIndex = url.indexOf('?'),
obj = {};
if(quesIndex === -1){ //->url中没有问号传参:直接返回空对象
return obj;
}
url = url.substring(quesIndex + 1);
var ary = url.split('&');
for(var i=0;i<ary.length;i++){
var curAry = ary[i].split('=');
obj[curAry[0]] = curAry[1];
}
return obj;
}
console.log(queryURLParameter('https://www.baidu.com/s?wd=javascript&rsv_spt=1&issp=1'));
方案三
使用正则实现功能
var str = 'https://www.baidu.com/s?wd=javascript&rsv_spt=1&issp=1'
String.prototype.myQueryURLParameter = function myQueryURLParameter(){
var obj = {},
reg = /([^=?&]+)=([^=?&]+)/g;
this.replace(reg, function(){
var arg = arguments;
obj[arg[1]] = arg[2];
});
return obj;
}
console.log(str.myQueryURLParameter());
验证码
真实项目中的验证码:
真实项目中验证码一般都是后台处理的,后台返回给客户端展示的是一个图片(图片中包含了验证码)
字母+数字
问答
12306 选择图片
成语填空
图片拼图
滑动拖拽
……
var codeBox = document.getElementById('codeBox');
//=>生成四五随机验证码:取值范围
var areaStr = '0123456789qwertyuioplkjhgfdsazxcvbnmQWERTYUIOPLKJHGFDSAZXCVBNM';
//->62位(0~61)
var result = '';
for(var i=0;i<4;i++){
//=>随机获取一个0~61之间的整数:作为接下来获取字符的索引
var ran = Math.round(Math.random()*(61-0)+0);
//=> 根据索引获取一个随机字符
var char = areaStr.charAt(ran);
//=> 把每一次循环获取的字符存放在最后结果中
result+=char;
}
codeBox.innerHTML = result;
对验证码进行封装
var codeBox = document.getElementById('codeBox');
//=>queryCode:获取思维验证码
function queryCode(){
var result = '',
areaStr = '0123456789qwertyuioplkjhgfdsazxcvbnmQWERTYUIOPLKJHGFDSAZXCVBNM';
for(var i=0;i<4;i++){
var ran = Math.round(Math.random()*(61-0)+0);
var char = areaStr.charAt(ran);
//验证一下新获取的char字符是否已经在result中存在了,如果存在了我们不存储,重新在获取一遍,反之才累加到result中
//=>去重:result.toLowerCase().indexOf(char.toLowerCase())统一转换为小写字母
if(result.toLowerCase().indexOf(char.toLowerCase())>-1){
i--;
continue;
}
result+=char;
}
codeBox.innerHTML = result;
}
//=> 加载页面需要执行一次这个方法:生成四位验证码
queryCode();
//=>点击盒子重新生成四位二维码 (此处不加下括号:这块只是再把函数绑定给元素的点击事件,方法还没有执行,点击的时候才执行)
codeBox.onclick = queryCode;
获取DOM元素的方法
-
document.getElementById 一个元素对象
-
[context].getElementByTagName 元素集合
-
[context].getElementClassName 元素集合
-
document.getElementByName 节点集合
-
document.documentElement 获取整个HTML对象
-
document.body 获取整个BODY对象
-
document.head 获取整个HEAD对象
-
[context].querySelector 一个元素对象
-
[context].querySelectorAll 获取元素集合
-
……
getElementById
此方法的上下文只能是document
一个HTML页面中元素的ID理论上是不能重复的1、如果页面中的ID重复了,我们获取的结果是第一个ID对应的元素对象
2、在IE7及更低版本浏览器中,会把表单元素的name值当做id来识别使用(项目中尽量不要让表单的name和其他元素的id相同)
3、如果我们把js放在结构的下面,我们可以直接使用id值来获取这个元素(不需要通过getElementById获取),而且这种方式会把页面中所有id是他的元素都获取到(元素对象 / 元素集合) => 不推荐
//=>获取页面中id值为#box1的所有元素标签
var allList = document.getElementByTagName('*'),
result = [];
for(var i=0;i<allList.lenght;i++){
var item = allList[i];
item.id === 'box1'?result.push(item):null;
}
comsole.log(result);
getElementsByTagName
上下文是可以自己来指定
获取到的结果是一个元素集合(类数组集合)1、获取的结果是集合,哪怕集合中只有一项,我们想要操作的是这一项(元素对象),需要先从集合中获取出来,然后再操作
<body>
<div></div>
<div></div>
<div></div>
</body>
<script>
var bodyBox = document.getElementsByTagName('body');
bodyBox.getElementsByTagName('div');//->Uncaught TypeError:bodyBox.getElementsByTagName is not a function 此时的bodyBox是一个类数组集合,我们需要使用的事其中的第一项,而不是整个集合
bodyBox[0].getElementsByTagName('div')
</script>
2、在指定的上下文中,获取所有子子孙孙元素中标签名叫作这个的(后代筛选)
<div id='box'>
<ul>
<li></li>
……
</ul>
<div>
<ul>
<li></li>
……
</ul>
</div>
……
</div>
<script>
box.getElementsByTagName('li');
box.getElementsByTagName('div');
</script>
getElementsByClassName
上下文也可以随意指定
获取的结果也是一个元素集合(类数组集合)1、真实项目中我们经常会通过样式类名来获取元素,getElementsByClassName这个方法在IE6~8中是不兼容的
function byClass(strClass, context){
context = context || document;
if(isHighVersion) return [].slice.call(context.getElementsByClassName(strClass));
strClass = strClass.replace(/^\s+|\s+$/g, '').split(/\s+/g);
var tagList context.getElementsByTagName('*'),
result = [];
for(var i=0;i<tagList.length;i++){
var item = tagList[i],
itemClass = item.className;
var isMatch = true;
for(var k = 0; k < strClass.length; k++){
var reg = new RegExp('(^| +)' + strClass[k] + '( +|&)');
if(!ret.test(itemClass)){
isMatch = false;
break;
}
}
isMatch ? result.push(item) : null;
}
return result;
}
getElementsByName
通过元素的NAME属性值获取一组元素(类数组:节点集合NodeList)
它的上下文也只能是document1、IE浏览器只能识别表单的name属性值,所以我们这个方法一般都是用来操作表单元素的
document.documentElement / documnt.body
获取HTML或者BODY(一个元素对象)
document.documentElement.clienWidth || document.body.clienWidth //=>获取当前浏览器窗口可视区域的宽度
//=>clientHeight 是获取高度
querySelector / querySelectorAll
在IE6~8下不兼容,而且也没什么特别好的处理他的兼容,所以这两个方法一般多用于移动端开发使用
querySelector:获取一个元素对象
querySelectorAll:获取的是 一个元素集合只要是CSS支持的选择器,这里大部分都支持
document.querySelectorAll('#box1'); //获取ID为box1的元素对象
document.querySelectorAll('.box'); //获取类名为box的元素对象
document.querySelectorAll('div'); //获取文档中所有的div标签元素集合
document.querySelectorAll('body>div');//获取body元素瞎的所有div标签元素集合
document.querySelectorAll('#box2 li');//获取id为box2下所有的li标签元素集合
DOM的节点
node:节点,浏览器认为在一个HTML页面中所有内容都是节点(包括标签、注释、文字文本等)
元素节点:HTML标签
文本节点:文字内容(高版本浏览器会把空格和换行也当做文本节点)
注释节点
document文档节点、
……
元素节点
nodeType:1
nodeName:大写标签名(在部分浏览器的怪异模式下,我们写的标签名是小写,他获取的就是小写,反之……)
nodeValue:null
[curEle].tagName
:获取当前元素的标签名(获取的标签名一般都是大写)
文本节点
nodeType: 3
nodeName: #text
nodeValue:文本内容
注释节点
nodeType:8
nodeName:#comment
nadeValue:注释内容
文档节点
nodeType:9
nodeName:#document
nodeValue:null节点是用来描述页面中每一部分之间关系的,只要我们可以获取页面中的一个节点,那么我们就可以通过相关的属性和方法获取页面中所有的节点
childNodes
获取当前元素所有的子节点(节点集合:类数组)
注:不仅仅是元素子节点,文本,注释等都会包含在内;子节点说明只是在儿子辈分中查找:
children
获取所有元素子节点(元素集合)
在IE6~8下获取的结果和标准浏览器中有区别(IE6~8中会把注释节点当做元素节点获取到)
parentNode
获取当前元素的父节点(元素对象)
previousSibling / nextSibling
previousSibling:获取当前节点的上一个哥哥节点(不一定是元素节点也可能是文本或注释)
nextSibling:获取当前节点的下一个弟弟节点
previousElementSibling / nextElementSibling
previousElementSibling:获取当前节点的上一个哥哥元素节点
nextElementSibling:获取当前节点的下一个弟弟元素节点
IE6~8下不兼容
firstChild / lastChild
firstChild:当前元素所有子节点中的第一个(也不一定是元素节点,可能是文本和注释)
lastChild:当前元素所有子节点中的最后一个firstElementChild lastElementChild (IE6~8不兼容)
DOM的增删改
真实项目中,我们偶尔会在JS中动态创建一些HTML标签,然后把其增加到页面中
document.createElement
在JS中动态创建一个HTML标签
appendChild
容器.appendChild(新元素)
把当前创建的新元素添加到容器的末尾位置
insertBefore
容器.insertBefore(新元素,老元素)
在当前容器中,把新创建的元素增加到老元素之前真实项目中很多需求都是通过动态创建元素来完成的,其中有一个需求:解析一个URL地址每一部分的信息(包含问号传递的参数值)
1、纯字符串拆分截取
2、编写强大的正则,捕获到需要的结果
3、通过动态创建一个A标签,利用A标签的一些内置属性来分别获取每一部分的内容
……
var link = document.createElement('a');
link.href = 'http://www.zhufengpeixun.cn/stu/?name=zxt&age=27#teacher'
//->此处地址就是我们需要解析的URL
/*
* hash:存储的事哈希值 ‘#teacher’
* hostname:存储的是域名 ‘www.zhufengpeixun.cn’
* pathname:路径 ‘/stu/’
* protocol:协议'http:'
* search:存储的是问号传递的参数值,没有传递是空字符串‘?name=zxt&age-27’
*/
function queryURLParameter(url){
var link = document.createElement('a');
link.href = url;
var search = link.search,
obj = {};
if(search.length === 0)return;
search = search.substr(1).split(/&|=/g);
for(var i=0;i<search.length;i+=2){
var key=search[i],
value=search[i+1];
obj[key]=value;
}
link=null;
return obj;
}
removeChild
容器.removeChild(元素)
在当前容器中把某一个元素移除掉
replaceChild
容器.replaceChild(新元素,老元素)
在当前容器中,拿新元素替换老元素
cloneNode
元素.cloneNode(false/true)
把原有的元素克隆一份一模一样的,false:只克隆当前元素本算,true:深度克隆,把当前元素本身以及元素的所有后代都进行克隆
[set/get/remove]Attribute
给当前元素设置/获取/移除 属性的(一般操作的都是他的自定义属性)
box.setAttribute(‘myindex’,0)
box.getAttribute(‘myindex’)
box.removeAttribute(‘myindex’)使用xxx.index=0 和 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结构存在映射关系的(设置的自定义属性可以呈现在结构上)
previousSibling 获取当前元素的上一个哥哥节点,兼容写法
首先获取当前元素的上一个哥哥节点,判断当前获取的节点是否为元素节点(nodeType===1),如果不是,基于当前获取的节点,找他的上一个哥哥节点……(找几次不知道)一直到找到的节点是元素节点为止
如果在查找过程中,发现没有上一个哥哥节点了(找到头了),则不在继续查找
使用 while 循环 while(条件){循环体}
function prev(curEle){
var p = curEle.previousSibling;
while(p && p.nodeType !==1){
p.curEle.previousSibling;
}
return p;
}
Date日期操作基础讲解
Date是日期类,通过他可以对时间进行处理
var time = new Date(); //->获取当前客户端本机时间(当前获取的时间不能作为重要的参考依据)
//->获取是的结果是一个日期格式的对象:Tue Mar 30 2021 00:46:17 GMT+0800 (中国标准时间)
//->typeof new Date() ->'object'
//->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-01-01 00:00:00’的毫秒差
var time = new Date('2017-10-22');//=>当new date中传递一个时间格式的字符串,相当于把这个字符串转换为标准的时间对象格式(转换完成后,就可以调取上面我们讲的哪些方法了)
//=>时间格式的字符串
'2021-03-30' (IE下识别不了)
'2021/03/30'
'2021/03/30 16:15:34'
1617036377914 (如果传递的是距离1970年的那个毫秒差,也是可以识别转换的,但是只能是数字,不能是字符串)
倒计时考试
var timeBox = document.getElementById('timeBox');
function computer() {
var nowTime = new Date(), //->当前时间
targeTime = new Date('2021-03-30 01:57:00'); //最终时间
var spanTime = targeTime - nowTime; //->获取的是两个时间之间的毫秒差
// 已到达时间我们就可以提醒开始考试了
if (spanTime <= 0) {
timeBox.innerHTML = '开始考试';
window.clearInterval(timeBox);
return;
}
//还未到达开始时间,在总的毫秒差中计算出时,分,秒
var hour = Math.floor(spanTime / (1000 * 60 * 60)); // 计算时
spanTime -= hour * 1000 * 60 * 60; //把小时占据的毫秒去除掉,剩下的计算分,秒
var minute = Math.floor(spanTime / (1000 * 60));
spanTime -= minute * 1000 * 60; //把分钟占据的毫秒去除掉,剩下的计算秒
var sucond = Math.floor(spanTime / 1000);
//给个位时间前加0
hour<10?hour='0'+hour:null;
minute<10?minute='0'+minute:null;
sucond<10?sucond='0'+sucond:null;
// 将获取到的时,分,秒拼接
timeBox.innerHTML = hour + ':' + minute + ':' + sucond;
}
computer();
//每一秒钟都重新执行computer();
window.setInterval(computer,1000);