JS有几种基本的数据类型
- undefined
- null
- string
- number
- boolean
- symbol
- bigint
这七种基本数据类型中外的所有其他类型(比如 对象,数组,函数等)都是对象,对象是引用类型,它们存储在内存中,针对基本数据类型我们可以使用typeof操作符来检查数据类型,不过针对null,识别为object,这个是历史遗留的BUG。
基本数据类型都存储的都是值,是没有函数可以调用的,如undefined.toString()
但是'1'.toString()是可以使用的,这种情况其实'1'已经不是原始类型,而被强制转换成了String类型,也就是对象类型,所以可以调用toString()方法
如何判断JS数据类型
typeof
针对基本数据类型(原始类型)来说,除了null,都可以使用typeof 来进行判断,typeof对于对象来说,除了函数都会显示为object,所以如果想判断对象类型,需要通过instanceof。
javascript
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof 1n // bigint
instanceof
instanceof通过原型链的方式来判断是否为构造函数的实例
javascript
const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true
var str = 'hello world'
str instanceof String // false
var str1 = new String('hello world')
str1 instanceof String // true
Object.prototype.toString.call
前两种方式或多或少存在一些缺陷,Object.prototype.toString.call综合来说是最佳选择
scss
const judge = (value) => {
console.log(Object.prototype.toString.call(value) )
}
judge(1) // [object Number]
judge('1')// [object String]
judge(null)// [object Null]
judge(undefined) // [object Undefined]
judge(console.log)// [object Function]
judge(Symbol()) // [object Symbol]
judge(47n)//[object BigInt]
judge({})//[object Object]
judge([1,2,3])//[object Array]
基于上述方法我们可以实现一个类型判断的方法
javascript
const judge = (value) => {
const newValue = Object.prototype.toString.call(value)
console.log( newValue.slice(8, -1))
}
typeof和instanceof的区别
两者都是用来判断数据类型的, 但是它们的使用场景却各不相同
typeof
- typeof是一个一元操作符,放在运算数前面,这个运算数可以是任何类型,它返回一个字符串,说明运算数的类型,
instanceof
- instanceof是一个二元操作符,用于检测一个对象是否是某个构造函数的实例
- 它会检查对象的原型链是否包含指定构造函数的原型
- instanceof仅仅适用于对象,不适用基本数据类型
- instanceof返回的是布尔值,用来检测构造函数的prototype属性是否出现在某个实例对象的原型链上
区别
- typeof会返回一个运算数的基本类型,instanceof返回的是布尔值
- instanceof可以准确判断引用数据类型,但是不能正确判断原始数据类型
- typeof虽然可以判断原型数据类型(null除外),但是无法判断引用数据类型(function除外)
如何判断一个变量是不是Array
- 使用instanceof [1,2,3] instanceof Array (有弊端后续讲)
- isArray Array.isArray(value) // ture or false
- Object.prototype.toString()
instanceof操作符的问题在于,它假定只有一个全局环境,如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array函数,如果你从一个框架向另一个框架传入一个输入,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数
javascript
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
var arr = [1,2,3];
xArray = window.frames[0].Array; //iframe中的构造函数
var arrx = new xArray(4,5,6);
console.log(arrx instanceof Array); //false
console.log(arrx.constructor == Array);// false
console.log(Array.prototype == xArray.prototype); //false
console.log(arr instanceof xArray); //false
console.log(arrx.constructor === Array);// false
console.log(arr.constructor === Array);// true
console.log(arrx.constructor === xArray);// true
console.log(Array.isArray(arrx)); //true
constructor也不能作为判断数组的标准,因为constructor可以被重写
==和===有什么不同
==
- ==用于比较两个值是否相等,在比较之前,会进行类型转换,使得两个值的类型相同
- 如果两个值的类型不同,会尝试将他们转换成相同的类型然后再进行比较
ini
1 == '1'; // true (会将字符串转换为数字再比较)
true == 1; // true (会将布尔值转换为数字再比较)
null == undefined; // true (它们会相等)
===
=== 也用于比较两个值是否相等,但是它不会进行类型转换,只有在类型和值都相等的下才会返回true
ini
1 === '1'; // false (类型不同,不会进行类型转换)
true === 1; // false (类型不同,不会进行类型转换)
null === undefined; // false (类型和值都不相等)
通常情况下,使用===运算符进行比较,因为它可以避免因类型转换导致的一些意外行为
类型转换规则
- 如果两个操作数类型相同,执行严格比较
- 如果两个操作数类型不同,则进行类型转换后再进行比较。规则如下:
-
- 如果一个操作数是数值(number),另一个操作数是字符串,则将字符串转换为数值,然后进行比较
- 如果一个操作数是布尔值,则将布尔值转换为数值,然后进行比较
- 如果一个操作数是对象,另一个操作数是数值或字符串,则将对象转换为原始值,然后进行比较
-
-
- 如果一个操作数是对象,另一个操作数是字符串或者数字,会首先调用对象的valueOf()方法,将对象转化为基本类型,再进行比较
- 当valueOf返回的不是基本类型的时候,才会调用他toString()方法
-
事件机制
JavaScript事件流一共分为三个阶段
- 从window上往事件触发处传播,遇到注册的捕获事件就会触发。
- 传播到事件触发处,触发注册事件。
- 从事件触发处往window上传播,遇到注册的冒泡事件触发。
addEventListener
如果我们在使用addEventListener注册事件时候,没有写第三个参数,那么默认这个事件是冒泡事件,因此在从window往事件触发处的时候,是无法触发捕获事件的。
注册事件和冒泡事件在同一事件中都是可以触发的
如果针对某个元素想要它既要触发捕获事件也要触发冒泡事件,那么在使用addEventListener的时候,需要注册两次
javascript
greenBox.addEventListener('click',()=>{
console.log('greenBox')
}) //冒泡
greenBox.addEventListener('click',()=>{
console.log('greenBox')
},true) //捕获
阻止默认事件
当我们只想触发其中一个或者多个事件,而其他事件不触发时,我们可以使用stopPropagation()和stopImmediatePropagation()函数。
stopPropagation()
和 stopImmediatePropagation()
是用于阻止事件传播的两个方法。
stopPropagation()
:
-
stopPropagation()
是用于阻止事件在 DOM 树中传播的方法,即停止事件的冒泡或捕获阶段的传播。- 当调用
stopPropagation()
后,事件将不再向上冒泡或向下传播,也就是说不会触发父元素或子元素的相同事件。 - 示例:
javascript
element.addEventListener('click', function(event) {
event.stopPropagation(); // 阻止事件传播
});
element.addEventListener('click', function(event) {
console.log('大萨达撒大所大') //该事件还是会触发
});
stopImmediatePropagation()
:
-
stopImmediatePropagation()
也用于阻止事件传播,但它比stopPropagation()
更强大。- 与
stopPropagation()
不同,stopImmediatePropagation()
会阻止当前元素上绑定的其他事件处理程序执行,并且不会继续传播到其他元素。 - 示例:
javascript
Element.addEventListener('click',(event)=>{
event.stopImmediatePropagation()
console.log('pinkBox') //该函数还是会触发
})
Element.addEventListener('click',(event)=>{
console.log('大萨达撒大所大')
})//不会再触发
在上述例子中,如果在同一个元素上绑定了多个点击事件处理程序,调用了 stopImmediatePropagation()
的事件处理程序会立即停止并阻止其他事件处理程序执行。
总的来说,stopPropagation()
和 stopImmediatePropagation()
都是用于控制事件传播的方法,可以根据具体的需求选择使用其中之一。需要注意的是,在使用这两个方法时要小心,确保它们的使用不会影响到其他的事件处理程序的执行。
事件委托
事件委托实现机制就是冒泡事件,通俗的说就是将元素的事件委托给他的父级元素或者更外级的元素处理而不是给每个子元素上添加事件,通过这种方式,可以减少事件处理程序的数量,提高性能,同时也可以处理动态添加或移除子元素
以下是事件委托的基本步骤
- 选择一个共同的父元素(选择一个包含所有需要处理事件的子元素的父元素)
xml
<ul id="parent">
<li><span>Item1</span></li>
<li>Item2</li>
<li>Item3</li>
</ul>
- 在父元素上添加事件监听
xml
<script>
let Ul=document.getElementById('parent')
Ul.addEventListener('click',(event)=>{
if(event.target.tagName === 'LI'){
fn()
}
})
</script>
e.preventDefault()是干什么的
e.preventDefault()是一个在JavaScript事件处理程序中常用方法,它用来阻止事件默认行为。例如:当点击一个链接时,浏览器会导航到链接指定的页面,通过调用e.preventDefault(),可以阻止这种默认行为的发生。
- 阻止链接的跳转
javascript
document.querySelector('a').addEventListener('click', function(e) {
e.preventDefault(); // 阻止链接跳转
});
- 阻止表单的提交
javascript
document.querySelector('form').addEventListener('submit', function(e) {
e.preventDefault(); // 阻止表单提交
});
- 阻止右键菜单的弹出
javascript
document.addEventListener('contextmenu', function(e) {
e.preventDefault(); // 阻止右键菜单弹出
});
- 阻止键盘事件的默认行为
javascript
document.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault(); // 阻止回车键提交表单等默认行为
}
});
JavaScript中的this是如何工作的
this是指当前执行上下文中的对象,this的指向是动态的,取决于函数被调用的方式
- 全局上下文的this
- 在全局上下文中,this指全局对象,浏览器中指window
arduino
console.log(this) // window
- 函数中的this
- 在一个普通函数中,this的值取决于函数如何被调用
- 如果函数是作为全局函数调用的,this将指向全局对象
- 如果函数是作为对象的方法调用的,this将指向调用该方法的对象
javascript
function sayHello() {
console.log(this);
}
sayHello(); // 在浏览器中输出全局对象(window)
const person = {
name: 'John',
sayHello: function() {
console.log(this);
}
};
person.sayHello(); // 输出包含 name 属性的对象
- 构造函数中的this
- 当一个函数被用作构造函数(通过new 关键字调用),this将指向新创建的实例对象
javascript
function Person(name){
this.name = name
}
const john = new Person('John');
console.log(john.name); // 输出 "John"
- 箭头函数中的this
- 箭头函数没有自己的this,它会捕获其所在上下文的this指
- 箭头函数的this,由定义它时的上下文决定,而不是调用时的上下文
ini
const arrowFunction = () => {
console.log(this);
};
arrowFunction(); // 输出定义时的上下文(可能是全局对象)
- 事件处理程序中的this
在事件处理程序中,this通常是指向触发事件的元素
javascript
const button = document.querySelector('button');
button.addEventListener('click', function() {
console.log(this); // 输出触发事件的按钮元素
});
button.addEventListener('click', ()=> {
console.log(this); // window
});
apply,call,bind的区别,以及源码实现
call apply
作用: 用来改变函数内部this的指向
特点: 任何函数都可以调用这两个方法,说明这俩个方法是在函数原型上的方法(Function.prototype),调用call和apply的函数会立即执行,两者的返回值就是函数的返回值
javascript
console.dir(Function.prototype)
- 调用call和apply指向undefined或者null的时候,会将this指向window
- 调用call和apply指向一个类型,会将this指向由它们的构造函数创建的实例
arduino
function foo() {
console.log(this)
}
foo.call(11)//[Number: 11]
foo.call('333')//[String: '333']
foo.apply('444')//[String: '444']
- 调用call和apply指向一个引用类型,会将this指向这个对象
javascript
var name = 'fishfan'
var obj = {
name: 'warbler',
}
function foo() {
console.log(this.name)
}
foo() //=> fishfan
这段代码很好理解,name 等价于 window.name , foo() 等价于 window.foo() ,我们打印出this.name,当前的 this 指向它的调用者 window, 也就是 window.name 得到 一尾流莺。
但是如果我想打印出 warbler 该怎么办呢?在 obj 里面再定义一个 obj.fn 么? 当然不需要, 我们只需要调用 call/apply 改变 this 的指向,指向 obj 这个对象就可以了。这个时候 this.name 等价于 obj.name ,就得到了 warbler 。
call和apply的区别
除了传参形式不同,没有什么区别
- call 接收多个参数,第一个为函数上下文也就是 this ,后边参数为函数本身的参数。
- apply 接收两个参数,第一个参数为函数上下文 this,第二个参数为函数参数只不过是通过一个 数组 的形式传入的。
手写call和apply
ini
Function.prototype.myCall =function (ctx,...args){
ctx = ctx || window;
// 把函数赋值给对象ctx的一个属性 用这个对象ctx去调用函数 this就指向了这个对象
//给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol();
ctx[key] = this;
const result = ctx[key](...args);
delete ctx.fn;
return result;
}
Function.prototype.myApply = function (ctx, args) {
ctx = ctx || window;
// 把函数赋值给对象ctx的一个属性 用这个对象ctx去调用函数 this就指向了这个对象
const key = Symbol();
ctx[key] = this;
const result =ctx[key](...args);
delete ctx.fn;
return result;
}
bind
也是改变函数内部this的指向,但是和call和apply区别
- call/apply改变了函数的this上下文后马上执行该函数
- bind则是返回改变上下文后的函数,不执行该函数
- call/apply返回函数的执行结果
- bind返回函数的拷贝,并指定了fun的this指向,保存了fun的参数。
javascript
var name = 'fishfan'
var obj = {
name: 'warbler',
}
// this 指向调用者document
document.onclick = function() {
console.dir(this); // => #document
}
// this 指向 obj
document.onclick = function() {
console.dir(this); // => #Object{name:'warbler}
}.bind(obj)
手写bind
javascript
Function.prototype.myBind = function (ctx, ...args1){
const self = this;
return function (...args2) {
return self.apply(ctx, args1.concat(args2));
}
}