15 补充
14.1 基础总结
14.1.1 数据类型
分为两类:
-
基本类型(值类型)
- String:任意字符串
- Number:任意数字
- boolean:true、false
- undefined:undefined
- Null:null
-
对象类型(引用类型)
- Object:任意对象
- Array:特别对象,通过下标访问
- Function:特别对象,可以执行
- 判断数据类型:
-
typeof 返回数据类型的字符串表达
- 可以判断undefined、数值、字符串、布尔值、function
- 不可以判断 null与object、object与array
-
instanceof 判断对象的具体类型:数组、函数。。。
- A instanceof B:A是不是B的实例
-
=== (尽量不用==,因为==会做数据转换)
- 可以判断undefined、null (因为这两个类型只有一个值)
// 基本类型
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()())
- undefined与null区别
- undefined (声明未赋值)
var a
console.log(a) //声明未赋值 undefined
- null (定义并赋值了,值为null)
// 起始
var b = null //初始赋值为null,表明将要赋值为对象
// 确定对象就赋值
b = [\'aedxf\', 12]
// 最后
b = null // 释放对象,让b指向的对象成为垃圾对象(被垃圾回收器回收)
- 什么时候给变量赋值为null
- 初始赋值,表明要将赋值为对象
- 结束前,让对象成为垃圾对象(被垃圾回收器回收)
- 严格区别变量类型与数据类型
- 数据的类型
- 基本类型
- 对象类型
- 变量的类型
- 基本类型:保存的就是基本类型的数据
- 引用类型:保存的是地址值
14.1.2 数据&变量&内存
- 内存
-
一小块内存中存储两个东西:内部存储的数据、地址值
简单数据类型也有地址,只是不用
-
内存分类
- 栈:全局变量、局部变量
- 堆:对象
函数在堆空间里,但函数名(标识函数的变量)在栈空间
-
变量(变量名+变量值)
每个变量都对应一块小内存,变量名用来查找对应内存,变量值就是内存中保存的数据
-
内存,数据,变量的关系
内存用来存储数据的空间
变量是内存的标识
-
关于赋值和内存的问题
var a = xxx,a内存中保存的是什么?
- xxx是基本数据,保存的就是这个数据
- xxx是对象,保存的是对象的地址值
- xxx是变量,保存的xxx的内存内容(可能是基本数据,也可能是地址)
- 关于引用变量赋值问题
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没有变化
- 在js调用函数时传递变量参数时,是值传递还是引用传递
- 理解1:都是值(基本值/地址值)传递
- 理解2:可能是值传递,也可能是引用传递(地址值)
var a = 3
function fn(a) { //这里的形参a相当于在fn中var a,和全局的a无关
a = a+1
}
fn(a) //这里传递进去的是值3
console.log(a) // 3,这里打印的是全局的a变量
- JS引擎如何管理内存
- 内存生命周期
- 分配小内存空间,得到使用权
- 存储数据,可以反复进行操作
- 释放小内存空间
- 释放内存
- 局部变量:函数执行完自动释放
- 对象:成为垃圾对象,后续被垃圾回收器回收
- 全局变量:不会被释放
var a = 3
var obj = {} //此时2个对象,内存空间没有被释放(因为是全局变量)
object = null // 此时2个对象,obj并没有被释放
function fn() {
var b = {}
var c = 4 // 函数被调用时才会初始化变量
}
fn() //函数执行完,b自动释放,b指向的对象是在后面某个时刻由垃圾回收器回收的
14.1.3 对象
- 什么是对象
多个数据的封装体
- 为什么用对象
统一管理多个数据
- 对象的组成
- 属性:属性名(字符串)和属性值(任意类型)组成
- 方法:一种特别的属性(属性值是函数)
- 访问属性
p.name 或者p[‘name’]
- 什么时候必须使用[‘属性名’]的方式
- 属性名包含特殊字符:- 空格 p[‘content-type’] = ‘text/json’
- 属性名不确定:属性名是变量
var propName = \'myAge\'
var age = 18
p[propName] = value
14.1.4 函数
- 什么是函数
- 实现特定功能的n条语句的封装体
- 只有函数是可执行的,其他类型的数据不能执行
- 如何调用函数
- test():直接调用
- obj.test():通过对象调用
- new test():new调用
- test.call/apply(obj):临时让test成为obj的方法进行调用
var obj = {}
function test2() {
this.xxx = \'atguigu\'
}
// 假设obj.test2()
test2.call(obj) // 可以让一个函数成为指定任意对象的方法进行调用
console.log(obj.xxx)// atguigu
14.1.5 回调函数
- 什么函数才是回调函数?
- 你定义的、没有(主动)调用的、但最终执行了的
- 常见的回调函数
- dom事件回调函数:比如点击之后触发调用函数
- 定时器回调函数
- ajax请求回调函数
- 生命周期回调函数
14.1.6 IIFE(立即执行函数)
全称:Immediately-Invoked Function Expression
作用:
- 隐藏实现
- 不会污染外部(全局)命名空间
- 用来编码js模块
(function () {
var a = 1;
function test(){
console.log(++a);
}
window.$ = function () { //向外暴露一个全局函数
return {
test: test
}
}
})
$().test() //1. $是一个函数 2.执行后返回的是一个对象
14.2 函数对象
14.2.1原型 & 原型链(隐式原型链)
- 结合尚硅谷和黑马,主要记住红框内部。具体见(4.7和7.1),因为所有函数都是Function的实例,所以Foo构造函数、Object构造函数以及Function构造函数都有个_proto _属性指向Function原型对象;因为原型对象默认是一个空Object对象所以Function原型对象的 _proto _属性指向Object原型对象
-
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()
14.2.2 执行上下文与执行上下文栈
- 全局执行上下文
-
在执行全局代码前将window确定为全局执行上下文
-
对全局数据进行预处理
- var 定义的全局变量 ===>undefined,添加为window的属性
- function声明的全局函数===>赋值(fun),添加为window的方法
- this===>赋值(window)
-
开始执行全局代码
- 函数执行上下文
- 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象
- 对局部数据进行预处理
- 形参变量===>赋值(实参)===>添加为执行上下文的属性
- arguments===>赋值(实参列表),添加为执行上下文的属性
- var定义的局部变量===>undefined,添加为执行上下文的属性
- function声明的函数===>赋值(fun),添加为执行上下文的方法
- this===>赋值(调用函数的对象)
- 开始执行函数体代码
- 执行上下文栈
- 在执行全局代码前,JS引擎会创建一个栈来存储管理所有的执行上下文对象
- 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
- 在函数执行上下文创建后,将其添加到栈中(压栈)
- 在当前函数执行完后,将栈顶的对象移除(出栈)
- 当所有的代码执行完后,栈中只剩下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.icode9.com/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
14.2.3 作用域与作用域链
- 区别1
- 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
- 全局执行上下文是在全局作用域确定之后, js代码马上执行之前创建
- 函数执行上下文是在调用函数时, 函数体代码执行之前创建
- 区别2
- 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
- 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放
- 联系
- 上下文环境(对象)是从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数使用域
- 面试题
// 测试题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
14.2.4 闭包
结合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()
- 如何产生闭包?
- 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包
- 闭包到底是什么?
- 使用chrome调试查看
- 理解一: 闭包是嵌套的内部函数(绝大部分人)
- 理解二: 包含被引用变量(函数)的对象(极少数人)推荐
- 注意: 闭包存在于嵌套的内部函数中
- 产生闭包的条件?
- 函数嵌套
- 内部函数引用了外部函数的数据(变量/函数)
- 常见闭包
- 将函数作为另一个函数的返回值
- 将函数作为实参传递给另一个函数调用
1.面向过程与面向对象
1.1 面向过程与面向对象对比
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了。
- 面向对象是把事务分解成为一个个对象,然后由对象之间分工与合作。
面向过程 | 面向对象 | |
---|---|---|
优点 | 性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程。 | 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 |
缺点 | 不易维护、不易复用、不易扩展 | 性能比面向过程低 |
2.对象与类
2.1对象
对象是由属性和方法组成的:是一个无序键值对的集合,指的是一个具体的事物
- 属性:事物的特征,在对象中用属性来表示(常用名词)
- 方法:事物的行为,在对象中用方法来表示(常用动词)
2.1.1创建对象
//以下代码是对对象的复习//字面量创建对象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);
如上两行代码运行结果为:
2.2类
- 在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。类抽象了对象的公共部分,它泛指某一大类(class)对象特指某一个,通过类实例化一个具体的对象
2.2.1创建类
- 语法:
//步骤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);
以上代码运行结果:
通过结果我们可以看出,运行结果和使用构造函数方式一样
2.2.2类创建添加属性和方法
// 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 关键字创建类, 类名我们还是习惯性定义首字母大写
- 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
- constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数
- 多个函数方法之间不需要添加逗号分隔
- 生成实例 new 不能省略
- 语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function
2.2.3类的继承
- 语法
// 父类
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(); //结果为 你的姓是刘
以上代码运行结果:
- 子类使用super关键字访问父类的方法
//定义了父类
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使用.
- constructor中的this指向的是new出来的实例对象
- 自定义的方法,一般也指向的new出来的实例对象
- 绑定事件之后this指向的就是触发事件的事件源
-
在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象
3.面向对象版tab 栏切换
3.1功能需求
- 点击 tab栏,可以切换效果.
- 点击 + 号, 可以添加 tab 项和内容项.
- 点击 x 号, 可以删除当前的tab项和内容项.
- 双击tab项文字或者内容项文字可以修改里面的文字内容
3.2案例准备
- 获取到标题元素
- 获取到内容元素
- 获取到删除的小按钮 x号
- 新建js文件,定义类,添加需要的属性方法(切换,删除,增加,修改)
- 时刻注意this的指向问题
3.3切换
-
为获取到的标题绑定点击事件,展示对应的内容区域,存储对应的索引
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\'; }
3.4添加
-
为添加按钮+ 绑定点击事件
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(); }
3.5删除
-
为元素的删除按钮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(); }
3.6编辑
-
为元素(标题与内容)绑定双击事件
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(); } }}
动态创建元素
- 第一种方法:createElement,innerHTML赋值,appendChild追加到父元素中
- 第二种方法(高级):insertAdjacentHTML()可以直接把字符串格式元素添加到父元素中
-------方法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);
4.构造函数和原型
4.1对象的三种创建方式–复习
-
字面量方式
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 指向这个新的对象。
- 执行构造函数里面的代码,给这个新对象添加属性和方法。
- 返回这个新对象(构造函数里面不需要 return )。
4.2静态成员和实例成员
4.2.1实例成员
实例成员就是构造函数内部通过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);//实例成员只能通过实例化的对象来访问
4.2.2静态成员
静态成员 在构造函数本身上添加的成员 如下列代码中 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);//静态成员只能通过构造函数来访问
4.3构造函数的问题
构造函数方法很好用,但是存在浪费内存的问题。
每创建一个对象,会为其中的函数开辟一个空间
4.4构造函数原型prototype
构造函数通过原型分配的函数是所有对象所共享的。
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();//我会唱歌
4.5 对象原型
对象都会有一个属性 __ proto__ (隐式原型)指向构造函数的 prototype 原型对象(显式原型),之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 __ proto__ 原型的存在。
ES6之前,程序员能直接操作显式原型,不能直接操作隐式原型
__proto__对象原型和原型对象 prototype 是等价的
console.log(ldh._prop_ === Star.prototype); ------ true
__proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
**方法的查找规则:**首先先看对象身上是否有方法,如果有就执行这个对象上的方法,如果么没有这个方法,因为有__ proto__ 的存在,就去构造函数原型对象prototype身上去查找方法。
4.6 constructor构造函数
对象原型( __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属性,如图:
总结:
- prototype(显式原型):每一个构造函数都有一个prototype属性,指向的是该构造函数的原型对象,默认值为一个空Object对象。
- __ proto__ (隐式原型):每一个实例对象都有一个__ proto __ 属性,指向构造函数的原型对象,默认值为构造函数的prototype属性值。
- constructor:实例对象原型__ proto__和构造函数prototype原型对象里面都有一个属性 constructor 属性 ,都指向了构造函数。
- 关系:
1.构造函数的prototype属性指向了构造函数原型对象。
2.实例对象是由构造函数创建的,实例对象的__ proto__属性指向了构造函数的原型对象。
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数。
4.7 原型链
每一个实例对象又有一个__ proto__ 属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有 __ proto__ 属性,这样一层一层往上找就形成了原型链。(查找方法是按照隐式原型链寻找的即 _ proto _,而不是按照显式原型链)
4.8 构造函数实例和原型对象三角关系
1.构造函数的prototype属性指向了构造函数原型对象2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数
4.9 原型链和成员的查找机制
任何对象都有原型对象,也就是prototype属性,任何原型对象也是一个对象,该对象就有__ proto__属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
- 如果没有就查找它的原型(也就是 __ proto__指向的 prototype 原型对象)。
- 如果还没有就查找原型对象的原型(Object的原型对象)。
- 此类推一直找到 Object 为止(null)。
4.10原型对象中this指向
构造函数中的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)]
4.11 通过原型为数组扩展内置方法
可以通过原型对象,对原来的内置对象进行扩展自定义的方法。
注意:数组和字符串内置对象不能给原型对象覆盖操作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()进行数据的求
5.继承
es6之前没有提供extends继承。可以通过构造函数+原型对象 模拟实现继承,被称为组合继承。
5.1 call()
调用这个函数,并且修改函数运行时的this指向
fun.call(thisArg, arg1, arg2, ...)// thisArg: 当前调用函数this的指向对象// arg1,arg2 传递的其他参数
- call()可以调用函数
- call()可以修改this的指向,使用call()的时候 参数1是修改后的this指向,参数2,参数3…使用逗号隔开连接
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)]
5.2借用构造函数继承父构造函数中的属性
核心原理:通过call()把父类型的this指向子类型的this,这样可以实现子类型继承父类型的属性。
- 先定义一个父构造函数
- 再定义一个子构造函数
- 子构造函数继承父构造函数的属性(使用call方法)
// 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);
5.3借用原型对象继承方法
- 先定义一个父构造函数
- 再定义一个子构造函数
- 子构造函数继承父构造函数的属性(使用call方法)
// 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);
如上代码结果如图:
6.ES5新增方法
Array.isArray(arr);
console.log(typeof arr);
6.1 数组方法forEach遍历数组
arr.forEach(function(value, index, array) { //参数一是:数组元素 //参数二是:数组元素的索引 //参数三是:当前的数组 }) //相当于数组遍历的 for循环 没有返回值
6.2 数组方法filter过滤数组
- 直接返回一个新数组
var arr = [12, 66, 4, 88, 3, 7]; var newArr = arr.filter(function(value, index,array) { //参数一是:数组元素 //参数二是:数组元素的索引 //参数三是:当前的数组 return value >= 20; }); console.log(newArr);//[66,88] //返回值是一个新数组
6.3 数组方法some
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返回的是布尔值,查找到第一个满足条件的元素就终止查找
6.4 数组方法map
将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。
不会改变原数组
var numbers = [1, 2, 3];var res = numbers.map(function (n) { return n + 1;});
6.5 数组方法every
对数组的每一项执行给定的函数,假如该函数每一项都返回true,最后结果才为true;只要有一项返回值为false,最后结果就是false。且后边的元素都不会再继续执行函数;
原数组不受影响;
var arr = [20,13,11,8,0,11];var arr1 = arr.every(function(value){ return value>10;})
6.6 筛选商品案例
-
定义数组对象数据
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);})
-
-
6.7 some和forEach、filter区别
- 如果查询数组中唯一的元素, 用some方法更合适,在some 里面 遇到 return true 就是终止遍历 迭代效率更高
- 在forEach 里面 return 不会终止迭代
- 在filter 里面 return 不会终止迭代
6.8 trim方法去除字符串两端的空格
var str = \' hello \';console.log(str.trim()) //hello 去除两端空格var str1 = \' he l l o \';console.log(str.trim()) //he l l o 去除两端空格
6.9 获取对象的属性名
Object.keys(对象) 获取到当前对象中的属性名 ,返回值是一个数组
var obj = { id: 1, pname: \'小米\', price: 1999, num: 2000};var result = Object.keys(obj);console.log(result)//[id,pname,price,num]
6.10 Object.defineProperty
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 则不允许删除这个属性 属性是否可以被删除或是否可以再次修改特性
7.函数的定义和调用
7.1函数的定义方式
-
方式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
-
所有函数都有隐式原型和显式原型两个属性
补充(尚硅谷):
7.2函数的调用
/* 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(\'人生的巅峰\');
})();
8.this
8.1函数内部的this指向
这些 this 的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this 的指向不同
一般指向我们的调用者.
8.2改变函数内部 this 指向
处理函数内部this的指向问题,常用:bind()、call()、apply( )
8.2.1 call方法
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
以上代码运行结果为:
8.2.2 apply方法
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); //得到数组的最大值
8.2.3 bind方法
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);
}
}
8.2.4 call、apply、bind三者的异同
-
共同点 : 都可以改变this指向
-
不同点:
- call 和 apply 会调用函数, 并且改变函数内部this指向.
- call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递
- bind 不会调用函数, 可以改变函数内部this指向.
-
应用场景
- call 经常做继承.
- apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
- bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的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
9.严格模式
9.1什么是严格模式
JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript变体的一种方式,即在严格的条件下运行 JS 代码。
严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。
严格模式对正常的 JavaScript 语义做了一些更改:
1.消除了 Javascript 语法的一些不合理、不严谨之处,减少了一些怪异行为。
2.消除代码运行的一些不安全之处,保证代码运行的安全。
3.提高编译器效率,增加运行速度。
4.禁用了在 ECMAScript 的未来版本中可能会定义的一些语法,为未来新版本的 Javascript 做好铺垫。比如一些保留字如:class,enum,export, extends, import, super 不能做变量名
9.2开启严格模式
严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。
-
情况一 :为脚本开启严格模式
-
有的 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函数开启了严格模式
-
9.3严格模式中的变化
严格模式对 Javascript 的语法和行为,都做了一些改变。
9.3.1 变量规定
- 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量.严格模式禁止这种用法,变量都必须先用var命令声明,然后再使用
- 严禁删除已经声明变量。例如,delete x; 语法是错误的
9.3.2 this 指向问题
-
以前在全局作用于函数中的this指向window对象
-
严格模式下全局作用域中函数中的 this 是 undefined
-
普通模式下,构造函数不加new也可以调用当普通函数使用,this指向全局对象window
-
严格模式下,如果构造函数不加new调用, this 指向的是undefined 如果赋值,会报错
-
new 实例化的构造函数指向创建的对象实例。
-
定时器 this 还是指向 window
-
事件、对象还是指向调用者
9.3.3 函数变化
- 函数不能有重名的参数
- 函数必须声明在最前面.新版本的JavaScript会引入“块级作用域”( ES6 中已引入)为了与新版本接轨,不允许在非函数的代码块内声明函数
\'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() {} //合法
}
更多严格模式要求参考
10.高阶函数
高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
此时fn 就是一个高阶函数
函数也是一种数据类型,同样可以作为参数,传递给另外一个参数使用。最典型的就是作为回调函数。
同理函数也可以作为返回值传递回来
11.闭包
11.1变量的作用域复习
变量根据作用域的不同分为两种:全局变量和局部变量。
- 函数内部可以使用全局变量。
- 函数外部不可以使用局部变量。
- 当函数执行完毕,本作用域内的局部变量会销毁。
11.2什么是闭包
闭包(closure)指有权访问另一个函数作用域中变量的函数。简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。 这个局部变量所在的函数就是闭包函数
11.3闭包的作用
作用:延伸变量的作用范围。
// 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();
缺点:函数执行完,函数内的局部变量没有释放,占用内存时间会变长;容易造成内存泄漏
解决:能不用闭包就不用,及时释放
11.4闭包的案例
- 利用闭包的方式得到当前li 的索引号
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的影响
- 闭包应用-3秒钟之后,打印所有li元素的内容
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
11.5案例(思考题)
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();
12.递归
12.1什么是递归
递归:如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。简单理解:函数内部自己调用自己, 这个函数就是递归函数
注意:递归函数的作用和循环效果一样,由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return。
12.2利用递归求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));
12.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));
12.4利用递归遍历数据
// 我们想要做输入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;
}
12.5 浅拷贝和深拷贝
-
浅拷贝:只拷贝一层,更深层次对象级别的只拷贝引用
-
深拷贝:拷贝多层,每一级别的数据都会拷贝
-
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;
}
}
}
13.正则表达式概述
13.1什么是正则表达式
正则表达式( Regular Expression )是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象。
正则表通常被用来检索、替换那些符合某个模式(规则)的文本,例如验证表单:用户名表单只能输入英文字母、数字或者下划线, 昵称输入框中可以输入中文(匹配)。此外,正则表达式还常用于过滤掉页面内容中的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等 。
其他语言也会使用正则表达式,本阶段我们主要是利用JavaScript 正则表达式完成表单验证。
13.2 正则表达式的特点
- 灵活性、逻辑性和功能性非常的强。
- 可以迅速地用极简单的方式达到字符串的复杂控制。
- 对于刚接触的人来说,比较晦涩难懂。比如:^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*.\\w+([-.]\\w+)*$
- 实际开发,一般都是直接复制写好的正则表达式. 但是要求会使用正则表达式并且根据实际情况修改正则表达式. 比如用户名: /^[a-z0-9_-]{3,16}$/
14.正则表达式在js中的使用
14.1正则表达式的创建
在 JavaScript 中,可以通过两种方式创建一个正则表达式。
方式一:通过调用RegExp对象的构造函数创建
var regexp = new RegExp(/123/);
console.log(regexp);
方式二:利用字面量创建 正则表达式
var rg = /123/;
14.2测试正则表达式
test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。
var rg = /123/;
console.log(rg.test(123));//匹配字符中是否出现123 出现结果为true
console.log(rg.test(\'abc\'));//匹配字符中是否出现123 未出现结果为false
15.正则表达式中的特殊字符
15.1正则表达式的组成
一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单和特殊字符的组合,比如 /ab*c/ 。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+ 等。
特殊字符非常多,可以参考:
MDN
jQuery 手册:正则表达式部分
[正则测试工具](<http://tool.oschina.net/regex)
15.2边界符
正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符
边界符 | 说明 |
---|---|
^ | 表示匹配行首的文本(以谁开始) |
$ | 表示匹配行尾的文本(以谁结束) |
如果 ^和 $ 在一起,表示必须是精确匹配。
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
15.3字符类
字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。
15.3.1 [] 方括号
表示有一系列字符可供选择,只要匹配其中一个就可以了
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
15.3.2量词符
量词符用来设定某个模式出现的次数。
量词 | 说明 |
---|---|
* | 重复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
15.3.3用户名表单验证
功能需求:
- 如果用户名输入合法, 则后面提示信息为: 用户名合法,并且颜色为绿色
- 如果用户名输入不合法, 则后面提示信息为: 用户名不符合规范, 并且颜色为红色
分析: - 用户名只能为英文字母,数字,下划线或者短横线组成, 并且用户名长度为6~16位.
- 首先准备好这种正则表达式模式/$[a-zA-Z0-9-_]{6,16}^/
- 当表单失去焦点就开始验证.
- 如果符合正则规范, 则让后面的span标签添加 right类.
- 如果不符合正则规范, 则让后面的span标签添加 wrong类.
<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>
15.3.4 括号总结
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
15.4预定义类
预定义类指的是某些常见模式的简写方式.
案例:验证座机号码
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}$/
//再次输入密码只需匹配与上次输入的密码值 是否一致
15.5正则替换replace
replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。
stringObject.replace(regexp/substr,replacement)
- 第一个参数:被替换的字符串 或者正则表达式
- 第二个参数:替换为的字符串
- 返回值是一个替换完毕的新字符串
/表达式/[switch]
switch(也称为修饰符)按照什么样的模式来匹配,有三种值:
- g:全局匹配
- i:忽略大小写
- gi:全局匹配 + 忽略大小写
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>
来源:https://blog.csdn.net/qq_27635591/article/details/123172215
本站部分图文来源于网络,如有侵权请联系删除。