【JS】面试题 1

  1. document load 和 document ready 的区别?
  2. JavaScript 中如何检测一个变量是一个 String 类型?
  3. 请用 js 去除字符串空格?
  4. 你对JavaScript的理解?怎样的语言?什么特点?
  5. == 和 === 的不同?
  6. 怎样添加、移除、移动、复制、创建和查找节点?
  7. 事件委托是什么?
  8. require与import区别?
  9. javascript 对象的几种创建方式
  10. JavaScript 继承的方式和优缺点
  11. 什么是原型链
  12. 复杂数据类型如何转变为字符串
  13. JavaScript的typeof返回哪些数据类型
  14. 在css/js代码上线后,开发人员会优化性能,从用户刷新网页开始,一次js请求一般情况下有哪些地方会有缓存处理?
  15. 列举3种强制类型转换和两种隐式类型转换
  16. 你对闭包的理解,优缺点
  17. 如何判断NaN
  18. new一个对象的过程中发生了什么?
  19. for in 和 for of
  20. 如何判断js变量类型

1.document load 和 document ready 的区别

页面加载完成有两种事件

  1. load是当页面所有资源全部加载完成后(包括DOM文档树,css文件,js文件,图片资源等),执行一个函数
    问题:如果图片资源较多,加载时间较长,onload后等待执行的函数需要等待较长时间,所以一些效果可能受到影响

  2. $(document).ready()是当DOM文档树加载完成后执行一个函数 (不包含图片,css等)所以会比load较快执行
    在原生的jS中不包括ready()这个方法,只有load方法就是onload事件

2. JavaScript 中如何检测一个变量是一个 String 类型?

https://blog.csdn.net/zjy_android_blog/article/details/81023177

三种方法(typeof、constructor、Object.prototype.toString.call())

①typeof
typeof('123') === "string" // true
typeof '123' === "string" // true
②constructor
'123'.constructor === String // true
③Object.prototype.toString.call()
Object.prototype.toString.call('123') === '[object String]' // true

3.请用 js 去除字符串空格?

  • replace 正则匹配方法、
  • str.trim()方法、
  • JQ 方法:$.trim(str)方法

解析:

方法一:replace 正则匹配方法
  • 去除字符串内所有的空格:str = str.replace(/\s*/g,"");
  • 去除字符串内两头的空格:str = str.replace(/^\s|\s$/g,"");
  • 去除字符串内左侧的空格:str = str.replace(/^\s*/,"");
  • 去除字符串内右侧的空格:str = str.replace(/(\s*$)/g,"");
    示例:
var str = " 6 6 ";
var str_1 = str.replace(/\s*/g, "");
console.log(str_1); //66

var str = " 6 6 ";
var str_1 = str.replace(/^\s*|\s*$/g, "");
console.log(str_1); //6 6//输出左右侧均无空格

var str = " 6 6 ";
var str_1 = str.replace(/^\s*/, "");
console.log(str_1); //6 6 //输出右侧有空格左侧无空格

var str = " 6 6 ";
var str_1 = str.replace(/(\s*$)/g, "");
console.log(str_1); // 6 6//输出左侧有空格右侧无空格

方法二:str.trim()方法

trim()方法是用来删除字符串两端的空白字符并返回,trim 方法并不影响原来的字符串本身,它返回的是一个新的字符串。缺陷:只能去除字符串两端的空格,不能去除中间的空格
示例:

var str = " 6 6 ";
var str_1 = str.trim();
console.log(str_1); //6 6//输出左右侧均无空格
方法三:JQ 方法:$.trim(str)方法

.trim() 函数会移除字符串开始和末尾处的所有换行符,空格(包括连续的空格)和制表符。如果这些空白字符在字符串中间时,它们将被保留,不会被移除。
示例:

var str = " 6 6 ";
var str_1 = $.trim(str);
console.log(str_1); //6 6//输出左右侧均无空格

4. js 是一门怎样的语言,它有什么特点

  • 1.脚本语言。
    JavaScript 是一种解释型的脚本语言,C、C++等语言先编译后执行,而 JavaScript 是在程序的运行过程中逐行进行解释。

  • 2.基于对象。
    JavaScript 是一种基于对象的脚本语言,它不仅可以创建对象,也能使用现有的对象。

  • 3.简单。
    JavaScript 语言中采用的是弱类型的变量类型,对使用的数据类型未做出严格的要求,是基于 Java 基本语句和控制的脚本语言,其设计简单紧凑。

  • 4.动态性。
    JavaScript 是一种采用事件驱动的脚本语言,它不需要经过 Web 服务器就可以对用户的输入做出响应。

  • 5.跨平台性。
    JavaScript 脚本语言不依赖于操作系统,仅需要浏览器的支持。

5. == 和 === 的不同

  • ==是抽象相等运算符(值),而===是严格相等运算符(地址)。

  • ==运算符是在进行必要的类型转换后,再比较。===运算符不会进行类型转换,所以如果两个值不是相同的类型,会直接返回false。使用==时,可能发生一些特别的事情,
    例如:

1 == "1"; // true
1 == [1]; // true
1 == true; // true
0 == ""; // true
0 == "0"; // true
0 == false; // true

注:如果你对==和===的概念不是特别了解,建议大多数情况下使用===

6. 怎样添加、移除、移动、复制、创建和查找节点?

  • 创建新节点
createDocumentFragment() //创建一个 DOM 片段
createElement() //创建一个具体的元素
createTextNode() //创建一个文本节点
  • 添加、移除、替换、插入
appendChild() //添加
removeChild() //移除
replaceChild() //替换
insertBefore() //插入
  • 查找
getElementsByTagName() //通过标签名称
getElementsByName() //通过元素的 Name 属性的值
getElementById() //通过元素 Id,唯一性

7. 事件委托是什么?

利用事件冒泡的原理,让自己的所触发的事件,让他的父元素代替执行!解析:

  • 1、那什么样的事件可以用事件委托,什么样的事件不可以用呢?

适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。
值得注意的是,mouseover 和 mouseout 虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。
不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,非常不好把控,在不如说 focus,blur 之类的,本身就没用冒泡的特性,自然就不用事件委托了。

  • 2、为什么要用事件委托
1.提高性能
‹ul›
  ‹li›苹果‹/li›
  ‹li›香蕉‹/li›
  ‹li›凤梨‹/li›
‹/ul›

// good
document.querySelector('ul').onclick = (event) =› {
  let target = event.target
  if (target.nodeName === 'LI') {
    console.log(target.innerHTML)
  }
}

// bad
document.querySelectorAll('li').forEach((e) =› {
  e.onclick = function() {
    console.log(this.innerHTML)
  }
})

2.新添加的元素还会有之前的事件。
3、事件冒泡与事件委托的对比

事件冒泡:box 内部无论是什么元素,点击后都会触发 box 的点击事件
事件委托:可以对 box 内部的元素进行筛选

4、事件委托怎么取索引?
    ‹ul id="ul"›
        ‹li›aaaaaaaa‹/li›
        ‹li›事件委托了 点击当前,如何获取 这个点击的下标‹/li›
        ‹li›cccccccc‹/li›
    ‹/ul›
    ‹script›
        window.onload = function () {
            var oUl = document.getElementById("ul");
            var aLi = oUl.getElementsByTagName("li");
            oUl.onclick = function (ev) {
                var ev = ev || window.event;
                var target = ev.target || ev.srcElement;
                if (target.nodeName.toLowerCase() == "li") {
                    var that = target;
                    var index;
                    for (var i = 0; i ‹ aLi.length; i++)
                        if (aLi[i] === target) index = i;
                    if (index ›= 0) alert('我的下标是第' + index + '个');
                    target.style.background = "red";
                }
            }
        }
    ‹/script›

拓展:

键盘事件:keydown keypress keyup
鼠标事件:mousedown mouseup mousemove mouseout mouseover

8. javascript 对象的几种创建方式

  • 第一种:Object 构造函数创建
var Person = new Object();
Person.name = "Nike";
Person.age = 29;

这行代码创建了 Object 引用类型的一个新实例,然后把实例保存在变量 Person 中。

  • 第二种:使用对象字面量表示法
var Person = {}; //相当于 var Person = new Object();
var Person = {
    name: 'Nike';
    age: 29;
}

对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。也就是说,第一种和第二种方式创建对象的方法其实都是一样的,只是写法上的区别不同在介绍第三种的创建方法之前,我们应该要明白为什么还要用别的方法来创建对象,也就是第一种,第二种方法的缺点所在:它们都是用了同一个接口创建很多对象,会产生大量的重复代码,就是如果你有 100 个对象,那你要输入 100 次很多相同的代码。那我们有什么方法来避免过多的重复代码呢,就是把创建对象的过程封装在函数体内,通过函数的调用直接生成对象。

  • 第三种:使用工厂模式创建对象
function createPerson(name, age, job) {
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = function() {
    alert(this.name);
  };
  return o;
}
var person1 = createPerson("Nike", 29, "teacher");
var person2 = createPerson("Arvin", 20, "student");

在使用工厂模式创建对象的时候,我们都可以注意到,在 createPerson 函数中,返回的是一个对象。那么我们就无法判断返回的对象究竟是一个什么样的类型。于是就出现了第四种创建对象的模式。

  • 第四种:使用构造函数创建对象
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = function() {
    alert(this.name);
  };
}
var person1 = new Person("Nike", 29, "teacher");
var person2 = new Person("Arvin", 20, "student");

对比工厂模式,我们可以发现以下区别:
1.没有显示地创建对象
2.直接将属性和方法赋给了 this 对象
3.没有 return 语句
4.终于可以识别的对象的类型。对于检测对象类型,我们应该使用 instanceof 操作符,我们来进行自主检测:

alert(person1 instanceof Object); //ture
alert(person1 instanceof Person); //ture
alert(person2 instanceof Object); //ture
alert(person2 instanceof Object); //ture

同时我们也应该明白,按照惯例,构造函数始终要应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。那么构造函数确实挺好用的,但是它也有它的缺点:就是每个方法都要在每个实例上重新创建一遍,方法指的就是我们在对象里面定义的函数。如果方法的数量很多,就会占用很多不必要的内存。于是出现了第五种创建对象的方法

  • 第五种:原型创建对象模式
function Person() {}
Person.prototype.name = "Nike";
Person.prototype.age = 20;
Person.prototype.jbo = "teacher";
Person.prototype.sayName = function() {
  alert(this.name);
};
var person1 = new Person();
person1.sayName();

使用原型创建对象的方式,可以让所有对象实例共享它所包含的属性和方法。如果是使用原型创建对象模式,请看下面代码:

function Person() {}
Person.prototype.name = "Nike";
Person.prototype.age = 20;
Person.prototype.jbo = "teacher";
Person.prototype.sayName = function() {
  alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); //'Greg' --来自实例
alert(person2.name); //'Nike' --来自原型

当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。这时候我们就可以使用构造函数模式与原型模式结合的方式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性

  • 第六种:组合使用构造函数模式和原型模式
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
}
Person.prototype = {
    constructor: Person,
    sayName: function() {
        alert(this.name);
    };
}
var person1 = new Person('Nike', 20, 'teacher');

9. JavaScript 继承的方式和优缺点

一、原型链继承
  • 缺点:
    1.引用类型的属性被所有实例共享
    2.在创建 Child 的实例时,不能向 Parent 传参
二、借用构造函数(经典继承)
  • 优点:
    1.避免了引用类型的属性被所有实例共享
    2.可以在 Child 中向 Parent 传参

  • 缺点:
    1.方法都在构造函数中定义,每次创建实例都会创建一遍方法。

三、组合继承
  • 优点:
    1.融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。
四、原型式继承
  • 缺点:
    1.包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。
五、寄生式继承
  • 缺点:
    1.跟借用构造函数模式一样,每次创建对象都会创建一遍方法。
六、寄生组合式继承
  • 优点:
    1.这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。
    2.与此同时,原型链还能保持不变;
    3.因此,还能够正常使用 instanceof 和 isPrototypeOf。
    开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式

10.复杂数据类型如何转变为字符串

首先,会调用 valueOf 方法,如果方法的返回值是一个基本数据类型,就返回这个值,
如果调用 valueOf 方法之后的返回值仍旧是一个复杂数据类型,就会调用该对象的 toString 方法,
如果 toString 方法调用之后的返回值是一个基本数据类型,就返回这个值,
如果 toString 方法调用之后的返回值是一个复杂数据类型,就报一个错误。
解析:

1

var obj = {
  valueOf: function() {
    return 1;
  }
};
console.log(obj + ""); //'1'

2

var obj = {
  valueOf: function() {
    return [1, 2];
  }
};
console.log(obj + ""); //'[object Object]';

3

var obj = {
  valueOf: function() {
    return [1, 2];
  },
  toString: function() {
    return 1;
  }
};
console.log(obj + ""); //'1';

4

var obj = {
  valueOf: function() {
    return [1, 2];
  },
  toString: function() {
    return [1, 2, 3];
  }
};
console.log(obj + ""); 
// 报错 Uncaught TypeError: Cannot convert object to primitive value

11. javascript 的 typeof 返回哪些数据类型

7 种分别为 stringbooleannumberObjectFunctionundefinedsymbol(ES6)

12.列举 3 种强制类型转换和 2 种隐式类型转换

强制(parseInt,parseFloat,Number)、隐式(+ -)

13.你对闭包的理解?优缺点?

概念:闭包就是能够读取其他函数内部变量的函数。

三大特性:
  • 函数嵌套函数。
  • 函数内部可以引用外部的参数和变量。
  • 参数和变量不会被垃圾回收机制回收。
优点:
  • 希望一个变量长期存储在内存中。
  • 避免全局变量的污染。
  • 私有成员的存在。
缺点:
  • 常驻内存,增加内存使用量。
  • 使用不当会很容易造成内存泄露。
    示例:
function outer() {
  var name = "jack";
  function inner() {
    console.log(name);
  }
  return inner;
}
outer()(); // jackfunction sayHi(name) {
  return () =› {
    console.log(`Hi! ${name}`);
  };
}
const test = sayHi("xiaoming");
test(); // Hi! xiaoming

虽然 sayHi 函数已经执行完毕,但是其活动对象也不会被销毁,因为 test 函数仍然引用着 sayHi 函数中的变量 name,这就是闭包。
但也因为闭包引用着另一个函数的变量,导致另一个函数已经不使用了也无法销毁,所以闭包使用过多,会占用较多的内存,这也是一个副作用。解析:由于在 ECMA2015 中,只有函数才能分割作用域,函数内部可以访问当前作用域的变量,但是外部无法访问函数内部的变量,所以闭包可以理解成“定义在一个函数内部的函数,外部可以通过内部返回的函数访问内部函数的变量“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

14.如何判断 NaN

isNaN()方法解析:

isNaN(NaN) // true

15.new 一个对象的过程中发生了什么

// 1\. 创建空对象;
var obj = {};
// 2\. 设置新对象的 constructor 属性为构造函数的名称,设置新对象的proto属性指向构造函数的 prototype 对象;
obj.__proto__ = ClassA.prototype;
// 3\. 使用新对象调用函数,函数中的 this 被指向新实例对象:
ClassA.call(obj); //{}.构造函数();
// 4\. 如果无返回值或者返回一个非对象值,则将新对象返回;如果返回值是一个新对象的话那么直接直接返回该对象。

16.for in 和 for of

1、for in

  • 1.一般用于遍历对象的可枚举属性。以及对象从构造函数原型中继承的属性。对于每个不同的属性,语句都会被执行。
  • 2.不建议使用 for in 遍历数组,因为输出的顺序是不固定的。
  • 3.如果迭代的对象的变量值是 null 或者 undefined, for in 不执行循环体,建议在使用 for in 循环之前,先检查该对象的值是不是 null 或者 undefined

2、for of

1.for…of 语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
解析:

var s = {
  a: 1,
  b: 2,
  c: 3
};
var s1 = Object.create(s);
for (var prop in s1) {
  console.log(prop); //a b c
  console.log(s1[prop]); //1 2 3
}
for (let prop of s1) {
  console.log(prop); //报错如下 Uncaught TypeError: s1 is not iterable
}
for (let prop of Object.keys(s1)) {
  console.log(prop); // a b c
  console.log(s1[prop]); //1 2 3
}

17. 如何判断 JS 变量的一个类型(至少三种方式)

typeofinstanceofconstructorprototype

18. for in、Object.keys 和 Object.getOwnPropertyNames 对属性遍历有什么区别?

for in 会遍历自身及原型链上的可枚举属性

Object.keys 会将对象自身的可枚举属性的 key 输出

Object.getOwnPropertyNames 会将自身所有的属性的 key 输出

解析:

ECMAScript 将对象的属性分为两种:数据属性和访问器属性。
var parent = Object.create(Object.prototype, {
  a: {
    value: 123,
    writable: true,
    enumerable: true,
    configurable: true
  }
});
// parent继承自Object.prototype,有一个可枚举的属性a(enumerable:true)。

var child = Object.create(parent, {
  b: {
    value: 2,
    writable: true,
    enumerable: true,
    configurable: true
  },
  c: {
    value: 3,
    writable: true,
    enumerable: false,
    configurable: true
  }
});
//child 继承自 parent ,b可枚举,c不可枚举
for infor (var key in child) {
  console.log(key);
}
// b
// a
// for in 

会遍历自身及原型链上的可枚举属性如果只想输出自身的可枚举属性,可使用 hasOwnProperty 进行判断(数组与对象都可以,此处用数组做例子)

let arr = [1, 2, 3];
Array.prototype.xxx = 1231235;
for (let i in arr) {
  if (arr.hasOwnProperty(i)) {
    console.log(arr[i]);
  }
}
// 1
// 2
// 3
//Object.keys
console.log(Object.keys(child));
// ["b"]
// Object.keys 会将对象自身的可枚举属性的key输出
//Object.getOwnPropertyNames
console.log(Object.getOwnPropertyNames(child));
// ["b","c"]
// Object.getOwnPropertyNames会将自身所有的属性的key输出

19. 如何判断一个对象是否为数组

第一种方法:使用 instanceof 操作符。
第二种方法:使用 ECMAScript 5 新增的 Array.isArray()方法。
第三种方法:使用使用 Object.prototype 上的原生 toString()方法判断。

20. Object.prototype.toString.call() 和 instanceOf 和 Array.isArray() 优缺点
Object.prototype.toString.call()
  • 优点:这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。
  • 缺点:不能精准判断自定义对象,对于自定义对象只会返回[object Object]

instanceOf

  • 优点:instanceof 可以弥补 Object.prototype.toString.call()不能判断自定义实例化对象的缺点。
  • 缺点: instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true,且不同于其他两种方法的是它不能检测出 iframes。

Array.isArray()

  • 优点:当检测 Array 实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 iframes
  • 缺点:只能判别数组

解析:
Object.prototype.toString.call()每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的话,会返回 [Object type],其中 type 为对象的类型。但当除了 Object 类型的对象外,其他类型直接使用 toString 方法时,会直接返回都是内容的字符串,所以我们需要使用 call 或者 apply 方法来改变 toString 方法的执行上下文。

const an = ["Hello", "An"];
an.toString(); // "Hello,An"
Object.prototype.toString.call(an); // "[object Array]"

这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。

Object.prototype.toString.call("An"); // "[object String]"
Object.prototype.toString.call(1); // "[object Number]"
Object.prototype.toString.call(Symbol(1)); // "[object Symbol]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(function() {}); // "[object Function]"
Object.prototype.toString.call({ name: "An" }); // "[object Object]"

缺点:不能精准判断自定义对象,对于自定义对象只会返回[object ]

function f(name) {
  this.name = name;
}
var f1 = new f("martin");
console.log(Object.prototype.toString.call(f1)); //[object Object]

Object.prototype.toString.call(); // 常用于判断浏览器内置对象。

instanceof的内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。使用 instanceof 判断一个对象是否为数组,instanceof 会判断这个对象的原型链上是否会找到对应的 Array 的原型,找到返回 true,否则返回 false。[] instanceof Array; // true但 instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true。[] instanceof Object; // true

优点:instanceof 可以弥补 Object.prototype.toString.call()不能判断自定义实例化对象的缺点。
缺点:instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true,且不同于其他两种方法的是它不能检测出 iframes。

function f(name) {
  this.name = name;
}
var f1 = new f("martin");
console.log(f1 instanceof f); //true

Array.isArray()

功能:用来判断对象是否为数组
instanceof 与 isArray

当检测 Array 实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 iframes

var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length - 1].Array;
var arr = new xArray(1, 2, 3); // [1,2,3]

// Correctly checking for Array
Array.isArray(arr); // true
Object.prototype.toString.call(arr); // true
// Considered harmful, because doesn't work though iframes
arr instanceof Array; // false

缺点:只能判别数组

Array.isArray() 与 Object.prototype.toString.call()

Array.isArray()是 ES5 新增的方法,当不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 实现。

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === "[object Array]";
  };
}

你可能感兴趣的:(【JS】面试题 1)