JS总结

238 阅读27分钟

一、JavaScript面向对象

1.两大编程思想

1、面向过程

面向过程:POP(Process-oriented programming)

面向过程就是分析出解决问题所需要的步骤,然后用函数把步骤一步一步实现,使用时一个一个的依次调用就可以了。<img

2、面向对象

面向对象:OOP (Object Oriented Programming)

面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。

面向对象和面向过程区别:

​ 面向过程:小项目

​ 面向对象:多人合作大项目

​ **比如:**盖小狗窝

​ 盖高楼

2.面向对象三大特性

​ 1.封装性【功能已封装好,负责用即可】

​ 2.继承性

​ 3.多态性【可公共使用,也可单独使用】

3.面向对象和过程优缺点

面向过程:

​ 优点:性能比面向对象高,步骤练习紧密

​ 缺点:不好维护,不易多次使用及扩展

面向对象:

​ 优点:易维护,可复用,可扩展,灵活性高

​ 缺点性能没有面向过程高

二、ES6中的类和对象

类是在ES6中新加进入的,学会区分类和对象的概念,

1.类:抽象

​ 类模拟抽象的,泛指的

​ 1、抽取,把对象的属性和行为封装成一个类

​ 2、对类进行实例化, 获取类的对象

2.对象:具体

​ 对象是一个具体的事物,例如,一本书、一辆汽车、一个人可以是“对象”

​ 在JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象。

3.对象是由属性和方法组成的:

​ 属性:事物的特征,在对象中用属性来表示(常用名词)

​ 方法:事物的行为,在对象中用方法来表示(常用动词)

4.面向对象的思维特点:

​ 1.抽取(抽象)对象共用的属性和行为组织(封装)成一个类(模板)

​ 2.对类进行实例化, 获取类的对象

三、类class

在ES6中新增加了类的概念,使用class关键字声明一个类,以这个构造函数实例化对象。

  • 类抽象了对象的公共部分,它泛指某一大类(class)
  • 对象特指某一个,通过类实例化一个具体的对象

1.创建类

语法:

class 类名(首字母大写) {
    
}  //【构造函数语法糖】

var ldh = new 类名(首字母大写) ( );

//注意类名首字母大写
//类要抽取公共属性方法,定义一个类

2.类constructor构造函数

语法:

class Star {
	constructor (uname,age){
		this.uname = uname;
		this.age = age;
	}
}

//构造函数作用:接收参数,返回实例对象,new的时候主动执行,主要放一些公共的属性
//注意:类里面的方法不带function,直接写既可

constructor() 方法是类的构造函数(默认方法),用于传递参数,返回实例对象通过new命令生成对象实例时,自动调用该方法

注意:每个类里面一定有构造函数,如没有显示定义, 类内部会自动给我们创建一个constructor()

注意:this代表当前实例化对象,谁new就代表谁

3.类添加方法

语法:注意方法和方法之间不能加逗号,同时方法不需要添加function 关键字

class Star {
	constructor (uname,age){}
    
    sing (){
        console.log('方法1');
    }

    tiao (){
        console.log('方法2');
    }
}


class 类名 { constructor(){}   方法名(){} }
//注意:类中定义属性,调用方法都得用this

总结:类有对象的公共属性和方法,用class创建,class里面包含constructor和方法,把公共属性放到constructor里面,把公共方法直接往后写既可,但是注意不要加逗号

四、类的继承

1.extends

语法:

	class Father {}

	class Son extends Father{}

//注意:子类继承父类

2.super关键字

应用的过程中会遇到父类、子类都有的属性,此时,没必要再写一次,可以直接调用父类的方法就可以了

super关键字用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数

//当子类没有constructor的时候可以随意用父类的,但是如果子类也含有的话,constructor会返回实例,this的指向不同,不可以再直接使用父类的东西

3.调用父类构造函数

class F { 
    constructor(name, age){} 
}

class S extends F { 
    constructor (name, age) {
        super(name,age); 
    }
}

//注意: 子类在构造函数中使用super, 必须放到this 前面(必须先调用父类的构造方法,在使用子类构造方法

4.调用父类普通函数

class F { 
    constructor(name, age){} 
    say () {} 
}

class S extends F { 
    constructor (name, age) { 
        super(name,age); 
    } 
    say () { 
        super.say() 
    } 
}

//注意:如果子类也有相同的方法,优先指向子类,就近原则

五、三个注意点

  • ES6中类没有变量提升,所以必须先定义类,才能通过类实例化对象
  • 类里面的共有属性和方法一定要加this使用。【this,对象调用属性和方法】按钮练习
  • 类里面的this指向问题.
  • constructor 里面的this指向实例对象, 方法里面的this 指向这个方法的调用者

1.类里面的this指向

  • 构造函数的this指向实例对象
  • 普通函数的this是调用者,谁调用this是谁

2.面向对象版tab 栏切换添加功能

1.点击+ 可以实现添加新的选项卡和内容

2.第一步: 创建新的选项卡li 和新的内容section

3.第二步: 把创建的两个元素追加到对应的父元素中.

4.以前的做法:  动态创建元素createElement, 但是元素里面内容较多, 需要innerHTML赋值,在appendChild追加到父元素里面.

5.现在高级做法:   利用insertAdjacentHTML() 可以直接把字符串格式元素添加到父元素中

6.appendChild不支持追加字符串的子元素, insertAdjacentHTML支持追加字符串的元素7.insertAdjacentHTML(追加的位置,‘要追加的字符串元素’)  

8.追加的位置有: beforeend插入元素内部的最后一个子节点之后

9.该方法地址:  https://developer.mozilla.org/zh-CN/docs/Web/API/Element/insertAdjacentHTML

语法:element.insertAdjacentHTML(position, text);
//position是相对于元素的位置,并且必须是以下字符串之一:
'beforebegin'
//元素自身的前面。
'afterend'
//元素自身的后面。
'afterbegin'
//插入元素内部的第一个子节点之前。
'beforeend'
//插入元素内部的最后一个子节点之后。

六、构造函数和原型

在典型的OOP 的语言中(如Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在ES6之前,JS 中并没用引入类的概念。

ES6,全称ECMAScript6.0 ,2015.06 发版。但是目前浏览器的JavaScript 是ES5 版本,大多数高版本的浏览器也支持ES6,不过只实现了ES6 的部分特性和功能。

在ES6之前,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。


创建对象可以通过以下三种方式:

  • 对象字面量

  • new Object()【构造函数】

  • 自定义构造函数

1.构造函数和原型

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与new一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面。

在JS 中,使用构造函数时要注意以下两点:

1.构造函数用于创建某一类对象,其首字母要大写

2.构造函数要和new 一起使用才有意义

new在执行时会做四件事情

  1. 在内存中创建一个新的空对象。

  2. 让this指向这个新的对象。

  3. 执行构造函数里面的代码,给这个新对象添加属性和方法。

  4. 返回这个新对象(所以构造函数里面不需要return)。

2.静态成员和实例成员

JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的this 上添加。通过这两种方式添加的成员,就分别称为静态成员和实例成员。
  • 静态成员:在构造函数本上添加的成员称为静态成员,只能由构造函数本身来访问
  • 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问

构造函数小问题:

当实例化对象的时候,都需要保存地址,又指向函数,而每创建一个对象,都会有一个函数,每个函数都得开辟一个空间。
存空间,此时浪费内存了,那么如何节省内存呢,我们需要用到原型

3.构造函数原型prototype

原型对象:就是一个属性对象,我们也称呼,prototype 为原型对象。

作用:为了共享方法,从而达到节省内存

注意:每一个构造函数都有prototype属性

构造函数通过原型分配的函数是所有对象所共享的。JavaScript 规定,每一个构造函数都有一个prototype 属性,指向另一个对象。注意这个prototype 就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。我们可以把那些不变的方法,直接定义在prototype 对象上,这样所有对象的实例就可以共享这些方法。

总结:所有的公共属性写到构造函数里面,所有的公共方法写到原型对象里面

## 4.对象原型:proto

构造函数和原型对象都会有一个属性__proto__ 指向构造函数的prototype 原型对象,之所以我们对象可以使用构造函数prototype 原型对象的属性和方法,就是因为对象有__proto__ 原型的存在。

注意:____proto____是一个非标准属性,不可以拿来赋值或者设置【只读属性】
  • ____proto____对象原型和原型对象prototype 是等价的
  • ____proto____对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象prototype

总结:每一个对象都有一个原型,作用是指向原型对象prototypeproto原型,prototype成为原型对象

5.constructor 构造函数

记录是哪个构造函数创建出来的

指回构造函数本身

原型(proto)和构造函数(prototype)原型对象里面都有一个属性constructor属性,constructor 我们称为构造函数,因为它指回构造函数本身。constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个constructor 指向原来的构造函数。

总结:constructor 主要作用可以指回原来的构造函数

构造函数、实例、原型对象三者之间的关系

6.原型链

作用;提供一个成员的查找机制,或者查找规则

7.JavaScript 的成员查找机制(规则)

当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。

如果没有就查找它的原型(也就是__proto__指向的prototype 原型对象)。

如果还没有就查找原型对象的原型(Object的原型对象)。

依此类推一直找到Object 为止(null)。

__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

// console.log(Star.prototype.__proto__.__proto__);
// console.log(Object.prototype);

8.扩展内置对象

可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能。
console.log( Array.prototype );
	// 添加求和方法
	Array.prototype.sum = function () {
		var sum = 0;
		for (var i = 0; i < this.length; i++) {
			sum += this[i];
		}
		return sum;
	}
	var arr = [1,2,3];
	console.log( arr.sum() );
	var newArr = [6,7,8,9];
	console.log( newArr.sum() );

七、继承

ES6之前并没有给我们提供extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。
call()

调用这个函数, 并且修改函数运行时的this 指向

fun.call(thisArg, arg1, arg2, ...);call把父类的this指向子类

thisArg :当前调用函数this 的指向对象

arg1,arg2:传递的其他参数

利用构造函数实现子类的继承:

1.属性的继承

function Father (uname,age) {
			// this指向父类的实例对象
			this.uname = uname;
			this.age = age;
			// 只要把父类的this指向子类的this既可
		}
		function Son (uname, age,score) {
			// this指向子类构造函数
			// this.uname = uname;
			// this.age = age;
			// Father(uname,age);
			Father.call(this,uname,age);
			this.score = score;
		}
		Son.prototype.sing = function () {
			console.log(this.uname + '唱歌')
		}
		var obj = new Son('刘德华',22,99);
		console.log(obj.uname);
		console.log(obj.score);
		obj.sing();

2.方法的继承:

实现方法:把父类的实例对象保存给子类的原型对象

一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。核心原理:

①将子类所共享的方法提取出来,让子类的prototype 原型对象= new 父类()  

②本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象

③将子类的constructor 从新指向子类的构造函数
function Father () {

		}
		Father.prototype.chang = function () {
			console.log('唱歌');
		}

		function Son () {

		}
		// Son.prototype = Father.prototype;
		Son.prototype = new Father();
		var obj = new Son();
		obj.chang();

		Son.prototype.score = function () {
			console.log('考试');
		}

		// obj.score();
		// console.log(Son.prototype);
		console.log(Father.prototype);

注意:一定要让Son指回构造函数

实现继承后,让Son指回原构造函数

Son.prototype = new Father();

Son.prototype.constructor = Son;

总结:用构造函数实线属性继承,用原型对象实线方法继承

3.类的本质

class本质还是function

类的所有方法都定义在类的prototype属性上

类创建的实例,里面也有__proto__ 指向类的prototype原型对象

所以ES6的类它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

所以ES6的类其实就是语法糖.

语法糖:语法糖就是一种便捷写法.   简单理解, 有两种方法可以实现同样的功能, 但是一种写法更加清晰、方便,那么这个方法就是语法糖
	class Star {}
	console.log( typeof Star );
	var obj = new Star();
	console.log(obj.__proto__);
	console.log(Star.prototype);

八、ES5 中的新增方法

ES5 中给我们新增了一些方法,可以很方便的操作数组或者字符串,这些方法主要包括:

数组方法

字符串方法

1.数组方法:

迭代(遍历)方法:forEach()、map()、filter()、some()、every();

这些方法都是遍历数组的

forEach()

array.forEach(function(currentValue, index, arr))

currentValue:数组当前项的值
index:数组当前项的索引
arr:数组对象本身
var arr = ['red','blue','yellow','orange'];

arr.forEach(function (elm,i,arrAbc) {
	console.log(elm,i,arrAbc);
});

filter()

array.filter(function(currentValue, index, arr))

filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组

注意它直接返回一个新数组

currentValue: 数组当前项的值

index:数组当前项的索引

arr:数组对象本身回调函数里面添加return添加返回条件
var arr = [100,66,99,123,333,33,44,66];
	var reArr = arr.filter(function (elm, a, n) {

	// console.log(elm,a, n);
	return elm % 2 == 0;

	});

	console.log(reArr);

some()

array.some(function(currentValue, index, arr)) 【注意:找到或者满足条件立刻停止】

some() 方法用于检测数组中的元素是否满足指定条件. 通俗点查找数组中是否有满足条件的元素

注意它返回值是布尔值, 如果查找到这个元素, 就返回true , 如果查找不到就返回false.

如果找到第一个满足条件的元素,则终止循环. 不在继续查找.

currentValue: 数组当前项的值index:数组当前项的索引

arr:数组对象本身
var arr = [100,200,300,400];
var re = arr.some(function (elm,i,arr) {
		// console.log(elm,i,arr);
		console.log(i);
		return elm >= 200;
	});
console.log(re);

九、函数进阶

1.字符串方法:

str.trim()
trim:删除字符串两侧的空白符

2.函数的定义和调用

  1. 函数声明方式function 关键字(命名函数)

  2. 函数表达式(匿名函数)【自调用函数】

  3. new Function()   var fn = new Function('参数1','参数2'..., '函数体')
    
    var fn = new Function('a','b','console.log(a,b);');
      
    fn(123,456);
    
  4. Function 里面参数都必须是字符串格式

  5. 第三种方式执行效率低,也不方便书写,因此较少使用

  6. 所有函数都是Function 的实例(对象)

  7. 函数也属于对象

3.函数的调用方式

1. 普通函数
2. 对象的方法
3. 构造函数
4. 绑定事件函数
5. 定时器函数
6. 立即执行函数

十、this指向

this:当前调用者

1.改变函数内部this 指向

JavaScript 为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部this 的指向问题,常用的有bind()、call()、apply() 三种方法。

2.call 方法

call() 方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的this 指向。

fun.call(thisArg, arg1, arg2, ...)
thisArg:在fun 函数运行时指定的this 值

arg1,arg2:传递的其他参数

返回值就是函数的返回值,因为它就是调用函数

function Father () {this}o
function Son () { Father.call(this,1,2) }

因此当我们想改变this 指向,同时想调用这个函数的时候,可以使用call,比如继承

3.apply 方法

fun.apply(thisArg, [argsArray]):调用函数

thisArg:在fun函数运行时指定的this值

argsArray:传递的值,必须包含在数组里面

返回值就是函数的返回值,因为它就是调用函数

因此apply 主要跟数组有关系,比如使用Math.max() 求数组的最大值

var obj = {name : '张三丰'}
	function fn (arr) {
		console.log(this);
		console.log(arr);
		console.log(arr2);
	}

	//#################################################
	var arr = [23,45,56,23,54];

	var n = Math.max.apply(null,arr);

	console.log(n);

4.bind 方法

bind() 方法不会调用函数。但是能改变函数内部this 指向

fun.bind(thisArg, arg1, arg2, ...)

thisArg:在fun 函数运行时指定的this 值

arg1,arg2:传递的其他参数

返回由指定的this 值和初始化参数改造的原函数拷贝

因此当我们只是想改变this 指向,并且不想调用这个函数的时候,可以使用bind

var btn = document.querySelector('input');

		btn.onclick = function () {
			this.disabled = true;
			window.setTimeout(function () {
				this.disabled = false;
			}.bind(btn),2000);
	}

5.三种方法的区别

相同点:  都可以改变函数内部的this指向.

区别点:  
1.call 和apply  会调用函数, 并且改变函数内部this指向.
2.call 和apply 传递的参数不一样, call 传递参数aru1, aru2..形式apply 必须数组形式[arg]
3.bind  不会调用函数, 可以改变函数内部this指向

主要应用场景:  
1.call 经常做继承. 
2.apply 经常跟数组有关系.比如借助于数学对象实现数组最大值最小值
3.bind  不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向

十一、严格模式

JS:两种模式[类似于HTML版本]

​ 1、正常模式

​ 2、严格模式

什么是严格模式

JavaScript 除了提供正常模式外,还提供了严格模式(strictmode)。ES5 的严格模式是采用具有限制性JavaScript 变体的一种方式,即在严格的条件下运行JS 代码。
严格模式在IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的JavaScript 语义做了一些更改:

1.消除了Javascript语法的一些不合理、不严谨之处,减少了一些怪异行为。【例如变量,不声明就报错】
2.消除代码运行的一些不安全之处,保证代码运行的安全。
3.提高编译器效率,增加运行速度。
4.禁用了在ECMAScript的未来版本中可能会定义的一些语法,为未来新版本的Javascript做好铺垫。比如一些保留字如:class, enum, export, extends, import, super 不能做变量名

1.开启严格模式

开启严格模式:"use strict"

	<script>"use strict"</script>:脚本开启严格模式
	<script>function fn () {"use strict"}</script>为函数开启严格模式
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。

为脚本开启严格模式

为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句“use strict”;(或‘use strict’;)。

<script>
	"use strict";
	console.log("这是严格模式。");
</script>

因为"use strict"加了引号,所以老版本的浏览器会把它当作一行普通字符串而忽略。

为函数开启严格模式

要给某个函数开启严格模式,需要把“use strict”;  (或'use strict'; ) 声明放在函数体所有语句之前。

function fn(){"use strict";return "这是严格模式。";}

将"use strict"放在函数体的第一行,则整个函数以"严格模式"运行。

2.严格模式中的变化

严格模式对Javascript的语法和行为,都做了一些改变。

变量规定

变量申明必须加var,而且不准删除变量

在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用。
n = 3;
严禁删除已经声明变量。例如,delete x; 语法是错误的。

严格模式下this 指向问题

严格模式下,普通函数this是undefined

以前在全局作用域函数中的this 指向window 对象。
严格模式下全局作用域中函数中的this是undefined。

其他的没有变化:
以前构造函数时不加new也可以调用,当普通函数,this 指向全局对象
严格模式下,如果构造函数不加new调用, this 指向的是undefined 如果给他赋值则会报错
new 实例化的构造函数指向创建的对象实例。
定时器this 还是指向window 。
事件、对象还是指向调用者。

函数变化

​ 参数不能重名

函数不能有重名的参数。

函数必须声明在顶层.新版本的JavaScript 会引入“块级作用域”(ES6 中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数。【iffor等里面定义函数也不可以,但是现在不可以】

更多严格模式要求参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode

错误写法:
function fn (a,a) {console.log(a+a);}
fn(1,2);

十二、高阶函数

高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
此时fn就是一个高阶函数

函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。

同理函数也可以作为返回值传递回来

<script>
	function fn(callback){
		callback&&callback();
	}
	fn(function(){
		alert('hi')
	})
</script>

<script>
	function fn(){
		return function() {}
	}
	fn();
</script>

十三、闭包

1.变量作用域

变量根据作用域的不同分为两种:全局变量和局部变量。

1. 函数内部可以使用全局变量。
2. 函数外部不可以使用局部变量。
3. 当函数执行完毕,本作用域内的局部变量会销毁。

2.什么是闭包

闭包作用:延伸变量的作用范围。

//闭包(closure)指有权访问另一个函数作用域中变量的函数。【很多种解释,都并不权威】

//简单理解就是,一个作用域可以访问另外一个函数内部的局部变量。

<script>
	function fn1(){
		// fn1 就是闭包函数
		var num = 10;
		function fn2(){
			console.log(num); // 10
		}
		fn2()
	}
	fn1();
</script>

思考:如何再函数外面访问到函数内部的变量

function fn () {

		var i = 7;
		return function () {
			console.log(i);
		}
		// function fn1 () {
		// 	console.log(i);
		// }
		// fn1();
	}
	var n = fn();
n();

练习:

注册事件练习:打印索引值
var lis = document.querySelectorAll('li');

	for (var i = 0; i < lis.length; i++) {

		(function (index) {
			lis[index].onclick = function () {
				console.log(index);
			}
		})(i);

}

十四、递归

1.什么是递归

递归:一个函数在内部可以调用其本身,这个函数就是递归函数。

简单理解:函数内部自己调用自己, 这个函数就是递归函数。

递归:函数调用函数其本身

**注意:**递归函数的作用和循环效果一样,由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return。

练习:

//利用递归求1~n的阶乘
//利用递归函数求1~n的阶乘 1 * 2 * 3 * 4 * ..n
 function fn(n) {
     if (n == 1) { //结束条件
       return 1;
     }
     return n * fn(n - 1);
 }
 console.log(fn(3));
//利用递归求斐波那契数列

// 利用递归函数求斐波那契数列(兔子序列)  1、1、2、3、5、8、13、21...
// 用户输入一个数字 n 就可以求出 这个数字对应的兔子序列值
// 我们只需要知道用户输入的n 的前面两项(n-1 n-2)就可以计算出n 对应的序列值
function fb(n) {
  if (n === 1 || n === 2) {
        return 1;
  }
  return fb(n - 1) + fb(n - 2);
}
console.log(fb(3));

//羊村:50人家,每户一只羊
	//每户只能看别人家的羊有木有病
	//每户只能杀自己家的羊
	//第一天,第二天 ,第三天,砰砰砰几声枪响,问杀了几只羊
利用递归遍历数据
var data = [{
   id: 1,
   name: '家电',
   goods: [{
     		id: 11,
    		gname: '冰箱',
    		goods: [{
       				id: 111,
      		 		gname: '海尔'
    				}, 
                     {
       				id: 112,
      				gname: '美的'
    				},]
   			}, 
           	{
     		id: 12,
     		gname: '洗衣机'
   			}]
 	}, {
   	id: 2,
  	name: '服饰'
		}];
//1.利用 forEach 去遍历里面的每一个对象
 function getID(json, id) {
   		var o = {};
   		json.forEach(function(item) {
     // console.log(item); // 2个数组元素
     if (item.id == id) {
       // console.log(item);
       o = item;
  
       // 2. 我们想要得里层的数据 11 12 可以利用递归函数
       // 里面应该有goods这个数组并且数组的长度不为 0 
     } else if (item.goods && item.goods.length > 0) {
       o = getID(item.goods, id);
     }
   });
   return o;
}

2.深拷贝和浅拷贝

拷贝不能直接赋值,对象赋值的是地址

1.浅拷贝:

只拷贝最外面一层

var obj = {
			name : '张三丰',
			age : 22
		};

		var newObj = {};
		for (key in obj) {
			newObj[key] = obj[key];
		}
		console.log(newObj);
		
//es6:新方法
Object.assign(target, sources);

console.log(newObj);

2.深拷贝

var obj = {
			name : '1张三丰',
			age : 22,
			messige : {
				sex : '男',
				score : 16
			},
			color : ['red','purple','qing']

		}
		var newObj = {};
		function kaobei (newObj,obj) {
			for (key in obj) {
				if (obj[key] instanceof Array) {
					newObj[key] = [];
					kaobei(newObj[key],obj[key]);
				} else if (obj[key] instanceof Object) {
					newObj[key] = {};
					kaobei(newObj[key],obj[key])
				} else {
					newObj[key] = obj[key];
				}
			}
		}
		obj.messige.sex = 99;
		kaobei(newObj,obj);
		console.log(newObj);

十五、正则表达式概述

1.什么是正则表达式

**正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。**在JavaScript中,正则表达式也是对象。 作用:检索关键字,过滤敏感字符,表单验证

正则表通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。

其他语言也会使用正则表达式,本阶段我们主要是利用JavaScript 正则表达式完成表单验证。

2.正则表达式的特点

1. 灵活性、逻辑性和功能性非常的强。
2. 可以迅速地用极简单的方式达到字符串的复杂控制。
3. 对于刚接触的人来说,比较晦涩难懂。比如:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$
4. 实际开发,一般都是直接复制写好的正则表达式. 但是要求会使用正则表达式并且根据实际情况修改正则表达式.   比如用户名:   /^[a-z0-9_-]{3,16}$/

3.正则表达式在js中的使用

创建正则表达式

//在 JavaScript 中,可以通过两种方式创建一个正则表达式。

//方式一:通过调用RegExp对象的构造函数创建 

    var regexp = new RegExp(/123/);
    console.log(regexp);

//方式二:利用字面量创建 正则表达式

     var rg = /abc/; 含义:只要包含abc就可以

4.测试正则表达式

//test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串

//注意正则里面没有引号
regexObj.test(str);
regexObj:正则表达式
//str:用户输入字符串

var rg = /123/;
console.log(rg.test(123));//匹配字符中是否出现123  出现结果为true
console.log(rg.test('abc'));//匹配字符中是否出现123 未出现结果为false

5.正则表达式中的特殊字符

正则表达式的组成

//个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单和特殊字符的组合,比如 /ab*c/ 。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+ 等。

//正则表达式:简单字符 和 特殊字符【元字符】

//特殊字符非常多,可以参考: 

//MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions

//jQuery 手册:正则表达式部分

//正则测试工具 : http://tool.oschina.net/regex

十六、边界符

//正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符

//^ : 表示匹配行首的文本(以谁开始)【/^abc/:以abc为开头】

//$:表示匹配行尾的文本(以谁结束)【/^abc$/:只能是abc】

如果 ^和 $ 在一起,表示必须是精确匹配。

var rg = /abc/; // 正则表达式里面不需要加引号 不管是数字型还是字符串型
// /abc/ 只要包含有abc这个字符串返回的都是true
console.log(rg.test('abc'));
console.log(rg.test('abcd'));
console.log(rg.test('aabcd'));
console.log('---------------------------');
var reg = /^abc/;
console.log(reg.test('abc')); // true
console.log(reg.test('abcd')); // true
console.log(reg.test('aabcd')); // false
console.log('---------------------------');
var reg1 = /^abc$/; // 精确匹配 要求必须是 abc字符串才符合规范
console.log(reg1.test('abc')); // true
console.log(reg1.test('abcd')); // false
console.log(reg1.test('aabcd')); // false
console.log(reg1.test('abcabc')); // false

十七、字符类

符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。

1.[ ] 方括号

表示有一系列字符可供选择,只要匹配其中一个就可以了【多选1var rg = /[abc]/; // 只要包含有a 或者 包含有b 或者包含有c 都返回为true
console.log(rg.test('andy'));//true
console.log(rg.test('baby'));//true
console.log(rg.test('color'));//true
console.log(rg.test('red'));//false
var rg1 = /^[abc]$/; // 三选一 只有是a 或者是 b  或者是c 这三个字母才返回 true
console.log(rg1.test('aa'));//false
console.log(rg1.test('a'));//true
console.log(rg1.test('b'));//true
console.log(rg1.test('c'));//true
console.log(rg1.test('abc'));//true
----------------------------------------------------------------------------------
var reg = /^[a-z]$/ //26个英文字母任何一个字母返回 true  - 表示的是a 到z 的范围  
console.log(reg.test('a'));//true
console.log(reg.test('z'));//true
console.log(reg.test('A'));//false
-----------------------------------------------------------------------------------
//字符组合
var reg1 = /^[a-zA-Z0-9]$/; // 26个英文字母(大写和小写都可以)任何一个字母返回 true  
------------------------------------------------------------------------------------
//取反 方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 false 。
var reg2 = /^[^a-zA-Z0-9]$/;
console.log(reg2.test('a'));//false
console.log(reg2.test('B'));//false
console.log(reg2.test(8));//false
console.log(reg2.test('!'));//true

/^[^a-z]$/:两个^,括号外面的是便边界,括号里面的是取反的含义

2.量词符

词符用来设定某个模式出现的次数。

// 量词		说明

// *		  重复0次或更多次【>=0次】/^[a-z]*$/

// +		  重复1次或更多次【>=1次】【/^[a-z]+$/】
 
// ?		  重复0次或1次

// {n}		  重复n次

// {n,}	      重复n次或更多次

// {n,m}	  重复n到m次

// 注意:{n,m}n和m之间不准有空格

3.用户名表单验证

功能需求:

  1. 如果用户名输入合法, 则后面提示信息为: 用户名合法,并且颜色为绿色
  2. 如果用户名输入不合法, 则后面提示信息为: 用户名不符合规范, 并且颜色为红色
var input = document.querySelector('input');
		var span = document.querySelector('span');

		var reg = /^[a-zA-Z0-9_-]{6,16}$/;

		input.onblur = function () {

			if (reg.test(this.value)) {
				span.innerHTML = '输入正确';
				span.className = 'right';
			}else {
				span.innerHTML = '错误内容';
				span.className = 'error';
			}
}

4.括号总结

1.大括号  量词符.  里面表示重复次数

2.中括号 字符集合。匹配方括号中的任意字符. 

3.小括号表示优先级

正则表达式在线测试 : https://c.runoob.com

5.预定义类

预定义类指的是某些常见模式的简写方式.

验证案例:

手机验证

var tel = document.getElementById('tel');
var regtel = /^[1][3|4|5|7|8][0-9]{9}$/;
tel.onblur = function () {

	if (regtel.test(tel.value)) {
		this.nextElementSibling.className = 'success';
		this.nextElementSibling.innerHTML = '手机输入正确<i class="success_icon"></i>';
		console.log(123);
	} else {
		tel.nextElementSibling.className = 'error';
		tel.nextElementSibling.innerHTML = '手机输入错误<i class="error_icon"></i>';
		console.log(456)
	}

}

QQ验证:

var regqq = /^[1-9][0-9]{4,}$/;

var regtel = /^1[3|4|5|7|8][0-9]{9}$/;
	var regqq = /^[1-9][0-9]{4,}$/;
	

	function jiance (obj, reg) {
		obj.onblur = function () {
			if (reg.test(this.value)) {
				this.nextElementSibling.className = 'success';
				this.nextElementSibling.innerHTML = '<i class="success_icon"></i> 手机输入正确';
			} else {
				this.nextElementSibling.className = 'error';
				this.nextElementSibling.innerHTML = '<i class="error_icon"></i> 手机输入错误';
			}
		}
	}

	jiance(tel,regtel);
	jiance(qq,regqq);

昵称验证:

var nikName = /^[\u4e00-\u9fa5]{2,8}$/;

短信验证:

var regmsg = /^[0-9]{6}$/;

6.replace替换:

/表达式/[修饰符]

g:全局匹配

i:忽略大小写

gi:全局+忽略

屏蔽敏感字符

<textarea name="" id="message"></textarea> <button>提交</button>
<div></div>
<script>
    var text = document.querySelector('textarea');
    var btn = document.querySelector('button');
    var div = document.querySelector('div');
    btn.onclick = function() {
    	div.innerHTML = text.value.replace(/激情|gay/g, '**');
    }
</script>