数据类型
null 和 undefined
== 和 ===
JS 比较对象和基本类型
!! 运算符
JavaScript 中的虚值
一行中计算多个表达式的值
作用域
作用域链
什么是提升
this指针
IIFE
new操作符
事件流
addEventListener
event.preventDefault() 和 event.stopPropagation()
event.target 和 event.currentTarget
闭包 Closure
- JavaScript是单线程的语言
-
数据类型
基本数据类型:Number
、String
、Boolean
、Null
、Undefined
引用数据类型:Object
是 JavaScript 中所有对象的父对象
新类型:Symbol
和bigInt
基本数据类型存储在栈中,占据空间小、大小固定,属于被频繁使用数据;
引用数据类型存储在堆中,指针放在栈中。占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
-
null 和 undefined
相似处 -
- 属于JS基本类型。
let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint'];
- 是虚值,可以使用
Boolean(value)
或!!value
将其转换为布尔值时,值为false。
console.log(!!null); // false
console.log(!!undefined); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
区别 -
null
是“不代表任何值的值”。null是已明确定义给变量的值。表示一个对象被定义了,但存放了空指针,转换为数值时为0
。
undefined
是未指定特定值的变量的默认值,或者没有显式返回值的函数,如:console.log(1)
,还包括对象中不存在的属性,这些 JS 引擎都会为其分配 undefined
值。,转换为数值时为NAN
。
typeof(null) -- object;
typeof(undefined) -- undefined
console.log(null == undefined); // true
console.log(null === undefined); // false
-
== 和 ===
==用于一般比较,会执行隐式强制转换数据类型。
===用于严格比较,只要类型不匹配就返回false。
假设我们要比较x == y的值。
如果x和y的类型相同,则 JS 会换成===操作符进行比较。
如果x为null, y为undefined,则返回true。
如果x为undefined且y为null,则返回true。
如果x的类型是number, y的类型是string,那么返回x == toNumber(y)。
如果x的类型是string, y的类型是number,那么返回toNumber(x) == y。
如果x为类型是boolean,则返回toNumber(x)== y。
如果y为类型是boolean,则返回x == toNumber(y)。
如果x是string、symbol或number,而y是object类型,则返回x == toPrimitive(y)。
如果x是object,y是string,symbol则返回toPrimitive(x) == y。
剩下的 返回 false
-
JS 比较对象和基本类型
在基本类型中,JS 通过值对它们进行比较,而在对象中,JS 通过引用或存储变量的内存中的地址对它们进行比较。
let a = { a: 1 };
let b = { a: 1 };
let c = a;
console.log(a === b); // 打印 false,即使它们有相同的属性
console.log(a === c); // true
-
!! 运算符
!!
运算符可以将右侧的值强制转换为布尔值,这也是将值转换为布尔值的一种简单方法。
console.log(!!null); // false
console.log(!!undefined); // false
console.log(!!''); // false
console.log(!!0); // false
console.log(!!NaN); // false
console.log(!!' '); // true
console.log(!!{}); // true
console.log(!![]); // true
console.log(!!1); // true
console.log(!![].length); // false
- JavaScript 中的虚值
const falsyValues = ['', 0, null, undefined, NaN, false];
简单的来说虚值就是是在转换为布尔值时(使用 Boolean 函数或者 !! 运算符)变为 false 的值。
-
一行中计算多个表达式的值
使用逗号运算符在一行中计算多个表达式。它从左到右求值,并返回右边最后一个项目或最后一个操作数的值。
let x = 5;
x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10);
function addFive(num) {
return num + 5;
}
-
作用域
JS 有三种类型的作用域:全局作用域、函数作用域和块作用域(ES6)。
- 全局作用域——在全局命名空间中声明的变量或函数位于全局作用域中,因此在代码中的任何地方都可以访问它们。
- 函数作用域——在函数中声明的变量、函数和参数可以在函数内部访问,但不能在函数外部访问。
- 块作用域-在块{}中声明的变量(
let
,const
)只能在其中访问。
-
作用域链
作用域也是一组用于查找变量的规则。如果变量在当前作用域中不存在,它将向外部作用域中查找并搜索,如果该变量不存在,它将再次查找直到到达全局作用域,如果找到,则可以使用它,否则引发错误,这种查找过程也称为作用域链。
-
什么是提升
执行上下文是当前正在执行的“代码环境”。执行上下文有两个阶段:编译和执行。
编译-在此阶段,JS 引荐获取所有函数声明并将其提升到其作用域的顶部,以便我们稍后可以引用它们并获取所有变量声明(使用var关键字进行声明),还会为它们提供默认值:undefined。
执行——在这个阶段中,它将值赋给之前提升的变量,并执行或调用函数(对象中的方法)。
注意:只有使用var声明的变量,或者函数声明才会被提升,相反,函数表达式或箭头函数,let和const声明的变量,这些都不会被提升。
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
function greet(name){
return 'Hello ' + name + '!';
}
var y;
上面分别打印:undefined,1, Hello Mark!
上面代码在编译阶段其实是这样的:
function greet(name) {
return 'Hello ' + name + '!';
}
var y; // 默认值 undefined
// 等待“编译”阶段完成,然后开始“执行”阶段
/*
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
*/
编译阶段完成后,它将启动执行阶段调用方法,并将值分配给变量。
function greet(name) {
return 'Hello ' + name + '!';
}
var y;
//start "execution" phase
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
- this指针
-
this
总是指向函数的直接调用者(而非间接调用者) - 如果有
new
关键字,this
指向new
出来的那个对象 - 在事件中,
this
指向目标元素,特殊的是IE的attachEvent中的this
总是指向全局对象window
。
this
指的是当前正在执行或调用该函数的对象的值。this
值的变化取决于我们使用它的上下文和我们在哪里使用它。
const carDetails = {
name: "Ford Mustang",
yearBought: 2005,
getName(){
return this.name;
},
isRegistered: true
};
console.log(carDetails.getName()); // Ford Mustang
这通常是我们期望结果的,因为在getName
方法中我们返回this.name
,在此上下文中,this
指向的是carDetails
对象,该对象当前是执行函数的“所有者”对象。
接下我们做些奇怪的事情:
var name = "Ford Ranger";
var getCarName = carDetails.getName;
console.log(getCarName()); // Ford Ranger
在全局作用域中使用var
关键字声明变量会在window
对象中附加与变量名称相同的属性。
解决这个问题的一种方法是在函数中使用apply
和call
方法。apply
和call
方法期望第一个参数是一个对象,该对象是函数内部this
的值。
console.log(getCarName.apply(carDetails)); // Ford Mustang
console.log(getCarName.call(carDetails)); // Ford Mustang
-
IIFE
IIFE或立即调用的函数表达式是在创建或声明后将被调用或执行的函数。
(function(){
...
} ());
(function () {
...
})();
(function named(params) {
...
})();
(() => {
});
(function (global) {
...
})(window);
const utility = (function () {
return {
...
}
})
经典面试题目:
var li = document.querySelectorAll('.list-group > li');
for (var i = 0, len = li.length; i < len; i++) {
(function (currentIndex) {
li[currentIndex].addEventListener('click', function (e) {
console.log(currentIndex);
})
})(i);
}
IIFE会为每次迭代创建一个新的作用域,我们捕获i
的值并将其传递给currentIndex
参数,因此调用IIFE时,每次迭代的currentIndex
值都是不同的。
- new操作符
- 创建一个空对象,并且
this
变量引用该对象,同时还继承了该函数的原型。 - 属性和方法被加入到
this
引用的对象中。 - 新创建的对象由
this
所引用,并且最后隐式的返回this
。
-
事件流
事件流分为三个阶段,捕获节点 ->目标节点 -> 冒泡
捕获节点 是从根节点(window
)开始执行,一直往子节点查找执行,直到查找执行到目标节点;
冒泡 是从目标节点开始执行,一直往父节点冒泡查找执行,直到查到根节点(window
)。
-
addEventListener
具有第三个可选参数useCapture,其默认值为false,事件将在冒泡阶段中发生,如果为true,则事件将在捕获阶段中发生。
el.addEventListener(event, callback, isCapture);
-
event.preventDefault() 和 event.stopPropagation()
event.preventDefault()
方法可防止元素的默认行为。如果在表单元素中使用,它将阻止其提交。如果在锚元素中使用,它将阻止其导航。如果在上下文菜单中使用,它将阻止其显示或显示。event.stopPropagation()
方法用于阻止捕获和冒泡阶段中当前事件的进一步传播。
我们可以在事件对象中使用event.defaultPrevented
属性。它返回一个布尔值用来表明是否在特定元素中调用了event.preventDefault()
。
-
event.target 和 event.currentTarget
event.target
是发生事件的元素或触发事件的元素
event.currentTarget
是我们在其上显式附加事件处理程序的元素
<div onclick="clickFunc(event)" style="text-align: center;margin:15px;
border:1px solid red;border-radius:3px;">
<div style="margin: 25px; border:1px solid royalblue;border-radius:3px;">
<div style="margin:25px;border:1px solid skyblue;border-radius:3px;">
<button style="margin:10px">
Button
</button>
</div>
</div>
</div>
function clickFunc(event) {
console.log(event.target);// 打印 button 标签
console.log(event.currentTarget);// 打印最外面的div标签
}
-
闭包 Closure
JS Closure
闭包指的是一个函数可以访问另一个函数作用域中变量。
常见的构造方法,是在一个函数内部定义另外一个函数。内部函数可以引用外层的变量;外层变量不会被垃圾回收机制回收。
注意,闭包的原理是作用域链,所以闭包访问的上级作用域中的变量是个对象,其值为其运算结束后的最后一个值。
优点:避免全局变量污染。缺点:容易造成内存泄漏。