JS高级教程
学会查文档ReferenceAPI函数
进阶版笔记有点乱,可以看百度网盘的JS高级教程md笔记
回忆
BOM:浏览器对象模型
window:navigator、location
、history、screen
、document(window下面的对象)
window:load、DOMContentLoaded、beforeunload(离开页面触发)、
dragover、drop
classList:add、remove、toggle、contains
自定义属性:data-xxx、dataset
关于JS的零碎补充
- 如果没有let,默认是全局变量,那么在函数外面就可以使用(全局变量污染),不建议不写let
- 在
ES6
中{}大括号包裹的代码块等价于局部作用域局部变量,括号外不能使用, let
声明的变量产生块作用域(window对象下没有),var
不会产生块作用域事全局变量(window对象里面有)const
声明的常量也会产生块作用域,推荐使用let
或const
(const声明的不能改变,其余和let没什么区别)- 作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,往上查找变量
答案:456(n=456是全局的被改变了)
arguments
是函数内部内置的伪数组变量,它包含了调用函数时传入的所有
实参,作用是动态获取函数的实参。
<script>
// 求生函数,计算所有参数的和
function sum() {
// console.log(arguments);
let s = 0;
for(let i = 0; i < arguments.length; i++) {
s += arguments[i];
}
console.log(s);
}
// 调用求和函数
sum(5, 10); // 两个参数
sum(1, 2, 4); // 两个参数
</script>
- 函数没传参的时候,形参是undefined,两个相加是NAN,并且NAN === NAN是false
- 剩余参数:只能用于最后一个形参,表示剩余的所有参数
- 当然括号内只有一个形参且为剩余参数时,则表示所有的参数fun(…a)
- … 是语法符号,置于最末函数形参之前,用于获取多余的实参,借助 … 获取的剩余实参
- 对象.属性(这个属性不能是变量,否则就变量名是属性了,和对象[‘属性’](这里括号里可以换成变量),比如obj.str[i]这样指代的是属性名为str[i]…,而只能对象[str[i]]
预解析
代码在执行之前,任何一个作用域都要预解析,解析变量和函数(也就是只解析定义的函数,不解析调用的和回调函数,其实原理和BOM里面的队列栈线程的同步异步类似)。这样,预解析解析完了再执行
,即便先调用,再声明,不会报错,是undefined,函数内部还有就继续被解析,但是let的声明不会被解析,就会报错
变量:带有申明的变量才会进行变量解析,变量提升,JS会把申明的变量,提升到当前作用域的最前面,只申明不赋值
函数:代有函数名的变量才会进行函数解析(回调函数和函数表达式不会被解析),函数提升,JS会把带有名字的函数,提升到当前作用域最前面,只定义不调用
<script>
// 调用函数
foo();
// 声明函数(会解析,被提前)
function foo() {
console.log('声明之前即被调用...');
}
// 不存在提升现象
bar();
var bar = function () {
console.log('函数表达式不存在提升现象...');
}
</script>
死区:let声明之前的,不会被解析,报错!!
但是下面代码如果var则可以,输出undefined
下面练习,
- 答案是:两个undefined,(简便想就是先调用再声明的代码,只要声明的是可以被解析的那就undefined,不能被解析的就报错)
- 答案是函数(){}和2
闭包
闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数,
闭包:一个作用域有权访问另外一个作用域的局部变量,
好处:可以把一个变量使用范围延伸
- 闭包本质仍是函数,只不是从函数内部返回的
- 闭包能够创建外部可访问的隔离作用域,避免全局变量污染
- 过度使用闭包可能造成内存泄漏
<script>
function foo() {
let i = 0;
// 函数内部分函数
function bar() {
console.log(++i);
}
// 将函数做为返回值
return bar;
}
// fn 即为闭包函数
let fn = foo();
fn(); // 1
</script>
- 注:回调函数也能访问函数内部的局部变量。
箭头函数
箭头函数是一种声明函数的简洁语法,它与普通函数并无本质的区别,差异性更多体现在语法格式上。
- 箭头函数属于表达式函数,因此不存在函数提升
- 箭头函数只有一个参数时可以省略圆括号
()
- 箭头函数函数体只有一行代码时可以省略花括号
{}
,并自动做为返回值被返回 - 箭头函数中没有
arguments
,只能使用...
动态获取实参 - 涉及到this的使用,不建议用箭头函数
- 1.当没有参数,小括号可以不写
<script>
// 箭头函数
let foo = () => {
console.log('^_^ 长相奇怪的函数...');
}
// 调用函数
foo();
// 更简洁的语法
let form = document.querySelector('form');
form.addEventListener('click', ev => ev.preventDefault());
</script>
-
2.函数体只返回一行时,可以省略大括号和return
箭头函数的使用场景如下(定时器的this也是指向window或者事件源的)
-
3.箭头函数没有预解析,所以必须先定义再调用
-
4.箭头函数中,不存在arguments
所以要换成…a,这样就可以获取输入的数组了
-
5.谁调用箭头函数,箭头函数里面的this就指向谁(箭头函数认为没有this,所以里面不要使用this而使用元素.什么)
-
普通函数是window调用哦,这个this指向window
-
匿名函数,事件源里面的这个this指向的事件源(事件调用者),也就是btn
对象里面含有属性,也有方法,调用的时候是对象调用,所以this指向对象
-
箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),此处指父级作用域,
而不是执行时的对象
, 定义它的时候,可能环境是window; 箭头函数可以方便地让我们在 setTimeout ,setInterval中方便的使用this
下面是箭头函数和匿名函数的比较!!!(匿名会指向当前调用的对象,但是箭头函数多数为window)
var s = 21;
const obj = {
s: 42,
m: () => console.log(this.s),
k: function(){console.log(this.s)}
};
obj.m(); // 21
obj.k(); // 42
<button>789798</button>
<script>
let btn = document.querySelector('button')
// 匿名函数的调用
// btn.addEventListener('click',function(){
// //console.log(this); //<input type="button">
// this.style.backgroundColor = 'red' //按钮变红
// },false);
//箭头函数
btn.addEventListener('click',() => {
console.log(this); //window{window:.....}
this.style.backgroundColor = 'red' //报错
},false);
</script>
如果上图是匿名函数,两个this分别指向调用者,也就是window和document
call
方法能够在调用函数的同时指定this
的值- 使用
call
方法调用函数时,第1个参数为this
指定的值 call
方法的其余参数会依次自动传入函数做为函数的参数
<script>
// 普通函数
function sayHi() {
console.log(this);
}
let user = {
name: '小明',
age: 18
}
let student = {
name: '小红',
age: 16,
}
// 调用函数并指定 this 的值
sayHi.call(user); // this 值为 user
sayHi.call(student); // this 值为 student
// 求和函数
function counter(x, y) {
return x + y;
}
// 调用 counter 函数,并传入参数
let result = counter.call(null, 5, 10);
console.log(result);
</script>
this的改变
call
使用 call
方法调用函数,同时指定函数中 this
的值,改变原有的this指向
这样obj就是o对象了
apply
<script>
// 普通函数
function sayHi() {
console.log(this);
}
let user = {
name: '小明',
age: 18
}
let student = {
name: '小红',
age: 16
}
// 调用函数并指定 this 的值
sayHi.apply(user); // this 值为 user
sayHi.apply(student); // this 值为 student
// 求和函数
function counter(x, y) {
return x + y;
}
// 调用 counter 函数,并传入参数
let result = counter.apply(null, [5, 10]);
console.log(result);
</script>
apply
方法能够在调用函数的同时指定this
的值- 使用
apply
方法调用函数时,第1个参数为this
指定的值 apply
方法第2个参数为数组,数组的单元值依次自动传入函数做为函数的参数
bind
bind
方法并不会调用函数,而是创建一个指定了 this
值的新函数,要执行就使用()进行调用,此时对象是李寻欢
点击五秒禁用,五秒后启用
bind
方法创建新的函数,与原函数的唯一的变化是改变了 this
的值。
三者区别
call:fun.call(this,arg1, arg2,…)
apply:fun.apply(this, [arg1, arg2,…])
bind:fun.bind(this, arg1, arg2,…)
- 相同点:
都可以用来改变this指向,第一个参数都是this指向的对象 - 区别:
call和apply:都会使函数执行,但是参数不同
bind:不会使函数执行,参数同call
解构
解构赋值是一种快速为变量赋值的简洁语法,本质上仍然是为变量赋值,分为数组解构、对象解构两大类型。
- 数组解构(其中也可以用…)
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法
1.赋值运算符=
左侧的[]
用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
2.变量的顺序对应数组单元值的位置依次进行赋值操作
3.变量的数量大于单元值数量时,多余的变量将被赋值为undefined
4.变量的数量小于单元值数量时,可以通过...
获取剩余单元值,但只能置于最末位
5.允许初始化变量的默认值,且只有单元值为undefined
时默认值才会生效
<script>
// 普通的数组
let arr = [1, 2, 3];
// 批量声明变量 a b c
// 同时将数组单元值 1 2 3 依次赋值给变量 a b c
let [a, b, c] = arr;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
</script>
- 对象解构
1.对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
2.赋值运算符 = 左侧的 {} 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
3.对象属性的值将被赋值给与属性名相同的变量
4.对象中找不到与变量名一致的属性时变量值为 undefined
5.允许初始化变量的默认值,属性不存在或单元值为 undefined 时默认值才会生效
为了防止和外面的变量重,
对象解构
答案是5个9最后一个报错
对象
字面量创建对象
构造函数创建new 创建
1.构造函数是专门用于创建对象的函数,如果一个函数使用 new 关键字调用,那么这个函数就是构造函数。
2.使用 new 关键字调用函数的行为被称为实例化
3.实例化构造函数时没有参数时可以省略 ()
4.构造函数的返回值即为新创建的对象
5.构造函数内部的 return 返回的值无效!
6.实践中为了从视觉上区分构造函数和普通函数,习惯将构造函数的首字母大写。
- 自定义构造函数
- obj instanceof B用于检测实例对象对应的构造函数
- 实例对象的 obj.constructor 属性指向了对象的构造函数
创建对象除了常规的那种,属性还可以放在constructor里面
实例成员和静态成员
实例成员:通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员。
- 构造函数内部
this
实际上就是实例对象,为其动态添加的属性和方法即为实例成员 - 为构造函数传入参数,动态创建结构相同但值不同的对象
- 实例对象的
constructor
属性指向了构造函数 instanceof
用于检测实例对象对应的构造函数
注:构造函数创建的实例对象彼此独立互不影响。
<script>
// 构造函数
function Person() {
// 构造函数内部的 this 就是实例对象
// 实例对象中动态添加属性
this.name = '小明';
// 实例对象动态添加方法
this.sayHi = function () {
console.log('大家好~');
}
}
// 实例化,p1 是实例对象
// p1 实际就是 构造函数内部的 this
let p1 = new Person();
console.log(p1);
console.log(p1.name); // 访问实例属性
p1.sayHi(); // 调用实例方法
</script>
静态成员:在 JavaScript 中底层函数本质上也是对象类型,因此允许直接为函数动态添加属性或方法,构造函数的属性和方法被称为静态成员
- 静态成员指的是构造函数本身的属性和方法(类本身的)
- 一般公共特征的属性或方法静态成员设置为静态成员
- 静态成员方法中的
this
指向构造函数本身
<script>
// 构造函数
function Person(name, age) {
// 省略实例成员
}
// 静态属性
Person.eyes = 2;
Person.arms = 2;
// 静态方法
Person.walk = function () {
console.log('^_^人都会走路...');
// this 指向 Person
console.log(this.eyes);
}
</script>
构造函数也是对象,可以通过.方法或者属性
引用类型Object、Array、RegExp
Object:是内置的构造函数,用于创建普通对象。
Array:是内置的构造函数,用于创建数组。
RegExp:内置的构造函数,用于创建正则表达式
- 简单数据类型(值传递),引用类型和复杂类型是引用传递,是在栈里面复制一个地址把地址传给他,堆里面还是那个数据
Object
Array
张飞会变张辽的
- 学会查文档ReferenceAPI函数
下面只是列举一些
任何一个属性都是Array构造函数的实例化对象
所以也可以用ary instance of Array //true
join引号里面可以加字符之间的符号,这里是’‘也就是无间隔,也可以’-'进行相连(字符串里面split以拆分)
sort和正序倒序,正序就是从小到大
foreach遍历方便
find()
findIndex
some和every
filter和Map
RegExp
包装类型
在 JavaScript 中的字符串、数值、布尔具有对象的使用特征,如具有属性和方法,之所以具有对象特征的原因是字符串、数值、布尔类型数据是 JavaScript 底层使用 Object 构造函数“包装”来的,被称为包装类型。(MDN网站查找文档)
- String
- Number
- Boolean
1.String
取出字符串某个字符,以及trim()
split(‘’)把字符串分割单个字符,如果是c那就分割成以c分割的字符串)
toLowerCase变成小写,toUpperCase都为大写
indexOf(‘字符/字符串’),查找元素第一次出现的位置、lastIndexOf()
slice(start, end)字符串截取(数组的截取是splice(start, length))、substring(start, end)也是截取、substr(start,length)也是
2.Number
<script>
// 使用构造函数创建布尔类型
let locked = new Boolean('10');
// 字面量创建布尔类型
let flag = true;
// 检测是否属于同一个构造函数
console.log(locked.constructor === flag.constructor);
</script>
总结
JavaScript 中一切皆为对象,还有以前学习的 window、Math 对象,最后补充一点无论是引用类型或是包装包类型都包含两个公共的方法 toString
和 valueOf
valueOf
方法获取原始值,数据内部运算的基础,很少主动调用该方法toString
方法以字符串形式表示对象- Null、undefined:除外
<script>
// 对象类型数据
let user = {name: '小明', age: 18}
// 数值类型
let num = 12.345;
// 字符串类型
let str = 'hello world!';
str.valueOf(); // 原始值
user.toString(); // 表示该对象的字符串
</script>
原型对象
实际上每一个构造函数都有一个名为 prototype
的属性,译成中文是原型的意思,prototype
的是对象类据类型,称为构造函数的原型对象,每个原型对象都具有 constructor
属性代表了该原型对象对应的构造函数。
- 我的理解
1.实际上prototype的话就可以去增添方法那些,后面实例化的对象都可以使用了,一般普通实例化的对象是不会对prototype的方法属性增添的,
2.还有就是如果实例化的对象自己有那个方法就优先用自己的,否则就使用prototype的)
- 看一个有意思的,简直是你中有我我中有你,
如果实例化的对象,本身没有prototype,就会通过自身的constructor构造函数去找原型对象prototype
原型链
在 JavaScript 对象中包括了一个非标准备的属性 proto 它指向了构造函数的原型对象,通过它可以清楚的查看原型对象的链状结构。
继承
extends
是 ECMAScript 6 中实现继承的简洁语法,是专门用于实现继承的语法关键字,Person
称为父类、Chinese
称为子类。
<script>
class Person {
// 父类的属性
legs = 2;
arms = 2;
eyes = 2;
// 父类的方法
walk () {
console.log('人类都会走路...');
}
// 父类的方法
sleep () {
console.log('人都得要睡觉...');
}
}
// Chinese 继承了 Person 的所有特征
class Chinese extends Person {}
// 实例化
let c1 = new Chinese(); //c1也有那些属性和方法
c1.walk();
</script>
- super
在继承的过程中子类中 constructor
中必须调 super
函数,否则会有语法错误,子类构造函数中的 super
函数的作用是可以将子类实例化时获得的参数传入父类的构造函数之中。
<script>
class Person {
// 构造函数
constructor (name, age) {
this.name = name;
this.age = age;
}
// 父类的属性
legs = 2;
arms = 2;
walk () {
console.log('人类都会走路...');
}
}
// 子类 English 继承了父类 Person
class English extends Person {
// 子类的构造函数
constructor (name, age) {
super(name, age);
}
// 子类的属性
skin = 'white';
language = '英文';
}
// 实例化
let e1 = new English('jack', 18);
console.log(e1.name);
</script>
拷贝
ECMAScript 6 中基于类的面向对象相较于构造函数和原型对象的面向对象本质上是一样的,基于类的语法更为简洁,未来的 JavaScript 中也都会是基于类的语法实现,当前阶段先熟悉基于类的语法,后面课程中会加强基于类语法的实践。
拷贝不是直接赋值(赋值的对象是改一个另一个改,传的地址)
浅拷贝:
含义:只拷贝最外面层的拷贝方式
let obj = {
uname : '张三丰',
age : 22,
sex : '男',
color : ['red', 'blue', 'yellow', 'pink'],
message : {
index : 1,
score : 99
}
}
let newObj = {};
Object.assign(newObj, obj);
console.log( obj, newObj );
深拷贝: