JS 学习
环境对象
定义:
目标:能够分析判断函数运行在不同环境中this所指代的对象
环境对象:指的是函数内部特殊的变量this,它代表着当前函数运行时所处的环境
this 指向的是window对象
function fn(){
console.log(this)
}
window.fn()这里的 window 就是DOM窗口的意思
const btn = document.querySelector('button')
btn.addEventListener('click', function () {
console.log(this)
})this指向这个函数的调用者,如上述代码,就会输出按钮那个标签
回调函数
定义:如果将函数 A 做为参数传递给函数 B 时,我们称函数 A 为回调函数
这有一个例子:
function fn() {
console.log('我是回调函数...')
}
// fn传递给了setInterval,fn就是回调函数setInterval(fn, 1000)冒泡

使用监听点击函数监听一个嵌套了多个父子级的元素,往往会全部触发,并由小到大,使用以下代码去阻止冒泡:
事件对象.stopPropagation()实例:
const fa = document.querySelector('.father')
const son = document.querySelector('.son')
document.addEventListener('click', function () {
alert('我是爷爷')
})
fa.addEventListener('click', function() {
alert('我是爸爸')
})
son.addEventListener('click', function(e) {
alert('我是儿子')
e.stopPropagation(e)
})解绑事件
//绑定事件
btn.onclick = function() {
alert('点击了')
}
//解绑事件
btn.onclick = nullfunction fn() {
alert ('点击了')
}
//绑定事件
btn.addEventListener('click', fn)
//解绑事件
btn.removeEventListener('click', fn)注意:匿名函数无法被解绑
事件委托
优点:减少注册次数,可以提高程序性能
原理:事件委托其实是利用事件冒泡的特点。
给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件
<body>
<ul>
<li>第1个孩子</li>|
<li>第2个孩子</li> ¹
<li>第3个孩子</li>
<li>第4个孩子</li>
<li>第5个孩子</li>
<p>你好</p>
</ul>
<script>
//点击每个小li当前li文字变为红色
//按照事件委托的方式委托给父级,事步写到父级身上
// 1.获得父元素
const ul = document.querySelector('ul')
ul.addEventListener('click', function (e) {
e.target.style.color = 'red'
})
</script>
</body>这样就不用给每个标签都用 for 写个事件监听了。事件对象 e 里面的 target 就是你所选择的目标。
如果我们不需要里面的 <p> 也变色,可以这么写:
if(e.target.targetName === "LI") {
e.target.style.color = 'red'
}
JS 取用自定义属性的值
阻止默认行为
比如你写了一个注册表单,按常理来说点击注册按钮就自动提交表单信息了,但是你并不想这么做,因为你需要检测用户填入的数据是否都合法,这里就需要阻止“点击就提交”的默认行为。
比如:
<body>
<form action="http://www.itcast.cn">
<input type="submit" value="免费注册" >
</form>
<script>
const form = document.querySelector('form')
form.addEventListener('submit', function(e) {
e.preventDefault()
}
</script>
</body>New
New 就是“调用函数时创建一个新的对象,并把这个新对象绑定为函数内部的this”
function Person(name) {
this.name = name
}
const P = new Person("Alice")
console.log(p.name)时间戳
是指1970年01月01日00时00分00秒起至现在的毫秒数,它是一种特殊的计量时间的方式



BOM
BOM 就是浏览器对象模型



!(function(){
console.log("hello")
})()这是一个立即调用函数表达式
通过创建新的作用域来避免污染全局命名空间。
创建新的异步上下文以在非异步上下文中使用
await。
使用复杂的逻辑计算值,例如将多个语句用作单个表达式。
JSON.stringify(obj)可以将一个对象转换为字符串,JSON.parse(string)即可把json转换为对象。


块级作用域
{}内部都称为一个块,块内声明的变量外部有可能无法方法。var 忽略块级作用域

闭包

闭包并不简单是把一个函数包在一个函数内,而是要求里面的函数要使用外面函数里面的变量。闭包的作用就是让外部能访问函数内部的变量
function outer() {
let a = 10
function fn() {
console.log(a)
}
return fn
}
outer()
let fun =outer()
fun()这就是一个典型的闭包的使用。
function count() {
let i = 0
function fn() {
i++
console.log(i)
}
return fn
}
const fun = count()
fun()如果我想让一个函数每次被调用的时候i就自增,但是如果这个i被改成了1000,再加就是1001了,为了防止变量被篡改,就可以使用闭包,就是如上的代码,这样这个i就无法被外部所访问了
function sum() {
let s = 0
for (let i = 0; i < arguments.length; i++) {
s += arguments[i]
}
console.log(s)
}
sum(5, 10)
sum(1, 2, 4)当我们不知道传过来的实参有多少个的时候,就可以使用arguments
function getSum(...arr) {
for (let i = 0;i < arr.length; i++) {
const element = arr[i];
console.log(arr[i]);
}
}
getSum(2, 4)
getSum(2, 4, 5)或者这样使用也是可以的。那么它和arguments有什么区别呢?...arr的意思是取参数的末尾
function getSum(a, b, ...arr) {
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
console.log(arr[i]);
}
}
getSum(2, 4, 5, 6, 7, 8)比如这段代码,就会给a 2,给b 4,给arr 5,6,7,8。这个就是剩余参数
... 这个是展开运算符,可以将一个数组进行展开。

典型的应用就是求数组的最大值,最小值或是合并数组
const arr = [1,2,3]
console.log(Math.max(...arr));因为Math.max() 里面只能填1,2,3 所有就需要拥到这个运算符把他拆分开来
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const arr = [...arr1, ...arr2]
console.log(arr);也可以通过它来合并数组
箭头函数
/*const fn = function () {
console.log(123);
}*/
const fn = (x) => {
console.log(x);
}
fn(x)箭头函数就是用来替代匿名函数的,写起来更简单,当然如果只有一个参数的话,连括号都是可以省去的:
const fn = x => {
console.log(x);
}
fn(1)只有一行代码的时候甚至能省略大括号:
const fn = x => console.log(x)
fn(1)只有一行代码的时候也可以省略return:
/*const fn = x => {
return x+x
}*/
const fn = x => x+x
console.log(fn(1));
这里的ev就是事件对象event,可以简写为e
const fn = (uname) => ({ uname: uname })
console.log(fn("liudehua"));箭头函数也可以返回对象。
但是箭头函数里面没有动态参数arguments,但是有剩余参数...arr
箭头函数会沿着作用域链上一级来找自己的this
数组解构
const [max,min,avg] = [100,60,80]
console.log(max);
console.log(min);
console.log(avg);这样就可以把数组里的数据分别赋值给这几个变量
const arr = [100, 60, 80]
const [max, min, avg] = arr
console.log(max);
console.log(min);
console.log(avg);也可以这么写
let a = 1
let b = 3;
[b,a] = [a,b]交换变量就可以简写成这样。但是第二行后面必须加上分好,否则会报错
对象解构

相对应的键值与变量名必须相等

改名的话加个冒号与新名即可

当然数组对象也可以
forEach

构造函数

构造函数的命名以大写开头,且只能用new操作符来执行。构造函数不用写return,但是有返回值,就是对象
实例成员与静态成员
通过构造函数构造的对象叫做实例对象,实例对象里的属性和方法都叫做实例成员(实例属性和实例方法)
构造函数和属性和方法被称为静态成员(静态属性和静态方法)
function Pig(name) {
this.name = name
}
Pig.eyes = 2
console.log(Pig.eyes);这里的Pig.eyes就是一个静态成员
function Pig(name) {
this.name = name
}
Pig.sayHi = function () {
console.log(this);
}
Pig.sayHi()这就是一个静态方法。
基本包装类型
正常来说一个简单数据类型是没有属性的,但是,
const str = "pink"
console.log(str.length);这个却可以打印出它的长度
const num = 12
console.log(num.toFixed(2));有的甚至还有方法
因为其实你声明的时候就相当于已经实例化了一个对象:
const str = new String("pink");Object静态方法


const o = { uname: 'pink', age: 18 }
Object.assign(o, { gender: "woman" })当然也可以使用这个静态方法来追加
数组的其他方法

字符串常见方法
两种编程思想


prototype

function Star(uname, age) {
this.uname = uname
this.age = age
}
Star.prototype.sing = function () {
console.log("sing");
}
const ldh = new Star("刘德华", 55);
const zxy = new Star("张学友", 58);
ldh.sing()
zxy.sing()但是构造函数有内存占用大的问题,这时候我们就需要通过prototype挂载函数,像这样,就可以进行复用。
prototype中还有一个属性叫做constructor,它的作用是指向该原型对象的构造函数。
为什么说实例对象能够访问原型上的东西?这主要靠对象原型,每个对象都有一个_proto_
原型继承
function Woman() {
this.ears = 2
this.head = 1
}
const red = new Woman();
console.log(red);
function Man() {
this.ears = 2
this.head = 1
}
const pink = new Man();
console.log(pink);如果这么写就太过于冗余了,我们其实可以定义一个person放到原型里面
const Person = {
ears:2,
head:1
}
function Woman() {
}
Woman.prototype = Person
Woman.prototype.constructor = Woman
const red = new Woman();
console.log(red)
console.log(Woman.prototype)
function Man() {
}
Man.prototype = Person
Man.prototype.constructor = Man
const pink = new Man();
console.log(pink);这样Man和Woman就都继承了Person
function Person() {
this.ears = 2
this.head = 1
}
function Woman() {
}
Woman.prototype = new Person()
Woman.prototype.constructor = Woman
const red = new Woman();
Woman.prototype.baby = function () {
console.log("宝贝")
}
console.log(red)
console.log(Woman.prototype)
function Man() {
}
Man.prototype = new Person()
Man.prototype.constructor = Man
const pink = new Man();
console.log(pink);也可以通过构造函数来继承,这样两个函数继承的就互不干扰互不影响,如果按照原来的来写的话,两个继承的指向的都是一个对象,有改动的话两个就都会有改动。
浅拷贝与浅拷贝
浅拷贝
const obj = {
uname: "pink",
age: 18,
}
const o = obj;
console.log(o);
o.age = 20;
console.log(o);
console.log(obj);如果这样直接把obj复制给o,他们在内存里指向的都是同一个数据,所以改一个另一个也会跟着改了。

const obj = {
uname: "pink",
age: 18,
}
const o = { ...obj }
console.log(o); // 输出 { uname: 'pink', age: 18 }
o.age = 20;
console.log(o); // 输出 { uname: 'pink', age: 20 }
console.log(obj);// 输出 { uname: 'pink', age: 18 } const obj = {
uname: "pink",
age: 18,
}
const o = {}
Object.assign(o, obj);
console.log(o);
o.age = 20;
console.log(o);
console.log(obj);但是只要用剩余计算符或assign就能解决这个问题,就不会相互影响了,这是一个浅拷贝的解决方案
深拷贝
