前端面试题整理http://www.yoloworld.site:3000/gitblog/
闭包
- 闭包是指有权访问另一个函数作用域中变量的函数,
- 创建闭包的最常见的方式就是在一个函数内创建另一个函数,
- 通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部
<ul id="testUL">
<li> index = 0</li>
<li> index = 1</li>
<li> index = 2</li>
<li> index = 3</li>
</ul>
const nodes = document.getElementsByTagName("li");
for (i = 0; i < nodes.length; i += 1) {
// nodes[i].onclick = function () {
// console.log(i); //不用闭包的话,console.log每次都是4
// }
// 闭包写法
// 声明一个匿名函数作为闭包的外层
// 调用outterFunc时将i作为参数index的值传入;
// outterFunc方法返回的function(){console.log(idnex)}作为onclick的事件处理函数
function outterFunc (index) {
return function() {
console.log(index);
}
}
nodes[i].onclick = outterFunc(i);
}
// 那如果使用事件冒泡就也可以实现
const ulNode = document.getElementById("testUL");
ulNode.onclick = function (e) {
console.log(e.target.innerText)
}
原型
原型
- 每个实例对象(object )都有一个私有属性(称之为__proto__)指向它的原型对象(prototype)。
- 无论什么时候创建一个函数,该函数都有一个prototype(原型)属性,即原型对象
- 默认情况下,原型对象都会自动获取一个constructor(构造函数)属性,该属性指向prototype属性所在的函数
- prototype(原型)就是通过构造函数来创建的对象实例的原型对象, 这个对象让所有对象实例可以共享它所包含的属性和方法
- 所有普通的[[Prototype]]链最终都会指向内置的Object.prototype
构造函数
- 任何函数,只要通过new操作符来调用,就可以作为构造函数
- 构造函数new一个对象实例的过程
- 创建一个新对象实例;
- 将构造函数的作用域赋给新对象实例
- 执行构造函数中的代码,为新对象实例添加属性
- 返回新对象实例
function Person() {
this.sayHello = function (params) {
console.log('hello');
};
}
Person.prototype.name = 'person';
const person1 = new Person();
// Person.prototype === person1.__proto__
console.log(JSON.stringify(Person.prototype)); // {"name":"person"}
console.log(JSON.stringify(person1.__proto__)); // {"name":"person"}
console.log(JSON.stringify(person1.prototype)); // undefined
console.log(Person.prototype === person1.__proto__); // true
console.log(`getPrototypeOf: ${Object.getPrototypeOf(person1) === Person.prototype}`); // true
console.log(`constructor: ${Person.prototype.constructor === Person}`); // true
console.log(person1.__proto__.constructor === Person.prototype.constructor); // true
console.log(person1.__proto__.constructor === Person); // true
console.log(person1.__proto__.constructor.prototype === Person.prototype); // true
// 对象实例中有一个constructor属性,指向构造函数
console.log(JSON.stringify(person1.constructor === Person))
最后一张图片的代码
function Animal(type) {
this.type = type || 'animal';
this.getType = function getType() {
};
}
function Dog() {
this.name = 'dog';
}
Dog.prototype = new Animal();
const dog = new Dog();
console.log(dog);
宏任务与微任务
代码题:
function app() {
setTimeout(() => {
console.log("1-1");
Promise.resolve().then(() => {
console.log("2-1");
});
});
console.log("1-2");
Promise.resolve().then(() => {
console.log("1-3");
setTimeout(() => {
console.log("3-1");
});
});
}
app();
结果
1-2
1-3
1-1
2-1
3-1
- 先执行同步
- 同一层级下(不理解层级,可以先不管,后面会讲),微任务永远比宏任务先执行
- 每个宏任务,都单独关联了一个微任务队列
宏任务包括
微任务包括
箭头函数
es6.ruanyifeng.com/#docs/funct…
箭头函数有几个使用注意点。
(1)箭头函数没有自己的this对象。
对于普通函数来说,内部的this指向函数运行时所在的对象,但是这一点对箭头函数不成立。 它没有自己的this对象,内部的this就是定义时上层作用域中的this
// 普通函数
function foo() {
setTimeout(function() {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// 浏览器执行 id: 21 node执行 undefined
>-----------------------------<
// 箭头函数
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 });
// id: 42
(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
this相关知识点
new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
this是在调用时绑定的
如果要判断一个运行中函数的this绑定,就需要找到这个函数的直接调用位置。找到之后就可以顺序应用下面这四条规则来判断this的绑定对象。——你不知道的JavaScript(上卷)
- 由new调用?绑定到新创建的对象。
- 由call或者apply(或者bind)调用?绑定到指定的对象。
- 由上下文对象调用?绑定到那个上下文对象。
- 默认:在严格模式下绑定到undefined,否则绑定到全局对象。
/**
* 优先级
* new绑定和隐式绑定的优先级
*/
function foo(something) {
this.a = something;
}
const obj1 = { foo };
const obj2 = {};
obj1.foo(2); // 此时foo的this指向obj1,所以foo执行时,this.a=2相当于执行了obj1.a=2
console.log(obj1.a);// 2
obj1.foo.call(obj2, 3); // 此时foo的this指向obj2,所以foo执行时,this.a=2相当于执行了obj1.a=2
console.log(obj2.a);// 3
const bar = new obj1.foo(4);
console.log(obj1.a);// 2
console.log(bar.a);// 4
new绑定
- 构造函数new一个对象实例的过程
- 创建一个新对象实例;
- 将构造函数的作用域赋给新对象实例
- 执行构造函数中的代码,为新对象实例添加属性
- 返回新对象实例
默认绑定
在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用。
function sayHi(){
console.log('Hello,', this.name);
}
var name = 'YvetteLau';
sayHi();
// 在调用 Hi() 时,应用了默认绑定,this 指向全局对象(非严格模式下),
// 严格模式下,this 指向 undefined,undefined 上没有 this 对象,会抛出错误。
隐式绑定
函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。典型的形式为 XXX.fun().
对象属性链中只有最后一层会影响到调用位置。 eg :person1.friend.sayHi();
显式绑定
就是通过 call,apply,bind 的方式
call 和 apply的功能相同,都是在调用函数,并修改this指向第一个参数;区别在于传参的方式不一样:
- fn.call(obj, arg1, arg2, ...),调用一个函数, 具有一个指定的this值和分别地提供的参数(参数的列表)。
- fn.apply(obj, [argsArray]),调用一个函数,具有一个指定的this值,以及作为一个数组(或类数组对象)提供的参数。
// node中执行
global.a = 3;
function foo() {
console.log(this.a);
}
const obj = { a: 2 };
foo.call(obj); // 2
// 如果将第一个参数传为一个基本类型2 此时this指向Number引用类型
// 例如 Boolean,String,Number,这个将基本类型转为引用类型的操作成为“装箱”
foo.call(2);
foo.call(null); // 如果把undefined和null作为绑定对象传给call或者apply,此时应用的是默认绑定规则
如果把undefined和null作为绑定对象传给call或者apply,此时应用的是默认绑定规则;
bind
bind(..)会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数。——你不知道的JavaScript(上卷)
硬绑定
应用场景:创建一个包裹函数,传入所有的参数并返回接收到的所有值;这就是ES5中bind的由来
/**
* 硬绑定
* 应用场景:创建一个包裹函数,传入所有的参数并返回接收到的所有值
*/
function foo() {
console.log(`foo: ${this.a}`);
}
global.a = 3; // node
window.a = 3; // 浏览器
const obj = { a: 2 };
function bar() {
// 强制将foo的this绑定到obj,对于bar函数的调用方式不会影响foo函数this的指向,
// 这种显式的强制绑定,成为硬绑定
foo.call(obj);
console.log(`bar: ${this.a}`);
}
bar(); // foo: 2 bar: 3
setTimeout(bar, 100); // foo: 2 bar: node环境中是undefined,浏览器中是3
// 硬绑定的bar不可能再修改它的this
bar.call(global); // foo: 2 bar: 3
bar.call(window); // foo: 2 bar: 3