1. event.preventDefault() 和event.stopPropagation()方法之间有什么区别?
event.preventDefault() 方法可防止元素的默认行为。如果在表单元素中使用,它将阻止其提交。如果在锚元素中使用,它将阻止其导航。如果在上下文菜单中使用,它将阻止其显示或显示。 event.stopPropagation()方法用于阻止捕获和冒泡阶段中当前事件的进一步传播。
2. 如何知道是否在元素中使用event.preventDefault()方法?
我们可以在事件对象中使用event.defaultPrevented属性。它返回一个布尔值用来表明是否在特定元素中调用了event.preventDefault()。
3. 为什么此代码 obj.someprop.x 会引发错误?
const obj = {};
console.log(obj.someprop.x);
显然,由于我们尝试访问someprop属性中的x属性,而 someprop 并没有在对象中,所以值为 undefined。记住对象本身不存在的属性,并且其原型的默认值为undefined。因为undefined没有属性x,所以试图访问将会报错。
4. 什么是 event.target ?
event.target是发生事件的元素或触发事件的元素。
<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,即使我们将事件附加在最外面的div上,它也将打印 button 标签,因此我们可以得出结论event.target是触发事件的元素。
5.什么是 event.currentTarget?
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.currentTarget);
}
6.== 和 === 有什么区别?
==用于一般比较,===用于严格比较,==在比较的时候可以转换数据类型,===严格比较,只要类型不匹配就返回flase。
先来看看 == 这兄弟:
强制是将值转换为另一种类型的过程。在这种情况下,==会执行隐式强制。在比较两个值之前,==需要执行一些规则。
假设我们要比较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
注意:toPrimitive首先在对象中使用valueOf方法,然后使用toString方法来获取该对象的原始值。
7. 为什么在 JS 中比较两个相似的对象时返回 false?
let a = { a: 1 };
let b = { a: 1 };
let c = a;
console.log(a === b); // 打印 false,即使它们有相同的属性
console.log(a === c); // true
JS 以不同的方式比较对象和基本类型。在基本类型中,JS 通过值对它们进行比较,而在对象中,JS 通过引用或存储变量的内存中的地址对它们进行比较。这就是为什么第一个console.log语句返回false,而第二个console.log语句返回true。a和c有相同的引用地址,而a和b没有。
8.!! 运算符能做什么?
!!运算符可以将右侧的值强制转换为布尔值,这也是将值转换为布尔值的一种简单方法。
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
9. 如何在一行中计算多个表达式的值?
可以使用逗号运算符在一行中计算多个表达式。它从左到右求值,并返回右边最后一个项目或最后一个操作数的值。
let x = 5;
x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10);
function addFive(num) {
return num + 5;
}
//上面的结果最后得到x的值为27。首先,我们将x的值增加到6,然后调用函数addFive(6)并将6作为参数传递并将结果重新分配给x,此时x的值为11。之后,将x的当前值乘以2并将其分配给x,x的更新值为22。然后,将x的当前值减去5并将结果分配给x x更新后的值为17。最后,我们将x的值增加10,然后将更新的值分配给x,最终x的值为27。
10.什么是提升?
提升是用来描述变量和函数移动到其(全局或函数)作用域顶部的术语。
为了理解提升,需要来了解一下执行上下文。执行上下文是当前正在执行的“代码环境”。执行上下文有两个阶段:编译和执行。
编译-在此阶段,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"));
11. 什么是作用域?
JavaScript 中的作用域是我们可以有效访问变量或函数的区域。JS 有三种类型的作用域:全局作用域、函数作用域和块作用域(ES6)。
全局作用域——在全局命名空间中声明的变量或函数位于全局作用域中,因此在代码中的任何地方都可以访问它们。
//global namespace
var g = "global";
function globalFunc(){
function innerFunc(){
console.log(g); // can access "g" because "g" is a global variable
}
innerFunc();
}
函数作用域——在函数中声明的变量、函数和参数可以在函数内部访问,但不能在函数外部访问。
function myFavoriteFunc(a) {
if (true) {
var b = "Hello " + a;
}
return b;
}
myFavoriteFunc("World");
console.log(a); // Throws a ReferenceError "a" is not defined
console.log(b); // does not continue here
块作用域-在块{}中声明的变量(let,const)只能在其中访问。
f
function testBlock(){
if(true){
let z = 5;
}
return z;
}
testBlock(); // Throws a ReferenceError "z" is not defined
作用域也是一组用于查找变量的规则。如果变量在当前作用域中不存在,它将向外部作用域中查找并搜索,如果该变量不存在,它将再次查找直到到达全局作用域,如果找到,则可以使用它,否则引发错误,这种查找过程也称为作用域链。
/* 作用域链
内部作用域->外部作用域-> 全局作用域
*/
// 全局作用域
var variable1 = "Comrades";
var variable2 = "Sayonara";
function outer(){
// 外部作用域
var variable1 = "World";
function inner(){
// 内部作用域
var variable2 = "Hello";
console.log(variable2 + " " + variable1);
}
inner();
}
outer(); // Hello World
12.JavaScript 中的虚值是什么?
const falsyValues = ['', 0, null, undefined, NaN, false];
简单的来说虚值就是是在转换为布尔值时变为 false 的值。
13. 如何检查值是否虚值?
使用 Boolean 函数或者 !! 运算符。
14.‘use strict’ 是干嘛用的?
“use strict” 是 ES5 特性,它使我们的代码在函数或整个脚本中处于严格模式。严格模式帮助我们在代码的早期避免 bug,并为其添加限制。
严格模式的一些限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错\
- 不能使用with语句
- 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
- eval不能在它的外层作用域引入变量
- eval和arguments不能被重新赋值
- arguments不会自动反映函数参数的变化
- 不能使用arguments.callee
- 不能使用arguments.caller
- 禁止this指向全局对象
- 能使用fn.caller和fn.arguments获取函数调用的堆栈
- 增加了保留字(比如protected、static和interface)
设立”严格模式”的目的,主要有以下几个:
消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
消除代码运行的一些不安全之处,保证代码运行的安全;
提高编译器效率,增加运行速度;
为未来新版本的Javascript做好铺垫。
14.对象的 prototype(原型) 是什么?
简单地说,原型就是对象的蓝图。如果它存在当前对象中,则将其用作属性和方法的回退。它是在对象之间共享属性和功能的方法,这也是JavaScript实现继承的核心。
const o = {};
console.log(o.toString()); // logs [object Object]
即使o对象中不存在o.toString方法,它也不会引发错误,而是返回字符串[object Object]。当对象中不存在属性时,它将查看其原型,如果仍然不存在,则将其查找到原型的原型,依此类推,直到在原型链中找到具有相同属性的属性为止。原型链的末尾是Object.prototype。
console.log(o.toString === Object.prototype.toString); // logs true
// which means we we're looking up the Prototype Chain and it reached
// the Object.prototype and used the "toString" method.
14.Function.prototype.apply 方法的用途是什么?
apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数
const details = {
message: 'Hello World!'
};
function getMessage(){
return this.message;
}
getMessage.apply(details); // 'Hello World!'
call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
const person = {
name: "Marko Polo"
};
function greeting(greetingMessage) {
return `${greetingMessage} ${this.name}`;
}
greeting.apply(person, ['Hello']); // "Hello Marko Polo!"
15.Function.prototype.call 方法的用途是什么?
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
const details = {
message: 'Hello World!'
};
function getMessage(){
return this.message;
}
getMessage.call(details); // 'Hello World!'
注意:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。
const person = {
name: "Marko Polo"
};
function greeting(greetingMessage) {
return `${greetingMessage} ${this.name}`;
}
greeting.call(person, 'Hello'); // "Hello Marko Polo!"
16.Function.prototype.apply 和 Function.prototype.call 之间有什么区别?
apply()方法可以在使用一个指定的 this 值和一个参数数组(或类数组对象)的前提下调用某个函数或方法。call()方法类似于apply(),不同之处仅仅是call()接受的参数是参数列表。
const obj1 = {
result:0
};
const obj2 = {
result:0
};
function reduceAdd(){
let result = 0;
for(let i = 0, len = arguments.length; i < len; i++){
result += arguments[i];
}
this.result = result;
}
reduceAdd.apply(obj1, [1, 2, 3, 4, 5]); // 15
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // 15
17.Function.prototype.bind 的用途是什么?
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
import React from 'react';
class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {
value : ""
}
this.handleChange = this.handleChange.bind(this);
// 将 “handleChange” 方法绑定到 “MyComponent” 组件
}
handleChange(e){
//do something amazing here
}
render(){
return (
<>
<input type={this.props.type}
value={this.state.value}
onChange={this.handleChange}
/>
</>
)
}
}
18.什么是函数式编程? JavaScript 的哪些特性使其成为函数式语言的候选语言?
函数式编程(通常缩写为FP)是通过编写纯函数,避免共享状态、可变数据、副作用 来构建软件的过程。数式编程是声明式 的而不是命令式 的,应用程序的状态是通过纯函数流动的。与面向对象编程形成对比,面向对象中应用程序的状态通常与对象中的方法共享和共处。
函数式编程是一种编程范式 ,这意味着它是一种基于一些基本的定义原则(如上所列)思考软件构建的方式。当然,编程范示的其他示例也包括面向对象编程和过程编程。
函数式的代码往往比命令式或面向对象的代码更简洁,更可预测,更容易测试 - 但如果不熟悉它以及与之相关的常见模式,函数式的代码也可能看起来更密集杂乱,并且 相关文献对新人来说是不好理解的。
JavaScript支持闭包和高阶函数是函数式编程语言的特点。
19. 什么是高阶函数?
高阶函数只是将函数作为参数或返回值的函数。
function higherOrderFunction(param,callback){
return callback(param);
}
20.为什么函数被称为一等公民?
在JavaScript中,函数不仅拥有一切传统函数的使用方式(声明和调用),而且可以做到像简单值一样赋值(var func = function(){})、传参(function func(x,callback){callback();})、返回(function(){return function(){}}),这样的函数也称之为第一级函数(First-class Function)。不仅如此,JavaScript中的函数还充当了类的构造函数的作用,同时又是一个Function类的实例(instance)。这样的多重身份让JavaScript的函数变得非常重要。
21.手动实现 Array.prototype.map 方法
map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
function map(arr, mapCallback) {
// 首先,检查传递的参数是否正确。
if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') {
return [];
} else {
let result = [];
// 每次调用此函数时,我们都会创建一个 result 数组
// 因为我们不想改变原始数组。
for (let i = 0, len = arr.length; i < len; i++) {
result.push(mapCallback(arr[i], i, arr));
// 将 mapCallback 返回的结果 push 到 result 数组中
}
return result;
}
}
22.手动实现Array.prototype.filter方法
filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
function filter(arr, filterCallback) {
// 首先,检查传递的参数是否正确。
if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function')
{
return [];
} else {
let result = [];
// 每次调用此函数时,我们都会创建一个 result 数组
// 因为我们不想改变原始数组。
for (let i = 0, len = arr.length; i < len; i++) {
// 检查 filterCallback 的返回值是否是真值
if (filterCallback(arr[i], i, arr)) {
// 如果条件为真,则将数组元素 push 到 result 中
result.push(arr[i]);
}
}
return result; // return the result array
}
23.手动实现Array.prototype.reduce方法
reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
function reduce(arr, reduceCallback, initialValue) {
// 首先,检查传递的参数是否正确。
if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function')
{
return [];
} else {
// 如果没有将initialValue传递给该函数,我们将使用第一个数组项作为initialValue
let hasInitialValue = initialValue !== undefined;
let value = hasInitialValue ? initialValue : arr[0];
、
// 如果有传递 initialValue,则索引从 1 开始,否则从 0 开始
for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
value = reduceCallback(value, arr[i], i, arr);
}
return value;
}
}
24.arguments 的对象是什么?
arguments对象是函数中传递的参数值的集合。它是一个类似数组的对象,因为它有一个length属性,我们可以使用数组索引表示法arguments[1]来访问单个值,但它没有数组中的内置方法,如:forEach、reduce、filter和map。
我们可以使用Array.prototype.slice将arguments对象转换成一个数组。
function one() {
return Array.prototype.slice.call(arguments);
}
注意:箭头函数中没有arguments对象。
function one() {
return arguments;
}
const two = function () {
return arguments;
}
const three = function three() {
return arguments;
}
const four = () => arguments;
four(); // Throws an error - arguments is not defined
当我们调用函数four时,它会抛出一个ReferenceError: arguments is not defined error。使用rest语法,可以解决这个问题。
const four = (...args) => args;
这会自动将所有参数值放入数组中。
25.如何创建一个没有 prototype(原型)的对象?
const o1 = {};
console.log(o1.toString()); // [object Object]
const o2 = Object.create(null);
console.log(o2.toString());
// throws an error o2.toString is not a function
26.为什么在调用这个函数时,代码中的b会变成一个全局变量?
function myFunc() {
let a = b = 0;
}
myFunc();
原因是赋值运算符是从右到左的求值的。这意味着当多个赋值运算符出现在一个表达式中时,它们是从右向左求值的。所以上面代码变成了这样:
function myFunc() {
let a = (b = 0);
}
myFunc();
首先,表达式b = 0求值,在本例中b没有声明。因此,JS引擎在这个函数外创建了一个全局变量b,之后表达式b = 0的返回值为0,并赋给新的局部变量a。
我们可以通过在赋值之前先声明变量来解决这个问题。
function myFunc() {
let a,b;
a = b = 0;
}
myFunc();
27.ECMAScript 是什么?
ECMAScript 是编写脚本语言的标准,这意味着JavaScript遵循ECMAScript标准中的规范变化,因为它是JavaScript的蓝图。
ECMAScript 和 Javascript,本质上都跟一门语言有关,一个是语言本身的名字,一个是语言的约束条件
只不过发明JavaScript的那个人(Netscape公司),把东西交给了ECMA(European Computer Manufacturers Association),这个人规定一下他的标准,因为当时有java语言了,又想强调这个东西是让ECMA这个人定的规则,所以就这样一个神奇的东西诞生了,这个东西的名称就叫做ECMAScript。
javaScript = ECMAScript + DOM + BOM(自认为是一种广义的JavaScript)
ECMAScript说什么JavaScript就得做什么!
JavaScript(狭义的JavaScript)做什么都要问问ECMAScript我能不能这样干!如果不能我就错了!能我就是对的!
——突然感觉JavaScript好没有尊严,为啥要搞个人出来约束自己,
那个人被创造出来也好委屈,自己被创造出来完全是因为要约束JavaScript。
28.ES6或ECMAScript 2015有哪些新特性?
箭头函数
类
模板字符串
加强的对象字面量
对象解构
Promise
生成器
模块
Symbol
代理
Set
函数默认参数
rest 和展开
块作用域
29.var,let和const的区别是什么?
var声明的变量会挂载在window上,而let和const声明的变量不会:
var a = 100;
console.log(a,window.a); // 100 100
let b = 10;
console.log(b,window.b); // 10 undefined
const c = 1;
console.log(c,window.c); // 1 undefined
var声明变量存在变量提升,let和const不存在变量提升:
console.log(a); // undefined ===> a已声明还没赋值,默认得到undefined值
var a = 100;
console.log(b); // 报错:b is not defined ===> 找不到b这个变量
let b = 10;
console.log(c); // 报错:c is not defined ===> 找不到c这个变量
const c = 10;
let和const声明形成块作用域
if(1){
var a = 100;
let b = 10;
}
console.log(a); // 100
console.log(b) // 报错:b is not defined ===> 找不到b这个变量
-------------------------------------------------------------
if(1){
var a = 100;
const c = 1;
}
console.log(a); // 100
console.log(c) // 报错:c is not defined ===> 找不到c这个变量
同一作用域下let和const不能声明同名变量,而var可以
var a = 100;
console.log(a); // 100
var a = 10;
console.log(a); // 10
-------------------------------------
let a = 100;
let a = 10;
// 控制台报错:Identifier 'a' has already been declared ===> 标识符a已经被声明了。
暂存死区
var a = 100;
if(1){
a = 10;
//在当前块作用域中存在a使用let/const声明的情况下,给a赋值10时,只会在当前作用域找变量a,
// 而这时,还未到声明时候,所以控制台Error:a is not defined
let a = 1;
}
const
/*
* 1、一旦声明必须赋值,不能使用null占位。
*
* 2、声明后不能再修改
*
* 3、如果声明的是复合类型数据,可以修改其属性
*
* */
const a = 100;
const list = [];
list[0] = 10;
console.log(list); // [10]
const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj); // {a:10000,name:'apple'}
30.什么是箭头函数?
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
//ES5 Version
var getCurrentDate = function (){
return new Date();
}
//ES6 Version
const getCurrentDate = () => new Date();
在本例中,ES5 版本中有function(){}声明和return关键字,这两个关键字分别是创建函数和返回值所需要的。在箭头函数版本中,我们只需要()括号,不需要 return 语句,因为如果我们只有一个表达式或值需要返回,箭头函数就会有一个隐式的返回。
//ES5 Version
function greet(name) {
return 'Hello ' + name + '!';
}
//ES6 Version
const greet = (name) => `Hello ${name}`;
const greet2 = name => `Hello ${name}`;
我们还可以在箭头函数中使用与函数表达式和函数声明相同的参数。如果我们在一个箭头函数中有一个参数,则可以省略括号。
const getArgs = () => arguments
const getArgs2 = (...rest) => rest
箭头函数不能访问arguments对象。所以调用第一个getArgs函数会抛出一个错误。相反,我们可以使用rest参数来获得在箭头函数中传递的所有参数。
const data = {
result: 0,
nums: [1, 2, 3, 4, 5],
computeResult() {
// 这里的“this”指的是“data”对象
const addAll = () => {
return this.nums.reduce((total, cur) => total + cur, 0)
};
this.result = addAll();
}
};
箭头函数没有自己的this值。它捕获词法作用域函数的this值,在此示例中,addAll函数将复制computeResult 方法中的this值,如果我们在全局作用域声明箭头函数,则this值为 window 对象。