1、call和apply哪个性能更好?
call
的性能要比 apply
稍微好点,尤其是传给函数的参数超过三个以上的时候。
2、console.time
console.time
可以测试出一段代码执行的时间:
console.time('A');
// code goes here ...
console.timeEnd('A'); // 输出代码执行需要的时间
3、箭头函数和普通函数的区别?
- 箭头函数语法上比普通函数简洁;
- 箭头函数没有
this
, 它里面的 this 是继承函数所处的上下文的this,使用call/apply也无法改变; - 箭头函数中没有
arguments
(类数组),只能基于...arg
获取传递的参数集合(数组); - 箭头函数不能被
new
执行,因为箭头函数没有 this 和 prototype;
4、回调函数
- 把一个函数B作为实参传递给另一个函数A,函数A执行的时候,可以把传进来的函数B去执行;
- 回调函数里的this一般都是window;
- 执行n次,可传值,可修改this指向;
function each(callback) {
var a = this
for(var i=0; i<this.length; i++) {
// 修改this指向,可传值
var flag = callback.call(a, a[i], i);
if(flag === false) {
break;
}
console.log(a[i])
}
}
Array.prototype.each = each;
var arr = [10,20,30,40];
arr.each(function(item, index){
if(index > 2) {
return false;
}
});
5、对象属性名的问题
// example 1
var a = {}, b = '123', c = 123;
a[b] = 'b';
a[c] = 'c';
console.log(a[b]); // 'c', 因为a["123"] 等价于 a[123]
// example 2
var a = {}, b = Symbol('123'), c = Symbol('123');
a[b] = 'b';
a[c] = 'c';
console.log(a[b]); // 'b', Symbol是ES6新增的数据类型;
// typeof Symbol('123') === 'symbol'
// 创建出来的是唯一值,Symbol('123') 不等于 Symbol('123')
// example 3
var a = {}, b = {key: '123'}, c = {key: '456'};
a[b] = 'b';
a[c] = 'c';
console.log(a[b]); // 'c'
// 1. 对象的属性名不能是一个对象(遇到对象属性名,会转换为字符串)。
// obj={}; arr=[12,34]; obj[arr]='xx'; obj=>{"12,34": "xx"}
// 普通对象.toString()调取的是Object.prototype上的方法(这个方法是用来检测数据类型的)
// obj={}; obj.toString() => "[object Object]"
// obj[b] = 'b' => obj["[object Object]"] = 'b'
6、验证是否为一个网址
[思路]:用正则。
// URL格式
// 1.协议:// http/https/ftp
// 2.域名: www.baidu.com | baidu.com | pan.baidu.com
// 3.请求路径: / | /index.html | /stu/ | /stu/index.html
// 4.问号传参:?xxx=xxx&xxx=xxx
// 5.哈希值:#xxx
var url = "http://www.baidu.com/index.html?name=xxx&age=20#nav";
var reg = /^((http|https|ftp):\/\/)?(([\w-]+\.)+[a-z0-9]+)((\/[^/?#]*)+)?(\?[^#]+)?(#.+)?$/i;
// ^在[]里面代表非
// .:匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。
7、类和普通对象的属性方法
function Foo () {
Foo.a = function () {
console.log(1);
}
this.a = function () {
console.log(2);
}
}
// 把Foo当作类,在原型上设置实例公有属性方法 实例.a()
Foo.prototype.a = function() {
console.log(3);
}
// 把Foo当作普通对象设置私有的属性方法 Foo.a()
Foo.a = function () {
console.log(4);
}
Foo.a(); // 4
let obj = new Foo(); // obj可以调取原型上的方法 Foo.a: f=>1 obj.a:f=>2
obj.a(); // 2 私有属性中有a
Foo.a(); // 1
8、正则中的正向预查
// 验证6-16位的字符串,必须同时有字母数字下划线
var reg = /(?!^[a-z0-9]+$)(?!^[A-Z0-9]+$)(?!^[a-zA-Z]+$)^[a-zA-Z0-9]{6,16}$/;
9、数组扁平化
方法一:Array.prototype.flat()
const arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
方法二:先转成字符串,再把每项转成数字
// 数组toString之后,不管有多少级,最终都变成以逗号分隔的字符串,没有中括号和所谓的层级
arr = arr.toString().split(',').forEach(item => {
return Number(item);
});
方法三:JSON.stringify()
var str = JSON.stringify(arr); // "[1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]]"
str.replace(/(\[|\])/g, ''); // "1, 2, 3, 4, 5, 6, 7, 8, 9, 10"
方法四:arr.some + 展开运算符
// some检测当数组的每一项还有没有是数组的
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
方法五:自己实现递归
function flatArr() {
let result = [];
let fn = ary => {
for (let i=0; i<ary.length; i++) {
let item = ary[i];
if (Array.isArray(item)) {
fn(item);
continue;
}
result.push(item);
}
}
fn(this);
return result;
}
Array.prototype.flatArr = flatArr;
const arr = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
console.log(arr.flatArr());
最后:去重且排序:
// ES6中的 new Set() 去重,返回一个对象
// [...new Set(arr)]
// Array.from(newSet(arr))
arr = Array.from(newSet(arr)).sort((a,b) => a-b);
10、自己实现new
考察是否知道 new
的过程:
- 向普通函数执行一样,形成一个私有的作用域(形参复制,变量提升);
- 默认创建一个对象,让函数中的this指向这个对象,这个对象就是当前类的一个实例;
- 代码执行;
- 默认把创建的对象返回。
function Rifle(name) {
this.name = name;
}
Rifle.prototype.fire = function() {
console.log(this.name + ' dadadadada');
}
Rifle.prototype.reload = function() {
console.log(this.name +' reload the gun');
}
// 内置new关键字
var m416 = new Rifle('m416');
m416.fire();
// 自己实现new
function _new(Fn, ...arg) {
// 创建一个空对象,让他的原型链指向Fn.prototype(作为Fn的一个实例)
let obj = {};
obj.__proto__ = Fn.prototype;
// 或者 使用Object.creat([AA]) 简化上面两行
// 创建一个空对象obj,并且让obj作为AA对象所属构造函数的实例(obj.__proto__==AA)
let obj = Object.create(Fn.prototype);
Fn.call(obj, ...arg);
return obj;
}
var akm = _new(Rifle, 'akm');
akm.reload();
11、本应匿名的函数有了函数名
- 本应匿名的函数如果设置了函数名,在外面无法调用,但是再函数里面可以使用;
- AA类似于创建常量,这个名字存储值不能再被修改。(非严格模式下不报错,但是没有任何效果,严格模式下报错,可以理解为const创建出来的)。
let fn = function AA() {
console.log(AA); // 当前函数
"use strict"
AA = 100; // "TypeError: Assignment to constant variable.
}
AA() // "ReferenceError: AA is not defined
fn();
一个题目:
var b = 10;
(function b() {
b = 20;
console.log(b); // 函数,因为不能被修改
// 如果想要输出20,可把b = 20改为var b = 20(b要是私有的);
})()
console.log(b); // 10
12、两个数对比
==
进行比较的时候,如果左右两边数据类型不一样,则先转换为相同的数据类型,然后进行比较:
{} == {}
:两个对象相比,比较的是堆内存的地址;null == undefined
:相等;NaN == NaN
:不相等,NaN和谁都不相等;[12] == '12
:对象和字符串相比,把对象toString()转换为字符串后再对比;- 剩余所有情况,都是转换为数字(前提数据类型不一样):
- 对象转数字:先转成字符串,再转成数字;
- 字符串转数字:只要出现一个非数字字符,就是NaN;
- 布尔转数字:true->1, false->0;
- null:0;
- undefined:NaN。
// 例如:
[12] == true // false Number([12].toString())
[] == false // true
[] == 1 // false
"1" == 1 //true
true == 2 //false
题目:
有没有可能 (a == 1 && a ==2 && a == 3)
为 true
// 第一种:对象实现
// 对象和数字对比,先对象.toString()变成字符串,再转数字。
var a = {
n: 0,
// 私有的属性方法
toString: function () {
return ++this.n;
}
}
// 此时调的不是Object.prototype.toString,而是私有的toString
if (a == 1 && a ==2 && a == 3) {
console.log('success');
}
// 第二种:数组实现:
// shift()删除数组第一项,把删除的内容返回,原数组改变
let a = [1, 2, 3];
a.toString = a.shift;
// 第三种:Object.defineProperty()
Object.defineProperty(window, 'a', {
get: function() {
this.value ? this.value++ : this.value = 1;
return this.value;
}
})
13、一个关于数组push的对象题
let obj = {
2: 3,
3: 4,
length: 2,
push: Array.prototype.push
}
obj.push(1); // obj[2] = 1
obj.push(2); // obj[3] = 2
console.log(obj) // obj = {2: 1, 3:2, length: 4, push: xxx}
思路主要是明白push方法的实现:
Array.prototype.push = function (val) {
this[this.length] = val;
// this.length加1
return this.length;
}
14、三大经典排序算法
①冒泡排序:
冒泡排序的思想:让数组中的当前项和后一项进行比较,如果当前项比后一项大,则两项交换位置。
需要比较 length-1
轮,5个数只需要把4个最大的依次放到末尾。
function bubble(arr) {
let temp = null;
// 外层循环控制比较的轮数
for (let i=0; i<arr.length-1; i++) {
// 内层循环控制每一轮比较的次数
for (let j=0; j<arr.length-1-i; j++) {
if (arr[j] > arr[j+1]) {
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
return arr;
}
var a = [1,35,24,65,2];
console.log(bubble(a));
②插入排序
插入排序思想:和打牌类似,每次从数组中拿一个,和现有的进行从后往前比较,大的话放后面,小的话继续往前比较。
function insert(arr) {
// 准备一个新数组,用于存储抓到的牌
let handle = [];
// 开始先抓一张牌
handle.push(arr[0]);
// 从第二项开始依次抓牌,一直把牌抓完
for (let i=1; i<arr.length; i++) {
// A是新拿到的牌
let A = arr[i];
// 和handle手里的牌比较(从后往前)
for(let j=handle.length-1; j>=0; j--) {
let B = handle[j];
// 如果新抓的牌比手里的大,放到后边
if (A > B) {
handle.splice(j+1, 0, A);
break;
}
// 比到第一项,把新抓的牌放到第一项
if (j === 0) {
handle.unshift(A);
}
}
}
return handle;
}
var a = [1,35,24,35,65,2];
console.log(insert(a));
③快速排序
function quick(arr) {
// 4.结束递归(arr中小于等于一项,不用处理)
if (arr.length <=1) {
return arr;
}
// 1.找到数组的中间项,在原有的数组中把它删除。
let middleIndex = Math.floor(arr.length/2),
middleItem = arr.splice(middleIndex, 1)[0];
// 2.准备左右两个数组,循环剩下数组中的每一项,小的放左数组,反之放右数组。
let leftArr = [],
rightArr = [];
for (let i=0; i<arr.length; i++) {
let item = arr[i];
item < middleItem ? leftArr.push(item) : rightArr.push(item);
}
// 3.递归方式让左右两边的数组持续处理,一直到左右两边都排好顺序,最后拼接。
return quick(leftArr).concat(middleItem, quick(rightArr));
}
let a = [2,5,22,11,53,11,41];
console.log(quick(a));
15、两个数组的交集
let arr1 = [1,1,1,1];
let arr2 = [1,1,1,1];
let result = [];
arr1.forEach((item, index) => {
let n = arr2.indexOf(item);
if(n >= 0) {
result.push(item);
arr2.splice(n, 1);
}
})
console.log(result)
16、this总结
①普通函数的调用,this指向的是window
②对象方法的调用,this指的是该对象,且是最近的对象
③构造函数的调用,this指的是实例化的新对象
④apply和call调用,this指向参数中的对象
⑤匿名函数的调用,this指向的是全局对象window
⑥定时器中的调用,this指向的是全局变量window