JavaScript 高级笔记
面向对象与面向过程
面向过程是指分析结局问题的步骤,然后按照分析的步骤一步步实现.
面向对象是指把事物分解成一个个对象,然后由对象之间分工合作.
面向过程 | 面向对象 | |
---|---|---|
优点 | 性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。 | 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 |
缺点 | 不易维护、不易复用、不易扩展 | 性能比面向过程低 |
面向对象的特点
封装 继承 多态
对象和类
对象是由属性和方法组成:是一个无序键值对的集合,是指一个具体的事物
(对象如果想批量生产出来,可以用类似现实生活中的图纸和车的关系一样
图纸:类 ; 车:对象)
属性:事物的特征,在对象中用属性来表示(常用名词)
方法:事物的行为,在对象中用方法来表示(常用动词)
//创建对象的三种简单方式: 1.字面量: var obj={ age :18,//属性名:属性值 uname :'Amy' fn:function(){ console.log('你好') }//方法名:方法 } 2.构造函数: function Person(name,age){ this.name = name; this.age = age; this.sayHi=function(){ console.log('你好') } } var xm = new Person('小明',20) 3.自定义构造函数: var obj = new Object(); obj.name='小明';//给对象追加属性 obj.sayHi=function(){ console.og('你好') }//给对象追加方法
工厂模式 | 自定义构造函数 | |
---|---|---|
不同 | 函数名是小写 有new, 有返回值 new之后的对象是当前的对象 直接调用函数就可以创建对象 | 函数名是大写(首字母) 没有new 没有返回值 this是当前的对象 通过new的方式来创建对象 |
相同 | 都是函数,都可以创建对象,都可以传入参数 |
<script> // 工厂模式 function fn(name, age) { var obj = {} obj.name = name obj.age = age return obj; } var zxy = fn('张学友', 20) console.log(zxy); // 构造函数 ----->实例化对象 function Star(name, age) { this.name = name this.age = age this.sing=function(){ console.log('我会唱歌') } } var ldh = new Star('刘德华', 20) console.log(ldh); /* 实例对象和构造函数之间的关系: * 1. 实例对象是通过构造函数来创建的---创建的过程叫实例化 如何判断对象是不是这个数据类型? 1) 通过构造器的方式 实例对象.构造器==构造函数名字 hero.constructor === Hero 2) 对象 instanceof 构造函数名字尽可能的使用第二种方式来识别 */ </script>
实例成员与静态成员
//实例成员是对象的成员 hero.name = 'zs'; //静态成员是直接给构造函数添加的成员 Hero.version = '1.0'; // 静态成员不能使用对象的方式来调用 console.log(hero.version); // 静态成员使用构造函数来调用 console.log(Hero.version); //实例成员 是构造函数内部通过this添加的成员 如下列代码中uname age sing 就是实例成员,实例成员只能通过实例化的对象来访问 function Star(uname, age) { this.uname = uname; this.age = age; this.sing = function() { console.log('我会唱歌'); } } var ldh = new Star('刘德华', 18); console.log(ldh.uname);//实例成员只能通过实例化的对象来访问 //静态成员 在构造函数本身上添加的成员 如下列代码中 sex 就是静态成员,静态成员只能通过构造函数来访问 function Star(uname, age) { this.uname = uname; this.age = age; this.sing = function() { console.log('我会唱歌'); } } Star.sex = '男'; var ldh = new Star('刘德华', 18); console.log(Star.sex);//静态成员只能通过构造函数来访问
类
ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。类抽象了对象的公共部分,它泛指某一大类(class)对象特指某一个,通过类实例化一个具体的对象.
1.创建类:语法 //使用class关键字 class name { //class student } //使用定义的类创建实例 使用new关键字 var xm = new name(); 2.示例 class Star { // constructor在NEW的时候会自动被调用,并且只能调用一次 // 公共的方法在new的时候不会被调用,只能我们自己主动调用 // 类的共有属性放到 constructor 里面 constructor是 构造器或者构造函数 constructor(uname, age) { this.uname = uname; this.age = age; } // 公共方法 // 类中所有的函数都不需要用function,并且不需要逗号隔开 sing(song) { console.log('我会唱' + song); } } //利用类创建对象 new var ldh = new Star('刘德华', 20); ldh.sing('笨小孩'); /* 注意点 1. 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写 2. 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象 3. constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数 4. 多个函数方法之间不需要添加逗号分隔 5. 生成实例 new 不能省略 6. 语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function */
类的继承
语法: // 父类 class Father{ } // 子类继承父类 class Son extends Father { } 示例: //继承 class Father { constructor(uname, age) { this.uname = uname; this.age = age; this.money1 = 300; } sing1() { return '我会唱歌'; } eat() { console.log('我还会吃饭'); } sleep() { console.log('我会睡觉'); } } // sing1() // extends继承父类 class Son extends Father { constructor(uname, age, money) { // super()调用父类的 constructor构造函数 super(uname, age, money); this.money = money; } hobby() { console.log('我喜欢看书' + super.sing1()); } salary() { console.log(this.money + this.money1); } } var xm = new Son('小明', 22, 100); // console.log(xm); xm.salary();//400 xm.hobby();//我喜欢看书我会唱歌 /* 注意: 1. 继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的 2. 继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则) 3. 如果子类想要继承父类的方法,同时在自己内部扩展自己的方法,利用super 调用父类的构造函数,super 必须在子类this之前调用 4. 时刻注意this的指向问题,类里面的共有的属性和方法一定要加this使用. 1. constructor中的this指向的是new出来的实例对象 2. 自定义的方法,一般也指向的new出来的实例对象 3. 绑定事件之后this指向的就是触发事件的事件源 5. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象 */
tab栏切换案例
<style> * { margin: 0; padding: 0; } .content { width: 50%; height: 400px; border: 1px solid #000; margin: 100px auto; position: relative; } .content .top { height: 70px; border-bottom: 1px solid green; } .content .top ul { overflow: hidden; } .content .top ul li { position: relative; float: left; list-style: none; width: 60px; text-align: center; line-height: 70px; border-right: 1px solid red; } .content .top ul li i { background-color: blue; position: absolute; right: 0; top: 0; line-height: normal; font-style: normal; cursor: pointer; } .content .top .liactive { background-color: pink; } .add { position: absolute; top: 25px; right: 0; } .content .bottom>div { display: none; } .content .bottom>div.dactive { display: block; } </style> </head> <body> <!-- 大盒子 --> <div class="content" id="content"> <!-- 上面的标题部分 --> <div class="top"> <ul> <li class="liactive"><span>1标签</span><i>x</i></li> <li><span>1标签</span><i>x</i></li> <li><span>1标签</span><i>x</i></li> </ul> <button class="add">+</button> </div> <!-- 下面的内容 --> <div class="bottom"> <div class="dactive">1内容</div> <div>2内容</div> <div>3内容</div> </div> </div> <script> // 创建一个类,存放公共的方法 class Tab { constructor(id) { this.main = document.querySelector(id); this.init(); } init() { // 获取标题的大盒子 this.titleBox = this.main.querySelector('.top ul'); // 获取标题的列表 this.titleList = this.main.querySelectorAll('.top li'); // 获取下面存放内容的大盒子 this.contentBox = this.main.querySelector('.bottom '); // 获取下面存放内容的列表 this.contentList = this.main.querySelectorAll('.bottom>div'); // 添加按钮 this.addBtn = this.main.querySelector('.add'); // 删除按钮 this.removeBtn = this.main.querySelectorAll('li i') this.toggle(); this.add(); this.remove(); this.revise(); } // 切换 toggle() { var that = this; // 排他思想,点击事件 for (var i = 0; i < this.titleList.length; i++) { // 设置自定义属性 this.titleList[i].setAttribute('index', i); // 点击事件开始 this.titleList[i].onclick = function () { for (var j = 0; j < that.titleList.length; j++) { // 点击的时候先清空类名,确定点击哪一个的时候把类名给附给哪一个 that.titleList[j].className = ''; that.contentList[j].className = ''; } // 点击的标签添加类名 this.className = 'liactive'; // 定义一个index 然后获取到点击的索引值 var index = this.getAttribute('index'); // 内容列表相同的下标的页面添加相同的类名显示出来 that.contentList[index].className = 'dactive'; } } } // 添加 add() { var that = this; this.addBtn.onclick = function () { // console.log('123'); var newli = '<li><span>新标签</span><i>x</i></li>'; var newdiv = '<div>新内容</div>'; // console.log(this);//指向add // console.log(that);//指向tab that.titleBox.innerHTML += newli; that.contentBox.innerHTML += newdiv; that.init(); that.titleList[that.titleList.length - 1].click(); } } // 删除按钮 remove() { var that = this; for (var i = 0; i < this.removeBtn.length; i++) { this.removeBtn[i].setAttribute('index', i); this.removeBtn[i].onclick = function (e) { e.stopPropagation(); // console.log('123'); // console.log(this); // console.log(that); var index = this.getAttribute('index'); that.titleList[index].remove(); that.contentList[index].remove(); // that.init(); if (index == 0) { // return; if (that.titleList.length == 1) { return; } else { that.init(); that.titleList[0].click(); } } // 如果删除以后发现选中的还在的话,就不需要切换 if (that.main.querySelectorAll('.liactive').length > 0) { return; } index--; that.titleList[index].click(); } } } // 修改标签 revise() { var that = this; for (var i = 0; i < this.titleList.length; i++) { this.titleList[i].setAttribute('index', i); this.titleList[i].ondblclick = function () { // console.log('123'); // console.log(this);//指向的是li标签 var newText = this.innerHTML; this.innerHTML= `<input type="text" >`; var input = this.children[0]; input.value = newText; input.focus() input.onblur = function () { this.parentNode.innerHTML = this.value } console.log(input); console.log(newText); } } } } new Tab('#content'); </script> </body>
原型(原型链)
//构造函数 原型对象 实例对象三者的关系 /* * 构造函数可以实例化对象 * 构造函数中有一个属性叫prototype,是构造函数的原型对象 * 构造函数的原型对象(prototype)中有一个constructor构造器,这个构造器指向的就 是自己所在的原型对象所在的构造函数 * 构造函数的原型对象(prototype)中的方法是可以被实例对象直接访问的 * 实例对象的原型对象(__proto__)指向的是该构造函数的原型对象 */ /* 实例对象.__proto__ ===>构造函数的原型对象 实例对象.__proto__.__proto__ ===>Object的原型对象 实例对象.__proto__.__proto__.__proto__ ===>null 实例对象 = new 构造函数 构造函数.prototype ===>构造函数的原型对象 原型对象.constructor ===>构造函数 实例对象.constructor ===>构造函数 原型对象.__proto__ ===>Object的原型对象 原型对象.__proto__.constructor ===>Object */
this指向
/*构造函数中的this就是实例对象 原型对象中方法中的this就是实例对象*/ function Mother(name, age) { this.name = name this.age = age } Mother.prototype.fn1 = function(){ console.log(this); } var son = new Mother('小明', 20) son.fn1();
原型添加方法
不需要共享的数据写在构造函数中,需要共享的数据写在原型中(实例对象可以直接访问原型对象中的属性和方法)
原型的作用: 1. 数据共享, 节省内存空间 2. 实现继承
/*构造函数里面的方法会在每次new一个新对象的时候, 都存储一次该方法, 这样就会造成内存冗杂 解决1: 把这个方法sayHi写成全局方法, 在构造函数中this.sayHi = sayHi; 这样可能会造成函数名重复, 更麻烦 况且如果此时有10个方法需要存储, 那么需要在全局作用域里面写10个函数, 也比较占内存 解决2: 原型 每一个构造函数都有一个属性 原型 / 也叫原型对象 可以给对象动态增加方法*/ function Student(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } Student.prototype.sayHi = function () { console.log('大家好,我是' + this.name); } //通过Student构造函数,创建的对象,可以访问Student.prototype中的成员 var s1 = new Student('lilei', 18, '男'); var s2 = new Student('hmm', 18, '女'); s1.sayHi(); s2.sayHi(); /*当调用对象的属性或者方法的时候,先去找对象本身的属性/方法 ,如果对象没有该属性或者方法。此时去调用原型中的属性/方法 如果对象本身没有该属性/方法,原型中也没有该属性或者方法,此时会报错*/ /* 1. 对象的__proto__ 等于 构造函数的Student.prototype 2. s1.__proto__ === Student.prototype //true 3. __proto__属性是非标准的属性->生产环境不能使用 4. 在原型对象中有一个属性 constructor 构造函数 5. constructor 作用记录了创建该对象的构造函数 记录了创建该对象的构造函数 6. s1.constructor === Student //true 7. 实例对象可以直接访问原型对象中的属性和方法 */
//如果需要增加的方法多的话, 都写给prototype就太复杂了可写成 Student.prototype = { sayHi: function () { console.log('sayHi'); }, eat: function () { console.log('eat'); } } //可是这样的话, Student.prototype就是一个新的对象了, 不能指回Student.prototype的构造函数, 所以必须修改为: Student.prototype = { constructor: Student, sayHi: function () { console.log('sayHi'); }, eat: function () { console.log('eat'); } } /* // test属性在原型对象上,而在设置属性的值的时候,不会搜索原型链 // 而是直接给对象新增一个test属性 s1.test = '123xxx'; 此时s1自身会增加一个test属性, 并且赋值为123xxx 而原型链里面也有一个test属性, 只不过被s1自身的test属性覆盖掉了 所以此时输出s2的test属性的话, 值仍然是原来的值 */
借用构造函数继承
function Father(name, age) { this.name = name this.age = age } Father.prototype.fn = function () { console.log(123); } function Son(name, age) { Father.call(this, name, age) } // Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化(父类的原型对象会有子类原型对象特有的方法exam,这就是问题) // 通过指定父类实例对象方法解决: Son.prototype = new Father(); // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数 Son.prototype.constructor = Son; var xm = new Son('小明', 20) console.log(xm); xm.fn()
this指向问题
调用方法 | this指向 |
---|---|
普通函数 | window |
构造函数 | 实例对象,原型对象里面的方法也指向实例对象 |
对象方法调用 | 该方法所属对象 |
事件绑定方法 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
箭头函数 | window |
//普通函数 function fn1(){ console.log(this)//window } //window.fn1() fn1() //构造函数 function Person(){ console.log(this) } var xm = new Person() //对象方法调用 var obj = { sayHi:function(){ console.log('对象方法的this:'+this); } } obj.sayHi(); //事件绑定方法 var btn = document.querySelector('button'); btn.onclick = function() { console.log('绑定时间函数的this:' + this); }; // 5. 定时器函数 this 指向的也是window window.setTimeout(function() { console.log('定时器的this:' + this); }, 1000); // 6. 立即执行函数 this还是指向window (function() { console.log('立即执行函数的this' + this); })(); //箭头函数 // 箭头函数中不绑定this,箭头函数中的this指向是它所定义的位置 var uname = '张三' var obj = { uname :'李四', fn: function () { setTimeout(() => console.log(this.uname), 1000)//this指向李四 } } obj.fn()
改变函数内部this指向
改变函数内this指向 js提供了三种方法 call() apply() bind()
//call() var obj = { name: 'andy' } function fn(a, b) { console.log(a + b); console.log(this); } fn(); //此时的this指向的是window fn.call(obj, 2, 3); //此时的this指向的是对象obj // call 第一个可以调用函数 第二个可以改变函数内的this 指向 // call 的主要作用可以实现继承 function Father(uname,age,sex){ this.uname= uname; this.age = age this.sex = sex; } function Son(uname,age,sex){ Father.call(this,uname,age,sex) } var son = new Son('小米',18,'男') console.log(son); //apply() function fn(a, b) { console.log(a + b); console.log(this); } var o = {} fn() //此时的this指向的是window fn.apply(o, [2, 3]) //此时的this指向的是对象o,参数使用数组传递 // 1. 也是调用函数 第二个可以改变函数内部的this指向 // 2. 但是他的参数必须是数组(伪数组) //bind() // 1. 不会调用原来的函数 可以改变原来函数内部的this 指向 // 2. 返回的是原函数改变this之后产生的新函数 // 3. 如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind function fn() { console.log(this); } var o = { name: '测试' } // fn.bind(o)()相当于以下代码 var fn1 = fn.bind(o) fn1()
call、apply、bind三者的异同
-
共同点 : 都可以改变this指向
-
不同点:
-
call 和 apply 会调用函数, 并且改变函数内部this指向.
-
call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递
-
bind 不会调用函数, 可以改变函数内部this指向.
-
-
应用场景
-
call 经常做继承.
-
apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
-
bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.
-
数组的方法
//forEach 替代for循环,缺点不能提前中止 var arr = [1, 2, 3]; var sum = 0; // forEach不能用break提前终止 arr.forEach(function (value, index, array) { console.log(value); //数组每个元素 console.log(index); //数组每个元素的索引 console.log(array); //数组 sum += value; }) console.log(sum); //map:映射 var arr = [2, 3, 4] var arr1 = arr.map(function (value) { return value * value; }) console.log(arr1); //filter:过滤 var arr = [12, 5, 36, 15, 21] var newArr = arr.filter(function (value, index, arrat) { // return value > 20 return value % 2 == 0 }) console.log(newArr); //some:判断只要有一个满足就是true var arr = [12, 5, 36, 15, 21] //返回值是布尔值,只要查找到满足条件的一个元素就立马终止循环 // 遍历数组,判断是否有满足条件的元素,如果有返回true,如果没有返回false var bool = arr.some(function (value, index, arrat) { return value % 2 == 0 }) console.log(bool); //every:只有每一项满足就是true var arr = [12, 5, 36, 15, 21] //返回值是布尔值,只要查找到满足条件的一个元素就立马终止循环 // 遍历数组,判断是否有满足条件的元素,如果有返回true,如果没有返回false var bool = arr.every(function (value, index, arrat) { return value % 2 == 0 }) console.log(bool); //reduce var arr = [2, 3, 4] var sum = arr.reduce(function (a, b) { console.log(a,b); return a + b; }) console.log(sum); //find let arr = [{ id: 1, name: '张三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' }]; var obj = arr.find(item => item.id == 2) console.log(obj); console.log(obj instanceof Object); //找数组里面符合条件的值,当数组中元素id等于2的查找出来,注意,只会匹配第一个 //要查找的条件,写到箭头函数的函数体中 //find接收的箭头函数的参数是固定的,item,属性值 //注意这里的属性值item是一个个的对象 //obj是id为2的item对象 //findIndex let arr = [{ id: 1, name: '张三' }, { id: 2, name: '李四' }, { id: 3, name: '王五' }]; var obj = arr.findIndex((item, index) => item.id == 2) console.log(obj); //1 返回的是索引号 //Array.from var arr = { 0: 1, 1: 2, 2: 3, length: 3 } var arr1 = Array.from(arr); //转化成真正的数组 console.log(arr1); console.log(arr1 instanceof Array); //true //扩展运算符... 将数组转为用逗号分隔的参数序列 let ary = [1, 2, 3]; ...ary // 1, 2, 3 console.log(...ary); // 1 2 3,相当于下面的代码 console.log(1,2,3); //扩展运算符可以应用于合并数组 // 方法一 let ary1 = [1, 2, 3]; let ary2 = [3, 4, 5]; let ary3 = [...ary1, ...ary2]; // 方法二 ary1.push(...ary2); //方式三 //concat() 方法用于连接两个或多个数组。 console.log(ary1.concat(ary2)); //将伪数组或可遍历对象转换为真正的数组 let oDivs = document.getElementsByTagName('div'); oDivs = [...oDivs];
数组方法练习---商品案例
<style> * { margin: 0; padding: 0; box-sizing: border-box; } .box { width: 500px; height: 400px; background-color: pink; border: 1px solid black; margin: 100px auto; } .bHead { width: 100%; height: 50px; text-align: center; border: 2px solid green; } .inText { width: 60px; height: 20px; } .bBody { width: 100%; height: 350px; text-align: center; border: 2px solid green; } .bBody table { width: 100%; height: 100%; } </style> </head> <body> <div class="box"> <div class="bHead"> 价格: <input type="text" class="inText" id="star">-<input type="text" class="inText" id="end"> <button id="pBtn">查询</button> 品牌: <input type="text" class="inText" id="bText"> <button id="tBtn">查询</button> </div> <div class="bBody"> <table border="1" cellspacing=0> <thead> <tr> <th>ID</th> <th>类型</th> <th>价格</th> </tr> </thead> <tbody> </tbody> </table> </div> </div> <script> var arr = [{ id: 1, name: '小米', price: 3099 }, { id: 2, name: 'oppo', price: 1999 }, { id: 3, name: '华为', price: 3599 } ] var tbody = document.querySelector('tbody'); var star = document.querySelector('#star'); var end = document.querySelector('#end'); var pBtn = document.querySelector('#pBtn'); var tBtn = document.querySelector('#tBtn'); var inText = document.querySelector('#bText'); function newArr(myArr) { tbody.innerHTML = '' myArr.forEach(function (value) { var tr = document.createElement('tr'); tr.innerHTML = '<td>' + value.id + '</td><td>' + value.name + '</td><td>' + value.price + '</td>'; tbody.appendChild(tr) }) } newArr(arr); pBtn.addEventListener('click', function () { // console.log('123'); var arr1 = arr.filter(function (value) { // console.log(value);//返回的是arr数组中的对象 return value.price >= star.value && value.price <= end.value; }) console.log(arr1); newArr(arr1); }) tBtn.addEventListener('click', function () { // console.log(inText) // console.log('123'); var searchArr = []; arr.some(function (value) { // console.log(value.name, inText.value) if (value.name == inText.value) { searchArr.push(value); console.log(searchArr); } console.log(searchArr) newArr(searchArr); }) }) </script> </body>
trim()方法去除空格
<input type="text"> <button>点击</button> <script> var input = document.querySelector('input'); var btn = document.querySelector('button'); btn.addEventListener('click', function () { var str = input.value.trim(); // console.log(str); if (str == '') { alert('请输入内容') } else { console.log(str); console.log(str.length); } }) </script> //正则表达式去除空格 // 自己实现一个trim String.prototype.trim1 = function () { return this.replace(/^\s+|\s$/g, "") } var str = ' 123'; var str1 = ' 456 '; console.log(str.trim1().length);
获取对象的属性名和数组的属性值
Object.keys(对象)和Object.values(对象) 返回值是一个数组
var data = { id: 1, pname: '小米', price: 3999 } var arr1 = Object.keys(data); var arr2 = Object.values(data); console.log(arr1); console.log(arr2);
修改或者设置对象中的属性Object.defineProperty
Object.defineProperty(对象,修改或新增的属性名,{ value:修改或新增的属性的值, writable:true/false, //如果值为false 不允许修改这个属性值 enumerable: false, //enumerable 如果值为false 则不允许遍历 configurable: false //configurable 如果为false 则不允许删除这个属性 属性是否可以被删除或是否可以再次修改特性 }) // writable/enumerable/configurable的默认值都为true.
函数的进阶
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出
//函数作为参数 function eat(fn) { setTimeout(function () { console.log('吃晚饭'); // 吃完晚饭之后做的事情 fn(); }, 2000); } eat(function () { console.log('去唱歌'); }); //函数作为返回值输出 // 第一次调用生成随机数,以后每次调用都返回第一次的随机值 function getRandom() { var random = parseInt(Math.random() * 10) + 1; return function () { return random; } } var fn = getRandom(); console.log(fn()); console.log(fn()); console.log(fn());
闭包 在一个作用域中可以访问另一个作用域的变量
function fn() { var n = 10; return function () { return n; } } var f = fn(); console.log(f()); /*闭包特点:延展了函数的作用域范围 fn返回值是一个函数, 这个函数的返回值是n, 因为n将来还需要被使用, 所以fn运行完毕之后不会被系统销毁, 而是等着将来提供n, 所以产生了闭包*/
递归 函数自己调用自己 递归,一般都要写一个结束的条件
var count = 0; //由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return function fn() { console.log(123); count++; if (count == 6) { return; // 递归里面必须加退出条件 } fn(); //fn自己调用自己,fn就是递归函数 } fn(); //递归求斐波那契数列 <script> function fn(n) { if (n === 1 || n === 2) { return 1 } return fn(n - 1) + fn(n - 2) } console.log(fn(10)); </script>
拷贝(浅拷贝和深拷贝)
//浅拷贝 /* 浅拷贝, 对象中的基本数据类型拷贝没问题, 但是如果对象中的复杂数据类型拷贝就有问题, 修改一个值, 都会跟着变化. 如下, 如果拷贝的obj1中修改了c的m, 那么obj2的name会跟着变化 浅拷贝只拷贝对象的第一层属性, 只拷贝了引用 */ var obj1 = { a: 2, b: 3, c: { m: 1, n: 2 } } // 思路一:用 for ... in 一个属性,赋值给新的对象 // var obj2 = {} // for (var key in obj1) { // obj2[key] = obj1[key] // }; // console.log(obj2); // 思路二 var obj2 = Object.assign({}, obj1) obj2.a = 4; obj1.c.m = 5; //此时两个对象都受影响 console.log(obj1); console.log(obj2); </script> //深拷贝 /* 必须先判断是否是组数, 再判断是否是对象, 因为数组也是对象, 所以反过来的话, 会把所有的数组也当成对象来处理了 */ var obj = { a: 2, b: 3, c: { m: 1, n: 2 }, d: [2, 3, 4, 5, 6] } var o = {} function deepCopy(newobj, oldobj) { for (var k in oldobj) { if (oldobj[k] instanceof Array) { newobj[k] = deepCopy([], oldobj[k]) } else if (oldobj[k] instanceof Object) { newobj[k] = deepCopy({}, oldobj[k]) } else { newobj[k] = oldobj[k] } }; return newobj; } deepCopy(o, obj); console.log(o); console.log(obj);
正则表达式 用于匹配字符串中字符组合的模式
边界符
边界符 | 说明 |
---|---|
^ | 表示匹配行首的文本(以谁开始) |
$ | 表示匹配行尾的文本(以谁结束) |
如果 ^和 $ 在一起,表示必须是精确匹配。
var rg = /abc/; // 正则表达式里面不需要加引号 不管是数字型还是字符串型 // /abc/ 只要包含有abc这个字符串返回的都是true console.log(rg.test('abc'));//true console.log(rg.test('abcd'));//true console.log(rg.test('aabcd'));//true 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
字符类
[]表示有一系列字符可供选择,只要匹配其中一个就可以了
// 中括号外边的^为边界符,代表从什么开始 var reg = /^[a,b,c]$/ console.log(reg.test('a')); //中括号里面的^意思为取反 var reg1 = /^[^a,b,c]$/ console.log(reg1.test('a'));
量词符
量词 | 说明 |
---|---|
* | 重复0次或更多次(*,理解为任意)(0,1,n) |
+ | 重复1次或更多次(+:代表大于等于1的数。以0为分界线,左边是负数-,右边是正数+) |
? | 重复0次或1次 (?问号,理解为有没有) |
{n} | 重复n次 |
{n,} | 重复n次或更多次(没有第二个数字,那就意味着是?,没有上界) |
{n,m} | 重复n到m次 ({}:区间符号,次数区间) |
// 1次或者多次 >=1 var reg1 = /^a+$/ // 0次或者多次 >=0 var reg2 = /^a*$/ // 0次或者1次 0||1 var reg3 = /^a?$/ // 3次 var reg4 = /^a{3}$/ // 3-5次 var reg5 = /^a{3,5}$/ // 3次以上 var reg6 = /^a{3,}$/ // 只能是数字 字母 下划线组成 var reg7 = /^[a-zA-Z0-9_]*$/ // 只能是数字 字母 下划线组成,长度6-18 var reg8 = /^[a-zA-Z0-9_]{6,18}$/
括号总结
1.大括号 量词符. 里面表示重复次数
2.中括号 字符集合。匹配方括号中的任意字符.
3.小括号表示优先级
预定义类
理解:
d:digit ,数字
w:word,单词
s:space,空白,间隔
预定义类 | 含义 |
---|---|
/d | 0-9任一数字,相当于[0-9] |
/D | 0-9数字以外的任意字符,相当于0-9 |
/w | 任意的数字字母以及下划线 [0-9a-zA-Z_] |
/W | 除去数字字母以及下划线的字符 0-9a-zA-Z_ |
/s | 匹配空格 (回车 tab 空格) 相当于[\t\r\n\v\f] |
/S | 匹配非空格 字符相当于[6\t\r\n\v\f] |
正则替换replace
replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。
//过滤敏感词汇案例 <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>
let
特点: 1. let声明的变量只存在于块级作用域 2.不存在变量提升 3.存在暂时性死区
//块级作用域 if (true) { // 大括号可以形成块级作用域 let a = 10; } console.log(a) //报错: a is not defined //无变量提升 console.log(a); // a is not defined let a = 20; //暂时性死区 利用let声明的变量会绑定在这个块级作用域,不会受外界的影响 var tmp = 123; if (true) { //因为在此块级作用域中,使用了let声明了tmp变量,那么tmp会与此块级作用域进行绑定,形成死区,不受外界影响(无法访问外部的tmp) tmp = 'abc';//这里会报错,变量未声明,不能赋值 let tmp; }
let经典面试题
此题的关键点在于变量i是全局的,函数执行时输出的都是全局作用域下的i值。 //没有用到let var arr = []; for (var i = 0; i < 2; i++) { arr[i] = function () { console.log(i); } } arr[0]();//2 arr[1]();//2 //用到let的时候 let arr = []; for (let i = 0; i < 2; i++) { arr[i] = function () { console.log(i); } } arr[0]();//0 arr[1]();//1 /* - 此题的关键点在于每次循环都会产生一个块级作用域, - 每个块级作用域中的变量都是不同的, - 函数执行时输出的是自己上一级(循环产生的块级作用域)作用域下的i值. */
小结
-
let关键字就是用来声明变量的
-
使用let关键字声明的变量具有块级作用域
-
在一个大括号中 使用let关键字声明的变量才具有块级作用域 var关键字是不具备这个特点的
-
防止循环变量变成全局变量
-
循环中的i,使用var声明的话是全局变量,但是这样不合理,而使用let就是局部变量
-
-
使用let关键字声明的变量没有变量提升
-
使用let关键字声明的变量具有暂时性死区特性
-
死区:不受外界影响
-
const 声明常量,常量就是值(内存地址)不能变化的量
常量:常态的量,值是常态的量,值无法改变
(const:constant常量)
1.存在块级作用域 2.声明的常量必须赋值,且赋值后无法修改 3.不存在变量提升
//存在块级作用域 if (true) { const a = 10; } console.log(a) // a is not defined //声明的常量必须赋值,且赋值后无法修改 const PI = 3.14; PI = 100; // Assignment to constant variable. //给常量重新指定值 const ary = [100, 200]; ary[0] = 'a';//复杂数据类型内部的值,是可以改变的 ary[1] = 'b'; console.log(ary); // ['a', 'b']; ary = ['a', 'b']; // Assignment to constant variable.//但是复杂数据类型变量本身无法改变
小结
-
const声明的变量是一个常量
-
既然是常量不能重新进行赋值,如果是基本数据类型,不能更改值,如果是复杂数据类型,不能更改地址值
-
复杂数据类型变量中存储的是地址值
-
-
声明 const时候必须要给定初始值(因为没有办法重新赋值,所以在声明时必须有初始值)
var let const 函数级作用域 块级作用域 块级作用域 变量提升 不存在变量提升 不存在变量提升 值可更改 值可更改 值不可更改 解构赋值 可以让我们更快捷的从数组或对象中提取值,然后对变量赋值
//数组 let [a, b, c] = [1, 2, 3]; console.log(a)//1 console.log(b)//2 console.log(c)//3 //如果解构不成功,变量的值为undefined //对象 let { name, age } = { name: 'zhangsan', age: 20 }; console.log(name); // 'zhangsan' console.log(age); // 20 //注意这里的name仅仅是用于属性匹配,不在是变量,myName才是变量 let {name: myName, age: myAge} = { name: 'zhangsan', age: 20 }; console.log(myName); // 'zhangsan' console.log(name);//无法获取到zhangsan console.log(myAge); // 20 //如果变量不想与对象的属性名保持一致,那么采用第二种方式解构
小结
-
解构赋值就是把数据结构分解,然后给变量进行赋值
-
如果解构不成功,变量跟数值个数不匹配的时候,变量的值为undefined
-
数组解构用中括号包裹,多个变量用逗号隔开,对象解构用花括号包裹,多个变量用逗号隔开
-
利用解构赋值能够让我们方便的去取对象中的属性跟方法
-
补充:
//不管是数组还是对象在去处理结构赋值时,都是通过key匹配变量,然后进行赋值. let [a, b, c] = [0:1, 1:2, 2:3]; let { name, age } = { name: 'zhangsan', age: 20 };
-
箭头函数
-
箭头函数中不绑定this,箭头函数中的this指向是它所定义的位置
-
理解:箭头函数不绑定this,箭头函数没有自己的this关键字,如果在箭头函数中使用this,this关键字将指向箭头函数定义位置中的this
-
再理解:理解为当前箭头函数作用域中没有this,要向上一级作用域查找
-
-
箭头函数的优点在于解决了this执行环境所造成的一些问题。
-
比如:解决了匿名函数this指向的问题(匿名函数的执行环境具有全局性),包括setTimeout和setInterval中使用this所造成的问题
-
//语法 /*函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号 如果形参只有一个,可以省略小括号 箭头函数不绑定this关键字,箭头函数中的this,指向的是函数定义位置的上下文this */ () => {} //():代表是函数; =>:代表指向,指向哪一个代码块;{}:代码块,函数体 const fn = () => {}//代表把一个函数赋值给fn //示例 const obj = { name: '张三'} function fn () { console.log(this);//this 指向 是obj对象 return () => { console.log(this); //this 指向 的是箭头函数定义区域的this,那么这个箭头函数定义在fn里面,而这个fn指向是的obj对象,所以这个this也指向是obj对象 //理解为当前箭头函数作用域中没有this,要向上一级作用域查找 } } const resFn = fn.call(obj); resFn();
string扩展方法
模板字符串中可以写代码,html代码(可以识别空格和换行等格式),js代码(写在${})
1.模板字符串中可以解析变量 2.模板字符串中可以换行 3.在模板字符串中可以调用函数 4.同样是通过${}来调用
let obj = { uname: '张三', age: 18 } ////模板字符串中,通过${变量名}的方式来获取变量值 var str = `你的名字是${obj.uname},你的年龄是${obj.age},${Math.random()}` console.log(str);//你的名字是张三,你的年龄是18,0.8866876958290935(随机数) var str1 = `<div> <h3>${obj.uname}</h3></div>` console.log(str1);//<div> <h3>张三</h3></div>
实例方法
startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
var str = 'abc.jpg'; var reg = /jpg$/ if (reg.test(str)) { console.log(1); } else { console.log(2); } console.log(str.endsWith('jpg')); //true 以什么结尾 console.log(str.startsWith('abc')); //true 以什么开头
repeat():表示将原字符串重复n次,返回一个新字符串
'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello"
set数据结构
实例方法:
-
add(value):添加某个值,返回 Set 结构本身
-
delete(value):删除某个值,返回一个布尔值,表示删除是否成功
-
has(value):返回一个布尔值,表示该值是否为 Set 的成员
-
clear():清除所有成员,没有返回值
const s = new Set(); s.add(1).add(2).add(3); // 向 set 结构中添加值 s.delete(2) // 删除 set 结构中的2值 s.has(1) // 表示 set 结构中是否有1这个值 返回布尔值 s.clear() // 清除 set 结构中的所有值 //注意:删除的是元素的值,不是代表的索引
遍历
Set 结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
const s = new Set(); s.add(1).add(2).add(3); s.forEach(value => console.log(value))