Web前端第三阶段–JsCode
文章目录
声明提升
- 声明:在内存中创建一个内存块,然后起个名字
- JS引擎先通篇扫描声明操作, 提升(移动)到作用域的顶部然后再执行调整过顺序后的代码
面试题
function a() {
console.log(1)
}
a()
function a() {
console.log(2)
}
a()
宿主环境
- JS运行所在的环境,称为宿主
- 浏览器提供了window,含有操作浏览器相关的Api 对象
- window称为: 全局对象 或 全局作用域
作用域
- 一类具有特殊功能的对象的称呼
- 全局作用域:存储了系统的方法、属性–window
- 局部作用域:函数在
运行时
临时
生成的对象, 用于存储函数中声明的变量们
作用域链
- 当多层作用域嵌套时, 当使用变量时, 则遵循
就近原则
, 来找到对应的变量
闭包Closure
- 闭包: 函数中使用了来自其他作用域的变量
- 怕: 其他作用域销毁, 而导致自身无法使用其中的变量
- 解决: 把其他作用域存储在自身的 scopes 属性里
- 称呼: 这种被存储在scopes里的函数作用域: 叫闭包
function a() {
var aa = 11
var bb = 22
function b() {
var bb = 33
var cc = 44
function c() {
var cc = 55
console.log(aa, bb, cc);
}
console.dir(c);
}
b()
}
a()
闭包应用
- 为函数制造私有的变量,避免全局污染
- 缺点:浪费内存
//制作一个函数displayName,每次可以调用都能打印出自身调用的次数
var displayName = (function () {
var i = 0
return function () {
i++
console.log('调用次数:', i)
}
})()
displayName()
displayName()
displayName()
displayName()
arguments
- 函数中, 隐藏自带的一个属性/变量
- 作用: 保存函数收到的
所有实参
- 应用场景:
- 制作实参个数不固定的函数, 例如: 求最大值
- 函数重载: 制作
多功能
函数, 根据实参的个数 或 类型 不同, 内部做判断 然后执行不同的代码逻辑
函数重载
// 任务: 制作一个计算折扣的函数
function zhekou(money) {
// 根据实参 来判定折扣的场景
// console.log(arguments);
// 如果是3个参数, 就是满减
if (arguments.length == 3) {
if (money >= arguments[1]) {
money -= arguments[2]
}
console.log('您最终消费:', money);
}
if (arguments.length == 2) {
if (typeof arguments[1] == 'number') {
money *= arguments[1]
}
if (typeof arguments[1] == 'string') {
var vip = arguments[1]
// 语法糖: 作者提供了一些简化语法
// if判断的{}中, 仅有一行代码, 可以省略{}
if (vip == 'vip1') money *= 0.95
if (vip == 'vip2') money *= 0.85
if (vip == 'vip3') money *= 0.7
}
console.log('最终消费:', money);
}
}
zhekou(3000, 0.7) //3000元 打7折
zhekou(3000, 2000, 400) // 满2000 减400
zhekou(3000, 'vip1') //打95折
zhekou(3000, 'vip2') //打85折
zhekou(3000, 'vip3') //打7折
this
- 函数的this属性: 代表当前函数运行时所在的对象
- 对象.函数() 或 对象函数名 : this就是对象
- 函数(): this就是window
面试题
var length = 10
function fn() {
// this = window
// this.length == window.length ==
console.log(this.length);
}
var obj = {
length: 5,
method: function (fn) {
fn() // 直接触发的函数, 其this是window -- 10
arguments[0]()
// [0]: 序号0的参数,就fn. fn是在arguments对象中,
// 所以: fn的this就是 arguments, 其length属性就是实参个数--2
console.log(arguments);
}
}
obj.method(fn, 1)
浅拷贝
- 浅拷贝: 把对象复制一份 而不是简单的 地址传递
var emp1 = {
ename: "mike",
age: 19,
phone: '18989898988'
}
// 1. 制作一个新的空对象
var emp2 = {}
// 2. 遍历 emp1 , 把其中的属性挨个复制到空对象里
for (key in emp1) {
console.log('key:', key);
//通过属性名读值
var value = emp1[key]
// 相同的 属性名和值, 存入到 emp2 对象里
emp2[key] = value
}
// 修改emp1的属性, 不会影响到emp2, 因为他们是不同的对象
emp1.age = 100
console.log(emp2);
属性访问
var obj = { gege: '格格', xuan: '子轩' }
var gege = 'xuan'
console.log(obj.gege) // 点语法, .后是属性名 -- '格格'
// 方括号, 值是JS代码. gege是个变量, 其值是'xuan'
// 实际读取的是 obj['xuan'] - 子轩
console.log(obj[gege])
构造函数
- 用于构造创建对象的函数
// 构造函数: 用于 构造对象 的函数
// 矩形对象:
// 命名规范: 用 大驼峰写法 给构造函数命名
// 目的: 让使用者 见名知意
function Rect(length, width) {
// 准备个空对象, 把值放进去, 返回对象
var obj = {}
obj.length = length
obj.width = width
return obj
}
// 调用构造函数, 传入值. 会返回一个对象
var r1 = Rect(10, 5)
console.log(r1);
var r2 = Rect(100, 30)
console.log(r2)
原型
- 一个共享的存储区域, 专门存放对象共享的方法
- __proto__原型链用于 链接到原型的属性
对象中的属性
- 独有的:不同对象有不同属性值,分别存在不同的对象里面
- 共享的:函数,可以
按照固定的逻辑
处理对象的属性- 为了节省内存:把共享的函数存储在 构造函数的
prototype
的属性里 - 生成对象时,要通过其
__proto__
属性关联到共享的prototype
属性 - prototype:原型–构造函数,存共享方法
- 为了节省内存:把共享的函数存储在 构造函数的
- 使用时
对象.方法()
: 对象会到其__proto__
链接到的 prototype 中查找方法并使用
- new运算符:简化构造函数的代码, 省
3
行var this = {}
//有的面试答案: 这是两步 声明空对象, 然后赋值给thisthis.__proto__ = 构造函数.prototype
return this
严格模式
ES5 提供了更多的报错, 辅助程序员写出更健康的代码use stirct
对象属性精确配置
- Object : 是对象类型的构造函数, 其中还包含很多操作对象类型的方法
defineProperty
- 参数1: 要配置的对象
参数2: 要配置的属性名
参数3: 具体的配置项
Object.defineProperty(emp, 'eid', {
// ctrl+i: 弹出提示
writable: false // 是否可写入新的值, true可以 false不可以
})
writable: false
// 是否可写入新的值, true可以 false不可以enumerable: false
//是否遍历 true可 false不可defineProperties
此方法可以一次配置多个属性
Object.defineProperties(emp, {
// 属性名: 配置项
salary: { enumerable: false },
// configurable: 是否可重新配置 true可 false不可
eid: { writable: false, configurable: false } //w回车 : f 回车
})
计算属性
- get:属性是函数类型,使用时不需要()触发
- set:可以监听的赋值属性,对值进行判定是否合法
- 合法:准备一个额外的属性,通常名称_xxx用于存储合法的值
- 读取时:搭配get计算属性,来从_xxx读值
- 不合法:抛出错误
- 合法:准备一个额外的属性,通常名称_xxx用于存储合法的值
var emp = { ename: "凯凯" }
// 细节: _salary 是为了辅助监听器而生, 属于幕后, 不应该被遍历出来
// 用 define方式新增的属性, 默认是 不可遍历, 不可配置, 不可赋值
Object.defineProperty(emp, '_salary', { writable: true })
// 任务: 添加 salary属性监听器...
Object.defineProperty(emp, 'salary', {
enumerable: true, //可以被遍历到
get() { return this._salary },
set(value) {
if (value >= 5000 && value <= 50000) {
this._salary = value
} else {
throw Error('薪资错误:' + value)
}
},
})
// 设置薪资: 范围 5000~50000
// emp.salary = 1200 //报错: 薪资错误
emp.salary = 24000 //正常赋值
console.log(emp.salary) //打印出 24000
console.log(emp);
保护对象属性
- 不能删:
preventExtensions
- 不能增删:
seal
- 不能增删改:
freeze
箭头函数
匿名化了匿名函数的书写,带有两个语法糖
- 参数只有1个,省略形参的()
- 函数体只有一行,省略return 和 {}
- 如果返回值是对象类型,{}用()包围,防止歧义
- 箭头函数的this指向
- 箭头函数自身没有this属性
- 根据作用域的就近原则,使用上级作用域中的this
数组高阶函数
every:判断数组每一个元素都符合条件–类似逻辑与
some:判断数组中有至少一个元素符合条件–类似逻辑或
filter:把满足条件的元素组合成新的数组
map:映射-把数组的元素 按照固定的规范,返回值组合成新的数组
<body>
<table>
<thead>
<tr>
<th>序号</th>
<th>名称</th>
<th>单价</th>
<th>数量</th>
<th>总价格</th>
</tr>
</thead>
<tbody id="box">
<tr>
<td>1</td>
<td>香蕉</td>
<td>¥9</td>
<td>4</td>
<td>¥36</td>
</tr>
</tbody>
</table>
<script>
var products = [
{ pname: "香蕉", price: 9, count: 4 },
{ pname: "葡萄", price: 25, count: 14 },
{ pname: "西瓜", price: 40, count: 6 },
{ pname: "桃子", price: 12, count: 8 },
{ pname: "杨梅", price: 29, count: 11 },
]
// 1. 是否所有单价都超过10元
var p = products.every(v => v.price > 10)
console.log(p ? '都超过10元' : '非都超过10元');
// 2. 是否有数量少于5个的
var p = products.some(v => v.count < 5)
console.log(p ? '有数量少于5个' : '没有数量少于5个');
// 3. 找出总价格高于100元的产品
var a = products.filter(v => v.price * v.count > 100)
console.log(a)
// 4. 商品显示在表格里
var a = products.map((v, index) => `<tr>
<td>${index + 1}</td>
<td>${v.pname}</td>
<td>¥${v.price}</td>
<td>${v.count}</td>
<td>¥${v.price * v.count}</td>
</tr>`)
box.innerHTML = a.join('')
</script>
</body>
-
forEach:遍历
- 遍历数组的方案
- for 普通循环
- for…in 遍历对象类型
- for…of 专为数组而生,直接遍历值
- 遍历数组的方案
-
reduce 数组合并
- 把每次累加后的值,传递给下一次循环的函数
var nums = [1, 32, 54, 645, 2, 43, 46, 576];
var a = nums.reduce((sum, value, index) => {
return sum + value;
}, 0);
console.log(a);
let和const
- 没有全局污染,声明的变量存储在
脚本作用域
- let可变 const不可变
- 关于声明提升,有提升 但是 存在暂存死区
块级作用域
{}配合let/const快速形成块级作用域–代替闭包制作私有变量
展开语法
…数组
…对象
//灵活应用场景 互换的变量的值
var a = 20;
var b = 40;
[a, b] = [b, a];
console.log(a, b);
var gname = {
gname: "LOL",
maker: "腾讯",
teams: ["web", "edg", "rng", "v5"],
desc: {
year: 2018,
},
};
var {
gname,
maker,
teams: [t1, t2, t3, t4],
desc: { year },
} = gname;
console.log(gname, maker, t1, t2, t3, t4, year);
解构语法
- 数组:const [变量,变量] = 数组
- 对象:const{变量,属性名:别名}=对象
- 函数的形参可以直接解构
var webs = [
{ title: "新闻", href: "http://tmooc.cn" },
{ title: "hao123", href: "https://www.douyu.com/" },
{ title: "地图", href: "https://developer.mozilla.org/zh-CN/" },
{ title: "贴吧", href: "https://www.jd.com/" },
{ title: "视频", href: "https://www.jd.com/" },
{ title: "图片", href: "https://www.jd.com/" },
];
var a = webs.map(
({ title, href }) => `
<a href="${href}">${title}</a>
`
);
box.innerHTML += a.join("");
函数增强语法
函数参数默认值:function(参数=值)
function show(name = "亮亮") {
console.log("name", name);
}
show();
show("泡泡");
剩余参数 :function(…args){}
function bb(x, y, ...args) {
console.log(x, y, args);
}
bb(11, 22, 32, 3, 4, 5, 6, 7, 66, 55);
函数的方法
函数的触发方式
用(),apply,bind,call
作用与区别
- call-调用函数时, 临时设置其this指向, 运行完毕会从对象里删除
- bind-返回一个新的函数, 把this指向和参数绑定在其上方, 延时触发
- apply-立刻触发函数, 区别是 参数用 数组来传递
//call
var r1 = { width: 10, height: 30 }
var r2 = { width: 55, height: 60 }
var r3 = { width: 13, height: 10 }
// 制作一个函数, 分别计算出他们的面积
// 把函数传入对象里, 执行
function area() {
console.log(this.width * this.height);
}
// area函数, 临时放到 r1 里执行. 函数中的this就是所在的对象
area.call(r1)
//apply
var r1 = { x: 10, y: 20 }
function bb(z, k) {
console.log(this.x + this.y + z + k)
}
// 普通方案: 把bb临时放到r1里执行
r1.bb = bb
r1.bb(30, 40) // bb函数前方的对象就是其this指向
delete r1.bb
// 用call方法实现: 函数.call(要放到的对象, 其他参数...)
bb.call(r1, 30, 40)
// apply: 与call相似, 但是区别是参数要放数组里
bb.apply(r1, [30, 40])
console.log(r1)
//bind
// bind: 用于绑定this指向, 延迟触发函数
function a(x, y) {
console.log(this, x, y);
}
var emp = { ename: "泡泡" }
// bind: 其返回值是一个函数, 其内存储了this指向和其余参数
var a_bind = a.bind(emp, 10, 20)
console.dir(a_bind);
// 2s后, 触发 a_bind
setTimeout(a_bind, 2000);
class语法
var emp = {
ename: "泡泡",
age: 18,
phone: '18898989898'
}
console.log(emp)
console.log(emp.ename)
构造函数(JAVA)
class Rect1 {
// java的类中, 不需要写 function 关键词
constructor(width, height) {
this.height = height
this.width = width
}
area() {
return this.width * this.height
}
zc() {
return (this.width + this.height) * 2
}
}
console.dir(Rect1) // 查看其prototype
var r2 = new Rect1(100, 50)
console.log(r2)
console.log(r2.area());
console.log(r2.zc());
继承
面向对象有三大特征: 封装 继承 多态
- 封装: 用{}把代码封在一起, 起个名字. 以后通过名字来调用这段代码
- 继承: 自己没有的 到父中查找… JS的原型链 proto
- 多态: 重写理论
class Father {
money = '500w'
eat() {
console.log('吃饭');
}
}
class Son extends Father {
phone = '19898989838'
play() {
console.log('玩游戏');
}
}
var s = new Son()
console.log(s) //查看其原型 __proto__
多态
多态: 一个父类 可以拥有不同的子类. 由于子类中可以重写, 所以不同的子类调用相同的方法或属性 可以出现不同的状态
// 多态: 一个父类 可以拥有不同的子类. 由于子类中可以重写, 所以不同的子类调用相同的方法或属性 可以出现不同的状态
class Father {
hair = "黑头发"
eye = '黑眼睛'
eat() {
console.log('吃粗粮');
}
}
// 子类 继承 父类, 拥有父类的所有属性和方法
class Son extends Father {
// 重写override: 子类中可以书写与父类中 属性同名的, 会覆盖
hair = '金发'
// 重写与父类同名的方法, 当使用eat方法时, 根据原型链的就近原则, 优先使用自身原型的eat方法
eat() {
// 强行使用父元素的eat, 通过关键词super
super.eat()
console.log('吃外卖');
}
}
var s1 = new Son()
console.log(s1)
console.log(s1.hair, s1.eye);
回调地狱
// JS的异步操作 通常利用回调函数来实现 -- 网络请求
// 当多个异步操作需要同步执行时, 就会出现回调地狱问题
// 期望:点击注册按钮时, 要有序的验证: 用户名是否重复->邮箱..->手机号->注册
Promise
// ES6提供了 构造函数Promise, 可以从语法上规避回调地狱问题
// 学习Promise, 必须背 其固定格式
// resolve:解决 reject:拒绝 then:然后 catch:抓取
// Promise有三种状态
// 1. pending: 待定. new Promise之后处于的状态
// 2. fulfilled: 已兑现. 调用resolve后的状态, 代表成功
// 3. rejected: 已拒绝. 调用reject后的状态, 代表失败
new Promise((resolve, reject) => {
// 设定: 状态只能从pending 切换成其他状态
// resolve: 会触发then中的函数, 其参数传递给then中的函数
// resolve({ msg: '成功', code: 200 }) //进入 fulfilled状态
// resolve(1111) //无法触发, 因为当前是fulfilled状态
// 拒绝: 代表失败. 会触发catch中的函数, 参数会传给catch的函数, 进入rejected 状态
reject(222222)
})
.then(res => {
console.log('res:', res);
})
.catch(err => {
console.log('err:', err);
})
<script>
// 改造1: 用变量存储 new出来的promise对象; 再用p调用then和catch
function checkUname() {
return new Promise((resolve, reject) => {
console.log('验证用户名...');
setTimeout(() => {
const n = Math.random() //获取随机数,范围 0~1
if (n > 0.3) {
// resolve代表成功, 触发then
resolve({ msg: "成功", n })
} else {
// reject代表失败, 触发catch
reject({ msg: "失败", n })
}
}, 1000);
})
// return p
}
// 检查邮箱
function checkEmail() {
// prom : 前提安装 ES6提示插件, 详见B站 vscode视频
return new Promise((resolve, reject) => {
console.log('验证邮箱...');
setTimeout(() => {
const n = Math.random()
if (n > 0.3) {
resolve({ msg: "邮箱验证成功", n })
} else {
reject({ msg: "邮箱验证失败", n })
}
}, 1000);
});
}
function checkPhone() {
return new Promise((resolve, reject) => {
console.log('验证手机号...');
setTimeout(() => {
const n = Math.random()
if (n > 0.3) {
resolve({ msg: "手机号正确!", n })
} else {
reject({ msg: "手机号错误!", n })
}
}, 1000);
});
}
function register() {
return new Promise((resolve, reject) => {
console.log('开始注册!');
setTimeout(() => {
const n = Math.random()
if (n > 0.3) {
resolve({ msg: "注册成功", n })
} else {
reject({ msg: "注册失败", n })
}
}, 1000);
});
}
// 通过调用函数, 返回值是 new Promise()
// then: 然后
checkUname().then(res => {
console.log('res:', res);
// 返回值会触发下一个 then
return checkEmail()
}).then(res => {
console.log('res:', res)
return checkPhone()
}).then(res => {
console.log('res:', res)
return register()
}).then(res => {
console.log('res:', res)
}).catch(err => {
console.log('err:', err)
})
</script>
<script>
var a = 10
// console.log(a * 2)
function b() {
// var a = 10
return 10
}
// console.log(b() * 2)
</script>
正则表达式
正则匹配
// 正则特殊字符: \d 代表1个数字, 等价于 [0-9]
var words = '亮亮666 泡泡789 凯凯123'
// 要求: 找出words中, 所有的数字
// 字符串提供了 match 方法, 可以找出符合正则表达式要求的字符
console.log(String.prototype);
// 正则的特殊字符要放在 // 中, JS才能识别
var a = words.match(/\d/) //match: 匹配单个
console.log(a)
正则替换
var phone = prompt("请输入手机号")
console.log('phone:', phone)
// 替换语法: 字符串.replace(正则, 替换成什么)
// 需求1: 把所有的数字改成*
var a = phone.replace(/\d/g, '*')
console.log('a:', a)
正则验证
var phone = prompt('请输入手机号')
console.log('phone:', phone)
// 手机号的规则:
// 1开头; 第二位 3-9; 共11个
// ^: 代表字符串的开头
// $: 代表字符串的结尾
var r = /^1[3-9]\d{9}$/
// 验证语法: 正则.test(字符串) 返回值是true和false 代表对还是不对
var a = r.test(phone)
console.log(a ? '正确' : '格式错误');
构造方式
// 正则的字面量写法: 简单
var a = /\d/
console.dir(a)
// 构造写法
// 参数1: 正则 参数2: 修饰符
// \ : 在JS字符串中转义符. 例如 \d 会转成 d
// \\: 把\转义成普通\ \\d -> \d
var b = new RegExp('\\d', 'g')
var words = 'abcd 1234'
console.log(words.match(b))