前端面试准备--基础篇之js

279 阅读7分钟

本文参照大神文章,也是自己复习准备面试的小笔记总结

本文主要内容:

1、js的数据类型
2、变量提升
3、作用域和作用域链
4、柯里化
5、深克隆、浅克隆

JS数据类型

js的数据类型分为基本数据类型和引用数据类型

基本数据类型:String、number、Boolean、undefined、null、Symbol(es6d的新类型)

引用数据类型:Object function Array Date RegExp

  • 各个数据类型常用的方法有哪些?
  1. String:

split(切割,不改变原值)、substring(截取)、slice(截取,不改变原值)

  • 数据类型判断
  1. typeof方法可以检测number String Boolean undefined 这四种基本数据类型,还可以检测出object和function两种引用数据类型

  2. null, String(null)

  3. 检测object的方法,Object.prototype.toString.call(obj).slice(8,-1).toLocaleLowerCase()

  • 数据类型转换
  1. -、*、/、%:一律转换成数值型再计算

  2. +: 数字 + 字符串 = 字符串

    var w = 1 + 'aaa' // "1aaa"

  3. . 数字 + 对象

    var w = 2 + {a: 1} // "2[object Object]"

数字 + Boolean/null

w = 1 + false
//1
w = 1 + true
//2
w = 1 + null
//1

数字 + undefined

w = 1 + undefined
//NaN

其他类型的数据转换

[1,2,3].toString()
//"1,2,3"
Object.prototype.toString.call({x:'a'})
//"[object Object]"
NaN !== NaN //true

变量提升

变量提升:提升变量和函数的定义,并没有提升变量的赋值。在提升变量定义的时候先给设置一个初始值undefined。

showName()
console.log(myname)
var myname = '变量'
function showName() { console.log('函数showName被执行');}

执行结果:

作用域/作用域链

在讲作用域概念之前,先了解一个概念,执行上下文环境(Execution Context)

  • 执行上下文环境(Execution Context)

包含三部分:

  1. 变量对象:执行上下文的一部分,可以抽象为一种数据作用域,存储该执行上下文中的所有变量和函数声明(不包含函数表达式)。

活动对象(AO):当变量对象处于active EC的时候,称其为活动对象。 说到活动对象,又要讲一下js的词法执行顺序: 每个函数执行的瞬间,就会生成一个对象,即活动对象AO 1. 先分析参数: 函数先接收形参,将这些形参赋值给AO的属性,初始值都为undefined 然后接受实参,将实参赋值给属性; 2.分析变量的声明: 如果AO里边没有这个属性,则新增该属性;若存在,则不作任何处理; 3.分析函数 如果AO属性里边没有该属性,则新增属性;若存在,则被新值覆盖;

function t(age) {
    console.log(age);//1
    var age = 99;
    console.log(age);//2
    function age() {
        
    }
    console.log(age);//3
}
t(5);

结果:

1function age(){}
2: 99
3: 99

分析:
这时候还涉及到一个变量提升的问题;在同一作用域,变量和函数优先声明,但是赋值不优先;
按照上边的过程一步步分析:
1. 在t函数被调用的瞬间会生成一个活动变量AO,分析形参,将age付给AO的一个属性,并且赋值为52. 在函数内部分析变量,age变量和函数的声明都提升到函数的最上边,也就是1的上边;
当声明变量的时候,发现AO的属性里有age这个属性,不做任何操作;
然后声明函数,声明函数的时候,发现有该属性,则把函数赋值给该属性;
所以在1打印的时候AO.age为age函数
3.执行到age = 99的时候,又将AO.age重新赋值给99
所以2打印为992-3之间没有做任何改变值的操作,所以3处也为99

2. 作用域链

当使用变量时,会在执行上下文访问到

3. this指向

类型:

  1. 全局执行上下文

  2. 函数执行上下文

  3. eval执行上下文

代码执行过程:

  1. 创建全局上下文(global EC)

  2. 全局执行上下文(caller)逐行自上而下执行,遇到函数时,函数执行上下文被push到执行栈的顶层

  3. 函数执行上下文被激活,成为active EC,开始执行函数中的代码,caller被挂起

  4. 函数执行完,callee被pop移除出执行栈,控制权交换给全局上下文(caller),继续执行

  • 作用域是什么?

含义:该上下文中声明的变量和函数声明的作用范围。

分类:块级作用域和函数作用域

这时候

var a = 1; 
function b() {
  console.log(a);
}
function c() {
  var a = 3; 
  b();
}
c();
  • 闭包

可以理解为:父函数被销毁的情况下,返回出的子函数的[[scope]]中仍然保留着父级的单变量对象和作用域链,因此可以继续访问到父级的变量对象,这样的函数称为闭包。

闭包由于存在局部变量中一直不能被释放,所以对性能有负面影响,所以能不用闭包尽量不用。

function test(){
 for(var i = 0;i < 5; i++) {
  setTimeout(() => {
    console.log(i);
  },10)
 }
}

test();

输出:5,5,5,5,5

因为setTimeout 是一个异步操作,当settimeout的回调被执行的时候,i已经为5了

为了解决这种问题,可以选择闭包

function test(){
 for(var i = 0;i < 5; i++) {
  (function(val){
    setTimeout(() => {
      console.log(i);
    },10)
  })(i);
 }
}

test();

另外举一个闭包的例子

function count() {
 var count = 0;
 var add = function() {
   count++;
   console.log(count);
 }
 return add;
}

var add = count();
add();// 1
add();// 2

柯里化

简单来说,就是一个函数本来需要传多个参数,通过柯里化的实现,每次只需要传部分参数。

举例:

// say anyword to anyone
// 正常情况是
function Hello(word, name) {
 console.log(name + word);
}
Hello('hi','andy');

柯里化之后的代码:

function Hello(word) {
 return function(name) {
   console.log(name + word);
 }
}

var sayHi = Hello('hi');
sayHi('Jack');

深克隆,浅克隆

浅克隆和深克隆的区别就是,如果属性值为引用数据类型的时候,没有改变指针;

一个简单的深度克隆代码:

function deepClone(obj) {
	let copy;

	// Handle the 3 simple types, and null or undefined
	if (null == obj || "object" != typeof obj) return obj;

	// Handle Date
	if (obj instanceof Date) {
		copy = new Date();
		copy.setTime(obj.getTime());
		return copy;
	}

	// Handle Array
	if (obj instanceof Array) {
		copy = [];
		for (let i = 0, len = obj.length; i < len; i++) {
			copy[i] = deepClone(obj[i]);
		}
		return copy;
	}

	// Handle Object
	if (obj instanceof Object) {
		copy = {};
		for (let attr in obj) {
			if (obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr]);
		}
		return copy;
	}

	throw new Error("Unable to copy obj! Its type isn't supported.");
}

一些读代码的题目

分析下边代码,写出执行结果:

var t = true;
window.setTimeout(function (){
    t = false;
},1000);
while (t){}
alert('end');

while(true){} 为死循环,所以永远没有alert, 也不会执行setTimeout

var city = "Rome"+function() {
  console.log(city);
  var city = "Paris";
}();

//undefined 执行console.log的时候,只有city的生命,还没有赋值;所以打印结果为undefined 所谓变量的提升,是指变量声明的提升,而变量赋值并没有提升。

var name = "The Window";
var object = {
    name: "My Object",
    getNameFunc: function () {
        return function () {
            return this.name;
        };
    }
};
alert(object.getNameFunc()());

// The Window

4.当点击button#2的时候,console中会打印什么?

var nodes = document.getElementsByTagName('button');
// assume there is totally 5 nodes
for (var i = 0; i < nodes.length; i++) {
    nodes[i].addEventListener('click', function() {
        console.log('You clicked element #' + i);
    });
}

结果为'you clicked element #5'。

怎样解决这种问题呢?

最简单的方式是将var 改成let生成块级作用域。

生成局部作用域的方式还有一种就是封装函数

var parentClassEle = document.getElementsByClassName('parent');
for(var i = 0; i < parentClassEle.length; i++) {
   addClick(i);
}
        
function addClick(i) {
   var j = i;
   parentClassEle[i].addEventListener('click', function() {
     console.log(i)
   })
}

5.考察原型原型链

function Mammal(name) {
    this.name = name;
    this.offspring = [];
}
Mammal.prototype.haveABaby = function () {
    var newBaby = new Mammal("Baby " + this.name);
    this.offspring.push(newBaby);
    return newBaby;
}

Mammal.prototype.toString = function () {
    return '[Mammal "' + this.name + '"]';
};     // 到目前为止,这  是一个Mammal对象的实现。

 

// 将Employee的原型指向Person的一个实例

// 因为Person的实例可以调用Person原型中的方法, 所以Employee的实例也可以调用Person原型中的所有属性。
Cat.prototype = new Mammal();

//修正constructor
Cat.prototype.constructor = Cat;
function Cat(name) {
    this.name = name;
}

Cat.prototype.toString = function () {
    return '[Cat "' + this.name + '"]';
}  // 到目前为止,这是Mammal的一个子类Cat。


var someAnimal = new Mammal('Mr. Biggles');
var myPet = new Cat('Felix');
alert(someAnimal);   
alert(myPet);        

myPet.haveABaby();
alert(myPet.offspring.length);      
alert(myPet.offspring[0]);

一些手写代码题

1.写一个方法,测试数组的每个元素是不是比2大?

function ArrayItemBig2(arr) {
   arr.every(val => {
      return val > 2;
   })
}

得到一个新的数组,得到比2大的元素

function ArrayFilterBiger2(arr) {
   var newArr = [];
   arr.forEach(element => {
     if(Number(element) > 2) {
        newArr.push(element);
      }
   });
   return newArr;
}

和对象相关的题目:

  • 生成对象的方式有几种?

    1. 字面量 var obj = {}
    2. new的方式 var obj = new Object()
    3. 定义原型对象 var obj = Object.create({a: 1}) //obj.proto = {a: 1}
  • 写一个构造函数

    function Person(name) { this.name = name; }

    Person.prototype = { getName: function() { console.log(thit.name) } }

  • 什么是prototype?和__proto__有什么区别

    prototype是构造函数的一个属性, 只有函数才有prototype属性; __proto__是对象的一个属性,每一个Object都有__proto__属性; 原型链是取决于__proto__的,而非prototype。

  • new一个对象都发生了哪些操作?

    例: var child = new Person(); 1.先创建一个空对象child = {} 2.将child.proto = Person.prototype 3.将this指向改为child

  • constructor是什么?

constructor就是原型的构造函数

下边可以通过这个图深刻理解一下prototype、__proto__、 constructor、之间的关系

  • instanceof

    例: child instanceof Person 为了检测构造函数的prototype属性是否出现在该对象的原型链中

作用域相关的问题:

作用域分为局部作用域和全局作用域。