1.JavaScript数据类型与运算符
数据类型
原始数据类型:
1.Number
2.String
3.Boolean
4.undefined
5.null
6.Symbol
7.bigint
复杂数据类型:
1.Function
2.非函数:
Array: 数组
Object: 对象
Date: 日期
RegExp: 正则
Map: 映射
Set: 集合
WeakMap: 弱映射
WeakSet: 弱集合
FormData: 表单数据
判断数据类型
1.typeof 运算符 ⼀般⽤来 判断基本数据类型 ,如:string,number,boolean,symbol,bigint(es10新
增⼀种基本数据类型bigint),undefined等。返回数据类型的字符串形式
typeof ⽬前能返回string,number,boolean,symbol,bigint,unfined,object,function这⼋种判断类型,但是注意 null 返回的是 Object。
为什么typeof null 是 Object?
因为在JavaScript中,不同的对象都是使⽤⼆进制存储的,如果⼆进制前三位都是0的话,系统会判断为是Object类型,⽽null的⼆进制全是0,⾃然也就判断为Object
扩展:
000 对象 - object
1 整型 - int
010 双精度类型 - double
100字符串 - strig
110布尔类型 - boolean
1000 Symbol类型
1001 bigint类型
2.instanceof 运算符⽤于判断对象的具体类型,⽐如判断⼀个变量是否是数组类型,⽤typeof是不行的,因为typeof返回的是
object,⽽instanceof可以,因为instanceof可以精准判断对象的具体类型
js
//⼿写实现
function myInstance(L, R) { //L代表instanceof左边,R代表右边
var RP = R.prototype
var LP = L.__proto__
while (true) {
if (LP == null) {
return false
}
if (LP == RP) {
return true
}
LP = LP.__proto__
}
}
console.log(myInstance({}, Object));
3.constructor 属性
与 instanceof 相似,但是对于 instanceof 只能再检测引⽤类型, ⽽ constructor 还可以检测基本类型,因为
constructor是原型对象的属性指向构造函数。
- null 和 undefined是⽆效的对象,因此是不会有 constructor 存在的,所以⽆法根据 constructor 来判
断。 - JS对象的 constructor 是不稳定的,这个主要体现在⾃定义对象上,当开发者重写prototype 后,原有
的 constructor 会丢失,constructor 会默认为Object - 类继承的也会出错,因为 Object 被覆盖了,检测结果就不对了
4.对象原型链判断:Object.prototype.toString.call(这个是判断类型最准的⽅法)
toString 是Object 原型对象上的⼀个⽅法,该⽅法默认返回其调⽤者的具体类型,更严格的讲,是 toString运⾏时this指向的对象类型, 返回的类型格式为[object,xxx],xxx是具体的数据类型 ,其中包括:String,Number,Boolean,undefined,null,Function,Date,Array,RegExp,Error,HTMLDocument... 基本上所有对象的类型都可以通过这个⽅法获取到。
2.undefined 和 undeclared 的区别
Undeclared(未声明) : 当尝试访问尚未使⽤ var、let 或 const 声明的变量时会发⽣这种情况。
Undefined(未定义) : 它发⽣在使⽤ var、let 或 const 声明变量但未赋值时。
在 JavaScript 中,两者很容易被混为⼀谈typeof 对 undefined 和 undeclared 的 都返回 "undefined"。
3.数组
- 数组的创建方式:
js
var arr = new Array(1, 2);
var arr = [1, 2];
- 数组的常用方法:
js
Array.from() // 浅拷⻉
从类数组对象或者可迭代对象中创建⼀个新的数组实例。 Array.from还可以接受第⼆个参数,作⽤类似于数组的
map⽅法,⽤来对每个元素进⾏处理,将处理后的值放⼊返回的数组。
Array.of() // 创建数组
根据⼀组参数来创建新的数组实例,⽀持任意的参数数量和类型,没有参数时返回 [],当参数只有⼀个的时候,实
际上是指定数组的⻓度。
Array.prototype.concat() // 连接数组
将传⼊的数组或非数组值与原数组合并,返回⼀个新数组,不影响原数组。
copyWithin(target,start,end)
将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
end(可选):到该位置前停⽌读取数据,默认等于数组⻓度。如果为负值,表示从末尾开始计算。
find()
⽤于找出第⼀个符合条件的数组成员
参数是⼀个回调函数,接受三个参数依次为当前的值、当前的位置和原数组
findIndex()
findIndex返回第⼀个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
find和findIndex这两个⽅法都可以接受第⼆个参数,⽤来绑定回调函数的this对象
fill()
还可以接受第⼆个和第三个参数,⽤于指定填充的起始位置和结束位置.
注意,如果填充的类型为对象,则是浅拷⻉.
push() pop() shift() unshift()
splice(index,howmay,item1,.....,itemX)
index : 必需。添加/删除项⽬位置,使⽤负数可从数组结尾处规定位置。
howmany :必需。要删除项⽬数量。如果设置为 0,则不会删除项⽬。
item1, ..., itemX: 可选。向数组添加新项⽬。
concat()
concat⽅法⽤于连接两个或多个数组。该⽅法将返回⼀个新数组,不影响原数组。
slice()
创建⼀个包含原有数组中⼀个或多个元素的新数组
reduce()
reduce() ⽅法不会改变原始数组。
filter()
将所有元素进⾏判断,将满⾜条件的元素作为⼀个新的数组返回
some()
将所有元素进⾏判断返回⼀个布尔值,如果存在元素都满⾜判断条件,则返回 true,若所有元素都不满⾜判断条
件,则返回 false:
every()
将所有元素进⾏判断返回⼀个布尔值,如果所有元素都满⾜判断条件,则返回 true,否则为 false:
join()
将数组中的所有元素转换⼀个字符串。
flat()
将数组展平,返回⼀个新数组,原数组不改变。
flatMap()
将数组中的每个元素都执行⼀个提供的函数,该函数返回⼀个新数组。
indexof()
返回数组中第一个符合条件的元素的索引,如果不存在,则返回-1。
includes()
返回一个布尔值,表示数组中是否包含给定的元素。
forEach()
遍历数组,将数组中的每个元素执行⼀个提供的函数。
map()
将数组中的每个元素都执行⼀个提供的函数,返回由每次函数调⽤结果组成的新数组。
reverse()
将数组中的元素顺序反转。
sort()
对数组中的元素进⾏排序。
toString()
将数组中的元素转换⼀个字符串。
4.数组去重
1.indexOf,filter
js
const arr = [1,2,3,2,3,4]
const newArr = arr.filter((item,index) => {
if(arr.indexOf(item) === index) {
return true
} else {
return false
}
})
2.Set
js
const set = new Set(arr)
const newArr = [...set]
3.Map
js
const map = new Map()
arr.forEach(item => {
if(!map.has(item) {
map.set(item,true)
})
})
const newArr = [...map.keys()]
4.forEach,includes
js
const arr = [1,2,3,4,3,4,5]
const newArr = []
arr.forEach(item => {
if(!newArr.includes(item)) {
newArr.push(item)
}
})
5.reduce
js
const newArr = arr.reduce((pre,i) => {
if(pre.indexOf(i) === - 1) {
pre.push(i)
}
},[])
6.暴力算法
js
const arr = [1,3,2,3,2,3,2,3,]
const newArr = []
for(let i = 0 ; i < arr.length;i++) {
for(let j = 0;j < i;j++) {
if(arr[i] !== arr[j]) {
newArr.push(arr[i])
} else {
break
}
}
}
5.数组拍平
1.flat
flat()⽅法⽤于将数组展平,返回⼀个新数组,原数组不改变。需要传入一个参数,这个参数表示要展平的层数
js
const arr = [1,2,[3,4,[5,6]]]
const newArr = arr.flat(Infinity)
2.toString + replace + split
js
const arr = [1,2,[3,4,[5,6]]]
const newArr = arr.toString().replace(/(\[|\])/g,'').split(',')
3.toString + replace + JSON.parse
js
const arr = [1,2,[3,4,[5,6]]]
const newArr = JSON.parse('[' + arr.toString().replace(/(\[|\])/g,'') + ']')
4.递归
js
const arr = [1,2,[3,4,[5,6]]]
const newArr = []
const fn = (arr) => {
for(let i = 0;i < arr.length;i++) {
if(Array.isArray(arr[i])) {
fn(arr[i])
} else {
newArr.push(arr[i])
}
}
}
fn(arr)
5.扩张运算符
js
const arr = [1,2,[3,4,[5,6]]]
const newArr = [].concat(...arr)
6.ES6新增的数组方法
1.find // 找到符合条件的第一个元素
2.findIndex // 找到符合条件的第一个元素索引
3.includes // 判断数组中是否包含某个元素
4.flat // 数组拍平
5.flatMap // 数组拍平,并且可以指定回调函数
6.Array.at(index) // 返回指定索引的元素
7.Array.from(arrayLike) // 将伪数组或可遍历对象转换为真数组
8.Array.of(element0, element1, ...) // 创建一个新的数组实例,并填充元素
9.Array.copyWithin(target, start = 0, end = this.length) // 从数组中拷贝元素到当前数组
10.Array.fill(value, start = 0, end = this.length) // 用指定的值填充数组
11.Array.entries() // 返回一个包含数组中每个索引的键值对的遍历器对象
12.Array.keys() // 返回一个包含数组中每个索引的键的遍历器对象
13.Array.values() // 返回一个包含数组中每个索引的值的遍历器对象
14.Array.sort(compareFunction) // 对数组中的元素进行排序
7.arguments
1.arguments是⼀个类数组对象,它包含函数调⽤时传⼊的参数。
这个对象存在于使用function声明的函数作用域中,并且只存在于函数内部。
这是一个类数组对象,可以进行遍历啊,也可以使使用下标获取元素含有callee,length属性
2.转为数组
js
const arr = Array.from(arguments)
const arr = [...arguments]
js
Array.prototype.slice.call(argument)
// Array.prototype.slice这个方法可以在传入一个类数组对象,该函数会调用类属组上的length属性进行遍历截取,因此在后两个参数中不传值则会将整个类数组都复制一遍,生成一个数组返回
3.扩展运算符
js
const arr = [...arguments]
const arr = [...arguments.callee]
8.对象
1.遍历对象
可以使用hasOwnProperty 函数来判断对象一个属性是不是存在与对象那个之中
对象也可以使用[]语法访问某一个属性
js
for(let key in obj) {} // 使用for...in 运算可以遍历对象的可枚举属性,包括对象继承的属性
Object.keys(obj) // 这个方法可以返回对象中的所有可枚举(不包括继承属性)属性组成的一个数组
Object.getOwnPropertyNames(obj) // 这个方法可以返回对象中所有的属性,包括不可枚举的属性
Object.getOwnPropertySymbols(obj) // 这个方法可以返回对象中所有的Symbol属性组成的数组
Reflect.ownKeys(obj) // 这个方法可以返回对象中所有的属性,包括不可枚举的属性和Symbol属性
Object.entries(obj) // 这个方法可以返回对象中所有可枚举的属性组成的数组
Object.values(obj) // 这个方法可以返回对象中所有可枚举的属性值组成的数组
Object.fromEntries(obj) // 这个方法可以将一个键值对组成的数组转换为对象
9.for...in 和 for...of
1.for...in // 遍历对象的可枚举属性,包括对象继承的属性(专门为遍历对象而构建)
2.for...of // 遍历数组,字符串,Map,Set,arguments等可迭代对象
10.原型与原型链
1.原型
每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。当这个函数作为构造函数创建实例时,该实例的内部[[Prototype]]指针被赋值为构造函数的prototype属性。这样构造函数的原型对象就和实例产生联系,实力就可以调用构造函数原型对象上的属性与方法。例如Array数组实例的绝大多数api都是来自Array.prototype原型对象。prototype有⼀个默认的constructor属性,⽤于记录实例由哪个构造函数创建.constructor,每⼀个原型都有⼀个constructor属性指向关联的构造函数.
2.原型链
每个实例对象都有一个内部属性[[Prototype]],指向构造函数的原型对象。这个原型对象也有一个自己的原型对象[[Prototype]],层层向上直到Object.特殊的 Function 对象,Function 的 proto 指向的是⾃身的 prototype。
⼀切对象都是继承⾃Object对象,Object 对象直接继承根源对象null。
⼀切的函数对象(包括 Object 对象),都是继承⾃ Function 对象。
Object 对象直接继承⾃ Function 对象。
Function对象的proto会指向⾃⼰的原型对象,最终还是继承⾃Object对象。
11.javaScript中的继承方式
1.原型链继承
由于原型链的存在,可以手动修改一个对象的原型,达到继承的目的。这样一来这个对象的原型对象上的属性与方法可以被该对象使用,近似的实现了两个对象之间的继承关系
js
// 父对象构造函数
function Person(name) {}
Person.prototype.name = 'zhangsan'
Person.prototype.say = function() {}
// 子对象构造函数
function Student(name) {}
Student.prototype = new Person()
const student = new Student()
student.name // zhangsan 实现两个对象之间的继承关系
优点:
- 原型链继承是JavaScript中实现继承的主要方式,也是最常用的继承方式。
- 原型链继承可以共享父对象的原型属性和方法,避免了重复创建对象。
- 原型链继承可以实现多继承,即一个子对象可以同时继承多个父对象。
缺点:
- 原型链继承的缺点是子对象共享父对象的原型属性和方法,如果父对象的原型属性是一个引用类型,那么子对象修改这个属性,会影响到其他子对象。
- 原型链继承的缺点是子对象无法向父对象传递参数,因为子对象的原型是父对象的一个实例,父对象的构造函数在创建子对象的原型时,子对象无法向父对象的构造函数传递参数。
- 原型链继承的缺点是子对象无法继承父对象的构造函数,因为子对象的原型是父对象的一个实例。
2.借用构造函数继承
通过利用javascript中的this关键字,在构造函数中调用父构造函数,并且指定父构造函数this指向为当前作用域this,即可以将父构造函数中添加属性与方法的操作在子构造函数中执行一遍,从而间接实现了继承。
js
// 父构造函数
function Parent (name) {
this.name = name
this.say = function () {}
}
// 子构造函数
function Child (name) {
Parent.call(this, name)
}
const child = new Child("zhangsan")
child.name // zhangsan
优点:
- 借用构造函数继承可以向子对象传递参数,解决了原型链继承的缺点。
- 借用构造函数继承可以实现多继承,即一个子对象可以同时继承多个父对象。
缺点:
- ⽅法都在构造函数中 定义,每次创建实例都会创建⼀遍⽅法
3.组合继承
组合继承是JavaScript中实现继承的主要方式,也是最常用的继承方式。
组合继承通过原型链继承父对象的属性和方法,通过借用构造函数继承父对象的方法。
js
// 父构造函数
function Parent (name) {
this.name = name
this.say = function () {}
}
// 子构造函数
function Child (name) {
Parent.call(this, name)
}
Child.prototype = new Parent()
const child = new Child("zhangsan")
child.name // zhangsan
优点:
- 组合继承可以共享父对象的原型属性和方法,避免了重复创建对象。
- 组合继承可以实现多继承,即一个子对象可以同时继承多个父对象。
- 组合继承可以向子对象传递参数,解决了原型链继承的缺点。
缺点:
- 组合继承的缺点是子对象的原型是父对象的一个实例,父对象的构造函数在创建子对象的原型时,子对象无法继承父对象的构造函数。
4.Class继承
ES6中新增的Class语法糖,通过extends关键字实现继承。
js
class Parent {
constructor(name) {
this.name = name
}
say() {}
}
class Child extends Parent {
constructor(name) {
super(name)
}
}
const child = new Child("zhangsan")
child.name // zhangsan
优点:
- Class继承可以共享父对象的原型属性和方法,避免了重复创建对象。
- Class继承可以实现多继承,即一个子对象可以同时继承多个父对象。
- Class继承可以向子对象传递参数,解决了原型链继承的缺点。
缺点:
- Class继承的缺点是子对象的原型是父对象的一个实例,父对象的构造函数在创建子对象的原型时,子对象无法继承父对象的构造函数。
12.new操作符创建一个对象的过程
1.创建一个空对象
new操作符使用会创建一个空对象,并且将这个空对象的原型指向构造函数的prototype属性。
2.修改this指向
3.调用构造函数
4.返回对象
手写一个new操作符
js
function newOperator(func,...args) {
// 1.创建新对象
const obj = {}
// 2.将新对象的原型指向构造函数的prototype属性
obj.__proto__ = func.prototype
// 3.修改this指向并调用构造函数
const res = func.apply(obj,args)
// 4.返回新对象
return typeof res === 'object' ? res : obj
}
13.instanceof 操作符
原理: instanceof操作符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。原理是判断构造函数的prototype属性是否出现在对象的原型链上
手写一个instanceof操作符
js
function instanceOf(left,right) {
// 循环判断构造函数是否出现在对象的原型链上
while(left !== null) {
if(left.__proto__ === right.prototype) {
return true
}
left = left.__proto__
}
return false
}