欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

js小结

程序员文章站 2024-01-20 20:44:28
...

JavaScript包含:

​ ES:描述了该语言的语法和基本对象

​ DOM:文档对象模型,描述处理网页内容的方法和接口

​ BOM:浏览器对象模型,描述与浏览器进行交互的方法和接口

创建变量:var,let,const,函数,类,模块导入,symbol创建唯一值

命名规范:区分大小写、驼峰命名、关键字保留字

数据类型:

  • 基本数据类型:

    • 数字number、

      常规数字和NaN

    • 字符串string、

      所以用单引号,双引号,反引号包起来的都是字符串。反引号为es6的模板字符串

    • 布尔boolean、

      true,false

    • 空对象指针null、

    • 未定义undefined

  • 引用数据类型

    • 对象数据类型object

      • {}普通对象
      • []数组对象
      • 正则对象
      • Math数学函数对象
      • 日期对象
    • 函数数据类型function

number数字类型

包含:常规数字、NaN

NaN

not a number:不是一个数,但它隶属于数字类型。NaN和任何值包括自己都不相等

// 验证是否为数字 NaN:不是一个数
    console.log('aa' == NaN);//false
    console.log(10 == NaN);//false
    console.log(NaN == NaN); //false

所以不能用相等的方式判断是否为有效数字

isNaN

检测一个值是否为非有效数字,不是有效数字返回true,是有效数字返回false

//isNaN([val])
	console.log(isNaN(10));  //false
	console.log(isNaN('a'));//true
    console.log(isNaN('10'));//false

在使用isNaN进行检测的时候,首先会验证检测的值是否为数字类型,如果不是,先基于Number()这个方法,把值转换为数字类型,再进行检测。

	console.log(isNaN('10'));//false
	// Number('10') => 10
    // isNaN(10) =>false

把其它类型值转换为数字类型

  • Number([val])

  • parseInt/parseFloat([val],[进制]):也是转换为数字的方法,对于字符串来说,它是从左到右依次查找有效数字字符,直到遇到非有效数字字符,停止查找(不管后面是否还有数字,都不再找了),并把找到的当做数字返回。

  • ==进行比较的时候,可能出现把其它类型值转为数字:

    eg: ‘10’ == 10结果为true,因为是先将字符串转为数字再作比较。

    布尔转为数字

        console.log(Number(true)); //1
        console.log(Number(false)); //0
        console.log(isNaN(false)); //false
    
    	console.log(Number(null));//0
        console.log(Number(undefined));//NaN
    

    把引用数据类型转换为数字,是先把他基于toString方法转换为字符串,然后再转换为数字

        console.log(Number({name:'10'}));//NaN
        console.log(Number({}));//NaN
        console.log(Number([]));//0
        console.log(Number([12]));//12
        console.log(Number([12,23]));//NaN
    

    总结:出现非有效数字就是NaN

        let str = '12.5px'
        console.log(Number(str)); //NaN
        console.log(parseInt(str));//12
        console.log(parseFloat(str));//12.5
    	console.log(parseFloat('width:12.5px'));//w不是有效数字所以返回NaN
    

String字符串类型

所有用单引号,双引号,反引号抱起来的都是字符串

把其它类型值转换为字符串

  • [val].toString()
  • 字符串拼接

null和undefined禁止直接toString()

    let a = 12
    console.log(a.toString());//'12'
    console.log('10' + 10); //1010
    console.log('10' - 10); //0
    let a = 10 + null + true + [] + undefined + 'lj' + null + [] + 10 + false
    console.log(a); //11undefinedljnull10false
    /*
      10 + null -> 10 + 0 =10
      10 + true -> 10 + 1 =11
      11 + [] -> 11 + '' = '11' 空数组变为数字,先要经历变为空字符串,遇到字符串,啥也别想直接变为字符		  串拼接
    */

boolean布尔数据类型

只有两个值true/false

把其它类型值转换为布尔类型

只有0,NaN,’ ',null,undefined五个值转换为false,其余都转换为true(而且没有任何特殊情况)

  • Boolean([val])

  • !/!! !!是取反再取反,相当于Boolean()

  • 条件判断

        if(1) {
          console.log('hh');
        }
    
        if('3px' + 3) {
          //'3px3'
          console.log('heihei');
        }
    
        if('3px' - 3) {
          // =>NaN-3=NaN  =>0
          console.log('hehe');
        }
    

    只输出前两个

null/undefied

null和undecided都代表没有

  • null:意料之中(一般开始不知道值,我们手动先设置为null,后期再给予赋值操作)

    eg:

    let num = null; 
    //=>let num = 0 一般最好用null作为初始空值,因为0不是空值,他在栈内存中有自己的存储空间
    num = 12;
    
  • undefined:意料之外 (不是我能决定的) 创建一个变量没有赋值,默认值是undefined

Object对象数据类型-普通对象

  • {[key]:[value],…} 任何一个对象都是由零到多组键值对(属性名:属性值)组成的,并且属性名不能重复

        let person = {
              name:'lj',
              age:22,
              height:185
        }
        //获取属性名对应的属性值 =>对象.属性名
        //                     =>对象[属性名] 属性名是数字或者字符串格式的
        //如果当前属性名不存在,则为undefined
        //如果属性名是数字,则不能使用点的方式获取属性值,[]可以获取
        console.log(person.name);
        console.log(person['name']);
    
        //删除属性
        //=>真删除 属性彻底删掉
        delete person['height'];
    
        //=>假删除 属性还在,值为空
        person.age = null;
        console.log(person);
    

    记得中括号里是字符串

数组

//数组是特殊的对象 1.我们中括号中设置的是属性值,它的属性名默认生产的有序数字,从0开始递增,并把其称为索引
                  //  2.天生默认一个属性名length,存储数字的长度
    let ary = [12,'hh',23,true];
    console.log(ary);
    console.log(ary.length);//4
    console.log(ary['length']);//4

    //输出数组的属性值 第一项索引为0 最后一项索引为ary.length-1
    console.log(ary[1]); //hh

    //向数组末尾增加元素
    ary[ary.length] = 100
    console.log(ary); //12,'hh',23,true,100

JS的数据类型检测

  • typeof [val]:用来检测数据类型的运算符(不是方法)

    /*
          基于typeof检测出来的结果
          1.首先是一个字符串
          2.字符串中包含对应的类型
    
          局限性
          1.typeof null => object 但是null不是对象
          2.基于typeof无法细分出当前值是普通对象还是数组对象,因为只要是对象数据类型,返回的结果都是"object"
        */
        let a = NaN
        console.log(typeof a);  //number
    
        console.log(typeof typeof typeof[]);//从右到左检测
        //=>typeof [] => "object"
        //=>typeof "object" => "string"
        //因为typeof检测的结果都是字符串,所以只要两个及以上同时检测,最后结果必然是"string"
    
  • instanceof:用来检测当前实例是否隶属于某个类

  • constructor:基于构造函数检测数据类型(也是基于类的方式)

  • Object.prototype.toString.call():用来检测类型最好的方法

vscode 07代码…

循环

  • for循环
  • for in循环
  • for of循环(ES6新增)
  • while循环
  • do while循环

continue和break

continue:结束当前这轮循环(continue后面的代码不再执行),继续执行下一轮循环

break:强制结束整个循环(break后面的代码不再执行),而且整个循环直接结束

元素对象的深层理解

    let box = document.getElementById('box');  //返回的是一个对象
    //通过方法获取的元素是对象数据类型的值
    //console.log(typeof box) =>"object"

    //基于dir能看到对象的详细信息
    /*
      id:操作元素的ID值
      className:操作元素的class样式类的值
      innerHTML:操作的元素的内容(可以识别标签)
      innerText:和innerHTML的区别是不能识别标签
      style:操作元素的行内样式 属性值是一个新的对象(CSSStyleDeclaration) box.style.display 即box是对象 style又是一个新的对象 display是style里的属性
      
      tagName:获取元素的标签名(一般大写)
      ....
      使用dir可以看到有许多onxxx事件,box本身是一个对象 所以可以通过对象名.属性名操作
      eg:box['onclick'] = function(){} 等同于box.onclick = function(){} 底层原理是因为onclick是它的一个属性
    */
    console.dir(box);

函数

函数就是一个方法或者一个功能体,函数就是把实现某个功能的代码放在一起进行封装。以后想再次实现这个功能,只需调用此函数。

  • 创建函数
    • 形参
    • 返回值
  • 执行函数
    • 实参

函数返回值相关

//函数中的返回值
    //函数执行的时候,函数体内部创建的变量我们是无法获取和操作的,后续闭包。如果想要获取内部的信息,我们需要return 没有写return 那么函数默认返回值是undefined
    function sum(n,m) {
      let result = n + m;
      //return的一定是值:此处是把result变量存储的值返回给外面
      return result;
    }
    let a = sum(10,20);
    console.log(a);
    // console.log(result); 直接在外面输出不写return的话会报uncaught referenceError
    //函数体遇到return 后面代码不再执行了

匿名函数

    //匿名函数

    //匿名函数之函数表达式:把一个匿名函数本身作为值赋值给其他东西,这种函数一般不是手动触发执行,而是靠其他程序驱动触发执行,例如触发某个事件的时候把它执行。
    // document.body.onclick = function() {};
    // setTimeout(() => {}, timeout);

    //立即执行函数
    (function(n) {
      //n=>100
    })(100);

函数中的arguments

arguments为函数内置的实参集合

任意数求和(执行函数的时候,传递N个值实现求和)

1.传递实参的个数不定

2.传递的值是否为有效数字不定

=>把传递的有效数字进行相加求和

arguments:函数内置的实参集合

1.类数组集合,集合中存储着所有函数执行时,传递的实参信息

2.不论是否设置形参,arguments都存在

3.不论是否传递实参,arguments也都存在

你传或不传 arguments就在这里

arguments.callee:存储的是当前函数本身(一般不用的,因为JS严格模式下禁止使用这些属性)

箭头函数

简单

	function sum(n,m) {
      return n + m
    }

    //改写成箭头函数
    let sum = (n,m) => {
      return n + m
    }
    //如果函数体中只有一行return,可以省略return和大括号;只有一个参数可省略小括号
    let sum = (n,m) => n + m

    console.log(sum(10,30));
    function fn(n) {
      return function(m) {
        return n + m
      }
    }

    let fn = n => m => n + m  //同上
	//形参赋值默认值:当没有给形参传递实参的时候,执行默认值
    let sum = (n = 0,m = 0) => n + m

箭头函数中没有arguments,但是可以使用剩余运算符…arg

Math

数学函数 :但它不是一个函数,是一个对象,对象中存储了很多操作数字的属性方法。

扩展:获取[n-m]之间的随机整数

包含n也包含m

n<m

Math.round(Math.random()*(m-n)+n)

数组及数组中常用的方法

  • 方法的作用和含义

  • 方法的实参(类型和含义)

  • 方法的返回值

  • 原来的数组是否会发生改变

1.实现数组增删改的方法

这一部分方法都会修改原数组

push

向数组末尾增加内容

@params 多个任意类型

@return 新增后数组的长度

let ary = [10,20];
let res = ary.push(30,'a');
console.log(res,ary);//4 [10,20,30,'a']
ary[ary.length-1+1] = 40;//原生js末尾添加元素

两种方法,但是push可以返回数组长度

unshift

向数组开始位置增加内容

@params 多个任意类型

@return 新增后数组的长度

let ary = [10,20];
let res = ary.unshift(30,'a');
console.log(res,ary);//4 [30,'a',10,20]

shift

删除数组第一项

@params 无

@return 删除的那一项

let ary = [10,20];
let res = ary.shift();
console.log(res,ary);//10 [20]

原生:基于原生中的delete,把数组当成普通的对象,确实可以删除掉某一项内容,但是不会影响数组本身的结构特点(length不会跟着修改),真实项目中不会这样使用

let ary = [20,40,50];
delete ary[0]
console.log(ary); //20 [empty,40,50]

pop

删除数组最后一项

@params 无

@return 删除的那一项

let ary = [10,20,30];
let res = ary.pop();
console.log(res,ary);//30 [10,20]

原生删除最后一项:数组长度干掉一位,默认最后一项

ary.length--;

splice

实现数组的增加,删除,修改。

@params n,m都是数字 从索引n开始删除m个元素(m不写的话,是默认删除到末尾)

@return 把删除的部分用新数组存储起来返回

删除:

let ary = [10,20,30,40,50,60,70];
let res = ary.splice(2,4)
console.log(res,ary);//[30,40,50,60,70]  [10,20,30]

清空数组

ary.splice(0)

删除最后一项和第一项,可以代替pop和shift

//删除最后一项和第一项,可以代替pop和shift
ary.splice(ary.length-1)
ary.splice(0,1)

所以删除数组最后一项一共三种方法:pop,length–,ary.splice(ary.length-1)

增加、修改

@params n,m,x 从索引n开始删除m个元素,用x占用删除的部分(修改)

​ n,0,x 从索引n开始,一个都不删,把x放到所以n的前面(增加)

@return 把删除的部分用新数组存储起来返回

//修改
let ary = [10,20,30,40,50];
let res = ary.splice(2,2,90,'zz');
console.log(res,ary);//[30,40]  [10,20,90,"zz",50]
//增加
let ary = [10,20,30,40,50];
let res = ary.splice(2,0,90,'zz');
console.log(res,ary);//[10,20,90,'zz',30,40,50]
//向数组末尾追加
let ary = [10,20,30,40,50];
let res = ary.splice(ary.length,0,'z','h');
console.log(res,ary);
//向数组开头追加
ary.splice(0,0,'z')
总结
  • 向数组末尾追加元素:
    • push:返回结果为新增数组长度,数组不改变
    • ary.length:操作原生JS键值对
    • splice:
  • 向数组开始追加元素:
  • 删除最后一项:
  • 删除第一项:
    • shift:

数组的查询和拼接

此组学习的方法,原数组不会改变

slice

实现数组查询

@params n,m 都是数字 从索引n开始,找到索引为m的地方(不包含m这一项) 不写m就找到末尾

@return 把找到的内容以一个新数组的形式返回

let ary = [10,20,30,40,50];
let res = ary.slice(1,3)
console.log(res); //[20,30]

concat

实现数组拼接 (也可以作为追加末尾元素使用)

@params 多个任意类型值

@return 拼接后的新数组(原来数组不变)

let ary1 = [10,20,30];
let ary2 = [40,50];
let res = ary1.concat(ary2)
console.log(res);//[10,20,30,40,50]

把数组转换为字符串

把数组转换为字符串:toString

原有数组不变

@params

@return 转换后的字符串 用逗号分隔

let ary = [10,20,30];
let res = ary.toString()
console.log(res);//10,20,30

join

把数组转换为字符串:join

原有数组不变

@params 指定的分隔符(字符串格式)

@return 转换后的字符串 用指定分隔符分隔

let ary = [10,20,30];
let res = ary.join('')//没有指定分隔符,空字符串所以输出102030 一般加' '
console.log(res);

eval把字符串变为JS表达式 此处用于数组求和:

let ary = [10,20,30];
let res = ary.join('+')
console.log(eval(res)); //60

检测数组中是否包含某一项

indexOf/lastIndexOf

检测当前项在数组中第一次或最后一次出现位置的索引值(IE6-8不兼容)

原来数组不变

@params 要检索的这一项内容

@return 这一项出现的位置索引值(数字),如果数组中没有这一项,返回-1

let ary = [10,20,30];
let res = ary.indexOf(10)
console.log(res); //0
let ary = [10,20,30,10];
let res = ary.lastIndexOf(10);
console.log(res); //3

ES6中includes也可以判断 返回true/false

数组的排序或者排列

reverse

翻转数组

原来数组会改变

@params

@return 返回排列后的新数组

let ary = [10,20,30,90];
ary.reverse()
console.log(ary); //[90,30,20,10]

sort

数组排序

原来数组会改变

@params 可以没有,也可以是个函数

@return 返回排序后的新数组

let ary = [12,15,9,28,10,22];
let res = ary.sort()
console.log(res); // [10, 12, 15, 22, 28, 9] 是有问题的

sort方法中如果不传递参数,是无法处理10以上数字排序的(它默认按照每一项第一个字符来排)

实现多位数正常排序需要在sort中传递一个函数:a-b为升序;b-a为降序

let ary = [12,15,9,28,10,22];
let res = ary.sort((a,b) => {
    //a,b是相邻的两项
    return a - b; //b-a是由大到小
})
console.log(res); // [9, 10, 12, 15, 22, 28]

遍历数组中每一项的方法
forEach

遍历数组中的每一项

原来数组会改变

@params 回调函数

@return 无

let ary = [12,15,9,28,10,22];
    ary.forEach((item,index) => {
      //数组中有多少项,函数就会被默认执行多少次
      console.log(item);
    })

array.prototype 查看数组方法

map

filter

find

reduce

some

every

数组去重

方案一:

let ary = [1,1,2,3,4,1,3,2];
//方案一:循环原有数组中的每一项,每拿到一项都往新数组中添加
//添加之前验证新数组中是否存在这一项,不存在则添加
let newAry = [];
for (let i = 0; i < ary.length; i++) {
    let item = ary[i];
    if (newAry.includes(item)) {
        //如果存在这一项,就不再增加到新数组中
        continue;
    }
    newAry.push(item);
}
console.log(newAry);

foreach优化:

//使用foreach优化上述代码
let ary = [1,1,2,3,4,1,3,2];
let newAry = [];
ary.forEach((item) => {
    if (newAry.includes(item)) return;
    newAry.push(item);
})
console.log(ary);

方案二:

方案三:

字符串中常用的方法

所有用单引号,双引号,反引号 包起来的都是字符串

let str = 'hhhhnihaoa'
str.length //获取字符串长度
str[0]  //获取索引为0的字符
str[str.length-1] //获取最后一项的索引
str[10000] //undefined 不存在这个索引

for(let i=0;i<str.length;i++){
    let char = str[i];
    console.log(char);
}

charAt/charCodeAt

charAt:根据索引获取指定位置的字符

charCodeAt:获取指定字符的ASCII码值

@params:n[number] 获取字符指定的索引

@return 返回查找到的字符(找不到返回空字符串而不是undefined,或者对应的编码值)

let str = 'nihaojintian';
console.log(str[0]);//n
console.log(str.charAt(0));//n

//虽然都能实现,但当不存在时
console.log(str[1000]);//undefined
console.log(str.charAt(1000));//不输出东西

substr/substring/slice

实现字符串的截取

substr(n,m) 从索引n开始,截取m个字符 m不指定的话就截取到末尾。后两个方法相同

substring(n,m) 从索引n开始找到索引为m处(不含m)

slice(n,m) 和substring一样,从索引n开始找到索引为m处(不含m)。但slice可以支持负数索引(不改变原数组)

indexOf/lastIndexOf

验证字符是否存在

indexOf(x,y) 获取x第一次出现位置的索引,y是控制查找起始位置索引

lastIndexOf(x) 最后一次出现位置的索引 如果没有这个字符 返回-1

同includes

toUpperCase/toLowerCase

字符串中字母的大小写转换

toUpperCase:转大写

toLowerCase:转小写

split

split([分隔符]):把字符串按照指定的分隔符拆分成数组(对应数组中的join)

split支持传递正则表达式

//把字符串的|变为,
//首先变为数组,然后使用join再变成字符串
let str = 'hh|heihei|xixi';
let ary = str.split('|');
str = ary.join(',')
console.log(str);//hh,heihei,xixi

replace

replace(老字符,新字符):实现字符串替换

//在不使用正则表达式的情况下,执行一次replace只能替换一个字符
let str = '我-是-谁'
str = str.replace('-','+')
console.log(str);//我+是-谁

//使用正则
let str = '我-是-谁'
str = str.replace(/-/g,'+')
console.log(str);//我+是+谁

实现一些常用的需求

时间字符串的处理

    //方案一:一路replace
    //2020年6月10日12时09分22
    time = time.replace('-','年').replace('-','月').replace(' ','日').replace(':','时').replace(':','分').replace(':','秒')

    //方案二:获取年月日时分秒的索引值,最后再拼接
    //获取值的方法一:基于indexOf获取指定符号索引,使用substring一点点截取
    let n = time.indexOf('-')
    let m = time.lastIndexOf('-')
    let x = time.indexOf(' ')
    let y = time.indexOf(':')
    let z = time.lastIndexOf(':')
    let year = time.substring(0,n)
    let month = time.substring(n+1,m)
    let day = time.substring(m+1,x)
    console.log(year,month,day); //2020 6 01
    //获取值的方法二:基于split一项项的拆分
    let n = time.split(' ') //["2020-6-01", "12:09:22"]
    let m = n[0].split('-') //["2020", "6", "01"]
    let x = n[1].split(':')
    console.log(x); //["12", "09", "22"]
	//获取值的方法三:使用正则最快捷
    let ary = time.split(/(?: |-|:)/g)
    console.log(ary); //["2020", "6", "01", "12", "09", "22"]
    let addZero = val => val =  val.length < 2 ? '0' + val : val
    time = ary[0] + '年' + addZero(ary[1]) + '月' + addZero(ary[2]) + '日'
    console.log(time); //2020年06月01日

合理利用正则省事

实现方法queryURLParameter

有一个URL地址’http://www.baidu.com/stu/?lx=1&name=AA&sex=man#teacher’;地址问号后面的内容是我们需要解析出来的参数信息
{
lx:1,
name:‘AA’,
sex:‘man’

HASH:‘zz’

}

//实现效果:{
// lx:1,
// name:'AA',
// sex:'man'
// HASH:'zz'
// }

var url = 'http://www.baidu.com/stu/?lx=1&name=AA&sex=man#teacher';
//1.获取问号和井号在字符串中索引位置
let askIndex = url.indexOf('?')
let wellIndex = url.indexOf('#')
//问号井号中间内容
let askText = url.substring(askIndex + 1,wellIndex) //"lx=1&name=AA&sex=man"
let wellText = url.substring(wellIndex + 1) //"teacher"

//2.问号后面值的详细处理
let result = {}
let askAry = askText.split('&') //["lx=1", "name=AA", "sex=man"]
askAry.forEach(item => {
    let n = item.split('=') //["lx", "1"]  ["name", "AA"] ["sex", "man"]
    //使拆分后的每个数组第一项作为属性名,第二项作为属性值
    let key = n[0]
    let value = n[1]
    result[key] = value  //给一个空对象加入属性名为key的value值

})
//还差一个属性值 自己手动加入
result['HASH'] = 'zz'
console.log(result);

hash值如果有一定在最后。封装成一个方法

queryURLParameter :获取URL地址中问号传参的信息和哈希值

@params url[string] 要解析的url字符串

@return [object] 包含参数和哈希值信息的对象

/*
 * 1.先找到问号,把问号后面的信息截取下来即可
 *  A.首先我们需要验证是否存在#哈希值,存在我们从问号开始截取到#,不存在我们直接截取到字符串的末尾
 * 2.以&进行拆分(数组)
 * 3.遍历数组中的每一项,把每一项在按照=进行拆分,把拆分后的第一项作为对象的属性名,第二项作为属性值进行存储即可
 */
function queryURLParameter (url) {
    //获取?和#后面的信息
    //分情况:?存在 #不存在时:直接从?截取到末尾即可; 末尾索引为length-1 后一项为length 所以截取到length
    //       ?不存在 #存在时:
    let askIn = url.indexOf('?'),
        wellIn = url.indexOf('#'),
        askText = '',
        wellText = '';
    //#不存在
    wellIn === -1 ? wellIn = url.length : null;
    //?存在
    askIn >= 0 ? askText = url.substring(askIn + 1,wellIn) : null;
    wellText = url.substring(wellIn + 1)
    //2.获取每一部分信息
    let result = {}
    wellText !== '' ? result['HASH'] = wellText : null;
    if(askText !== ''){
        let ary = askText.split('&')
        ary.forEach(item => {
            let itemAry = item.split('=')
            result[itemAry[0]] = itemAry[1]
        })
    }
}
var url = 'http://www.baidu.com/stu/?lx=1&name=AA&sex=man#teacher';
let obj = queryURLParameter(url);
console.log(obj);

提高眼界:

~function (pro) {
    pro.queryURLParameter = function () {
        var obj = {},
            reg = /([^?=&#]+)(?:=([^?=&#]+)?)/g;
        this.replace(reg, function () {
            var key = arguments[1],
                value = arguments[2] || null;
            obj[key] = value;
        });
        return obj;
    }
}(String.prototype);

var str = 'http://www.zhufengpeixun.cn/stu/?lx=1&name=&sex=#teacher';
console.log(str.queryURLParameter());

实现一个low的验证码:数字+字符共四位

验证码目的:防止外挂程序恶意批量注入

Dom

获取DOM元素的方法

getElementById

通过元素的ID获取指定的元素对象,使用的时候都是 document.getElementById('') 此处的document是限定了获取元素的范围,我们把它称之为“上下文(context)”

  1. getElementById的上下文只能是document

因为严格意义上,一个页面中的ID是不能重复的,浏览器规定在整个文档中既可以获取这个唯一的ID

2.如果页面中的ID重复了,我们基于这个方法只能获取到第一个元素,后面相同ID元素无法获取

3.在IE6~7浏览器中,会把表单元素(input…)的name属性值当做ID来使用(建议:以后使用表单元素的时候,不要让name和id的值有冲突)

getElementsByTagName

[context].getElementsByTagName在指定的上下文中,根据标签名获取到一组元素集合(所有)(HTMLCollection)

  1. 获取的元素集合是一个类数组(不能直接的使数组中的方法)

  2. 它会把当前上下文中,子子孙孙(后代)层级内的标签都获取到(获取的不仅仅是儿子级的)

  3. 基于这个方法获取到的结果永远都是一个集合(不管里面是否有内容,也不管有几项,它是一个容器或者集合),如果想操作集合中具体的某一项,需要基于索引获取到才可以

getElementsByClassName

[context].getElementsByClassName()在指定的上下文中,基于元素的样式类名(class=‘xxx’)获取到一组元素集合

1.真实项目中,我们经常是基于样式类来给元素设置样式,所以在JS中,我们也会经常基于样式类来获取元素,但是此方法在IE6~8下不兼容

兼容处理方案参考:

Node.prototype.queryElementsByClassName = function queryElementsByClassName() {
    if (arguments.length === 0) return [];
    var strClass = arguments[0],
        nodeList = utils.toArray(this.getElementsByTagName('*'));
    strClass = strClass.replace(/^ +| +$/g, '').split(/ +/);
    for (var i = 0; i < strClass.length; i++) {
        var reg = new RegExp('(^| +)' + strClass[i] + '( +|$)');
        for (var k = 0; k < nodeList.length; k++) {
            if (!reg.test(nodeList[k].className)) {
                nodeList.splice(k, 1);
                k--;
            }
        }
    }
    return nodeList;
};

getElementsByName

document.getElementsByName()它的上下文也只能是document,在整个文档中,基于元素的name属性值获取一组节点集合(也是一个类数组)

1.在IE浏览器中(IE9及以下版本),只对表单元素的name属性起作用(正常来说:我们项目中只会给表单元素设置name,给非表单元素设置name,其实是一个不太符合规范的操作)

querySelector

[context].querySelector() 在指定的上下文中基于选择器(类似于CSS选择器)获取到指定的元素对象(获取的是一个元素,哪怕选择器匹配了多个,我们只获取第一个)

querySelectorAll

在querySelector的基础上,我们获取到选择器匹配到的所有元素,结果是一个节点集合(NodeList)

  1. querySelector/querySelectorAll 都是不兼容IE6~8浏览器的(不考虑兼容的情况下,我们能用ById或者其它方式获取的,也尽量不要用这两个方法,这两个方法性能消耗较大)
let navList = document.querySelectorAll('.tab li:nth-child(2)')
//获得tab下的第二个li

document.head

获取HEAD元素对象

document.body

获取BODY元素对象

document.documentElement

获取HTML元素对象

document.documentElement

获取HTML元素对象

//=>需求:获取浏览器一屏幕的宽度和高度(兼容所有的浏览器)
document.documentElement.clientWidth || document.body.clientWidth

document.documentElement.clientHeight || document.body.clientHeight

面试题:获取当前页面中所有ID为HAHA的和元素(兼容所有的浏览器)

//=>不能使用querySelectorAll

/*
 * 1.首先获取当前文档中所有的HTML标签
 * 2.依次遍历这些元素标签对象,谁的ID等于HAHA,我们就把谁存储起来即可
 */
function queryAllById(id) {
    //->基于通配符*获取到整个文档中所有的HTML标签
    var nodeList = document.getElementsByTagName('*');

    //->遍历集合中的每一项,把元素ID和传递ID相同的这一项存储起来
    var ary = [];
    for (var i = 0; i < nodeList.length; i++) {
        var item = nodeList[i];
        item.id === id ? ary.push(item) : null;
    }
    return ary;
}
console.log(queryAllById('HAHA'));

节点 (node)

在一个HTML文档中出现的所有东西都是节点

  • 元素节点(HTML标签)
  • 文本节点(文字内容)
  • 注释节点(注释内容)
  • 文档节点(document)

每一种类型的节点都会有一些属性区分自己的特性和特征

  • nodeType:节点类型
  • nodeName:节点名称
  • nodeValue:节点值

元素节点
nodeType:1
nodeName:大写标签名
nodeValue:null

文本节点
nodeType:3
nodeName:’#text’
nodeValue:文本内容

在标准浏览器中会把空格/换行等都当做文本节点处理

注释节点
nodeType:8
nodeName:’#comment’
nodeValue:注释内容

文档节点
nodeType:9
nodeName:’#document’
nodeValue:null

描述节点之间关系的属性

parentNode

获取当前节点唯一的父亲节点

childNodes

获取当前元素的所有子节点

  • 子节点:只获取儿子级别的
  • 所有:包含元素节点、文本节点等

children

获取当前元素所有的元素子节点

在IE6~8中会把注释节点也当做元素节点获取到,所以兼容性不好

previousSibling

获取当前节点的上一个哥哥节点(获取的哥哥可能是元素也可能是文本等)

previousElementSibling:获取上一个哥哥元素节点(不兼容IE6~8)

nextSibling

获取当前节点的下一个弟弟节点

nextElementSibling:下一个弟弟元素节点(不兼容)

firstChild

获取当前元素的第一个子节点(可能是元素或者文本等)

firstElementChild

lastChild

获取当前元素的最后一个子节点

lastElementChild


需求一:获取当前元素的所有元素子节点

基于children不兼容IE低版本浏览器(会把注释当做元素节点)

let box = document.getElementById('box')
//标准浏览器(非IE6-8)中会把空格和换行当做文本节点处理(childNodes包含所有节点)
//如果只要元素节点 但在IE6-8下 使用children会把注释也当做元素节点
// console.log(box.children);

//获取指定上下文中所有的元素子节点
//@params context[element object] 指定的上下文元素信息
//@return [array] 返回所有的元素子节点集合
function getChildren(context) {
    //1.先获取所有的子节点
    let res = []
    let nodeList = context.childNodes
    //2.循环遍历所有的子节点,找出元素子节点 存储到res中
    for(var i = 0;i < nodeList.length;i++) {
        var item = nodeList[i]
        item.nodeType === 1 ? res.push(item) : null //因为元素节点nodeType为1
    }
    return res
}
//假如想获得box下所有的元素子节点
console.log(getChildren(box)); //3个li

需求二:获取当前元素的上一个哥哥元素节点

previousSibling:上一个哥哥节点
previousElementSibling:上一个哥哥元素节点,但是不兼容

/*
 * prev:get the last elder brother element node of the current element
 * @parameter
 *    curEle:[object] current element
 * @return
 *    [object] last elder brother element
 * by team on 2018/04/07 12:44
 */
function prev(curEle) {
    //=>先找当前元素的哥哥节点,看是否为元素节点,不是的话,基于哥哥,找哥哥的上一个哥哥节点...一直到找到元素节点或者已经没有哥哥了(说明我就是老大)则结束查找
    var pre = curEle.previousSibling;
    while (pre && pre.nodeType !== 1) {
        /*
         * pre && pre.nodeType !== 1
         *   pre是验证还有没有,这样写代表有,没有pre是null
         *   pre.nodeType是验证是否为元素
         */
        pre = pre.previousSibling;
    }
    return pre;
}

回去后扩展:jQuery中有next下一个弟弟元素节点,prevAll获取所有哥哥元素节点,nextAll获取所有弟弟元素节点,siblings获取所有兄弟元素节点,index获取当前元素的索引…

关于DOM的增删改

createElement 创建元素对象 document.createElement([标签名])

createTextNode 创建文本对象

appendChild

把一个元素对象插入到指定容器的末尾
[container].appendChild([newEle])

insertBefore

把一个元素对象插入到指定容器中某一个元素标签之前
[container].insertBefore([newEle],[oldEle])

cloneNode

把某一个节点进行克隆

[curEle].cloneNode():浅克隆,只克隆当前的标签
[curEle].cloneNode(true):深克隆,当前标签及其里面的内容都一起克隆了

removeChild

在指定容器中删除某一个元素

[container].removeChild([curEle])

set/get/removeAttribute

设置/获取/删除 当前元素的某一个自定义属性

var oBox=document.getElementById('box');

//=>把当前元素作为一个对象,在对象对应的堆内存中新增一个自定义的属性
oBox.myIndex = 10;//=>设置
console.log(oBox['myIndex']);//=>获取
delete oBox.myIndex; //=>删除

//=>基于Attribute等DOM方法完成自定义属性的设置
oBox.setAttribute('myColor','red'); //=>设置
oBox.getAttribute('myColor'); //=>获取
oBox.removeAttribute('myColor'); //=>删除

上下两种机制属于独立的运作*,不能互相混淆使用
- 第一种是基于对象键值对操作方式,修改当前元素对象的堆内存空间来完成
- 第二种是直接修改页面中HTML标签的结构来完成(此种办法设置的自定义属性可以在结构上呈现出来)

基于setAttribute设置的自定义属性值都是字符串

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-42BcQaEz-1594872955042)(E:/1Study/1前端笔记/珠峰/2018年第二期源码、笔记/基础知识公开课/0407DAY9/1523088229070.png)]

2020 6.11

复习前面的知识

var a = 12;
/*
    * 1.先声明一个变量a,没有赋值(默认值是undefined)
    * 2.在当前作用域中开辟一个位置存储12这个值
    * 3.让变量a和12关联在一起(定义:赋值)
    */
var b = a;
b = 13;
console.log(a);//12

var ary1 = [12, 23];
var ary2 = ary1;
ary2.push(100);
console.log(ary1); //[12, 23, 100]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HPJPKiaI-1594872955043)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-11_14-28-17.png)]

function sum() {
    var total = null;
    for (var i = 0; i < arguments.length; i++) {
        var item = arguments[i];
        item = parseFloat(item);
        !isNaN(item) ? total += item : null;
    }
    return total;
}
console.log(sum(12, 23, '34', 'AA'));
//直接写sum就是函数本身 sum()是执行这个函数 sum()结果为return的结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H7xHtCx3-1594872955043)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-11_14-36-47.png)]

变量提升

 /*
  * 变量提升:
  *   =>当栈内存(作用域)形成,JS代码自上而下执行之前,浏览器首先会把所有带 “VAR”/“FUNCTION” 关键词的进行提前 “声明” 或者 “定义” ,这种预先处理机制称之为 “变量提升”
  *
  *   =>声明(declare):var a  (默认值undefined)
  *   =>定义(defined):a=12 (定义其实就是赋值操作)
  *
  *   [变量提升阶段]
  *   =>带“VAR”的只声明未定义
  *   =>带“FUNCTION”的声明和赋值都完成了
  *
  *   =>变量提升只发生在当前作用域(例如:开始加载页面的时候只对全局作用域下的进行提升,因为此时函数中存储的都是字符串而已)
  *   =>在全局作用域下声明的函数或者变量是“全局变量”,同理,在私有作用域下声明的变量是“私有变量” [带VAR/FUNCTION的才是声明]
  *
  *   =>浏览器很懒,做过的事情不会重复执行第二遍,也就是,当代码执行遇到创建函数这部分代码后,直接的跳过即可(因为在提升阶段就已经完成函数的赋值操作了)
  */
  console.log(a);//=>undefined
  var a = 12;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lIICsZ1A-1594872955044)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-11_16-10-29.png)]

带var和不带var的区别

//=>在全局作用域下声明一个变量,也相当于给WINDOW全局对象设置了一个属性,变量的值就是属性值(私有作用域中声明的私有变量和WINDOW没啥关系)
/*
console.log(a);//=>undefined
console.log(window.a);//=>undefined
console.log('a' in window); //=>TRUE 在变量提升阶段,在全局作用域中声明了一个变量A,此时就已经把A当做属性赋值给WINDOW了,只不过此时还没有给A赋值,默认值UNDEFINED  in:检测某个属性是否隶属于这个对象
var a = 12;//=>全局变量值修改,WIN的属性值也跟着修改
console.log(a);//=>全局变量A  12
console.log(window.a);//=>WINDOW的一个属性名A  12

a = 13;
console.log(window.a);//=>13

window.a = 14;
console.log(a);//=>14
//=>全局变量和WIN中的属性存在 “映射机制”
*/

//=>不加VAR的本质是WIN的属性
/*
// console.log(a);//=>Uncaught ReferenceError: a is not defined
console.log(window.a);//=>undefined
// console.log('a' in window);//=>false
a = 12;//=>window.a=12
console.log(a);//=>12
console.log(window.a);//=>12
*/

var a = 12,
    b = 13; //这样写b带var

var a = b = 12;//这样写b是不带var的 等同于var a = 12;b = 12

习题:

console.log(a,b); //undefined undefined
var a = 12;
b = 12;
function fn() {
    console.log(a,b); //undefined 12
    var a = b = 13;
    console.log(a,b);//13 13
}
fn();
console.log(a,b); //12 13

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-no5KsvgK-1594872955045)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-11_18-05-39.png)]

作用域链的扩展

function fn() {
    /!*
     * 变量提升:无
     *!/
    // console.log(b);//=>Uncaught ReferenceError: b is not defined
    b = 13;
    //console.log('b' in window);//=>true  在作用域链查找的过程中,如果找到WIN也没有这个变量,相当于给WIN设置了一个属性B (window.b=13)
    console.log(b);//=>13
}

fn();
console.log(b);//=>13

在全局作用域下声明的变量都相当于给window加了一个属性 window是一个对象

重名问题的处理

/*
 * 1.带VAR和FUNCTION关键字声明相同的名字,这种也算是重名了(其实是一个FN,只是存储值的类型不一样)
 */
    var fn = 12;
    function fn() {
    }


/*
 * 2.关于重名的处理:如果名字重复了,不会重新的声明,但是会重新的定义(重新赋值)[不管是变量提升还是代码执行阶段皆是如此]
 */

/*
 * 变量提升:
 *   fn = ...(1)
 *      = ...(2)
 *      = ...(3)
 *      = ...(4)
 */
/*
fn();//=>4
function fn() {console.log(1);}
fn();//=>4
function fn() {console.log(2);}
fn();//=>4
var fn=100;//=>带VAR的在提升阶段只把声明处理了,赋值操作没有处理,所以在代码执行的时候需要完成赋值 FN=100
fn();//=>100() Uncaught TypeError: fn is not a function
function fn() {console.log(3);}
fn();
function fn() {console.log(4);}
fn();
*/

let创建的变量不存在变量提升

/*
 * 在ES6中基于LET/CONST等方式创建变量或者函数,不存在变量提升机制
 *
 *  =>切断了全局变量和WINDOW属性的映射机制
 *
 *  =>在相同的作用域中,基于LET不能声明相同名字的变量(不管用什么方式在当前作用域下声明了变量,再次使用LET创建都会报错)
 *
 *   虽然没有变量提升机制,但是在当前作用域代码自上而下执行之前,浏览器会做一个重复性检测(语法检测):自上而下查找当前作用域下所有变量,一旦发现有重复的,直接抛出异常,代码也不会在执行了(虽然没有把变量提前声明定义,但是浏览器已经记住了,当前作用域下有哪些变量)
 */
// console.log(a);//=>Uncaught ReferenceError: a is not defined
// let a = 12;
// console.log(window.a);//=>undefined
// console.log(a);//=>12

/*let a = 12;
console.log(a);
let a = 13;//=>Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);*/

/*b = 12;
console.log(b);//=>12
a = 12;//=>Uncaught ReferenceError: a is not defined
console.log(a);
let a = 13;
console.log(a);*/

let a = 10,
    b = 10;
let fn = function () {
    console.log(a, b);//=>Uncaught ReferenceError: a is not defined
    let a = b = 20;
    /*
     * let a=20;
     * b=20; //=>把全局中的 b=20
     */
    console.log(a, b);
};
fn();
console.log(a, b);

习题1:

var a = 12,
    b = 13,
    c = 14;
function fn(a) {
    console.log(a,b,c); //12 undefined 14 (c是全局的)
    var b = c = a = 20; //带var的在私有作用域变量提升阶段都声明为私有变量,和外界没有关系。
    //不带var不是私有变量,会向上级作用域查找
    console.log(a,b,c); //20 20 20
}
fn(a)
console.log(a,b,c); //12 13 20 ?
/*
 * JS中的内存分为堆内存和栈内存
 *   堆内存:存储引用数据类型值(对象:键值对  函数:代码字符串)
 *   栈内存:提供JS代码执行的环境和存储基本类型值
 *
 * [堆内存释放]
 *   让所有引用堆内存空间地址的变量赋值为null即可(没有变量占用这个堆内存了,浏览器会在空闲的时候把它释放掉)
 *
 * [栈内存释放]
 *   一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉(在栈内存中存储的值也都会释放掉),但是也有特殊不销毁的情况:
 *   1.函数执行完成,当前形成的栈内存中,某些内容被栈内存以外的变量占用了,此时栈内存不能释放(一旦释放外面找不到原有的内容了)
 *   2.全局栈内存只有在页面关闭的时候才会被释放掉
 *   ...
 *   如果当前栈内存没有被释放,那么之前在栈内存中存储的基本值也不会被释放,能够一直保存下来
 */

闭包

/*
 * [闭包]
 *   =>函数执形成一个私有的作用域,保护里面的私有变量不受外界的干扰,这种保护机制称之为“闭包”
 *
 *   =>市面上的开发者认为的闭包是:形成一个不销毁的私有作用域(私有栈内存)才是闭包
 */
/*
//=>闭包:柯理化函数
function fn() {
    return function () {

    }
}
var f = fn();
*/

/*
//=>闭包:惰性函数
var utils = (function () {
    return {

    }
})();
*/


//=>闭包项目实战应用
//==>真实项目中为了保证JS的性能(堆栈内存的性能优化),应该尽可能的减少闭包的使用(不销毁的堆栈内存是耗性能的)
//1.闭包具有“保护”作用:保护私有变量不受外界的干扰
//> 在真实项目中,尤其是团队协作开发的时候,应当尽可能的减少全局变量的使用,以防止相互之前的冲突(“全局变量污染”),那么此时我们完全可以把自己这一部分内容封装到一个闭包中,让全局变量转换为私有变量
/*
(function () {
    var n = 12;
    function fn() {

    }

    //...
})();
*/

//> 不仅如此,我们封装类库插件的时候,也会把自己的程序都存放到闭包中保护起来,防止和用户的程序冲突,但是我们又需要暴露一些方法给客户使用,这样我们如何处理呢?
//1.JQ这种方式:把需要暴露的方法抛到全局
/*
(function () {
    function jQuery() {
        //...
    }

    //...
    window.jQuery = window.$ = jQuery;//=>把需要供外面使用的方法,通过给WIN设置属性的方式暴露出去
})();
// jQuery();
// $();
*/

//2.Zepto这种方式:基于RETURN把需要共外面使用的方法暴露出去
/*
var Zepto=(function () {
    //...
    return {
        xxx:function () {

        }
    };
})();
Zepto.xxx();
*/

//2.闭包具有“保存”作用:形成不销毁的栈内存,把一些值保存下来,方便后面的调取使用

面向对象设计模式(OOP)

单例设计模式

/*
 * 单例设计模式(singleton pattern)
 *  1.表现形式
 *  var obj = {
 *      xxx:xxx,
 *      ...
 *  };
 *  在单例设计模型中,OBJ不仅仅是对象名,它被称为“命名空间[NameSpace]”,把描述事务的属性存放到命名空间中,多个命名空间是独立分开的,互不冲突
 *
 *  2.作用
 *  =>把描述同一件事务的属性和特征进行“分组、归类”(存储在同一个堆内存空间中),因此避免了全局变量之间的冲突和污染
 *  var pattern1={name:'xxx'}
 *  var pattern2={name:'xxx'}
 *
 *  3.单例设计模式命名的由来
 *  =>每一个命名空间都是JS中Object这个内置基类的实例,而实例之间是相互独立互不干扰的,所以我们称它为“单例:单独的实例”
 */

/*
var name = "陆相莹";
var age = 18;
var sex = "girl";

var name = "刘司南";
var age = 81;
var sex = "boy";
*/

/*
var person1={
    name:"陆相莹",
    age:18
};
var person2={
    name:"刘司南",
    age:81
};*/


/*
 * 高级单例模式
 *   1.在给命名空间赋值的时候,不是直接赋值一个对象,而是先执行匿名函数,形成一个私有作用域AA(不销毁的栈内存),在AA中创建一个堆内存,把堆内存地址赋值给命名空间
 *
 *   2.这种模式的好处:我们完全可以在AA中创造很多内容(变量OR函数),哪些需要供外面调取使用的,我们暴露到返回的对象中(模块化实现的一种思想)外边想用谁就暴露谁
 */
/*var nameSpace = (function () {
    var n = 12;
    function fn() {
        //...
    }
    function sum() {

    }
    return {
        fn: fn,
        sum: sum
    }
})();*/

/*
 * THIS
 *   1.给当前元素的某个事件绑定方法, 当事件触发方法执行的时候,方法中的THIS是当前操作的元素对象
 *   oBox.οnclick=function(){
 *      //=>this:oBox
 *   }
 *
 *   2.普通函数执行,函数中的THIS取决于执行的主体,谁执行的,THIS就是谁(执行主体:方法执行,看方法名前面是否有“点”,有的话,点前面是谁this就是谁,没有this是window)
 *   function fn(){//=>AAAFFF000
        console.log(1);
     }
     var obj={
        fn:fn //=>fn:AAAFFF000
     };

     //=>执行的是相同的方法(不同地方在于函数执行方法中的this是不一样的)
     obj.fn();//=>this:obj
     fn();//=>this:window

     //=>自执行函数执行,方法中的this是window
     ~function(){
         //=>this:window
     }();
 */

/*
var n = 2;
var obj={
    n:3,
    fn:(function (n) {
        n*=2;
        this.n+=2;
        var n=5;
        return function (m) {
            this.n*=2;
            console.log(m + (++n));
        }
    })(n)//=>obj.n会报错
};
var fn = obj.fn;
fn(3);
obj.fn(3);
console.log(n, obj.n);*/


/*
 * 模块化开发
 *   1.团队协作开发的时候,会把产品按照功能板块进行划分,每一个功能板块有专人负责开发
 *   2.把各个版块之间公用的部门进行提取封装,后期在想实现这些功能,直接的调取引用即可(模块封装)
 */

var utils=(function () {
    return {
        aa:function () {

        }
    }
})();

//=>少帅
var skipRender = (function () {
    var fn = function () {
        //...
    };
    //...
    return {
        init: function () {

        },
        fn:fn
    }
})();
skipRender.init();

//=>敏洁
var weatherRender = (function () {
    var fn = function () {

    };
    return {
        init: function () {
            fn();//=>调取自己模块中的方法直接调取使用即可
            skipRender.fn();//=>调取别人模块中的方法
        }
    }
})();
weatherRender.init();
/*
 * 基于构造函数创建自定义类(constructor)
 *   1.在普通函数执行的基础上“new xxx()”,这样就不是普通函数执行了,而是构造函数执行,当前的函数名称之为“类名”,接收的返回结果是当前类的一个实例
 *
 *   2.自己创建的类名,最好第一个单词首字母大写
 *
 *   3.这种构造函数设计模式执行,主要用于组件、类库、插件、框架等的封装,平时编写业务逻辑一般不这样处理
 */
/*function Fn() {

}

// Fn();//=>普通函数执行
var f = new Fn();//=>Fn是类 f是类的一个实例
var f2 = new Fn();//=>f2也是Fn的一个实例,f2和f是独立分开的,互不影响*/

/*
 * JS中创建值有两种方式
 *   1.字面量表达式
 *   2.构造函数模式
 */
// var obj = {};//=>字面量方式
// var obj = new Object();//=>构造函数模式
// //=>不管是哪一种方式创造出来的都是Object类的实例,而实例之间是独立分开的,所以 var xxx={} 这种模式就是JS中的单例模式

//=>基本数据类型基于两种不同的模式创建出来的值是不一样的
//> 基于字面量方式创建出来的值是基本类型值
//> 基于构造函数创建出来的值是引用类型
//->NUM2是数字类的实例,NUM1也是数字类的实例,它只是JS表达数字的方式之一,都可以使用数字类提供的属性和方法
// var num1 = 12;
// var num2 = new Number(12);
// console.log(typeof num1);//=>"number"
// console.log(typeof num2);//=>"object"

//=================================

/*function Fn(name, age) {
    var n = 10;
    this.name = name;
    this.age = age + n;
}

//=>普通函数执行
/!*
//1.形成一个私有的作用域
//2.形参赋值
//3.变量提升
//4.代码执行
//5.栈内存释放问题
Fn();
*!/

//=>构造函数执行
var f1 = new Fn('xxx', 20);
var f2 = new Fn('aaa', 30);

console.log(f1 === f2);//=>false:两个不同的实例(两个不同的堆内存地址)
console.log(f1.age);//=>30
console.log(f2.name);//=>'aaa'
console.log("name" in f1);//=>true name&age在两个不同的实例都有存储,但是都是每个实例自己私有的属性
console.log(f1.n);//=>undefined 只有this.xxx=xxx的才和实例有关系,n是私有作用域中的一个私有变量而已(this是当前类的实例)*/


/*
 * 构造函数执行,不写RETURN,浏览器会默认返回创建的实例,但是如果我们自己写了RETURN?
 *   1.return是的一个基本值,返回的结果依然是类的实例,没有受到影响
 *   2.如果返回的是引用值,则会把默认返回的实例覆盖,此时接收到的结果就不在是当前类的实例了
 *
 *   =>构造函数执行的时候,尽量减少RETURN的使用,防止覆盖实例
 */
function Fn() {
    var n = 10;
    this.m = n;
    // return;//=>这样RETURN是结束代码执行的作用,并且不会覆盖返回的实例
    // console.log(1);
}

var f = new Fn();//=>new Fn;  在构造函数执行的时候,如果Fn不需要传递实参,我们可以省略小括号,意思还是创建实例(和加小括号没有区别)
console.log(f);

//=>instanceof:检测某一个实例是否隶属于这个类
// console.log(f instanceof Fn);//=>TRUE
// console.log(f instanceof Array);//=>FALSE
// console.log(f instanceof Object);//=>TRUE (万物皆对象:所有的对象,包含创建的实例都是Object的实例)

//=>in:检测当前对象是否存在某个属性(不管当前这个属性是对象的私有属性还是公有属性,只要有结果就是TRUE)
// console.log('m' in f);//=>TRUE
// console.log('n' in f);//=>FALSE
// console.log('toString' in f);//=>TRUE toString是它的公有属性

//=>hasOwnProperty:检测当前属性是否为对象的私有属性(不仅要有这个属性,而且必须还是私有的才可以)
// console.log(f.hasOwnProperty('m'));//=>TRUE
// console.log(f.hasOwnProperty('n'));//=>FALSE 连这个属性都没有
// console.log(f.hasOwnProperty('toString'));//=>FALSE 虽然有这个属性但是不是私有的属性

//=>思考题:编写一个方法hasPubProperty,检测当前属性是否为对象的公有属性,和hasOwnProperty对应
function hasPubProperty(obj, attr) {
    //=>OBJ:要检测的对象
    //=>ATTR:要检测的属性
    //...
}
hasPubProperty(f, 'm');//=>FALSE
hasPubProperty(f, 'n');//=>FALSE
hasPubProperty(f, 'toString');//=>TRUE
/*
 * 原型(prototype)、原型链(__proto__)
 *
 *  [函数]
 *    普通函数、类(所有的类:内置类、自己创建的类)
 *
 *  [对象]
 *    普通对象、数组、正则、Math、arguments...
 *    实例是对象类型的(除了基本类型的字面量创建的值)
 *    prototype的值也是对象类型的
 *    函数也是对象类型的
 *    ...
 *
 *  1.所有的函数数据类型都天生自带一个属性:prototype(原型),这个属性的值是一个对象,浏览器会默认给它开辟一个堆内存
 *  2.在浏览器给prototype开辟的堆内存中有一个天生自带的属性:constructor,这个属性存储的值是当前函数本身
 *  3.每一个对象都有一个__proto__的属性,这个属性指向当前实例所属类的prototype(如果不能确定它是谁的实例,都是Object的实例)
 */

function Fn() {
    var n = 100;
    this.AA = function () {
        console.log(`AA[私]`);
    };
    this.BB = function () {
        console.log(`BB[私]`);
    };
}
Fn.prototype.AA = function () {
    console.log(`AA[公]`);
};

var f1 = new Fn;
var f2 = new Fn;

console.log(f1.n);


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-riHUkw2a-1594872955046)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-16_18-09-44.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xPvAtujW-1594872955046)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-16_18-10-27.png)]

less

它是CSS预编译语言,和它类似的还有sass/stylus…

css是标记语言,不是编程语言,没有类、实例、函数、变量等东西;而less等预编译语言就是让css具备面向对象编程的思想;但是浏览器不能直接识别和渲染less代码,需要我们把less代码预先编译为正常的css后,再交给浏览器渲染解析;

less的编译

  • 在开发环境下编译(产品没有开发完,正在开发中,这个是开发环境)

导入less.js即可

//=>rel="stylesheet/less" 这块有修改
<link rel="stylesheet/less" href="css/demo1.less">

//=>导入JS文件即可
<script src="js/less-2.5.3.min.js"></script>
  • 在生产环境下编译(产品开发完成了,需要部署到服务器上)

项目上线,不能把less部署,这样用户每一次打开页面都需要重新的编译,非常耗性能,我们部署到服务器上的是编译后的css

1.在当前电脑的全局环境下安装less模块
  $ npm install less -g

  验证是否安装成功:$ lessc -v

2.基于命令把我们的less编译成css
  $ lessc xxx/xxx.less xxx/xxx.min.css -x

  把指定目录中的less编译成为css(并且实现了代码的压缩),把编译后的css存入到具体指定路径中的文件中;上线前在HTML中导入的是css文件;
  • 目前基于webpack和框架实现工程化开发的时候,我们都是在webpack配置文件中,配置出less的编译(需要安装less/less-loader等模块),这样不管是开发环境下的预览,还是部署到生产环境下,都是基于webpack中的less模块编译的

超级简单的less语法

1.变量

用变量存储一个公共值,后期需要使用这个值,直接调取变量即可,以后如果值需要修改,只需要更改变量的值,那么所有用到这个变量的地方都跟着修改了

//定义变量
//对于颜色
@link-color:#555;
.hover{
	color:@link-color;
}

//对于图片 避免因为换目录图片无法显示
@bg-src: "../img";
.box{
	background: url("@{bg-src}/news_1.png") no-repeat;
}

& > .bg { /*.pub > .bg*/

  }

  &.bg { /*.pub.bg*/

  }

  &:hover { /*.pub:hover*/

  }



@H: 200;
.pub {
  @H: 100;
  .bg { /*.pub .bg*/
    a {
      width: unit(@H, px); /*300 找最近的 变量提升*/
    }
    @H: 300;
  }

原型相关

  1. 函数的prototype属性

    • 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)

    • 原型对象中有一个属性constructor, 它指向函数对象

  2. 给原型对象添加属性(一般都是方法) 实例对象可以访问

    • 作用: 函数的所有实例对象自动拥有原型中的属性(方法)

显示原型和隐式原型

  1. 每个函数function都有一个prototype,即显式原型(属性)

  2. 每个实例对象都有一个__ proto __,可称为隐式原型(属性)

  3. 对象的隐式原型的值为其对应构造函数的显式原型的值

  4. 总结:

    • 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象

    • 对象的__ proto __属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值

    • 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)

 //定义构造函数
  function Fn() {   // 内部语句: this.prototype = {}

  }
  // 1. 每个函数function都有一个prototype,即显式原型属性, 默认指向一个空的Object对象
  console.log(Fn.prototype)
  // 2. 每个实例对象都有一个__proto__,可称为隐式原型

  //创建实例对象
  var fn = new Fn()  // 内部语句: this.__proto__ = Fn.prototype
  console.log(fn.__proto__)
  // 3. 对象的隐式原型的值为其对应构造函数的显式原型的值
  console.log(Fn.prototype===fn.__proto__) // true
  //给原型添加方法
  Fn.prototype.test = function () {
    console.log('test()')
  }
  //通过实例调用原型的方法
  fn.test()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N10vVBbL-1594872955047)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-17_14-33-24.png)]

原型链

访问一个对象的属性时,先在自身属性中查找,找到返回。

如果没有, 再沿着 __ proto __这条链向上查找, 找到返回如果最终没找到, 返回undefined。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SRlXdxw5-1594872955048)(E:\1Study\1前端笔记\info\images\Snipaste_2020-06-17_16-07-26.png)]

我们可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性;使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true

原型链的属性问题

  1. 读取对象的属性值时: 会自动到原型链中查找
  2. 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
  3. 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
function Fn() {

}
Fn.prototype.a = 'xxx'
var fn1 = new Fn()
console.log(fn1.a) //xxx

var fn2 = new Fn()
fn2.a = 'yyy' //另一个实例改变a的值=>不能改变
console.log(fn1.a) //xxx
console.log(fn2.a) //yyy
console.log(fn2);

另种:

函数有三种角色

  1. 普通函数

    • 堆栈内存释放
    • 作用域链
    • prototype:原型
    • __ proto __:原型链
    • 实例
  2. 普通对象

    • 和普通的一个OBJ没啥区别,就是对键值对的增删改查

    三种角色间没有什么必然关系

function Fn() {
    var n = 10;
    this.m = 100;
}

Fn.prototype.aa = function () {
    console.log('aa');
};
Fn.bb = function () {
    console.log('bb');
};
//=>普通函数 这里执行的fn和后面两个原型和键值对的增删改查无关系
Fn();//=>this:window  有一个私有变量n  和原型以及属性bb没有关系 window的m设为200

//=>构造函数执行
var f = new Fn;//=>这里的this就不是window了 而是fn的实例f
console.log(f.n);//=>undefined:Fn里的n是私有变量和实例没有关系
console.log(f.m);//=>100 实例的私有属性 this.m this指的f所以有关系
f.aa();// 'aa' =>实例通过__proto__找到Fn.prototype上的方法
console.log(f.bb);//=>undefined:bb是把Fn当做一个普通的对象设置的属性而已,和实例等没有半毛钱关系

//=>普通对象
// Fn.bb();

//============================================
//=>JQ这个类库中提供了很多的方法,其中有一部分是写在原型上的,有一部分是把它当做普通对象来设置的
~function () {
    function jQuery() {
        //...
        return [JQ实例]
    }
    jQuery.prototype.animate=function(){}
    //...
    jQuery.ajax=function(){}
    //....
    window.jQuery = window.$ = jQuery;
}();
// $().ajax() //=>调不了
// $().anaimte() //=>这样可以调取
// $.ajax() //=>直接的对象键值对操作 $就是function jQuery
// $.animate() //=>对象上没有animate这个属性,这个属性在和实例相关的原型上


js小结js小结

总结:

原型上的公共方法,只能通过 实例.方法 调取使用

普通对象的内置键值对 只能Number.属性名

this的不同情况:

  1. 以函数的形式调用时,this永远都是window
  2. 以方法的形式(前面有点)调用时,this就是调用方法的对象
  3. 以构造函数的形式调用时,this就是新创建的对象
  4. 使用call和apply调用时,this就是指定的那个对象
  5. 在全局作用域中this代表window

练习题:

function Foo() {
    getName = function () {
        console.log(1);
    };
    return this;
}
Foo.getName = function BBB() {
    console.log(2);
};
Foo.prototype.getName = function AAA() {
    console.log(3);
};
var getName = function () {
    console.log(4);
};
function getName() {
    console.log(5);
}

Foo.getName();//=>2 把Foo当做一个对象,找Foo的私有方法执行
getName();//=>4 执行全局下的GET-NAME
Foo().getName();//=>1 先把FOO当做普通函数执行,执行返回的结果在调取GET-NAME执行
getName();//=>1 执行的依然是全局下的GET-NAME

console.log(new Foo.getName());;//=>A:(Foo.getName) =>new A()  =>2
new Foo().getName();//=>B:new Foo() =>B.getName() =>3
console.log(new new Foo().getName());//=>C:new Foo() =>new C[Foo的实例].getName() =>D:C.getName =>new D(); =>3  (先计算new Foo()创建一个实例f,然后new f.getName(),先找到f.getName,在把这个函数new一下,最后其实相当于把f.getName当做一个类,返回这个类的一个实例)
  • 每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)

  • 原型对象中有一个属性constructor, 它指向函数对象本身

var和let的区别:let不能变量提升,var可以;let不能重复声明;全局作用域下,let声明的变量和window没有关系;暂时性死区;let能形成块级作用域

call,apply,bind改变某一个函数this指向

window.name = 'zz';
let fn = function () {
    console.log(this.name);
};
let obj = {
    name: "OBJ",
    fn: fn
};
let oo = {name: "OO"};
fn();//=>this:window "zz"
obj.fn();//=>this:obj "OBJ"

call:

// [fn].call([this],[param]...)
// fn.call:当前实例(函数FN)通过原型链的查找机制,找到Function.prototype上的call方法  
// =>function call(){[native code]}

// fn.call():把找到的call方法执行
// 当call方法执行的时候,内部处理了一些事情
// =>首先把要操作函数中的THIS关键字变为CALL方法第一个传递的实参值
// =>把CALL方法第二个及第二个以后的实参获取到
// =>把要操作的函数执行,并且把第二个以后的传递进来的实参传给函数
fn.call(oo) //=>this:00
fn.call(obj) //=>this:obj
fn.call(obj,10) //把fn中的this变成obj,10和20作为实参传给obj

CALL中的细节:

  1. 非严格模式下,如果参数不传,或者第一个传递的是null/undefined,THIS都指向WINDOW
  2. 在严格模式下,第一个参数是谁,THIS就指向谁(包括null/undefined),不传THIS是undefined
  let fn = function (a, b) {
    console.log(this,a,b);
  };
  let obj = {name: "OBJ"};
// fn.call(obj, 10, 20);//=>this:obj a=10 b=20
// fn.call(10, 20);//=>this:10 a=20 b=undefined
// fn.call();//=>this:window a=undefined b=undefined 没有指定this,填null/undefined时就是window
// fn.call(null);//=>this:window
// fn.call(undefined);//=>this:window

apply:

  1. 和call基本上一模一样,唯一区别在于传参方式
  2. APPLY把需要传递给FN的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给FN一个个的传递:fn.apply(obj,[10,20])

bind:

  1. 语法和call一模一样,唯一的区别在于立即执行还是等待执行
fn.call(obj,10,20) //改变FN中的THIS,并且把FN立即执行
 fn.bind(obj,10,20) //改变FN中的THIS,此时的FN并没有执行(不兼容IE6~8)

正则

是一个用来处理字符串的规则

  • 正则只能用来处理字符串

  • 处理一般包含两方面:

    A:验证当前字符串是否符合某个规则 “正则匹配”

    B:把一个字符串中符合规则的字符获取到 “正则捕获”

  • 学习正则其实就是在学习如何编写规则,每一个正则都是由修饰“元字符”、“符”两部分组成

创建正则的两种方式

let reg1 = /^\d+$/g;//=>字面量方式
let reg2 = new RegExp("^\\d+$", "g");//=>构造函数方式

正则两个斜杠之间包起来的都是“元字符”,斜杠后面出现的都是“修饰符”

常用的修饰符

  • i:ignoreCase 忽略大写小匹配
  • m:multiline 多行匹配
  • g:global 全局匹配

一些细节:

  1. 中括号中出现的元字符一般都是代表本身含义的

  2. 中括号中出现的两位数,不是两位数,而是两个数字中的任意一个

  3. 一个正则设置了^和$,那么代表的含义其实就是只能是xxx

上一篇: 设计模式-工厂模式

下一篇: