阅读 14

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

本文参照大神文章,也是自己复习准备面试的小笔记总结
本文主要内容:
1.原型/构造函数/实例/原型链
2.作用域/作用域链复制代码

作用域/作用域链

在讲作用域概念之前,先了解一个概念,执行上下文环境(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);复制代码
结果:
1:function age(){}
2: 99
3: 99

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

JS数据类型

js的数据类型分为基本数据类型和引用数据类型
基本数据类型:number String Boolean undefined null
引用数据类型:Object function Array Date RegExp
  • 数据类型判断
  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"复制代码
  1. . 数字 + 对象
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复制代码

一些读代码的题目

分析下边代码,写出执行结果:
var t = true;
window.setTimeout(function (){
    t = false;
},1000);
while (t){}
alert('end');复制代码
while(true){} 为死循环,所以永远没有alert, 也不会执行setTimeout
2.
var city = "Rome"+function() {
  console.log(city);
  var city = "Paris";
}();复制代码
//undefined 执行console.log的时候,只有city的生命,还没有赋值;所以打印结果为undefined 所谓变量的提升,是指变量声明的提升,而变量赋值并没有提升。
3.
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属性是否出现在该对象的原型链中复制代码

作用域相关的问题:

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


关注下面的标签,发现更多相似文章
评论