分为两类:
基本类型(值类型)
对象类型(引用类型)
typeof 返回数据类型的字符串表达
instanceof 判断对象的具体类型:数组、函数。。。
=== (尽量不用==,因为==会做数据转换)
// 基本类型 var a; console.log(a, typeof a === undefined) // undefined false console.log(a, typeof a, typeof a === 'undefined', a===undefined) // undefined "undefined" true true ---------------------------------------------------------------------------- a = 3 console.log(typeof a === 'number') // true ----------------------------------------------------------------------------- a = 'asdsd' console.log(typeof a === 'string') // true ----------------------------------------------------------------------------- a = true console.log(typeof a === 'boolean') // true ----------------------------------------------------------------------------- a = null console.log(typeof a, a === null) // 'object' true // 对象----------------------------------------------------------------------- var b1 { b2: [1,'abc',console.log], b3: function() { console.log('b3') return function() { return 'xfdfds' } } } console.log(b1 instanceof Object, b1 instanceof Array) // true false // Object是一个构造函数(因为new Object() ) ------------------------------------------------------------------------------ console.log(typeof b1.b2) // 'object' console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) // true true // 数组Array也是对象 ------------------------------------------------------------------------------ console.log(b1.b3 instanceof Function, b1.b3 instanceof Object) // true true // 函数Function也是对象 console.log(typeof b1.b3, typeof b1.b3 === 'function') // 'function' true console.log(typeof b1.b2[2] === 'function') // true ------------------------------------------------------------------------------ b1.b2[2](4) // 这是一个函数 // 输出 xfdfds 怎么调用 console.log(b1.b3()())
var a console.log(a) //声明未赋值 undefined
// 起始 var b = null //初始赋值为null,表明将要赋值为对象 // 确定对象就赋值 b = ['aedxf', 12] // 最后 b = null // 释放对象,让b指向的对象成为垃圾对象(被垃圾回收器回收)
一小块内存中存储两个东西:内部存储的数据、地址值
简单数据类型也有地址,只是不用
内存分类
函数在堆空间里,但函数名(标识函数的变量)在栈空间
变量(变量名+变量值)
每个变量都对应一块小内存,变量名用来查找对应内存,变量值就是内存中保存的数据
内存,数据,变量的关系
内存用来存储数据的空间
变量是内存的标识
关于赋值和内存的问题
var a = xxx,a内存中保存的是什么?
var obj1 = {name: 'Tom'} var obj2 = obj1 obj1.name = 'Jack' console.log(obj2.name) // 'Jack' function fn(obj){ obj.name = 'A' } fn(obj1) //这时候有三个变量指向同一个对象(obj1、obj2、fn的形参obj) function fn2(obj) { obj = {age:15} //这是一个垃圾对象,函数执行完,局部变量会被回收 } fn2(a) console.log(a.age) // 15 // a传给fn2的形参obj是a的地址值,此时obj保存的是a的地址值 // 但是在函数中,obj重新被复制了一个地址值,a没有变化
var a = 3 function fn(a) { //这里的形参a相当于在fn中var a,和全局的a无关 a = a+1 } fn(a) //这里传递进去的是值3 console.log(a) // 3,这里打印的是全局的a变量
var a = 3 var obj = {} //此时2个对象,内存空间没有被释放(因为是全局变量) object = null // 此时2个对象,obj并没有被释放 function fn() { var b = {} var c = 4 // 函数被调用时才会初始化变量 } fn() //函数执行完,b自动释放,b指向的对象是在后面某个时刻由垃圾回收器回收的
多个数据的封装体
统一管理多个数据
p.name 或者p[‘name’]
var propName = 'myAge' var age = 18 p[propName] = value
var obj = {} function test2() { this.xxx = 'atguigu' } // 假设obj.test2() test2.call(obj) // 可以让一个函数成为指定任意对象的方法进行调用 console.log(obj.xxx)// atguigu
全称:Immediately-Invoked Function Expression
作用:
(function () { var a = 1; function test(){ console.log(++a); } window.$ = function () { //向外暴露一个全局函数 return { test: test } } }) $().test() //1. $是一个函数 2.执行后返回的是一个对象
instanceof是如何判断的?
A instanceof B:如果B函数的显示原型对象在A对象的原型链上,返回true,否则返回false
面试题:
// 测试1 function A() {} A.prototype.n = 1; var b = new A(); A.prototype = { n: 2, m: 3 } var c = new A(); console.log(b.n, b.m, c.n, c.m); // 1 undefined 2 3 -------------------------------------------------------------------- // 测试2 var F = function(){} Object.prototype.a = function(){ console.log('a()') } Function.prototype.b = function(){ console.log('b()') } var f = new F(); f.a() // a() f.b() // 报错 f.b is not a function F.a() // a() F.b() // b()
在执行全局代码前将window确定为全局执行上下文
对全局数据进行预处理
开始执行全局代码
// 1.依次输出什么 2.整个过程中产生了几个执行上下文 console.log('global begin:' + i) var i = 1 foo(1) function foo(i) { if(i == 4) { return; } console.log('foo() begin:' + i); foo(i + 1); console.log('foo() end:' + i); } console.log('global() end:' + i); ![请添加图片描述](https://www.www.zyiz.net/i/ll/?i=36281d246e904abfa67e5f968a18e46b.png) // 产生了5个执行上下文
当函数声明与变量名 相同时,在变量赋值前,函数声明依旧是函数声明,不会被覆盖;当变量赋值后,函数声明被同变量覆盖。
// 测试题1:先执行变量提升,再执行函数提升(存疑) function a() {} var a; console.log(typeof a) // 'function' ------------------------------------------------------------ // 测试题2 if(!(b in window)) { var b = 1; } console.log(b); // undefined ------------------------------------------------------------ // 测试题3 var c = 1; function c(c) { console.log(c); } c(2); // c is not a function
// 测试题1:输出多少 var x = 10; function fn() { console.log(x); } function show(f) { var x = 20; f(); } show(fn); // 10 // 作用域在函数定义时就已经确定,所以存在三种作用域:全局、fn()、show() // 当fn()调用时,先在自身作用域查找x没找到,再去全局作用域寻找,所以x = 10 ---------------------------------------------------------------- // 测试题2:输出情况 var fn = function () { console.log(fn) } fn() var obj = { fn2: function () { console.log(fn2) } } obj.fn2() // 结果 ƒ () { console.log(fn) } ReferenceError: fn2 is not defined // 当fobj.fn2()调用时,先在自身作用域查找fn2没找到,再去全局作用域寻找,所以报错 // 要查找到fn2:this.fn2
结合11
// 需求: 点击某个按钮, 提示"点击的是第n个按钮" for(var i=0;i<btns.length;i++) { // 每次都会计算btns.length // for(var i=0,length=btns.length;i<length;i++) 只会计算一次btns.length var btn = btns[i] btn.onclick = function () { alert('第'+(i+1)+'个') } } console.log(i); // 4 [i是全局变量] ------------------------------------------------------------ function fn1() { //闭包 var a=2; function fn2() { console.log(a); } } fn1()
面向过程 | 面向对象 | |
---|---|---|
优点 | 性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。 | 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 |
缺点 | 不易维护、不易复用、不易扩展 | 性能比面向过程低 |
对象是由属性和方法组成的:是一个无序键值对的集合,指的是一个具体的事物
//以下代码是对对象的复习//字面量创建对象var ldh = { name: '刘德华', age: 18}console.log(ldh);//构造函数创建对象 function Star(name, age) { this.name = name; this.age = age; }var ldh = new Star('刘德华', 18)//实例化对象console.log(ldh);
如上两行代码运行结果为:
//步骤1 使用class关键字 class name { // class body } //步骤2使用定义的类创建实例 注意new关键字 var xx = new name();
// 1. 创建类 class 创建一个 明星类 class Star { // 类的共有属性放到 constructor 里面 constructor(name, age) { this.name = name; this.age = age; } } // 2. 利用类创建对象 new var ldh = new Star('刘德华', 18); console.log(ldh);
以上代码运行结果:
通过结果我们可以看出,运行结果和使用构造函数方式一样
// 1. 创建类 class 创建一个类 // 1. 创建类 class 创建一个类 class Star { // 类的共有属性放到 constructor 里面 constructor是 构造器或者构造函数 constructor(uname, age) { this.uname = uname; this.age = age; }//------------------------------------------->注意,方法与方法之间不需要添加逗号 sing(song) { console.log(this.uname + '唱' + song); } } // 2. 利用类创建对象 new var ldh = new Star('刘德华', 18); console.log(ldh); // Star {uname: "刘德华", age: 18} ldh.sing('冰雨'); // 刘德华唱冰雨
以上代码运行结果:
注意哟:
// 父类 class Father{ } // 子类继承父类 class Son extends Father { }
class Father { constructor(surname) { this.surname= surname; } say() { console.log('你的姓是' + this.surname); } } class Son extends Father{ // 这样子类就继承了父类的属性和方法 } var damao= new Son('刘'); damao.say(); //结果为 你的姓是刘
以上代码运行结果:
//定义了父类 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); //使用super调用了父类中的构造函数 } } var son = new Son(1, 2); son.sum(); //结果为3
注意:
继承中,如果实例化子类输出一个方法,先看子类有没有这个方法,如果有就先执行子类的
继承中,如果子类里面没有,就去查找父类有没有这个方法,如果有,就执行父类的这个方法(就近原则)
如果子类想要继承父类的方法,同时在自己内部扩展自己的方法,利用super 调用父类的构造函数,super 必须在子类this之前调用
// 父类有加法方法 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之前调用,放到this之后会报错 super(x, y); this.x = x; this.y = y; } subtract() { console.log(this.x - this.y); } } var son = new Son(5, 3); son.subtract(); //2 son.sum();//8
以上代码运行结果为:
时刻注意this的指向问题,类里面的共有的属性和方法一定要加this使用.
在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象
为获取到的标题绑定点击事件,展示对应的内容区域,存储对应的索引
this.lis[i].index = i; this.lis[i].onclick = this.toggleTab;
使用排他,实现只有一个元素的显示
toggleTab() { //将所有的标题与内容类样式全部移除 for (var i = 0; i < this.lis.length; i++) { this.lis[i].className = ''; this.sections[i].className = ''; } //为当前的标题添加激活样式 this.className = 'liactive'; //为当前的内容添加激活样式 that.sections[this.index].className = 'conactive'; }
为添加按钮+ 绑定点击事件
this.add.onclick = this.addTab;
实现标题与内容的添加,做好排他处理
addTab() { that.clearClass(); // (1) 创建li元素和section元素 var random = Math.random(); var li = '<li class="liactive"><span>新选项卡</span><span class="iconfont icon-guanbi"> </span></li>'; var section = '<section class="conactive">测试 ' + random + '</section>'; // (2) 把这两个元素追加到对应的父元素里面 that.ul.insertAdjacentHTML('beforeend', li); that.fsection.insertAdjacentHTML('beforeend', section); that.init(); }
为元素的删除按钮x绑定点击事件
this.remove[i].onclick = this.removeTab;
获取到点击的删除按钮的所在的父元素的所有,删除对应的标题与内容
removeTab(e) { e.stopPropagation(); // 阻止冒泡 防止触发li 的切换点击事件 var index = this.parentNode.index; console.log(index); // 根据索引号删除对应的li 和section remove()方法可以直接删除指定的元素 that.lis[index].remove(); that.sections[index].remove(); that.init(); // 当我们删除的不是选中状态的li 的时候,原来的选中状态li保持不变 if (document.querySelector('.liactive')) return; // 当我们删除了选中状态的这个li 的时候, 让它的前一个li 处于选定状态 index--; // 手动调用我们的点击事件 不需要鼠标触发 that.lis[index] && that.lis[index].click(); }
为元素(标题与内容)绑定双击事件
this.spans[i].ondblclick = this.editTab; this.sections[i].ondblclick = this.editTab;
在双击事件处理文本选中状态,修改内部DOM节点,实现新旧value值的传递
editTab() { var str = this.innerHTML; // 双击禁止选定文字 window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty(); // alert(11); this.innerHTML = '<input type="text" />'; var input = this.children[0]; input.value = str; input.select(); // 文本框里面的文字处于选定状态 // 当我们离开文本框就把文本框里面的值给span input.onblur = function() { this.parentNode.innerHTML = this.value; }; // 按下回车也可以把文本框里面的值给span input.onkeyup = function(e) { if (e.keyCode === 13) { // 手动调用表单失去焦点事件 不需要鼠标离开操作 this.blur(); } }}
动态创建元素
-------方法1---------var li = document.createElement('li');li.innerHTML = '<span>新选项卡</span> <span class="iconfont icon-guanbi"></span>';that.ul.appendChild(li);-------方法2---------var li = '<li class="liactive"><span>新选项卡</span> <span class="iconfont icon-guanbi"></span></li>';that.ul.insertAdjacentHTML('beforeend', li);
字面量方式
var obj = {};
new关键字
var obj = new Object();
构造函数方式
function Person(name,age){ this.name = name; this.age = age;}var obj = new Person('zs',12);
new执行做的四件事情:
实例成员就是构造函数内部通过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);//静态成员只能通过构造函数来访问
构造函数方法很好用,但是存在浪费内存的问题。
每创建一个对象,会为其中的函数开辟一个空间
构造函数通过原型分配的函数是所有对象所共享的。
JavaScript 规定,每一个构造函数都有一个prototype 属性,指向另一个对象(原型对象),默认值为一个空Object对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
原型的作用:资源共享
console.log(typeof Date.prototype); // 'object' function Star(uname, age) { this.uname = uname; this.age = age; } Star.prototype.sing = function() { console.log('我会唱歌'); } var ldh = new Star('刘德华', 18); var zxy = new Star('张学友', 19); ldh.sing();//我会唱歌 zxy.sing();//我会唱歌
对象都会有一个属性 __ proto__ (隐式原型)指向构造函数的 prototype 原型对象(显式原型),之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __ proto__ 原型的存在。
ES6之前,程序员能直接操作显式原型,不能直接操作隐式原型
__proto__对象原型和原型对象 prototype 是等价的 console.log(ldh._prop_ === Star.prototype); ------ true __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
**方法的查找规则:**首先先看对象身上是否有方法,如果有就执行这个对象上的方法,如果么没有这个方法,因为有__ proto__ 的存在,就去构造函数原型对象prototype身上去查找方法。
对象原型( __proto__)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。 constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。 一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数如:
function Star(uname, age) { this.uname = uname; this.age = age; } // 很多情况下,我们需要手动的利用constructor 这个属性指回 原来的构造函数 //Star.prototype.sing = function() { // console.log('我会唱歌'); //}; //Star.prototype.movie = function() { // console.log('我会演电影'); //}; //以下写法导致原型对象的constructor属性被对象覆盖了,没有指回构造函数Star Star.prototype = { // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数 // 手动设置指回原来的构造函数 constructor: Star, sing:function() { console.log('我会唱歌'); }, movie:function() { console.log('我会演电影'); } } var zxy = new Star('张学友', 19); console.log(zxy);
以上代码运行结果,设置constructor属性如图:
如果未设置constructor属性,如图:
总结:
每一个实例对象又有一个__ proto__ 属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有 __ proto__ 属性,这样一层一层往上找就形成了原型链。(查找方法是按照隐式原型链寻找的即 _ proto _,而不是按照显式原型链)
1.构造函数的prototype属性指向了构造函数原型对象2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数
任何对象都有原型对象,也就是prototype属性,任何原型对象也是一个对象,该对象就有__ proto__属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;
构造函数中的this和原型对象的this,都指向我们new出来的实例对象
function Star(uname, age) { this.uname = uname; this.age = age;}var that;Star.prototype.sing = function() { console.log('我会唱歌'); that = this;}var ldh = new Star('刘德华', 18);// 1. 在构造函数中,里面this指向的是对象实例 ldhconsole.log(that === ldh);//true// 2.原型对象函数里面的this 指向的是 实例对象 ldh
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bDVEzhRS-1645973133178)(/Users/yinyuting/学习/前端学习/阶段三:JavaScript 网页编程资料/05-JavaScript高级资料/04-JavaScript高级资料/JavaScript 高级_day02(3-6小节)/4-笔记/images/img6.png)]
可以通过原型对象,对原来的内置对象进行扩展自定义的方法。
注意:数组和字符串内置对象不能给原型对象覆盖操作Array.prototype = {},只能是Array.prototype.xxx = function() {}的方式。
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];arr.sum();var arr1 = new Array(11,22,33);arr1.sum();//此时数组对象中已经存在sum()方法了 可以始终 数组.sum()进行数据的求
es6之前没有提供extends继承。可以通过构造函数+原型对象 模拟实现继承,被称为组合继承。
调用这个函数,并且修改函数运行时的this指向
fun.call(thisArg, arg1, arg2, ...)// thisArg: 当前调用函数this的指向对象// arg1,arg2 传递的其他参数
function fn(x, y) { console.log(this); console.log(x + y); } var o = { name: 'andy' }; fn.call(o, 1, 2);//调用了函数此时的this指向了对象o,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KXk2BfbG-1645973133178)(/Users/yinyuting/学习/前端学习/阶段三:JavaScript 网页编程资料/05-JavaScript高级资料/04-JavaScript高级资料/JavaScript 高级_day02(3-6小节)/4-笔记/images/img10.png)]
核心原理:通过call()把父类型的this指向子类型的this,这样可以实现子类型继承父类型的属性。
// 1. 父构造函数 function Father(uname, age) { // this 指向父构造函数的对象实例 this.uname = uname; this.age = age; } // 2 .子构造函数 function Son(uname, age, score) { // this 指向子构造函数的对象实例 3.使用call方式实现子继承父的属性 Father.call(this, uname, age); this.score = score; } var son = new Son('刘德华', 18, 100); console.log(son);
// 1. 父构造函数 function Father(uname, age) { // this 指向父构造函数的对象实例 this.uname = uname; this.age = age; } Father.prototype.money = function() { console.log(100000); }; // 2 .子构造函数 function Son(uname, age, score) { // this 指向子构造函数的对象实例 Father.call(this, uname, age); this.score = score; } // Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化 Son.prototype = new Father(); // 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数 Son.prototype.constructor = Son; // 这个是子构造函数专门的方法 Son.prototype.exam = function() { console.log('孩子要考试'); } var son = new Son('刘德华', 18, 100); console.log(son);
如上代码结果如图:
Array.isArray(arr);
console.log(typeof arr);
arr.forEach(function(value, index, array) { //参数一是:数组元素 //参数二是:数组元素的索引 //参数三是:当前的数组 }) //相当于数组遍历的 for循环 没有返回值
var arr = [12, 66, 4, 88, 3, 7]; var newArr = arr.filter(function(value, index,array) { //参数一是:数组元素 //参数二是:数组元素的索引 //参数三是:当前的数组 return value >= 20; }); console.log(newArr);//[66,88] //返回值是一个新数组
some 查找数组中是否有满足条件的元素 var arr = [10, 30, 4]; var flag = arr.some(function(value,index,array) { //参数一是:数组元素 //参数二是:数组元素的索引 //参数三是:当前的数组 return value < 3; });console.log(flag);//false返回值是布尔值,只要查找到满足条件的一个元素就立马终止循环
filter和some区别:filter返回的是数组,而且把所有满足条件的元素返回;some返回的是布尔值,查找到第一个满足条件的元素就终止查找
将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。
不会改变原数组
var numbers = [1, 2, 3];var res = numbers.map(function (n) { return n + 1;});
对数组的每一项执行给定的函数,假如该函数每一项都返回true,最后结果才为true;只要有一项返回值为false,最后结果就是false。且后边的元素都不会再继续执行函数;
原数组不受影响;
var arr = [20,13,11,8,0,11];var arr1 = arr.every(function(value){ return value>10;})
定义数组对象数据
var data = [{ id: 1, pname: '小米', price: 3999 }, { id: 2, pname: 'oppo', price: 999 }, { id: 3, pname: '荣耀', price: 1299 }, { id: 4, pname: '华为', price: 1999 }, ];
使用forEach遍历数据并渲染到页面中
data.forEach(function(value) { var tr = document.createElement('tr'); tr.innerHTML = '<td>' + value.id + '</td><td>' + value.pname + '</td><td>' + value.price + '</td>'; tbody.appendChild(tr); });
根据价格筛选数据
获取到搜索按钮并为其绑定点击事件
search_price.addEventListener('click', function() {});
使用filter将用户输入的价格信息筛选出来
search_price.addEventListener('click', function() { var newDate = data.filter(function(value) { //start.value是开始区间 //end.value是结束的区间 return value.price >= start.value && value.price <= end.value; }); console.log(newDate); });
将筛选出来的数据重新渲染到表格中
将渲染数据的逻辑封装到一个函数中
function setDate(mydata) { // 先清空原来tbody 里面的数据 tbody.innerHTML = ''; mydata.forEach(function(value) { var tr = document.createElement('tr'); tr.innerHTML = '<td>' + value.id + '</td><td>' + value.pname + '</td><td>' + value.price + '</td>'; tbody.appendChild(tr); }); }
将筛选之后的数据重新渲染
search_price.addEventListener('click', function() { var newDate = data.filter(function(value) { return value.price >= start.value && value.price <= end.value; }); console.log(newDate); // 把筛选完之后的对象渲染到页面中 setDate(newDate);});
根据商品名称筛选
获取用户输入的商品名称
为查询按钮绑定点击事件,将输入的商品名称与这个数据进行筛选
search_pro.addEventListener('click', function() { var arr = []; data.some(function(value) { if (value.pname === product.value) { // console.log(value); arr.push(value); return true; // return 后面必须写true } }); // 把拿到的数据渲染到页面中 setDate(arr);})
var str = ' hello ';console.log(str.trim()) //hello 去除两端空格var str1 = ' he l l o ';console.log(str.trim()) //he l l o 去除两端空格
Object.keys(对象) 获取到当前对象中的属性名 ,返回值是一个数组
var obj = { id: 1, pname: '小米', price: 1999, num: 2000};var result = Object.keys(obj);console.log(result)//[id,pname,price,num]
Object.defineProperty() 设置或修改对象中的属性
Object.defineProperty(obj,prop,descriptor)// obj: 必需,目标对象// prop: 必需,需定义或修改的属性的名字// descriptor: 必需,目标属性所拥有的特性//desccriptor以对象形式{}书写,以下属性 value:修改或新增的属性的值, 默认为undefined writable:true/false,//如果值为false 不允许修改这个属性值 enumerable: false,//enumerable 如果值为false 则不允许遍历 configurable: false //configurable 如果为false 则不允许删除这个属性 属性是否可以被删除或是否可以再次修改特性
方式1 函数声明方式 function 关键字 (命名函数)
function fn(){}
方式2 函数表达式(匿名函数)
var fn = function(){}
方式3 new Function()
Function 里面参数都必须是字符串格式
第三种方式执行效率低,也不方便书写,因此较少使用
所有函数都是 Function 的实例(对象)
函数也属于对象
var f = new Function('a', 'b', 'console.log(a + b)');f(1, 2);var fn = new Function('参数1','参数2'..., '函数体')console.log(fn instanceof Object); // true
所有函数都有隐式原型和显式原型两个属性
补充(尚硅谷):
/* 1. 普通函数 */ function fn() { console.log('人生的巅峰'); } fn(); /* 2. 对象的方法 */ var o = { sayHi: function() { console.log('人生的巅峰'); } } o.sayHi(); /* 3. 构造函数*/ function Star() {}; new Star(); /* 4. 绑定事件函数*/ btn.onclick = function() {}; // 点击了按钮就可以调用这个函数 /* 5. 定时器函数*/ setInterval(function() {}, 1000); 这个函数是定时器自动1秒钟调用一次 /* 6. 立即执行函数(自调用函数)*/ (function() { console.log('人生的巅峰'); })();
这些 this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this 的指向不同
一般指向我们的调用者.
处理函数内部this的指向问题,常用:bind()、call()、apply( )
call()方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向
应用场景: 经常做继承.
var o = { name: 'andy'} function fn(a, b) { console.log(this); console.log(a+b)};fn(1,2)// 此时的this指向的是window 运行结果为3fn.call(o,1,2)//此时的this指向的是对象o,参数使用逗号隔开,运行结果为3
以上代码运行结果为:
apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
应用场景: 经常跟数组有关系
fun.apply(thisArg,[argsArray]) // thisArg: 在fun函数运行时指定的this的值 // argsArray: 传递的值,必需包含在数组里面
var o = { name: 'andy' } function fn(a, b) { console.log(this); console.log(a+b) }; fn()// 此时的this指向的是window 运行结果为3 fn.apply(o,[1,2])//此时的this指向的是对象o,参数使用数组传递 运行结果为3 ------------------主要应用--------------------- var arr = [1,66,3,99,4]; var max = Math.max.apply(Math, arr); //得到数组的最大值
bind() 方法不会调用函数,但是能改变函数内部this 指向,返回的是原函数改变this之后产生的新函数
如果只是想改变 this 指向,并且不想调用这个函数的时候,可以使用bind
应用场景: 不调用函数,但是还想改变this指向
var o = { name: 'andy' }; function fn(a, b) { console.log(this); console.log(a + b); }; var f = fn.bind(o, 1, 2); //此处的f是bind返回的新函数 f();//调用新函数 this指向的是对象o 参数使用逗号隔开 -------------------------------------------------------- // 1. 不会调用原来的函数 可以改变原来函数内部的this 指向 // 2. 返回的是原函数改变this之后产生的新函数 // 3. 如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind // 4. 我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮 var btn1 = document.querySelector('button'); btn1.onclick = function() { this.disabled = true; // 这个this 指向的是 btn 这个按钮 // var that = this; setTimeout(function() { // that.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), 2000); } }
共同点 : 都可以改变this指向
不同点:
应用场景
function Person(color) { this.color = color; this.setColor = function(color) { return this.color; }}var test = p.setColor;test(); // this指向window--------------------------------------------------------------------------function fun1() { function fun2() { console.log(this); } fun2();}fun2(); // this指向window
JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript变体的一种方式,即在严格的条件下运行 JS 代码。
严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:
1.消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
2.消除代码运行的一些不安全之处,保证代码运行的安全。
3.提高编译器效率,增加运行速度。
4.禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class,enum,export, extends, import, super 不能做变量名
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
情况一 :为脚本开启严格模式
有的 script 脚本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他
script 脚本文件。
(function (){ //在当前的这个自调用函数中有开启严格模式,当前函数之外还是普通模式 "use strict"; var num = 10; function fn() {}})();//或者 <script> "use strict"; //当前script标签开启了严格模式</script><script> //当前script标签未开启严格模式</script>
情况二: 为函数开启严格模式
要给某个函数开启严格模式,需要把“use strict”; (或 ‘use strict’; ) 声明放在函数体所有语句之前。
function fn(){ "use strict"; return "123";} //当前fn函数开启了严格模式
严格模式对 Javascript 的语法和行为,都做了一些改变。
以前在全局作用于函数中的this指向window对象
严格模式下全局作用域中函数中的 this 是 undefined
普通模式下,构造函数不加new也可以调用当普通函数使用,this指向全局对象window
严格模式下,如果构造函数不加new调用, this 指向的是undefined 如果赋值,会报错
new 实例化的构造函数指向创建的对象实例。
定时器 this 还是指向 window
事件、对象还是指向调用者
'use strict' num = 10 console.log(num)//严格模式后使用未声明的变量 -------------------------------------------------------------------------------- var num2 = 1; delete num2;//严格模式不允许删除变量 -------------------------------------------------------------------------------- function fn() { console.log(this); // 严格模式下全局作用域中函数中的 this 是 undefined } fn(); --------------------------------------------------------------------------------- function Star() { this.sex = '男'; } Star(); console.log(window.sex); //正常模式下,普通构造函数中的this指向window,输出 ’男‘ // 严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错. --------------------- var ldh = new Star(); console.log(ldh.sex); ---------------------------------------------------------------------------------- setTimeout(function() { console.log(this); //严格模式下,定时器 this 还是指向 window }, 2000); ---------------------------------------------------------------------------------- function fn(a, a) { console.log(a + a); //严格模式下函数里面的参数不允许有重名 }; fn(1, 2); ---------------------------------------------------------------------------------- function baz() { function eit() {} //合法 }
更多严格模式要求参考
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
此时fn 就是一个高阶函数
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。
同理函数也可以作为返回值传递回来
变量根据作用域的不同分为两种:全局变量和局部变量。
闭包(closure)指有权访问另一个函数作用域中变量的函数。简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。 这个局部变量所在的函数就是闭包函数
作用:延伸变量的作用范围。
// fn外面的作用域可以访问fn内部的局部变量 function fn() { var num = 10; function fun() { console.log(num); } return fun; } var f = fn(); //类似于 var f = function fun() { console.log(num); } 因为fn的返回值是函数fun f();
缺点:函数执行完,函数内的局部变量没有释放,占用内存时间会变长;容易造成内存泄漏
解决:能不用闭包就不用,及时释放
for (var i = 0; i < lis.length; i++) { // 利用for循环创建了4个立即执行函数 // 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量 (function(i) { lis[i].onclick = function() { console.log(i); } })(i); } // 因为立即函数内部形成了作用域,console.log(i)只会使用这个作用域内部的i,不会受到外界for循环i的影响
for (var i = 0; i < lis.length; i++) { (function(i) { setTimeout(function() { console.log(lis[i].innerHTML); }, 3000) })(i); }
/*需求分析 打车起步价13(3公里内), 之后每多一公里增加 5块钱. 用户输入公里数就可以计算打车价格 如果有拥堵情况,总价格多收取10块钱拥堵费*/ var car = (function() { var start = 13; // 起步价 局部变量 var total = 0; // 总价 局部变量 return { // 正常的总价 price: function(n) { if (n <= 3) { total = start; } else { total = start + (n - 3) * 5 } return total; }, // 拥堵之后的费用 yd: function(flag) { return flag ? total + 10 : total; } } })(); console.log(car.price(5)); // 23 console.log(car.yd(true)); // 33
var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { return function() { return this.name; }; } }; console.log(object.getNameFunc()()) // "The Window" -----------------拆解------------------ var f = object.getNameFunc(); var f = function() { return this.name; } f(); //此时this指向window ----------------------------------------------------------------------------------- var name = "The Window"; var object = { name: "My Object", getNameFunc: function() { var that = this; return function() { return that.name; }; } }; console.log(object.getNameFunc()()) //My Object -----------------拆解------------------ var f = object.getNameFunc(); var f = function() { return that.name; //obeject.name } f();
递归:如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。简单理解:函数内部自己调用自己, 这个函数就是递归函数
注意:递归函数的作用和循环效果一样,由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return。
//利用递归函数求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));
// 我们想要做输入id号,就可以返回的数据对象 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; return o; // 2. 我们想要得里层的数据 11 12 可以利用递归函数 // 里面应该有goods这个数组并且数组的长度不为 0 } else if (item.goods && item.goods.length > 0) { o = getID(item.goods, id); } }); return o; }
浅拷贝:只拷贝一层,更深层次对象级别的只拷贝引用
深拷贝:拷贝多层,每一级别的数据都会拷贝
Object.assign(target, …source) es6新增方法可以浅拷贝
var obj = { id: 1, name: 'andy', msg: { age: 18 } }; var o = {}; ------------------浅拷贝------------- // 拷贝的是msg的地址 // for (var k in obj) { // // k 是属性名 obj[k] 属性值 // o[k] = obj[k]; // } // o.msg.age = 20; Object.assign(o, obj); o.msg.age = 20; console.log(obj); ------------------深拷贝------------- // 封装函数 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; } } }
正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象。
正则表通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。
其他语言也会使用正则表达式,本阶段我们主要是利用JavaScript 正则表达式完成表单验证。
在 JavaScript 中,可以通过两种方式创建一个正则表达式。
方式一:通过调用RegExp对象的构造函数创建
var regexp = new RegExp(/123/); console.log(regexp);
方式二:利用字面量创建 正则表达式
var rg = /123/;
test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。
var rg = /123/; console.log(rg.test(123));//匹配字符中是否出现123 出现结果为true console.log(rg.test('abc'));//匹配字符中是否出现123 未出现结果为false
一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单和特殊字符的组合,比如 /ab*c/ 。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+ 等。
特殊字符非常多,可以参考:
MDN
jQuery 手册:正则表达式部分
[正则测试工具](<http://tool.oschina.net/regex)
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
边界符 | 说明 |
---|---|
^ | 表示匹配行首的文本(以谁开始) |
$ | 表示匹配行尾的文本(以谁结束) |
如果 ^和 $ 在一起,表示必须是精确匹配。
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
字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。
表示有一系列字符可供选择,只要匹配其中一个就可以了
var 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'));//false ---------------------------------------------------------------------------------- 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个英文字母(大写和小写都可以)任何一个字母、0-9、下划线_、短横线- 返回 true console.log(reg1.test('a'));//true console.log(reg1.test('B'));//true console.log(reg1.test(8));//true console.log(reg1.test('-'));//true console.log(reg1.test('_'));//true console.log(reg1.test('!'));//false ------------------------------------------------------------------------------------ //取反 方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 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
量词符用来设定某个模式出现的次数。
量词 | 说明 |
---|---|
* | 重复0次或更多次 |
+ | 重复1次或更多次 |
? | 重复0次或1次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
// * 相当于 >=0出现0次或者很多次var reg = /^a*$/console.log(reg.test('')); //trueconsole.log(reg.test('a')); //trueconsole.log(reg.test('aaaa')); //true// + 相当于 >=1出现1次或者很多次var reg = /^a+$/console.log(reg.test('')); //falseconsole.log(reg.test('a')); //trueconsole.log(reg.test('aaaa')); //true// ? 相当于 1||0var reg = /^a?$/console.log(reg.test('')); // trueconsole.log(reg.test('a')); // trueconsole.log(reg.test('aaaa')); // false// {3} 重复3次var reg = /^a{3}$/console.log(reg.test('')); // falseconsole.log(reg.test('a')); // falseconsole.log(reg.test('aaaa')); // falseconsole.log(reg.test('aaa')); // true// {3,} 相当于 >=3var reg = /^a{3,}$/console.log(reg.test('')); // falseconsole.log(reg.test('a')); // falseconsole.log(reg.test('aaaa')); // trueconsole.log(reg.test('aaa')); // true// {3,6} 相当于 >=3 && <=6var reg = /^a{3,6}$/console.log(reg.test('')); // falseconsole.log(reg.test('a')); // falseconsole.log(reg.test('aaaa')); // trueconsole.log(reg.test('aaa')); // trueconsole.log(reg.test('aaaaaaa')); // false
功能需求:
<input type="text" class="uname"> <span>请输入用户名</span> <script> // 量词是设定某个模式出现的次数 var reg = /^[a-zA-Z0-9_-]{6,16}$/; // 这个模式用户只能输入英文字母 数字 下划线 中划线 var uname = document.querySelector('.uname'); var span = document.querySelector('span'); uname.onblur = function() { if (reg.test(this.value)) { console.log('正确的'); span.className = 'right'; span.innerHTML = '用户名格式输入正确'; } else { console.log('错误的'); span.className = 'wrong'; span.innerHTML = '用户名格式输入不正确'; } } </script>
1.大括号 量词符. 里面表示重复次数
2.中括号 字符集合。匹配方括号中的任意字符.
3.小括号表示优先级
正则表达式在线测试
// 中括号 字符集合.匹配方括号中的任意字符. var reg = /^[abc]$/; // a 也可以 b 也可以 c 可以 a ||b || c // 大括号 量词符. 里面表示重复次数 var reg = /^abc{3}$/; // 它只是让c重复三次 abccc console.log(reg.test('abc')); // false console.log(reg.test('abcabcabc')); // false console.log(reg.test('abccc')); // true // 小括号 表示优先级 var reg = /^(abc){3}$/; // 它是让abcc重复三次 console.log(reg.test('abc')); // false console.log(reg.test('abcabcabc')); // true console.log(reg.test('abccc')); // false
预定义类指的是某些常见模式的简写方式.
案例:验证座机号码
var reg = /^\d{3}-\d{8}|\d{4}-\d{7}$/; var reg = /^\d{3,4}-\d{7,8}$/;
表单验证案例
//手机号验证:/^1[3|4|5|7|8][0-9]{9}$/; //验证通过与不通过更换元素的类名与元素中的内容 if (reg.test(this.value)) { // console.log('正确的'); this.nextElementSibling.className = 'success'; this.nextElementSibling.innerHTML = '<i class="success_icon"></i> 恭喜您输入正确'; } else { // console.log('不正确'); this.nextElementSibling.className = 'error'; this.nextElementSibling.innerHTML = '<i class="error_icon"></i>格式不正确,请从新输入 '; }
//QQ号验证: /^[1-9]\d{4,}$/; //昵称验证:/^[\u4e00-\u9fa5]{2,8}$/ //验证通过与不通过更换元素的类名与元素中的内容 ,将上一步的匹配代码进行封装,多次调用即可 function regexp(ele, reg) { ele.onblur = function() { if (reg.test(this.value)) { // console.log('正确的'); this.nextElementSibling.className = 'success'; this.nextElementSibling.innerHTML = '<i class="success_icon"></i> 恭喜您输入正确'; } else { // console.log('不正确'); this.nextElementSibling.className = 'error'; this.nextElementSibling.innerHTML = '<i class="error_icon"></i> 格式不正确,请从新输入 '; } } };
//密码验证:/^[a-zA-Z0-9_-]{6,16}$/ //再次输入密码只需匹配与上次输入的密码值 是否一致
replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。
stringObject.replace(regexp/substr,replacement)
/表达式/[switch]
switch(也称为修饰符)按照什么样的模式来匹配,有三种值:
var str = 'andy和red'; var newStr = str.replace('andy', 'baby'); console.log(newStr)//baby和red //等同于 此处的andy可以写在正则表达式内 var newStr2 = str.replace(/andy/, 'baby'); console.log(newStr2)//baby和red //全部替换 var str = 'abcabc' var nStr = str.replace(/a/,'哈哈') console.log(nStr) //哈哈bcabc //全部替换g var nStr = str.replace(/a/g,'哈哈') console.log(nStr) //哈哈bc哈哈bc //忽略大小写i var str = 'aAbcAba'; var newStr = str.replace(/a/gi,'哈哈')//"哈哈哈哈bc哈哈b哈哈"
案例:过滤敏感词汇
<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>