JS的数据类型及类型判断方式:
1、JS的数据类型
(1)基本数据类型
(1)String
(2)Number
(3)Boolean
(4)Null
(5)Undefined
(6)Symbol
(2)引用数据类型
(1)Object
1、Object 对象
2、Array 数组
3、Date 日期对象
4、Math 数学对象
5、RegExp 正则对象
6、Function 函数对象
2、基本数据类型和引用数据类型的区别
(1)基本数据类型存储在栈内存中,引用数据类型存储在堆内存中;
(2)数据存储时,基本数据类型在变量中存的是值,引用数据类型在变量中存储的是空间地址;
(3)基本数据操作的是值,引用数据类型操作的是空间地址
3、数据类型的判断方式(4种)
(1)typeOf 判断基本数据类型
typeOf的检测原理:在计算机底层根据js数据类型的二进制的值进行检测。
typeOf检测null类型时返回的是"Object"原因:null的二进制是000,
而object类型的二进制都是以000开头的,
所以typeOf检测null时返回"object",这是一个历史遗留问题,js的设计缺陷
typeof 1 //number
typeof '1' //string
typeof true //boolean
typeof undefined //undefined
typeOf无法判断null 和 Object
typeOf null //object
typeOf object //object
typeOf array //object
(2)instanceOf 判断左侧的对象是否属于右侧构造函数的实例(右侧构造函数的prototype(原型对象)是否出现在左侧实例对象的原型链上)
let obj = {};
obj instanceOf Object //true
let arr = [];
arr instanceOf Array //true
(3)constructor 原型对象的constructor属性指向构造函数
*****无法检测undefined和null类型,因为他俩没有原生的构造函数
let num = 123;
let str = '123';
let bool = true;
let fun = function(){};
let arr = [1,2,3];
num.constructor //[Function:Nmber]
str.constructor //[Function:String]
bool.constructor //[Function:Boolean]
fun.constructor //[Function:Function]
arr.constructor //[Function:Array]
(4)Object.prototype.toString.call() 繁琐但通用
1.toString()方法,返回的是对象的字符串表现
let obj = {name:"张三"};
console.log(obj.toString()); //[object Object]
2.Array,function等类型作为对象的实例,都各自重写了toString()方法
let arr = [1,2,3,4,5,6,7];
console.log(arr.toString()); //1,2,3,4,5,6,7
function fn(){return 1};
console.log(fn.toString()); //function fn(){return 1}
3.所以要判断具体类型,都要使用Object原型对象身上的toString()方法,而不能使用它们自己重写的toString()方法
4.Object.prototype.toString()返回的是Object.prototype(Object原型对象)的字符串变现,所以通过调用call方法来改变this指向,从而判断具体类型
Object.prototype.toString.call(null) //"[object Null]"
Object.prototype.toString.call(123) //"[object Number]"
Object.prototype.toString.call(undefined) //"[object Undefined]"
Object.prototype.toString.call([1,2,3,4,5,6,7]) //[object Array]
Object.prototype.toString.call(function fn(){return 1}) //[object Function]
js中字符串常用的相关方法
1. length 返回字符串的长度。
let str = "hello world";
console.log(str.length); // 11
2. charAt(index) 返回指定索引位置的字符。
let str = "hello world";
console.log(str.charAt(1)); // e
3. concat(str1, str2, ...) 多个字符串拼接起来
let str1 = "my"
let str2 = "hello world";
let str3 = "!!!"
console.log(str1.concat(" ",str2," ",str3)); //my hello world !!!
4. indexOf 返回指定字符串在目标字符串中首次出现的索引位置。
let str = "Hello World";
console.log(str.indexOf("o")); // 4
5. lastIndexOf 返回指定字符串在目标字符串中最后一次出现的索引位置
let str = "Hello World";
console.log(str.indexOf("o")); // 8
6. substring(startIndex, endIndex)
返回从起始索引到结束索引之间的子字符串(含起始索引 不含结束索引)
substring 方法使用startIndex和endIndex两者中的较小值作为子字符串的起始点
例如 str.substring(0,5) 和 str.substring(5,0) 将返回相同的子字符串
如果 startIndex 或 endIndex 为负数,那么将其替换为0 ,子字符串的长度等于 startIndex 和 endIndex 之差的绝对值。
let str = "Hello World";
console.log(str.substring(2, 8)); //llo Wo
console.log(str.substring(1, 3)); //el
console.log(str.substring(5, 0)); //Hello
console.log(str.substring(0, 5)); //Hello
console.log(str.substring(5, -2)); //Hello 因为有负数,相当于str.substring(5, 0)
console.log(str.substring(-5, 2)); //He 因为有负数,相当于str.substring(0, 2)
7.sunstr(startIndex,length) 返回一个从起始索引开始的指定长度的子字符串
如果 length 为 0 或负数,将返回一个空字符串。
如果没有指定该参数,则子字符串将延续到字符串的最后位置
let str = "Hello World";
console.log(str.substr (1, 3)); //ell
console.log(str.substr (5, 0)); //空字符串
console.log(str.substr (0, 5)); //Hello
8.slice(startIndex, endIndex) 返回从起始索引到结束索引之间的子字符串,**支持负数索引**
let str = "Hello World";
console.log(str.slice (-5, -1)); //Worl
9.trim() 只去除字符串两端的空白字符,字符串中间的空白字符不去除
let str = " Hello World ";
console.log(str.trim()); //Hello World
10.replace(searchValue, replaceValue)
用替换值替换目标字符串中的匹配项,
如果多个匹配项,只能替换掉第一个匹配项,
如果想要全部替换可以使用 replaceAll
let str = "11 Hello Hello World";
console.log(str.replace('Hello','Hi')); //11 Hi Hello World
console.log(str.replaceAll('Hello','Hi')); //11 Hi Hi World
11.split 将字符串分割成数组
let str = "11,Hello,Hello,World";
console.log(str.split(',')); //['11', 'Hello', 'Hello', 'World']
12.includes(searchValue, startIndex) 检查字符串是否包含指定的搜索值
let str = "11,Hello,Hello,World";
console.log(str.includes('Hello')); //true
console.log(str.includes('Hello',10)); //false 从指定的下标值开始搜索
13.padStart(targetLength, padString) 字符串用指定的填充字符填充到达目标长度。从开头填充
padEnd(targetLength, padString) 字符串用指定的填充字符填充到达目标长度,从末尾填充
let str = "Hello";
console.log(str.padStart(15,'*')); //**********Hello
console.log(str.padEnd(15,'*')); //Hello**********
14.toUpperCase() 字符串转换为大写。
toLowerCase() 字符串转换为小写。
js中数组常用的相关方法
**一、改变原数组的7个方法 **
(1)push()和pop() 数组末尾操作
push() 将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
pop() 从数组中删除最后一个元素,并返回该元素的值。
let arr = ["唐僧","孙悟空","猪八戒","沙和尚"];
let pushlength = arr.push("白龙马");
console.log(arr); //['唐僧', '孙悟空', '猪八戒', '沙和尚', '白龙马']
console.log(pushlength); //5
let popdata = arr.pop();
console.log(arr); //['唐僧', '孙悟空', '猪八戒', '沙和尚']
console.log(popdata); // 白龙马
(2)shift()和 unshift() 数组首操作
shift() 从数组中删除第一个元素,并返回该元素的值。
unshift() 将一个或多个元素添加到数组的开头,并返回该数组的新长度
let arr = ["唐僧","孙悟空","猪八戒","沙和尚"];
let pushlength = arr.unshift("白龙马");
console.log(arr); // ['白龙马', '唐僧', '孙悟空', '猪八戒', '沙和尚']
console.log(pushlength); //5
let popdata = arr.shift();
console.log(arr); //['唐僧', '孙悟空', '猪八戒', '沙和尚']
console.log(popdata); // 白龙马
(5)sort() 数组排序(对数组元素进行排序)
let arr = ["C","D","A","B"];
console.log(arr.sort()); // ['A', 'B', 'C', 'D']
(5)reverse 反转数组
let arr = ["唐僧","孙悟空","猪八戒","沙和尚"];
console.log(arr.reverse()); // ['沙和尚', '猪八戒', '孙悟空', '唐僧']
(6)splice(数组下标,删除元素个数,插入元素)
增 示例
let arr = ["唐僧","孙悟空","猪八戒","沙和尚"];
arr.splice(0,0,"白龙马")
console.log(arr); //['白龙马', '唐僧', '孙悟空', '猪八戒', '沙和尚']
删 示例
let arr = ["唐僧","孙悟空","猪八戒","沙和尚"];
arr.splice(0,1)
console.log(arr); //['孙悟空', '猪八戒', '沙和尚']
改 示例
let arr = ["唐僧","孙悟空","猪八戒","沙和尚"];
arr.splice(0,1,"白龙马")
console.log(arr);// ['白龙马', '孙悟空', '猪八戒', '沙和尚']
**二、不会改变原数组的方法**
(1)concat()---连接两个或更多的数组,并返回结果。
(2)indexOf()---搜索数组中的元素,并返回它所在的位置。
(3)join()---把数组的所有元素放入一个字符串。
let arr = ["唐僧","孙悟空","猪八戒","沙和尚"];
console.log(arr.join()); //唐僧,孙悟空,猪八戒,沙和尚
console.log(arr.toString()); //唐僧,孙悟空,猪八戒,沙和尚
(4)toString()---把数组转换为字符串,并返回结果。
let arr = ["唐僧","孙悟空","猪八戒","沙和尚"];
console.log(arr.toString()); //唐僧,孙悟空,猪八戒,沙和尚
(5)lastIndexOf()---返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。
(6)slice()---选取数组的的一部分,并返回一个新数组。
(7)flat() 将二维数组变为一维数组
let arr = ["唐僧","孙悟空","猪八戒","沙和尚",["白龙马","白骨精"]];
console.log(arr.flat()); //['唐僧', '孙悟空', '猪八戒', '沙和尚', '白龙马', '白骨精']
**三、数组的迭代方法**
(1)every()---检测数组元素的每个元素是否都符合条件。都符合返回true,否则返回false
let arr = [1,2,3,4,5,6,7];
let flag = arr.every((item)=>{
return item > 0
})
console.log(flag) //true
(2)some()---检测数组元素中是否有元素符合指定条件。只要有一个符合则返回true,都不符合返回false
let arr = [1,2,3,4,5,6,7];
let flag = arr.some((item)=>{
return item > 6
})
console.log(flag) //true
(3)filter()---过滤数组元素,并返回符合条件所有元素的数组。
let arr = [1,2,3,4,5,6,7];
let filterArr = arr.filter((item)=>{
return item > 4
})
console.log(filterArr) //[5, 6, 7]
(4)map()---通过指定函数处理数组的每个元素,并返回处理后的数组。
let arr = [1,2,3,4,5,6,7];
let mapArr = arr.map((item)=>{
return item * 10
})
console.log(mapArr) //[10, 20, 30, 40, 50, 60, 70]
(5)forEach()---遍历数组
(6)find() ---找到数组中符合条件的选项,并停止遍历
let arr = [1,2,3,4,5,6,7];
let data = arr.find((item)=>{
return item > 5
})
console.log(data) //6
(6)reduce() ---数组的累加方法(适用于上一次遍历的返回值作为下一次遍历的初始值,从左向右)
js原型与原型链
原型链类 :(原型对象 , 构造函数 ,实例)
-原型:每个函数都有 prototype 属性,该属性指向原型对象;使用原型对象的好处是所有对象实例共享它所包含的属性和方法。
-原型链:主要解决了继承的问题;每个对象都拥有一个原型对象,通过__proto__ 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null。
原型的作用:
1.数据共享 节约内存空间
2.实现继承
注意:函数也是一个对象,对象不一定是函数。对象有__proto__属性,函数有prototype属性
闭包
1、闭包如何产生:(函数嵌套),当内部函数引用了它的外部函数的变量时 ,就产生了闭包
2、闭包的作用:
1、使函数内部的变量在函数执行完成之后,仍然保存在内存中
2、在函数的外部依然可以操作到函数内部的变量(方法)
3、闭包的缺点:函数执行后,函数内部的局部变量没有释放,占用内存空间,容易造成内存泄漏
解决方案:及时释放(内部函数 == null)
什么是内存溢出:程序运行出错,当运行程序需要的内存超过了剩余的内存时,就会导致内存溢出。
什么是内存泄漏:占用的内存没有及时的释放,内存泄漏积累多了就会导致内存变小,容易造成内存溢出
常见的内存泄漏:1.意外的全局变量 2.没有及时的清理回调函数或者计时器 3.闭包
js的执行上下文
1、全局执行上下文:
(1)在全局代码执行之前,将window确定为全局执行上下文
(2)对全局数据进行预处理:
1、将var定义的全局变量赋值为undefined,添加为window的属性
2、function声明的全局函数赋值为fun(),添加为window的方法
(3)this赋值为window
(4)开始执行全局代码
2、函数执行上下文:
(1) 在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象(虚拟的,存在与栈中)
(2) 对局部数据进行预处理
1、形参变量赋值为实参,添加为执行上下文属性
2、arguments赋值为实参列表,添加为执行上下文属性
3、将var定义的局部变量赋值为undefined ,添加为执行上下文属性
4、function声明的函数赋值为fun(),添加为执行上下文方法
5、this赋值为(调用函数的对象)
(3) 开始执行函数体代码
js的作用域
作用域:一个代码块所在的区域就是作用域,它是静态的(相对与执行上下文),在编写代码时就确定了。
作用域的作用:隔离变量=>不同作用域下的同名变量不会有冲突
作用域链:多个上下级关系的作用域形成的链,方向是(从内到外),查找变量时就是沿着作用域链来查找的
js中继承的方式
1.原型链继承:核心--将父类的实例对象作为子类的原型对象
优点
1.父类的属性和方法都可以继承并且父类原型链上的方法和属性也能继承
缺点
1.引用类型的属性被所有实例共享,容易造成数据污染。(当创建多个子类实例对象后,因为子类实例对象共用一个原型对象)
2.创建子类实例时,无法向父类构造函数传参。
3.无法实现多继承
//父类
function Animal(type){
this.type = type || "未知动物";
this.hoppy = ["吃鱼","吃骨头"];
this.sayHello = function(){
console.log("hello" + this.type);
};
}
Animal.prototype.getPrototype = function(){
console.log("原型对象的getPrototype方法");
}
//子类
function Dog(){}
//将父类的实例对象作为子类的原型对象
Dog.prototype = new Animal();
//创建子类实例对象
let dog1 = new Dog();
let dog2 = new Dog();
console.log(dog1.hoppy);//['吃鱼', '吃骨头']
console.log(dog2.hoppy);//['吃鱼', '吃骨头']
dog1.sayHello();//hello未知动物
dog2.sayHello();//hello未知动物
dog1.getPrototype();//原型对象的getPrototype方法
dog1.hoppy.push("吃肉");
console.log(dog2.hoppy);//['吃鱼', '吃骨头', '吃肉']
2.构造函数继承:使用父类的构造函数来增强子类的实例对象,相当于把父类的属性/方法复制给子类
优点
1.解决了原型链继承中引用类型的属性被所有实例共享的缺点。
2.可以向父类的构造函数传参。
3.可以实现多继承。
缺点
1.只能继承父类声明的属性/方法,不能继承父类原型对象上的属性/方法
代码演示
//父类
function Animal(type){
this.type = type || "未知动物";
this.hoppy = ["吃鱼","吃骨头"];
this.sayHello = function(){
console.log("hello" + this.type);
};
}
Animal.prototype.getPrototype = function(){
console.log("原型对象的getPrototype方法");
}
//子类
function Dog(name,type){
Animal.call(this,type) //相当与复制的父类的属性/方法
this.name = name
}
//创建子类实例对象
let dog1 = new Dog("大黄","小狗");
let dog2 = new Dog("小黑","大狗");
dog1.sayHello();//hello小狗
dog2.sayHello();//hello大狗
dog1.hoppy.push("吃肉");
console.log(dog1.hoppy);//['吃鱼', '吃骨头', '吃肉']
console.log(dog2.hoppy);//['吃鱼', '吃骨头']
dog1.getPrototype();//dog1.getPrototype is not a function 无法继承父类原型对象的属性/方法
3.组合式继承(原型链继承+构造函数继承): 组合式继承通过调用父类构造函数并可以给父类构造函数传承,继承父类的属性/方法,然后将父类的原型对象 作为 子类的原型对象。
优点
1.可以想父类的构造函数传参
2.将父类的方法可以定义在父类的原型对象上,实现方法的复用
3.引用类型的属性不会被所有实例共享
缺点
1.调用了两次父类的构造函数,导致存放了一份多余的父类的属性
//父类
function Animal(type){
this.type = type || "未知动物";
this.hoppy = ["吃鱼","吃骨头"];
this.sayHello = function(){
console.log("hello" + this.type);
};
}
Animal.prototype.getPrototype = function(){
console.log("原型对象的getPrototype方法");
}
//子类
function Dog(name,type){
Animal.call(this,type) //相当与复制的父类的属性/方法
this.name = name
}
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
//创建子类实例对象
let dog1 = new Dog("大黄","小狗");
let dog2 = new Dog("小黑","大狗");
dog1.sayHello();//hello小狗
dog2.sayHello();//hello大狗
dog1.hoppy.push("吃肉");
console.log(dog1.hoppy);//['吃鱼', '吃骨头', '吃肉']
console.log(dog2.hoppy);//['吃鱼', '吃骨头']
dog1.getPrototype();//原型对象的getPrototype方法
4.寄生式组合继承(经典继承):寄生组合式继承是js中最理想的继承方式,最大限度的节省了内存空间
//父类
function Animal(type){
this.type = type || "未知动物";
this.hoppy = ["吃鱼","吃骨头"];
this.sayHello = function(){
console.log("hello" + this.type);
};
}
Animal.prototype.getPrototype = function(){
console.log("原型对象的getPrototype方法");
}
//子类
function Dog(name,type){
Animal.call(this,type) //相当与复制的父类的属性/方法
this.name = name
}
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog;
//创建子类实例对象
let dog1 = new Dog("大黄","小狗");
console.log(dog1);
let dog2 = new Dog("小黑","大狗");
dog1.sayHello();//hello小狗
dog2.sayHello();//hello大狗
dog1.hoppy.push("吃肉");
console.log(dog1.hoppy);//['吃鱼', '吃骨头', '吃肉']
console.log(dog2.hoppy);//['吃鱼', '吃骨头']
dog1.getPrototype();//原型对象的getPrototype方法
5.es6的extends继承
//父类
class Animal{
constructor(type){
this.type = type || "未知动物";
}
hoppy = ["吃鱼","吃骨头"];
AnimalsayHello(){
console.log("hello" + this.type);
};
}
//子类
class Dog extends Animal{
constructor(color,type){
super(type)
this.color = color;
}
DogsayHello(){
console.log("hello" + this.type);
};
}
//创建子类实例对象
let dog1 = new Dog("大黄","小狗");
console.log(dog1);
let dog2 = new Dog("小黑","大狗");
dog1.DogsayHello();//hello小狗
dog2.AnimalsayHello();//hello大狗
dog1.hoppy.push("吃肉");
console.log(dog1.hoppy);//['吃鱼', '吃骨头', '吃肉']
console.log(dog2.hoppy);//['吃鱼', '吃骨头']
new一个对象发生了什么???
//构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
let p = new Person("Alice", 23);
以下为new Person() 的过程
1、创建一个空对象
let obj = {};
2、将空对象的原型对象指向构造函数的原型属性。从而继承原型方法。
obj.__proto__ = Person.prototype;
3、执行构造函数 并改变this指向。
let result = Person.call(obj);
4、返回新对象。
return obj;
call,apply,bind用法
1. call,apply,bind的作用
1.都可以改变函数运行时的this指向。
2.call,apply,bind 是挂在 Function 对象上的方法,所以调用这三个方法的必须是一个函数。
2. call,apply,bind区别
1.call()和apply()方法会立即执行此函数,而bind()方法有返回值,通过调用返回值执行函数
2.call()和apply()传参的写法不同,call传递的是参数序列,apply传递的则是已数组形式。
let obj = {
name:"世界",
sayHello(...args){
console.log("你好" + this.name);
console.log(...args);
}
}
let obj1 = {
name:"我的世界"
}
let obj2 = {
name:"完美世界"
}
obj.sayHello(); //你好世界
//call 和 apply 立即执行了sayHello方法
obj.sayHello.call(obj1,1,2,3) //你好我的世界, 1 2 3
obj.sayHello.apply(obj1,[4,5,6]) //你好我的世界, 4 5 6
// 没有执行sayHello方法, 调用bind()方法有返回值, 通过调用返回值执行 sayHello方法
let func = obj.sayHello.bind(obj2,7,8,9)
func(); //你好完美世界 7 8 9
js的事件循环机制 event loop
js是单线程语言,是运行在浏览器的渲染主线程中的,而浏览器的渲染主线程不只解析js,而且还要解析html,css,渲染页面等。
所以渲染主线程解析js过程中,如果js中有一个任务卡死了。那么后面所有的任务都无法被执行。或者有某个任务耗时很长,那么就会导致后面所有的任务都被延迟执行,可能会导致页面卡蹦掉。
所以javascript诞生了两个任务种类:同步任务和异步任务
当js执行到异步任务时,异步任务不会马上执行,而是把异步任务放入任务队列中,先去执行同步任务代码,当同步任务代码执行完成后,再执行异步任务代码。
异步任务分为 宏任务和微任务,在执行异步任务时会优先执行任务队列中的微任务。在所有的微任务执行完毕之后,再去宏任务队列中执行一个宏任务,执行完一个宏任务之后会再去微任务队列中检查是否有新的微任务,有则全部执行,再回到宏任务队列执行下一个宏任务,以此循环(每一个宏任务执行时,都要保证微任务已全部执行完毕)。这一套流程,就是事件循环机制
src和href的区别
1.src 是用于替换当前内容。常用在script标签/img标签,
当浏览器解析到src时 会加载src指定资源并暂停页面对其他资源的下载,
直到src指定资源加载、编译、执行完成后才会继续加载其他资源,
这就是js 脚本放在底部而不是头部的原因。
2.href是用于建立当前页面与引用资源之间的链接。常用与link标签/a标签,
在加载href资源时不会暂停对其他资源的下载