打卡学习-JavaScript面试题(四)

100 阅读13分钟

菜鸡打卡

1.自执行函数? 用于什么场景?好处?

自执行函数:

  • 声明一个匿名函数
  • 马上调用这个匿名函数。

作用:创建一个独立的作用域

好处: 防止变量弥散到全局,以免各种 js 库冲突。隔离作用域避免污染,或者截断作用域链,避免闭包造成引用变量无法释放。利用立即执行特性,返回需要的业务函数或对象,避免每次通过条件判断来处理场景:一般用于框架、插件等场景。

2.数组方法 pop﴾﴿ push﴾﴿ unshift﴾﴿ shift﴾﴿

  • arr. pop() 从后面删除元素,只能是一个,返回值是删除的元素
  • arr. push() 从后面添加元素,返回值为添加完后的数组的长度
  • arr. unshift() 从前面添加元素, 返回值是添加完后的数组的长度
  • arr. shift() 从前面删除元素,只能删除一个 返回值是删除的元素

3.事件绑定与普通事件有什么区别

  • 用普通事件添加相同事件,下面会覆盖上面的,而事件绑定不会普通
  • 事件是针对非 dom 元素,事件绑定是针对 dom 元素的事件

4.javascript 中 this 的指向问题

  • 全局环境、普通函数(非严格模式)指向 window
  • 普通函数(严格模式)指向 undefined
  • 函数作为对象方法及原型链指向的就是上一级的对象构造函数指向构造的对象
  • DOM 事件中指向触发事件的元素箭头函数
  • ...

解析

  1. 全局环境

全局环境下,this 始终指向全局对象(window),无论是否严格模式;

// 在浏览器中,全局对象为 window 对象:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
  1. 普通函数

普通函数内部的 this 分两种情况,严格模式和非严格模式。

  • 非严格模式下,没有被上一级的对象所调用, this 默认指向全局对象 window
function f1() {
 return this;
}
f1() === window; // true
  • 严格模式下,this 指向 undefined
function f2() {
 "use strict"; // 这里是严格模式
 return this;
}
f2() === undefined; // true
  1. 函数作为对象的方法
  • 函数有被上一级的对象所调用,那么 this 指向的就是上一级的对象

  • 多层嵌套的对象,内部方法的 this 指向离被调用函数 近的对象(window 也是对象,其内部对象调用方法的 this 指向内部对象, 而非 window)

//方式1
var o = {
    prop: 18,
    f: function () {
        return this.prop
    }
}
// 当o.f()被调用时,函数内的this将绑定到o对象
console.log(o.f()) //18

//方式2
var o = {
    prop: 18
}

function independent() {
    return this.prop;
}
//函数f作为o的成员方法调用
o.f = independent;
console.log(o.f()); // 18

//方式3
//this 的绑定只受靠近的成员引用的影响
o.b = {
    g: independent,
    prop: 42
};
console.log(o.b.g()); // 42

特殊例子

// 例1
var o3 = {
    a: 10,
    b: {
        //a:12,
        fn: function () {
            console.log(this.a);//undefined
            console.log(this);//{fn: f}
        }
    }
}
o3.b.fn()

// 例2
var o4 = {
    a: 10,
    b: {
        a: 12,
        fn: function () {
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o4.b.fn;
j()

/* this永远指向的是后调用它的对象,也就是看它执行的时候是谁调用的,
   例子2中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并
   没有执行所以终指向的是 window,这和例子1是不一样的,例子1是直接执行了fn
*/

  1. 原型链中的 this
  • 1.如果该方法存在于一个对象的原型链上,那么 this 指向的是调用这个方法的对象,就像该方法在对象上一样
var o = {
 f: function() {
 return this.a + this.b;
 }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5

上述例子中,对象 p 没有属于它自己的 f 属性,它的 f 属性继承自它的原型。当执行 p. f()时,会查找 p 的原型链,找到 f 函数并执行。因为 f 是作为 p 的方法调用的,所以函数中的 this 指向 p

  • 2.相同的概念也适用于当函数在一个 getter 或者 setter 中被调用。用作 getter或 setter 的函数都会把this 绑定到设置或获取属性的对象

  • 3.call()和 apply()方法:当函数通过 Function 对象的原型中继承的方法 call() 和 apply() 方法调用时, 其函数内部的 this 值可绑定到 call() & apply() 方法指定的第一个对象上, 如果第一个参数不是对象,JavaScript 内部会尝试将其转换成对象然后指向它

function add(c, d) {
 return this.a + this.b + c + d;
}
var o = {
 a: 1,
 b: 3
};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

function tt() {
 console.log(this);
}
// 第一个参数不是对象,JavaScript内部会尝试将其转换成对象然后指向它。
tt.call(5); // 内部转成 Number {[[PrimitiveValue]]: 5}
tt.call("asd"); // 内部转成 String {0: "a", 1: "s", 2: "d", length: 3, [[PrimitiveValue]]: "asd"}
  • 4.bind()方法:由 ES5 引入, 在 Function 的原型链上, Function. prototype. bind。通过 bind 方法绑定后,函数将被永远绑定在其第一个参数对象上, 而无论其在什么情况下被调用
function f() {
 return this.a;
}
var g = f.bind({
 a: "azerty"
});
console.log(g()); // azerty
var o = {
 a: 37,
 f: f,
 g: g
};
console.log(o.f(), o.g()); // 37, azerty
  1. 构造函数中的 this

当一个函数用作构造函数时(使用 new 关键字),它的 this 被绑定到正在构造的新对象。构造器返回的默认值是 this 所指的那个对象,也可以手动返回其他的对象

function C() {
 this.a = 18;
}
var o = new C();
console.log(o.a); // 18
// 为什么this会指向o?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代

function C2() {
 this.a = 18;
 return {
 a: 19
 }; // 手动设置返回{a:19}对象
}
o = new C2();
console.log(o.a); // 19

特殊例子

当 this 碰到 return 时

// 例子1
function fn() {
 this.user = "追梦子";
 return {};
}
var a = new fn();
console.log(a.user); //undefined

// 例子2
function fn() {
 this.user = "追梦子";
 return function() {};
}
var a = new fn();
console.log(a.user); //undefined

// 例子3
function fn() {
 this.user = "追梦子";
 return 1;
 }
var a = new fn();
console.log(a.user); //追梦子

// 例子4
function fn() {
 this.user = "追梦子";
 return undefined;
}
var a = new fn();
console.log(a.user); //追梦子

// 例子5
function fn() {
 this.user = "追梦子";
 return undefined;
}
var a = new fn();
console.log(a); //fn {user: "追梦子"}

// 例子6
// 虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊
function fn() {
 this.user = "追梦子";
 return null;
}
var a = new fn();
console.log(a.user); //追梦子
// 总结:如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例
  1. setTimeout & setInterval
  • 对于延时函数内部的回调函数的 this 指向全局对象 window

  • 可以通过 bind()方法改变内部函数 this 指向

//默认情况下代码
function Person() {
this.age = 0;
setTimeout(function() {
console.log(this);
}, 3000);
}

var p = new Person(); //3秒后返回 window 对象
//通过bind绑定
function Person() {
this.age = 0;
setTimeout(
function() {
console.log(this);
}.bind(this),
3000
);
}

var p = new Person(); //3秒后返回构造函数新生成的对象 Person{...}
  1. 在 DOM 事件中

1.作为一个 DOM 事件处理函数

当函数被用作事件处理函数时,它的 this 指向触发事件的元素(针对 addEventListener 事件)

// 被调用时,将关联的元素变成蓝色
function bluify(e) {
 //this指向所点击元素
 console.log("this === e.currentTarget", this === e.currentTarget); // 总是 true
 // 当 currentTarget 和 target 是同一个对象时为 true
 console.log("this === e.target", this === e.target);
 this.style.backgroundColor = "#A5D9F3";
}
// 获取文档中的所有元素的列表
var elements = document.getElementsByTagName("*");
// 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
for (var i = 0; i < elements.length; i++) {
 elements[i].addEventListener("click", bluify, false);
}

2.作为一个内联事件处理函数

  • 1.当代码被内联处理函数调用时,它的 this 指向监听器所在的 DOM 元素

  • 2.当代码被包括在函数内部执行时,其 this 指向等同于 普通函数直接调用的情况,即在非严格模式指向全局对象 window,在严格模式指向 undefined

<button onclick="console.log(this)">show me</button>
<button onclick="(function () {console.log(this)})()">show inner this</button>
<button onclick="(function () {'use strict'; console.log(this)})()">
 use strict
</button>
// 控制台打印
<button onclick="console.log(this)">show me</button>
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
undefined

7.箭头函数

1.全局环境中

在全局代码中,箭头函数被设置为全局对象:

var globalObject = this;
var foo = () => this;
console.log(foo() === globalObject); // true

2.this 捕获上下文

箭头函数没有自己的 this,而是使用箭头函数所在的作用域的 this,即指向箭头函数定义时(而不是运行时)所在的作用域

//1、箭头函数在函数内部,以非方法的方法使用
function Person() {
 this.age = 0;
 setInterval(() => {
 this.age++;
 }, 3000);
}
var p = new Person(); //Person{age: 0}

//普通函数作为内部函数
function Person() {
 this.age = 0;
 setInterval(function() {
 console.log(this);
 this.age++;
 }, 3000);
}
var p = new Person(); //Window{...}

在 setTimeout 中的 this 指向了构造函数新生成的对象,而普通函数指向了全局 window 对象

3.箭头函数作为对象的方法使用

箭头函数作为对象的方法使用,指向全局 window 对象;而普通函数作为对象的方法使用,则指向调用的对象

var obj = {
 i: 10,
 b: () => console.log(this.i, this),
 c: function() {
 console.log(this.i, this);
 }
};
obj.b(); // undefined window{...}
obj.c(); // 10 Object {...}
  1. 箭头函数中,call()、apply()、bind()方法无效
var adder = {
 base: 1,
 //对象的方法内部定义箭头函数,this是箭头函数所在的作用域的this,
 //而方法add的this指向adder对象,所以箭头函数的this也指向adder对象。
 add: function(a) {
     var f = v => v + this.base;
     return f(a);
    },
 //普通函数f1的this指向window
 add1: function() {
     var f1 = function() {
     console.log(this);
 };
     return f1();
 },
 addThruCall: function inFun(a) {
     var f = v => v + this.base;
     var b = {
     base: 2
   };
     return f.call(b, a);
 }
};
console.log(adder.add(1)); // 输出 2
adder.add1(); //输出全局对象 window{...}
console.log(adder.addThruCall(1)); // 仍然输出 2(而不是3,其内部的this并没有因为call() 而改变,其
this值仍然为函数inFun的this值,指向对象adder

5.this 指向固定化

箭头函数可以让 this 指向固定化,这种特性很有利于封装回调函数

var handler = {
 id: "123456",
 init: function() {
 document.addEventListener(
     "click",
     event => this.doSomething(event.type),
     false
     );
 },
 doSomething: function(type) {
 console.log("Handling " + type + " for " + this.id);
 }
};

上面代码的 init 方法中,使用了箭头函数,这导致这个箭头函数里面的 this,总是指向 handler 对象。如果不使用箭头函数则指向全局 document 对象

6.箭头函是不适用场景

  • 箭头函数不适合定义对象的方法(方法内有 this),因为此时指向 window

  • 需要动态 this 的时候,也不应使用箭头函数

//例1,this指向定义箭头函数所在的作用域,它位于对象cat内,但cat不能构成一个作用域,所以指向全局window,改成普通函数后this指向cat对象

const cat = {
 lives: 9,
 jumps: () => {
 this.lives‐‐;
 }
};
//例2,此时this也是指向window,不能动态监听button,改成普通函数后this指向按钮对象。
var button = document.getElementById("press");
button.addEventListener("click", () => {
 this.classList.toggle("on");
});

5.去除数组重复成员的方法

  • 方法1 扩展运算符和 Set 结构相结合,就可以去除数组的重复成员
// 去除数组的重复成员
[...new Set([1, 2, 2, 3, 4, 5, 5])];
// [1, 2, 3, 4, 5]
  • 方法2
function dedupe(array) {
 return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]); // [1, 2, 3]
  • 方法3 ES5
function unique(arry) {
 const temp = [];
 arry.forEach(e => {
 if (temp.indexOf(e) == ‐1) {
 temp.push(e);
 }
 });
 return temp;
}

6.JS 中 文档碎片的理解和使用

// 1、什么是文档碎片?
document.createDocumentFragment(); // 一个容器,用于暂时存放创建的dom元素
// 2、文档碎片有什么用?
// 将需要添加的大量元素,先添加到文档碎片中,再将文档碎片添加到需要插入的位置,大大减少dom操作,提高性能IE和火狐比较明显)

解析

// 普通方式:(操作了 100次dom)
for (var i = 100; i > 0; i-- ){
    var elem = document.createElement("div");
    document.body.appendChild(elem); //放到body中
}
// 文档碎片:(操作1次dom)
var df = document.createDocumentFragment();
for (var i = 100; i > 0; i-- ){
    var elem = document.createElement("div");
    df.appendChild(elem);
}
//后放入到页面上
document.body.appendChild(df);

7. JavaScript 原型,原型链 ? 有什么特点?

  • 原型对象也是普通的对象,是对象一个自带隐式的__proto__属性,原型也有可能有自己的原型,如果一个原型对象的原型不为null 的话,我们就称之为原型链。

  • 原型链是由一些用来继承和共享属性的对象组成的(有限的)对象链

  • JavaScript 的数据对象有那些属性值

    • writable:这个属性的值是否可以改。 configurable:这个属性的配置是否可以删除,修改

    • enumerable:这个属性是否能在 for…in 循环中遍历出来或在 Object. keys 中列举出来

    • value:属性值

  • 当我们需要一个属性的时,Javascript 引擎会先看当前对象中是否有这个属性, 如果没有的话,就会查找他的 Prototype 对象是否有这个属性

function clone(proto) {
 function Dummy() {}
 Dummy.prototype = proto;
 Dummy.prototype.constructor = Dummy;
 return new Dummy(); //等价于Object.create(Person);
}
function object(old) {
 function F() {}
 F.prototype = old;
 return new F();
}
var newObj = object(oldObject);

8. 对象浅拷贝和深拷贝有什么区别

在 JS 中,除了基本数据类型,还存在对象、数组这种引用类型。基本数据类型,拷贝是直接拷贝变量的值,而引用类型拷贝的其实是变量的地址

let o1 = {a: 1}
let o2 = o1

在这种情况下,如果改变 o1 或 o2 其中一个值的话,另一个也会变,因为它们都指向同一个地址。

o2.a = 3
console.log(o1.a) // 3

而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有重新创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。

9.为什么 for 循环嵌套顺序会影响性能?

把循环次数大的放在内层,执行时间会比较短

var t1 = new Date().getTime()
for (let i = 0; i < 100; i++) {
 for (let j = 0; j < 1000; j++) {
 for (let k = 0; k < 10000; k++) {}
 }
}
var t2 = new Date().getTime()
console.log('first time', t2 ‐ t1)

image.png

for (let i = 0; i < 10000; i++) {
 for (let j = 0; j < 1000; j++) {
 for (let k = 0; k < 100; k++) {
 }
 }
} var t3 = new Date().getTime()
console.log('two time', t3 ‐ t2)

image.png

10.什么是跨域?跨域请求资源的方法有哪些?

(1)porxy 代理 定义和用法:proxy 代理用于将请求发送给后台服务器,通过服务器来发送请求,然后将请求的结果传递给前端。 实现方法:通过 nginx 代理; 注意点:1、如果你代理的是 https 协议的请求,那么你的 proxy 首先需要信任该证书(尤其是自定义证书)或者忽略证书检查,否则你的请求无法成功。

(2)CORS 【Cross‐Origin Resource Sharing】 定义和用法:是现代浏览器支持跨域资源请求的一种 常用的方式。 使用方法:一般需要后端人员在处理请求数据的时 候,添加允许跨域的相关操作。如下:

res.writeHead(200, { 
 "Content‐Type": "text/html; charset=UTF‐8", 
 "Access‐Control‐Allow‐Origin":'http://localhost', 
 'Access‐Control‐Allow‐Methods': 'GET, POST, OPTIONS', 
 'Access‐Control‐Allow‐Headers': 'X‐Requested‐With, Content‐Type' 
}); 

(3)jsonp 定义和用法:通过动态插入一个 script 标签。浏览器对 script 的资源引用没有同源限制,同时资源加载到页面后会 立即执行(没有阻塞的情况下)。 特点:通过情况下,通过动态创建 script 来读取他域的动态资源,获取的数据一般为 json 格式。 实例 如下:

<script> 
 function testjsonp(data) { 
   console.log(data.name); // 获取返回的结果
 }
</script>
<script>
 var _script = document.createElement('script');
 _script.type = "text/javascript";
 _script.src = "http://localhost:8888/jsonp?callback=testjsonp";
 document.head.appendChild(_script);
</script>

缺点:

1、这种方式无法发送post请求(这里)

2、另外要确定jsonp的请求是否失败并不容易,大多数框架的实现都是结合超时时间来判定。