前端面试题目总结-js相关

298 阅读8分钟

一. this指向问题 

1. 普通调用,this指向为调用者 (谁调用,则this便指向谁)

(1)对象方法的调用,this指向该对象

var obj = {
  a: 'this is obj',
  test: function () {
    console.log(this.a);
  }
}
obj.test();
// this is obj

(2)“单纯”函数调用,this指向window

var a = 'this is window'
function test () {
  console.log(this.a);
}
test();
// this is window

相当于

window.a = 'this is window'
window.test = function test () {
  console.log(this.a);
  // 此时是window为调用者,即this会指向window
}
window.test();

(3)构造函数调用,this指向该构造函数的实例对象

function test () {
  this.a = 'this is test';
  console.log(this.a);
  console.log(this);
}
test();
// this is test
// Window {}

换成构造函数调用

function Test () {
  this.a = 'this is test';
  console.log(this.a);
  console.log(this);
}
var test = new Test();
// this is test
// Test {a: 'this is test'}

2.  call/apply/bind调用,this指向为当前thisArg参数 

(1)call调用

fun.call(thisArg, arg1, arg2, ...)

function Test () {
  this.a = 'this is test';
  console.log(this.a);
  console.log(this);
}
function Test2 () {
  Test.call(this)
}
var test = new Test2();
// this is test
// Test2 {a: 'this is test'}

验证一个对象的类型

var obj = {};
Object.prototype.toString.call(obj) 
//"[object Object]"

类(伪)数组使用数组方法

var divElements = document.getElementsByTagName('div'); //虽然 divElements 有length属性,但是他是一个伪数组,不能使用数组里面的方法
Array.isArray(divElements);// false
 
var domNodes = Array.prototype.slice.call(document.getElementsByTagName('div'));
// 将数组对象Array里的this指向伪数组document.getElementsByTagName('div'), 
//slice() 方法可从已有的数组中返回选定的元素,不传参数是,返回整个数组 
Array.isArray(domNodes);// true

(2)apply调用

fun.apply(thisArg, [argsArray])

var array1 = [12,'foo',{name:'Joe'},-2458];
var array2 = ['Doe' , 555 , 100];
Array.prototype.push.call(array1, array2);
// 这里用 call 第二个参数不会把 array2 当成一个数组,而是一个元素
//等价于array1.push(['Doe' , 555 , 100]);
//array1.length=5;
 
Array.prototype.push.apply(array1, array2); // 这里用 apply 第二个参数是一个数组
// 等价于:  array1.push('Doe' , 555 , 100);
//array1.length=7;

(3)bind调用

var bar = function(){
    console.log(this.x);
}
var foo = {
    x:3
}
bar(); // undefined
var func = bar.bind(foo); //此时this已经指向了foo,但是用bind()方法并不会立即执行,而是创建一个新函数,如果要直接调用的话 可以 bar.bind(foo)()func(); // 3

bind里面的this指向,会永远指向bind到的当前的thisArg

a.bind(b).call(c),最终的this指向会是b

多次 bind() 是无效的

bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的

var bar = function(){
  console.log(this.x);
}
var foo = {
  x:3
}
var sed = {
  x:4
}
var func = bar.bind(foo).bind(sed);
func(); //3
  
var fiv = {
  x:5
}
var func = bar.bind(foo).bind(sed).bind(fiv);
func(); //3

3.  箭头函数,this指向为当前函数的this指向 

function test () {
  (() => {
    console.log(this);
  })()
}
test.call({a: 'this is thisArg'})
// Object {a: 'this is thisArg'}

即使是定时器函数

function test () {
  setTimeout(() => {
    console.log(this);
  }, 0)
}
test.call({a: 'this is obj'})
// Object {a: 'this is obj'}

普通函数的定时器this指向window

function test () {
  setTimeout(function () {
    console.log(this);
  }, 0)
}
test.call({a: 'this is obj'})
// Window {...}

二. 原型&原型链

function Test () {
  this.a = 'this is Test';
}
Test.prototype = {
  b: function () {
    console.log("this is Test's prototype");
  }
}
function Test2 () {
  this.a = 'this is Test2'
}
Test2.prototype = new Test();
var test = new Test2();
test.b();
//this is Test's prototype
console.log(test.prototype);
//undefined
console.log(test);
//Test2 {a: "this is Test2"}

1. 普通对象与函数对象

o1 o2 o3 为普通对象,f1 f2 f3 为函数对象

凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象

var o1 = {}; 
var o2 =new Object();
var o3 = new f1();

function f1(){}; 
var f2 = function(){};
var f3 = new Function('str','console.log(str)');

console.log(typeof Object); //function 
console.log(typeof Function); //function  

console.log(typeof f1); //function 
console.log(typeof f2); //function 
console.log(typeof f3); //function   

console.log(typeof o1); //object 
console.log(typeof o2); //object 
console.log(typeof o3); //object


2. 构造函数

实例的构造函数属性(constructor)指向构造函数

function Person(name, age, job) {
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = function() { alert(this.name) } 
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer');
var person2 = new Person('Mick', 23, 'Doctor');
console.log(person1.constructor == Person); //true
console.log(person2.constructor == Person); //true

3. 原型对象

每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性

function Person() {}
Person.prototype = {
   name:  'Zaxlct',
   age: 28,
   job: 'Software Engineer',
   sayName: function() {
     alert(this.name);
   }
}var person1 = new Person();
person1.sayName(); // 'Zaxlct'

var person2 = new Person();
person2.sayName(); // 'Zaxlct'

console.log(person1.sayName == person2.sayName); //true

在默认情况下,所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的函数(Person)

Person.prototype.constructor == Person

person1.constructor == Person

结论:原型对象(Person.prototype)是 构造函数(Person)的一个实例

原型对象其实就是普通对象(但 Function.prototype 除外,它是函数对象,但它很特殊,他没有prototype属性(前面说道函数对象都有prototype属性))

 var A = new Function ();
 Function.prototype = A;

function Person(){};
 console.log(Person.prototype) //Person{}
 console.log(typeof Person.prototype) //Object
 console.log(typeof Function.prototype) // Function,这个特殊
 console.log(typeof Object.prototype) // Object
 console.log(typeof Function.prototype.prototype) //undefined

原型对象主要作用是用于继承

 var Person = function(name){
    this.name = name; // tip: 当函数执行时这个 this 指的是谁?
  };
  Person.prototype.getName = function(){
    return this.name;  // tip: 当函数执行时这个 this 指的是谁?
  }
  var person1 = new person('Mick');
  person1.getName(); //Mick

4. __proto__

JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__ 的内置属性,用于指向创建它的构造函数的原型对象

person1.__proto__ == Person.prototype

Person.prototype.constructor == Person;
person1.__proto__ == Person.prototype;
person1.constructor == Person;

5. 构造器

可以创建对象的构造器不仅仅有 Object,也可以是 Array,Date,Function

var obj = new Object()
obj.constructor === Object
obj.__proto__ === Object.prototype

var b = new Array();
b.constructor === Array;
b.__proto__ === Array.prototype;

var c = new Date(); 
c.constructor === Date;
c.__proto__ === Date.prototype;

var d = new Function();
d.constructor === Function;
d.__proto__ === Function.prototype;

6. 原型链

person1.__proto__ === Person.prototype

Person.__proto__ === Function.prototype

Person.prototype.__proto__ === Object.prototype

Object.__proto__ === Function.prototypeObject.prototype.__proto__ === null

7. 函数对象

所有函数对象的__proto__都指向Function.prototype,它是一个空函数

Number.__proto__ === Function.prototype  // true
Number.constructor == Function //true

Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true

String.__proto__ === Function.prototype  // true
String.constructor == Function //true

// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Object.__proto__ === Function.prototype  // true
Object.constructor == Function // true

// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true

Array.__proto__ === Function.prototype   // true
Array.constructor == Function //true

RegExp.__proto__ === Function.prototype  // true
RegExp.constructor == Function //true

Error.__proto__ === Function.prototype   // true
Error.constructor == Function //true

Date.__proto__ === Function.prototype    // true
Date.constructor == Function //true

JavaScript中有内置(build-in)构造器/对象共计12个(ES5中新加了JSON),这里列举了可访问的8个构造器

Global不能直接访问,Arguments仅在函数调用时由JS引擎创建

Math,JSON是以对象形式存在的,无需new,它们的proto是Object.prototype

Math.__proto__ === Object.prototype  // true
Math.construrctor == Object // true

JSON.__proto__ === Object.prototype  // true
JSON.construrctor == Object //true

所有的构造器也都是一个普通 JS 对象,可以给构造器添加/删除属性等。同时它也继承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等

Function.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null

8. prototype

var Person = new Object()

PersonObject 的实例,所以 Person 继承Object 的原型对象Object.prototype上所有的方法

var num = new Array()

numArray 的实例,所以 num 继承Array 的原型对象Array.prototype上所有的方法

Array.prototype  //[]

var arrayAllKeys = Array.prototype; // [] 空数组
// 只得到 arrayAllKeys 这个对象里所有的属性名(不会去找 arrayAllKeys.prototype 中的属性)
console.log(Object.getOwnPropertyNames(arrayAllKeys)); 
/* 输出:
["length", "constructor", "toString", "toLocaleString", "join", "pop", "push", 
"concat", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "forEach", 
"some", "every", "map", "indexOf", "lastIndexOf", "reduce", "reduceRight", 
"entries", "keys", "copyWithin", "find", "findIndex", "fill"]
*/

//Array.prototype 虽然没这些方法,但是它有原型对象(__proto__)
var num = [1];
console.log(num.hasOwnPrototype()) // false (输出布尔值而不是报错)

// 上面我们说了 Object.prototype 就是一个普通对象。
Array.prototype.__proto__ == Object.prototype

当我们创建一个函数时

var f = new Function("x","return x*x;");
//当然你也可以这么创建 f = function(x){ return x*x }
console.log(f.arguments) // arguments 方法从哪里来的?
console.log(f.call(window)) // call 方法从哪里来的?
console.log(Function.prototype) // function() {} (一个空的函数)
console.log(Object.getOwnPropertyNames(Function.prototype)); 
/* 输出
["length", "name", "arguments", "caller", "constructor", "bind", "toString", "call", "apply"]
*/

所有函数对象的 __proto__ 都指向 Function.prototype,它是一个空函数(Empty function)

所有对象的 __proto__ 都指向其构造器的 prototype

 JS 内置构造器

var obj = {name: 'jack'}
var arr = [1,2,3]
var reg = /hello/g
var date = new Date
var err = new Error('exception')
 
console.log(obj.__proto__ === Object.prototype) // true
console.log(arr.__proto__ === Array.prototype)  // true
console.log(reg.__proto__ === RegExp.prototype) // true
console.log(date.__proto__ === Date.prototype)  // true
console.log(err.__proto__ === Error.prototype)  // true

自定义的构造器

function Person(name) {
  this.name = name;
}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true

function Person(name) {
    this.name = name
}
// 修改原型
Person.prototype.getName = function() {}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true

function Person(name) {
    this.name = name
}
// 重写原型
Person.prototype = {
    getName: function() {}
}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false

Person.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器ObjectObject.prototype是一个空对象{}{}自然与{getName: function(){}}不等

var p = {}
console.log(Object.prototype) // 为一个空的对象{}
console.log(p.constructor === Object) // 对象直接量方式定义的对象其constructor为Object
console.log(p.constructor.prototype === Object.prototype) // 为true

9. 原型链               

function Person(){}
var person1 = new Person();
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype) //true
console.log(Object.prototype.__proto__) //null

Person.__proto__ == Function.prototype; //true
console.log(Function.prototype)// function(){} (空函数)

var num = new Array()
console.log(num.__proto__ == Array.prototype) // true
console.log( Array.prototype.__proto__ == Object.prototype) // true
console.log(Array.prototype) // [] (空数组)
console.log(Object.prototype.__proto__) //null

console.log(Array.__proto__ == Function.prototype)// true

原型和原型链是JS实现继承的一种模型

原型链的形成是真正是靠__proto__ 而非prototype

 var animal = function(){};
 var dog = function(){};

 animal.price = 2000;
 dog.prototype = animal;
 var tidy = new dog();
 console.log(dog.price) //undefined
 console.log(tidy.price) // 2000

 var dog = function(){};
 dog.prototype.price = 2000;
 var tidy = new dog();
 console.log(tidy.price); // 2000
 console.log(dog.price); //undefined

 var dog = function(){};
 var tidy = new dog();
 tidy.price = 2000;
 console.log(dog.price); //undefined

实例(tidy)和 原型对象(dog.prototype)存在一个连接。不过,要明确的真正重要的一点就是,这个连接存在于实例(tidy)与构造函数的原型对象(dog.prototype)之间,而不是存在于实例(tidy)与构造函数(dog)之间


三. 继承

1. 构造函数继承

function Test () {
  this.a = 'this is test';
  console.log(this.a);
  console.log(this);
}
function Test2 () {
  Test.apply(this)
  // or Test.apply(this)
}
var test = new Test2();
// this is test
// Test2 {a: 'this is test'}


2. 原型链继承

原型链继承,会通过子类prototype属性等于(指向)父类的实例

Child.prototype = new Parent();

var obj = {}; // 初始化一个对象obj。
obj.__proto__ = Parent.prototype; // 将obj的__proto__原型指针指向父类Parent的prototype属性
Parent.call(obj); // 初始化Parent构造函数

function Parent () {
  this.a = 'this is Parent'
}
Parent.prototype = {
  b: function () {
    console.log(this.a);
  }
}
function Child () {
  this.a = 'this is Child'
}
Child.prototype = {
  b: function () {
    console.log('monkey patch');
    Parent.prototype.b.call(this);
  }
}
var test = new Child()
test.b()
// monkey patch
// this is Child


四. 闭包

为了可以访问函数内的局部变量而定义的内部函数

每一个function内都有一个属于自己的执行上下文,即特定的context指向

var a = 'this is window'
function test () {
  var b = 'this is test'
  function test2 () {
    var c = 'this is test2';
    console.log(a);
    console.log(b);
    console.log(c);
  }
  test2();
}
test();
// this is window
// this is test
// this is test2

变量访问的指向是当前context上下文的指向的相反方向,且不可逆

function test () {
  var b = 'this is test';
}
console.log(b); // Uncaught ReferenceError: b is not defined

for循环配合setTimeout的异步任务

function test () {
  for (var i = 0; i < 4; i++) {
    setTimeout(function () {
      console.log(i);
    }, 0)
  }
}
test();
//答案会打印4次4

想依次打印0,1,2,3又该怎么做

function test () {
  for (var i = 0; i < 4; i++) {
    (function (e) {
      setTimeout(function () {
        console.log(e);
      }, 0)
    })(i)
  }
}
test();
// 0 -> 1 -> 2 -> 3

还有一种常见的方式可以实现上面的效果,即从自执行匿名函数中返回一个函数

function test () {
  for(var i = 0; i < 4; i++) {
    setTimeout((function(e) {
      return function() {
        console.log(e);
      }
    })(i), 0)
  }
}
test();