1.面向对象编程介绍:
1.1面向过程与面向对象:
面向过程是以步骤来划分问题,面向对象是以对象来划分问题。
1.2面向过程和面向对象的对比:
2.ES6中的类和对象:
2.1对象:万物皆对象,特指某一个,通过类实例化一个具体的对象。
对象是由属性(特征)和方法(行为)组成的。
2.2类class:抽象了对象的公共部分,它泛指某一大类。
面向对象的思维特点:
(1)抽象对象公用的属性和方法封装成一个类。
(2)对类进行实例化,获取类的对象。
2.3类构造函数constructor:用于传参,返回实例对象,通过new命令生成对象时自动调用该函数。如果我们不写这个函数,类也会自动生成这个实例。<script> //1.创建一个类 class Player { constructor(uname, age) { this.uname = uname; this.age = age; } // 方法放在类里面,不需要逗号 play(basketball) { console.log(this.uname + basketball); } } //2.利用类创建对象 var Kobe = new Player('科比', 41); // console.log(Kobe.uname, Kobe.age); Kobe.play('打篮球'); </script>
3.继承extends和super:
3.1继承:子类可以继承父类的一些属性和方法。
<script> //报错,son可以调用父类sum方法, // 但是sum方法里面的this指向的是父类构造函数里面的数据 //而new Son里面传递的数据是传给了son的构造函数 class Father { constructor(x, y) { this.x = x; this.y = y; } sum() { console.log(this.x + this.y); } } class Son extends Father { constructor(x, y) { this.x = x; this.y = y; } } var son = new Son(3, 6); son.sum(); </script>
3.2super关键字:用于访问和调用父类的函数。<script> class Father { constructor(x, y) { this.x = x; this.y = y; } sum() { console.log(this.x + this.y); } } class Son extends Father { constructor(x, y) { super(x, y); //调用父类中的构造函数 } } var son = new Son(3, 6); son.sum(); </script>
- super.Play()就是调用父类中的普通函数Play。
- 继承中的属性和方法的查找原则:就近。
- super必须放到子类this之前:
<script> class Father { constructor(x, y) { this.x = x; this.y = y; } sum() { console.log(this.x + this.y); } } //子类继承父类的方法,同时扩展新方法 class Son extends Father { constructor(x, y) { //利用super调用父类的函数函数 //super必须在子类this之前调用,先有爸爸才能有儿子 super(x, y); this.x = x; this.y = y; } subtract() { console.log(this.x - this.y); } } var son = new Son(3, 6); son.subtract(); son.sum(); </script>
3.3使用类的几个注意点:
<body> <button>点击</button> <script> // 1. ES6中类没有变量提升,必须先定义类,才能new // 2. 类里面的共有属性和方法一定要加this来使用 // 3. this指向问题:构造函数里面的this指向实例化对象,方法主要是看谁调用就指向谁 // var ldh = new Star('刘德华', 18); 错误 var that; //验证this指向问题 var _that; class Star { constructor(uname, age) { that = this; //构造函数里面的this指向的是实例化对象 this.uname = uname; this.age = age; // this.sing(); //只要new就会自动执行构造函数,所以会执行sing(); this.btn = document.querySelector('button'); this.btn.onclick = this.sing; //不加括号点击完才调用,加了括号就立马调用了 } sing() { // console.log(uname); 错误 // 当实例化的对象调用sing时,this指向的还是实例化对象 // 当你点击btn时,sing中的this指向btn console.log(this); console.log(that.uname + '暗里着迷'); //that里面存的是构造函数里面的this } dance() { //dance里面的this指向ldh,因为是ldh调用了这个函数 _that = this; console.log(this); } } var ldh = new Star('刘德华', 18); console.log(that === ldh); ldh.dance(); console.log(_that === ldh); </script> </body>
4.构造函数和原型:
4.1概述:
在典型的OOP语言中,都存在类的概念,类就是对象的模板,对象就是类的实例。但是ES6前JS并没有引入类的概念。
在ES6之前,对象不是基于类创建的,而是用构造函数来定义对象和它们的特征。<script> // 创建对象的三种方式 // 1.利用 new Object()创建对象 var obj1 = new Object(); // 2.利用对象字面量创建对象 var obj2 = {}; // 3.利用构造函数创建对象 function Star(uname, age) { this.uname = uname; this.age = age; this.sing = function() { console.log('唱歌'); } } var ldh = new Star('刘德华', 18); console.log(ldh); </script>
4.2构造函数:
用来初始化对象,与new一起使用。把对象的公共属性和方法抽取出来,封装到这个函数里面。
new在执行时做的四件事情:
- 1.在内存中创建一个新的空对象;
- 2.让this指向这个新的对象;
- 3.执行构造函数里面的代码,给这个新对象添加属性和方法;
- 4.返回这个新对象。(不需要return)
JS的构造函数中可以添加一些成员。
- 静态成员:在构造函数上添加的成员,只能由构造函数来访问。
- 实例成员:在构造函数内部创建的对象成员,只能由实例化对象来访问。
<script> function Star(uname, age) { this.uname = uname; this.age = age; this.sing = function() { console.log('唱歌'); } } var ldh = new Star('刘德华', 18); ldh.uname; //1.实例成员:构造函数内部通过this添加的,如 uname age sing //只能通过实例化对象来访问 // Star.uname; 不能通过构造函数来访问实例成员 //2.静态成员:构造函数本身添加的成员 sex就是静态成员 Star.sex = '男'; // 静态成员只能通过构造函数来访问 console.log(Star.sex); // ldh.sex; 错误,不能通过实例化对象来访问 </script>
4.3构造函数的问题:
会存在浪费内存的问题。
如创建了100个对象,就会开辟100块内存空间来存放函数。
4.4构造函数原型prototype:
- 构造函数通过原型分配的函数是所有对象所共享的。
- JS规定,每一个构造函数里面都有prototype属性,指向另一个对象。这个prototype是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
- 我们可以把那些不变的方法,直接定义到prototype对象上,这样所有对象的实例就可以共享这些方法了。
<script> function Star(uname, age) { this.uname = uname; this.age = age; // this.sing = function() { // console.log('唱歌'); // } } Star.prototype.sing = function() { console.log('唱歌'); } // console.log(ldh.sing === zxy.sing); var ldh = new Star('刘德华', 18); var zxy = new Star('张学友', 19); ldh.sing(); zxy.sing(); </script>
原型的作用:共享方法。
一般情况下,公共属性定义到构造函数里面,公共方法放到原型对象上(共享方法,节约内存)。
4.5对象原型__proto__:
对象身上自动添加__proto__ 指向构造函数的原型对象prototype。之所以我们对象可以使用prototype原型对象的属性和方法,就是因为对象有__proto__原型的存在。
对象身上的__proto__ 和 构造函数的prototype是等价的:console.log(ldh.__proto__ === Star.prototype); //true
4.6constructor构造函数:
- 对象原型__proto__和构造函数原型对象prototype里面都有一个constructor属性。constructor我们称为构造函数,因为它指回构造函数本身。
- constructor主要用于记录该对象引用于那个构造函数,它可以让原型对象重新指回原来的构造函数。
<script> function Star(uname, age) { this.uname = uname; this.age = age; } //很多情况下,需要手动的利用constructor这个属性指回原来的构造函数 // Star.prototype.sing = function() { // console.log('唱歌'); // } // Star.prototype.movie = function() { // console.log('演电影'); // } Star.prototype = { // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象, //则必须手动利用constructor指回原来的构造函数。 constructor: Star, sing: function() { console.log('唱歌'); }, movie: function() { console.log('演电影'); } } var ldh = new Star('刘德华', 18); var zxy = new Star('张学友', 19); console.log(Star.prototype); console.log(ldh.__proto__); console.log(Star.prototype.constructor); console.log(ldh.__proto__.constructor); </script>
4.7构造函数、实例、原型对象三者之间的关系:
4.8原型链:
<script> function Star(uname, age) { this.uname = uname; this.age = age; } Star.prototype.sing = function() { console.log('唱歌'); } var ldh = new Star('刘德华', 18); //1.只要是对象就有__proto__原型,指向原型对象 console.log(Star.prototype); //2.Star原型对象里面的__proto__原型指向的是Object.prototype console.log(Star.prototype.__proto__ === Object.prototype); //true //3.Object.prototype里面的原型对象里面的 __proto__原型 指向为null console.log(Object.prototype.__proto__); //null </script>
4.9JS的成员查找规则:就近原则,如果没有则去上一层查找,直到找到或者为空。
- (1)当访问一个对象的属性或者方法时,首先在这个对象自身查找;
- (2)如果没有找到,就查找它的原型(__proto__指向的原型对象prototype);
- (3)如果还没就查找原型对象的原型(Object的原型对象);
- (4)依次类推一直找到Objecct为止(null)。
- (5)__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
<script> function Star(uname, age) { this.uname = uname; this.age = age; } Star.prototype.sing = function() { console.log('唱歌'); } // Star.prototype.sex = '男'; Object.prototype.sex = '男'; var ldh = new Star('刘德华', 18); // ldh.sex = '男'; console.log(ldh.sex); console.log(Object.prototype); console.log(ldh.toString()); </script>
4.10原型对象this指向:<script> function Star(uname, age) { this.uname = uname; this.age = age; } var that; Star.prototype.sing = function() { console.log('唱歌'); that = this; } // 1.在构造函数中,this指向的是对象实例ldh var ldh = new Star('刘德华', 18); ldh.sing(); // 2.原型对象函数里面的this指向的是实例对象ldh console.log(that === ldh); //true </script>
4.11扩展内置对象:<script> //原型对象的应用 扩展内置对象方法 Array.prototype.sum = function() { var sum = 0; for (var i = 0; i < this.length; i++) { sum = sum + this[i]; } return sum; } //下面这种添加方法是错误的,因为会覆盖掉原来的方法 // Array.prototype = { // sum: function() { // var sum = 0; // for (var i = 0; i < this.length; i++) { // sum = sum + this[i]; // } // return sum; // } // } var arr = [1, 2, 3]; console.log(arr.sum()); console.log(Array.prototype); </script>
5.继承:
ES6之前并没有extends继承方法。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。
5.1call():调用这个函数,并且修改函数运行时的this指向。
<script> //call() function fn(x, y) { console.log('我想出去玩'); console.log(this); console.log(x + y); } var o = { name: '杰尼龟' }; //fn();以前的调用方式 // 1.call()方法调用函数 // fn.call(); //2.call()可以改变这个函数的this指向 fn.call(o, 3, 6); //此时this指向o这个对象,可以理解为fn呼叫o来调用它 </script>
5.2利用构造函数继承父类型属性:
核心原理:通过call()把父类型的this指向子类型的this。<script> //借用父构造函数继承属性 // 1.父构造函数 function Father(uname, age) { //this指向父构造函数的对象实例 this.uname = uname; this.age = age; } // 2.子构造函数 function Son(uname, age, score) { //this指向子构造函数的对象实例 Father.call(this, uname, age); //调用Father这个函数,并修改Father的指向为Son this.score = score; } var son = new Son('刘德华', 18, 100); console.log(son); </script>
5.3利用原型对象继承父类中的方法:<script> //借用父构造函数继承属性 // 1.父构造函数 function Father(uname, age) { //this指向父构造函数的对象实例 this.uname = uname; this.age = age; } Father.prototype.money = function() { console.log(1000000); }; // 2.子构造函数 function Son(uname, age, score) { //this指向子构造函数的对象实例 Father.call(this, uname, age); //调用Father这个函数,并修改Father的指向为Son this.score = score; } // Son.prototype = Father.prototype; 错误,会同时修改父原型对象 Son.prototype = new Father(); //重点,Father实例对象相当于一个中间量 //如果利用对象的形式修改了原型对象,要用constructor指回原来的原型对象 Son.prototype.constructor = Son; // 子构造函数专门的方法 Son.prototype.exam = function() { console.log('孩子要考试'); } var son = new Son('刘德华', 18, 100); console.log(son); console.log(Father.prototype); console.log(Son.prototype.constructor); </script>
6.类的本质:
- 类的本质其实还是一个函数。
- 也可以简单认为,类就是构造函数的另外一种写法。
- ES6的类其实就是语法糖。
<script> // ES6之前通过 构造函数+原型 实现面向对象编程 // (1)构造函数有原型对象prototype // (2)构造函数原型对象prototype里面有constructor指向构造函数本身 // (3)构造函数可以通过原型对象添加方法 // (4)构造函数创建的实例对象有__proto__原型 指向构造函数的原型对象 // ES6通过类实现面向对象变成 class Star { } console.log(typeof Star); //1.类的本质其实还是一个函数 // (1)类有原型对象prototype console.log(Star.prototype); // (2)类原型对象prototype里面有constructor指向类本身 console.log(Star.prototype.constructor); // (3)类可以通过原型对象添加方法 Star.prototype.sing = function() { console.log('冰雨'); }; // (4)类创建的实例对象有__proto__原型 指向类的原型对象 var ldh = new Star(); console.log(ldh.__proto__ === Star.prototype); //true </script>
7.ES5中的新增方法:
7.1数组方法:
迭代(遍历)方法:forEach()、map()、filter()、some、every();
forEach():<script> // forEach 迭代遍历数组 var arr = [1, 2, 3]; var sum = 0; arr.forEach(function(value, index, array) { console.log('每个数组元素' + value); console.log('每个数组元素的索引号' + index); console.log('数组本身' + array); sum = sum + value; }) console.log(sum); // filter():筛选数组,它直接返回一个新数组。 var arr = [12, 20, 4, 31]; var newArr = arr.filter(function(value, index) { // return value > 20; return value % 2 != 0; }); console.log(newArr); </script>
some():查找数组中是否有满足条件的元素,它返回的是布尔值,true为查找到,false为没找到。如果找到第一个满足条件的元素,则终止循环,不再继续查找。
<script> // some查找数组是否有满足条件的元素 var arr = [20, 30, 15]; var flag = arr.some(function(value, index) { return value > 25; }); console.log(flag); //true var arr1 = ['Bill', 'Jack', 'Jay']; var flag1 = arr1.some(function(value) { return value === 'Eason'; }); console.log(flag1); //false </script>
filter和some方法的区别:
- filter查找满足条件的元素,返回的是一个数组,而且返回的是所有满足条件的元素;
- some查找满足条件的元素是否存在,返回的是一个布尔值,如果找到第一个满足条件的元素,则终止循环。
forEach和some方法的区别:
如果查询数组中唯一的元素,用some更合适。<script> var arr = ['Bill', 'Jack', 'Jay']; // 1.forEach迭代遍历 arr.forEach(function(value) { if (value == 'Jack') { console.log('找到了该元素'); return true; //在forEach里面找到了也不会终止迭代 } console.log(11); }) // 2.some查找数组 arr.some(function(value) { if (value == 'Jack') { console.log('找到了该元素'); return true; //在some里面找到了会终止迭代,迭代效率更高 } console.log(11); }) </script>
7.2字符串方法:trim():去除空格
<script> // trim方法去除字符串两侧空格 var str = ' Kobe '; console.log(str); var str1 = str.trim(); console.log(str1); var input = document.querySelector('input'); var btn = document.querySelector('button'); var div = document.querySelector('div'); btn.onclick = function() { var str = input.value.trim(); if (str === '') { alert('请输入内容'); } else { console.log(str); console.log(str.length); div.innerHTML = str; } } </script>
7.3对象方法:
<script> var obj = { id: 1, pname: '小米', price: 1999 }; //1.以前给一个对象添加或修改属性的方式 // obj.num = 300; // obj.price = 99; // console.log(obj); //2.Object.defineProperty() 定义新属性或者修改原有的属性 Object.defineProperty(obj, 'num', { value: 1000 }); Object.defineProperty(obj, 'price', { value: 9.9 }); Object.defineProperty(obj, 'id', { writable: false //不允许修改这个属性值 }); obj.id = 2; //id还是=1 Object.defineProperty(obj, 'address', { value: '中国山东找蓝翔', writable: false, // enumerable值为false,则不允许遍历,默认值是false enumerable: false, // configurable如果为false,则不允许删除和不允许修改第三个参数里面的特性,默认为false configurable: false }); console.log(obj); console.log(Object.keys(obj)); delete obj.address; console.log(obj); </script>
8.函数进阶:
8.1函数的定义和调用:
(1)函数的定义方式:<script> // 函数的定义方式 // 1.自定义函数(命名函数) function fn() { }; // 2.函数表达式(匿名函数) var fun = function() {}; // 3.利用new Function('参数1','参数2','函数体'); var f = new Function('a', 'b', 'console.log(a + b)'); f(3, 7); // 4.所有函数都是Function的实例化对象 console.dir(f); //有__proto__ 属性 // 5.函数也属于对象 console.log(f instanceof Object); //true </script>
(2)函数的调用方式:
<script> //函数的调用方式: //1.普通函数 function fn() { console.log('普通函数'); } // fn(); fn.call(); // 2.对象的方法 var o = { sayHi: function() { console.log('对象的方法'); } } o.sayHi(); // 3.构造函数 function Star() {}; var ldh = new Star(); // 4.绑定事件函数 btn.onclick = function() {}; //点击了按钮就调用这个函数 // 5.定时器函数 setInterval(function() {}, 1000); //定时器自动1秒钟调用1次 // 6.立即执行函数 (function() { console.log('立即执行函数'); //立即执行函数是自动调用 })(); </script>
8.2this:
(1)函数内this的指向:
(2)改变函数内部this指向:
call方法:调用函数的方式,但是它可以改变函数this的指向。<script> // 改变函数内this指向 js提供了三种方法 call() apply bind() // 1.call() 可以调用函数,并且改变函数内部的this指向 var o = { name: 'Kobe' } function fn(a, b) { console.log(this); console.log(a + b); }; fn.call(o, 1, 2); // call的主要作用可以实现继承 function Father(uname, age) { this.uname = uname; this.age = age; }; function Son(uname, age) { Father.call(this, uname, age); } var son = new Son('ldh', 18); console.log(son); </script>
apply方法:作用同上。不同之处在于传递的值是数组。
<script> // apply() // 1.也是调用函数,并且第二个可以改变函数内部的this指向 var o = { name: 'Kobe' }; function fn(arr) { console.log(this); console.log(arr); //'yellow' }; // 2.但是它的参数必须是数组 fn.apply(o, ['yellow']); // 3.apply的主要应用 比如说我们可以利用apply借助于数学内置对象求最大值 Math.max(); var arr = [22, 55, 11, 6, 33]; var max = Math.max.apply(Math, arr); var min = Math.min.apply(Math, arr); console.log(max, min); </script>
bind方法:不会调用函数,但是可以改变this指向。
返回的是原函数改变this之后产生的新函数。<body> <button>点击</button> <button>点击</button> <button>点击</button> <script> // bind() // 1.不会调用原来的函数,但是可以改变this指向 // var o = { // name: 'Kobe' // }; // function fn(a, b) { // console.log(this); // console.log(a + b); // }; // // 2.bind()返回的是原函数改变this之后产生的新函数 // var f = fn.bind(o, 3, 7); // f(); // // 3.如果有的函数我们不需要立即调用,但是又想改变函数内部的this指向就用bind // // 如:我们有一个按钮,点击之后就禁用这个按钮,3秒钟之后再开启 // var btn = document.querySelector('button'); // btn.onclick = function() { // this.disabled = true; //这个this指向btn按钮 // setTimeout(function() { // // this.disabled = false; //定时器里面的this指向的是window // this.disabled = false; //此时定时器里面的this指向的是btn // }.bind(this), 3000) //这个this指向的是btn对象 // } var btns = document.querySelectorAll('button'); for (var i = 0; i < btns.length; i++) { btns[i].onclick = function() { this.disabled = true; setTimeout(function() { this.disabled = false; }.bind(this), 1000); } } </script> </body>
call apply bind总结:
- 相同点:都可以改变函数内部的this指向。
- 区别点:
- call和apply会调用函数,但是传递的参数不一样。call传递的是普通参数,apply传递的必须是数组形式。
- bind不会调用函数。
- 主要应用场景:
- call经常做继承。
- apply经常跟数组有关系。
- bind不调用函数且需要改变内部this指向。
8.3严格模式:
(1)什么是严格模式?
(2)开启严格模式:
严格模式可以应用到整个脚本或个别函数中。因此我们可以将严格模式分为 为脚本开启严格模式和为函数开启严格模式两种情况。
- 为脚本开启严格模式:
在所有语句之前放一个特定语句"use strict";
<body> <!-- 为整个脚本-script标签开启严格模式 --> <script> // 下面的js代码就会按照严格模式执行代码 "use strict"; //1.第一种方式 </script> <script> (function() { 'use strict'; //2.第二种方式 })(); </script> </body>
- 为函数开启严格模式:
<body> <!-- 为某个函数开启严格模式 --> <script> function fn() { // 只给函数fn开启严格模式 'use script'; // 下面的代码按照严格模式执行 } function fun() { // 里面的还是按照普通函数执行 } </script> </body>
(3)严格模式中的变化:严格模式对JS的语法和行为都做了一些改变。
变量规定:
- 变量必须先声明才能使用。
- 不能随意删除已经声明好的变量。
this指向问题:
- 以前在全局作用域函数中this指向window对象,严格模式下this是undefined。
- 构造函数不加new来调用,this会报错(因为它指向undefined)。
- 定时器里的this指向的还是window。
- 事件、对象还是指向调用者。
<script> 'use strict' //1.变量必须先声明才能使用 // var num = 10; // console.log(num); //2.不能随意删除已经声明好的变量 // delete num; // 3.严格模式下全局作用域中函数的 this是undefined // function fn() { // console.log(this); //undefined // } // fn(); // 4.严格模式下 构造函数不加new来调用,this会报错(因为它指向undefined) // function Star() { // this.sex = '男'; // } // // Star(); // // 5. new 实例化后,构造函数指向创建对象的实例 // var ldh = new Star(); // console.log(ldh.sex); // 6. 定时器里的this指向的还是window setTimeout(function() { console.log(this); }, 1000) </script>
函数变化:
- 函数不能有重名的参数。
- 不允许在非函数的代码块内声明函数。
8.4高阶函数:
参数或者返回值是函数。
<script> // 高阶函数 -函数可以作为参数传递 function fn(a, b, callback) { console.log(a + b); callback && callback(); } fn(1, 2, function() { console.log('我是最后调用的'); }); </script>
8.5闭包:
(1)变量作用域:全局变量和局部变量。
- 函数内部可以使用全局变量。
- 函数外部不可以使用局部变量。
- 当函数执行完毕,本作用域内的局部变量会销毁。
(2)什么是闭包?
- 闭包:指有权访问另一个函数作用域中变量的函数。(JavaScript高级程序设计)
简单理解:一个作用域可以访问另一个函数内部的局部变量。
- 闭包的主要作用:延伸了变量的作用范围。
<script> // 闭包:有权访问另一个函数作用域中变量的函数 // 闭包:我们fun这个函数作用域,访问了函数fn里面的局部变量num // fn外面的作用域可以访问 fn内部的局部变量 // 闭包的主要作用:延伸了变量的作用范围 function fn() { var num = 10; // function fun() { // console.log(num); // } // return fun; return function() { console.log(num); } } var f = fn(); f(); // 类似于 var f = // function fun() { // console.log(num); // } </script>
闭包案例:
- 点击li打印当前索引号。
<body> <ul class="nav"> <li>榴莲</li> <li>菠萝</li> <li>苹果</li> <li>梨</li> </ul> <script> //闭包应用-点击li输出当前li的索引号 // 1.我们可以利用动态添加属性的方式 var lis = document.querySelector('.nav').querySelectorAll('li'); for (var i = 0; i < lis.length; i++) { lis[i].index = i; lis[i].onclick = function() { console.log(this.index); } } // 2.利用闭包的方式得到当前小li的索引号 for (var i = 0; i < lis.length; i++) { // 利用for循环创建了4个立即执行函数 //立即执行函数也称为小闭包,因为立即执行函数里面的任何一个函数都可以使用它的i变量 (function(a) { //这里的a是形参,相当于 a= i; lis[a].onclick = function() { console.log(a); } })(i); //这里的i是实参 = for里面的i } </script> </body>
- 3秒钟之后打印li内容。
<body> <ul class="nav"> <li>榴莲</li> <li>菠萝</li> <li>苹果</li> <li>梨</li> </ul> <script> // 闭包应用-3秒钟之后,打印所有li元素的内容 var lis = document.querySelector('.nav').querySelectorAll('li'); for (var i = 0; i < lis.length; i++) { (function(a) { setTimeout(function() { console.log(lis[a].innerHTML); }, 3000) })(i); } </script> </body>
(3)闭包总结:
- 闭包是一个函数。
- 闭包的作用:延伸变量的作用范围。
9.递归:
9.1什么是递归?
- 递归:函数内部自己调用自己。
- 由于递归很容易发生栈溢出错误,所以必须加退出条件return。
9.2利用递归求阶乘:
<body> <script> // 利用递归求1~n的阶乘 // 详细思路 假如用户输入的是5 // return 5*fn(4) -->return 4*fn(3) --> return 3*fn(2) // return 2*fn(1) -->fn(1)==1 从后往前开始返回(递归的性质) function fn(n) { if (n == 1) { return 1; } return n * fn(n - 1); } var num = fn(5); console.log(num); </script> </body>
9.3利用递归求斐波那契数列:<body> <script> //利用递归函数求斐波那契数列(兔子序列)1、1、2、3、5、8、13、21 //用户输入一个数字n就可以求出 这个数字对应的兔子序列值 //知道用户输入的n的前两项 n-1 n-2 就可以计算n对应的序列值 function fn(n) { if (n == 1 || n == 2) { return 1; } return fn(n - 1) + fn(n - 2); } var num = fn(8); console.log(num); </script> </body>
9.4浅拷贝和深拷贝:
- 浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用。
<script> // 浅拷贝只拷贝一层,更深层次对象级别的只拷贝引用 var obj = { id: 1, name: 'Kobe', msg: { age: 18 } }; var o = {}; // for (var k in obj) { // // k是属性名 obj[k]属性值 // o[k] = obj[k]; // } // console.log(o); // o.msg.age = 20; //会一起变化 // console.log(obj); console.log('-----'); Object.assign(o, obj);//ES6的语法糖 console.log(o); </script>
o.msg和obj.msg指向同一个地址,如果修改了o.msg.age,obj.msg.age的值也会改变。
- 深拷贝拷贝多层,每一级别的数据都会拷贝。
<body> <script> //深拷贝拷贝多层,每一级别的数据都会拷贝 var obj = { id: 1, name: 'Kobe', msg: { age: 18 }, color: ['yellow', 'skyblue'] }; var o = {}; // 封装函数 function deepCopy(newobj, oldobj) { for (var k in oldobj) { // 判断我们的属性值属于那种数据类型 // 1.获取属性值 oldobj[k] var item = oldobj[k]; // 2.判断这个值是否是数组 if (item instanceof Array) { newobj[k] = []; deepCopy(newobj[k], item); } else if (item instanceof Object) { // 3.判断这个值是否是对象 newobj[k] = {}; deepCopy(newobj[k], item); } else { // 4.属于简单数据类型 newobj[k] = item; } } } deepCopy(o, obj); console.log(o); </script> </body>
10.正则表达式:
10.1正则表达式概述:
- 正则表达式是用于匹配字符串中字符组合的模式。在JS中,正则表达式也是对象。
- 正则表达式通常被用来检索、替换、提取等。
10.2正则表达式在JS中的使用:
- 1.正则表达式的创建:
(1)利用RegExp对象来创建。
(2)通过字面量创建。
- 2.测试正则表达式test:
<body> <script> //正则表达式在js中的使用 // 1.利用RegExp对象来创建正则表达式 var regexp = new RegExp(/123/); console.log(regexp); // 2.字面量创建 var rg = /123/; // 3.test方法用来测试字符串是否符合正则表达式 console.log(rg.test(123)); //true console.log(rg.test('ac')); //false </script> </body>
10.3正则表达式中的特殊字符:
- 1.正则表达式的组成:参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
- 2.边界符:用来提示字符所处的位置。^开头,$结尾。
<body> <script> // 边界符 ^ $ var rg = /abc/; //正则表达式里面不需要加引号,不管是数字型还是字符串型 // /abc/ 只要包含有abc(不断开)这个字符串,返回的都是true console.log(rg.test('aaabc')); //true console.log(rg.test('abcad')); //true console.log('--------------'); var reg1 = /^abc/; //必须以abc开头 console.log(reg1.test('aaabcd')); //false console.log(reg1.test('abcad')); //true console.log('--------------'); var reg2 = /^abc$/; //精确匹配:要求必须是abc字符串才符合规范 console.log(reg2.test('aaabcd')); //false console.log(reg2.test('abcad')); //false </script> </body>
- 3.字符类:
(1)[] 表示有一系列字符可供选择,只要匹配其中一个就可以了
(2)[-] -表示范围<body> <script> // 字符类: [] 表示有一系列字符可供选择,只要匹配其中一个就可以了 var rg = /[abc]/; // 只要包含有a,或者b,或者c就可以 console.log(rg.test('kobe')); //true console.log(rg.test('kid')); //false var rg1 = /^[abc]$/; //三选一,只有是a/b/c 这三个字母才返回true console.log(rg1.test('a')); //true console.log(rg1.test('aa')); //false console.log('------------'); //[-] -表示范围 var rg2 = /^[a-z]$/; //26个英文字母返回的都是true //字符组合 var rg3 = /^[a-zA-Z0-9_-]$/; //26个英文字母(大写和小写都可以)任何一个都返回true console.log(rg3.test('a3')); //false console.log(rg3.test('!')); //false console.log(rg3.test('_')); //true console.log('------------'); //如果[]里面有^,表示取反,和边界符的结果不一样 var rg4 = /^[^a-zA-Z0-9_-]$/; console.log(rg4.test('a3')); //false console.log(rg4.test('!')); //true console.log(rg4.test('_')); //false </script> </body>
- 4.量词符:用来设定某个模式出现的次数。
<body> <script> // 4.量词符:用来设定某个模式出现的次数。 //简单理解:让下面的a这个字符重复多少次 // var reg = /^a$/; // * 相当于 >=0 可以出现0次或多次 // var reg = /^a*$/; // console.log(reg.test('')); //true // console.log(reg.test('a')); //true // console.log(reg.test('aaa')); //true // + 相当于 >=1 可以出现1次或多次 // var reg = /^a+$/; // console.log(reg.test('')); //false // console.log(reg.test('a')); //true // console.log(reg.test('aaa')); //true // ? 相当于 1 || 0 出现1次或0次 // var reg = /^a?$/; // console.log(reg.test('')); //true // console.log(reg.test('a')); //true // console.log(reg.test('aaa')); //false // // {3} 就是重复3次 // var reg = /^a{3}$/; // console.log(reg.test('')); //false // console.log(reg.test('a')); //false // console.log(reg.test('aaa')); //true // // {3,} 大于等于3次 // var reg = /^a{3,}$/; // console.log(reg.test('a')); //false // console.log(reg.test('aaa')); //true // console.log(reg.test('aaaa')); //true // {316} 大于等于3次 并且小于16 var reg = /^a{3,16}$/; console.log(reg.test('a')); //false console.log(reg.test('aaa')); //true console.log(reg.test('aaaa')); //true console.log(reg.test('aaaaaaaaaaaaaaaaaaaaaaaa')); //false </script> </body>
- 量词重复某个模式的次数:
<body> <script> // 量词是设定某个模式出现的次数 var reg = /^[a-zA-Z0-9_-]{3,16}$/; //这个模式用户只能输入英文字母 数字 下划线 短横线 // 界符和[]就限定了只能多选1 console.log(reg.test('a')); //false console.log(reg.test('ae86')); //true console.log(reg.test('ae!86')); //false </script> </body>
- 5.括号总结:
<body> <script> // 中括号 字符集合,匹配方括号中的任意字符 // var reg = /^[abc]$/; // 大括号 量词符,里面表示重复次数 var reg = /^abc{3}$/; // 此时{}只是让c重复3次 以a开头,c结尾且c重复3次 console.log(reg.test('abcabcabc')); //false console.log(reg.test('abccc')); //true // 小括号 表示优先级 var reg1 = /^(abc){3}$/; console.log(reg1.test('abcabcabc')); //true console.log(reg1.test('abccc')); //false </script> </body>
正则表达式在线测试:https://c.runoob.com/front-end/854/
6.预定义类:某些常见模式的简写方式。
10.4正则表达式中的替换:
- 1.replace替换:
<script> // //替换replace 第一个参数 被替换的字符串/正则表达式 第二个参数 替换的字符串 // var str = 'Kobe和Plua'; // // var newStr = str.replace('Plua', 'Gasol'); // var newStr = str.replace(/Plua/, 'Gasol'); // console.log(newStr); var text = document.querySelector('textarea'); var btn = document.querySelector('button'); var div = document.querySelector('div'); btn.onclick = function() { div.innerHTML = text.value.replace(/激情/g, '**'); } </script>
- 2.正则表达式参数: