前端技术梳理

222 阅读17分钟

JavaScript

变量和类型

1. JavaScript规定了几种数据类型?

Undefined、Null、String、Number、Boolean、Object、Symbol(ES6)

2. JavaScript对象的底层数据结构是什么?

Java 中的对象可以认为是类的一种实例化结果,而 JavaScript 中并没有类这样的语言构造。JavaScript 中的对象是一个名称与值配对的集合。这种名称与值的配对被称为属性。这样一来,JavaScript 对象可以定义为属性的集合。

3. Symbol类型再实际开发中的应用,手写一个简单的Symbol。

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。

  • 背景

    ES5 的对象属性名都是字符串,这容易造成属性名的冲突。

  • Symbol 特性回顾

1. Symbol 值通过 Symbol 函数生成,使用 typeof,结果为 "symbol"

var s = Symbol('foo');
console.log(typeof s); // "symbol"

2. Symbol 函数前不能使用 new 命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。

3. instanceof 的结果为 false

var s = Symbol('foo');
console.log(s instanceof Symbol); // false

4. Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

var s1 = Symbol('foo');
console.log(s1); // Symbol(foo)

5. 如果 Symbol 的参数是一个对象,就会调用该对象的 toString 方法,将其转为字符串,然后才生成一个 Symbol 值。

const obj = {
  toString() {
    return 'abc';
  }
};
const sym = Symbol(obj);
console.log(sym); // Symbol(abc)

6. Symbol 函数的参数只是表示对当前 Symbol 值的描述,相同参数的 Symbol 函数的返回值是不相等的。

// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();
console.log(s1 === s2); // false
// 有参数的情况
var s1 = Symbol('foo');
var s2 = Symbol('foo');
console.log(s1 === s2); // false

ES2019 提供了一个实例属性description,直接返回 Symbol 的描述。

const sym = Symbol('foo');
sym.description // "foo"

7. Symbol 值不能与其他类型的值进行运算,会报错。

var sym = Symbol('My symbol');
console.log("your symbol is " + sym); 
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string

8. Symbol 值可以显式转为字符串。

var sym = Symbol('My symbol');
console.log(String(sym)); // 'Symbol(My symbol)'
console.log(sym.toString()); // 'Symbol(My symbol)'

9. Symbol 值可以作为标识符,用于对象的属性名,可以保证不会出现同名的属性。

var mySymbol = Symbol();
// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
var a = {
  [mySymbol]: 'Hello!'
};
// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
console.log(a[mySymbol]); // "Hello!"

10. Symbol 作为属性名,该属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols 方法,可以获取指定对象的所有 Symbol 属性名。

var obj = {};
var a = Symbol('a');
var b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
var objectSymbols = Object.getOwnPropertySymbols(obj);
console.log(objectSymbols);
// [Symbol(a), Symbol(b)]

11. 如果我们希望使用同一个 Symbol 值,可以使用 Symbol.for。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。

console.log(s1 === s2); // false

4. JavaScript中的变量再内存中的具体存储形式?

JavaScript中的变量分为基本类型和引用类型基本类型是保存在栈内存中的简单数据段,它们的值都有固定的大小,保存在栈空间,通过按值访问。

引用类型是保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,JavaScript不允许直接访问堆内存中的位置,因此操作对象时,实际操作对象的引用。

5. 基本类型对应的内置对象,以及他们之间的装箱拆箱操作。

  • 内置对象 Object是 JavaScript 中所有对象的父对象 。

    数据封装类对象:Object、Array、Boolean、Number 和 String 。

    其他对象:Function、Math、Date、RegExp、Error。

    特殊的基本包装类型(String、Number、Boolean)。

    arguments: 只存在于函数内部的一个类数组对象。

  • 装箱

    把基本数据类型转化为对应的引用数据类型的操作,装箱分为隐式装箱和显示装箱

    • 隐式装箱

      在《javascript高级程序设计》中有这样一句话: 每当读取一个基本类型的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。(隐式装箱)

      	let a = 'sun'
      	let b = a.indexof('s') // 0 // 返回下标
      
      	// 上面代码在后台实际的步骤为:
          let a = new String('sun')
          let b = a.indexof('s')
          a = null
      
    • 显示装箱 通过内置对象可以对Boolean、Object、String等可以对基本类型显示装箱

      let a = new String('sun')
      
  • 拆箱:

    拆箱和装箱相反,就是把引用类型转化为基本类型的数据,通常通过引用类型的valueof()和toString()方法实现

    let name = new String('sun')
    let age = new Number(24)
    console.log(typeof name) // object
    console.log(typeof age) //  object
    // 拆箱操作
    console.log(typeof age.valueOf()); // number // 24  基本的数字类型
    console.log(typeof name.valueOf()); // string  // 'sun' 基本的字符类型
    console.log(typeof age.toString()); // string  // '24' 基本的字符类型
    console.log(typeof name.toString()); // string  // 'sun' 基本的字符类型
    

6. 理解基本类型和引用类型

数据类型分为基本类型和引用类型:

基本类型:String、Number、Boolean、Null、Undefined

引用类型:Object、Array、Date、Function、Error、RegExp、Math、Number、String、Boolean、Globle。

  • 基本类型

    1. 占用空间固定,保存在栈中
    2. 保存与复制的是本身
    3. 使用typeof检测数据的类型
    4. 基本类型数据是值类型
  • 引用类型

    1. 占用空间不固定,保存在堆中
    2. 保存与复制的是指对象的指针
    3. 使用instanceof检测数据类型
    4. 使用new()方法构造出的对象是引用型

instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

语法

object instanceof constructor

参数

  • object

    某个实例对象

  • constructor

    某个构造函数

7. null和undefined的区别

在 JavaScript 中,null 是 "nothing"。它被看做不存在的事物。typeof 返回 object。

  • 用法:
    • 作为函数的参数,表示该函数的参数不是对象
    • 作为对象原型链的终点

在 JavaScript 中,没有值的变量,其值是 undefined。typeof 也返回 undefined。

  • 用法:
    • 变量被声明了,但是没有赋值
    • 调用函数时,应该提供参数却没有提供,此时该参数等于undefined
    • 对象没有赋值的属性,改属性的值为undefined

8. 至少可以说出三种判断JavaScript数据类型的方式,以及他们的优缺点,如何准确的判断数组类型。

判断数据类型的方法一般可以通过:typeof、instanceof、constructor、toString四种常用方法

  1. typeof:(可以对基本类型做出准确的判断,但对于引用类型,用它就有点力不从心了)

    typeof 返回一个表示数据类型的字符串,返回结果包括:number、boolean、string、object、undefined、function等6种数据类型。

    注意:typeof null会返回object

    typeof null     //"object"
    typeof {}       //"object"
    typeof []       //"object"
    typeof 11     	//"number"
    typeof ''       //"string"
    typeof true     //"boolean"
    typeof Number   //"function"
    
  2. instanceof

    判断对象和构造函数在原型链上是否有关系,如果有关系,返回真,否则返回假

    function Aaa(){}
    
    var a1 = new Aaa();
    console.log( a1 instanceof Aaa);  //true
    //判断a1和Aaa是否在同一个原型链上,是的话返回真,否则返回假
    
    var arr = [];
    console.log( arr instanceof Aaa);//false
    
    var str = 'hello';
    console.log(str instanceof String);//false
    var bool = true;
    console.log(bool instanceof Boolean);//false
    var num = 123;
    console.log(num instanceof Number);//false
    var nul = null;
    console.log(nul instanceof Object);//false
    var und = undefined;
    console.log(und instanceof Object);//false
    var oDate = new Date();
    console.log(oDate instanceof Date);//true
    var json = {};
    console.log(json instanceof Object);//true
    var arr = [];
    console.log(arr instanceof Array);//true
    var reg = /a/;
    console.log(reg instanceof RegExp);//true
    var fun = function(){};
    console.log(fun instanceof Function);//true
    var error = new Error();
    console.log(error instanceof Error);//true
    
    //从上面的运行结果我们可以看到,基本数据类型是没有检测出他们的类型。
    //但是以new形式创建即显示装箱得到的基本类型的实例,是可以检测出类型的。
    var num = new Number(123);
    console.log(num instanceof Number);//true
    var str = new String('abcdef');
    console.log(str instanceof String);//true
    var bool = new Boolean(true);
    console.log(bool instanceof Boolean);//true
    
  3. constructor:查看对象对应的构造函数

    constructor 在其对应对象的原型下面,是自动生成的。当我们写一个构造函数的时候,程序会自动添加:构造函数名.prototype.constructor = 构造函数名

    var str = 'hello';
    console.log(str.constructor == String);//true
    var bool = true;
    console.log(bool.constructor == Boolean);//true
    var num = 123;
    console.log(num.constructor ==Number);//true
    var oDate = new Date();
    console.log(oDate.constructor == Date);//true
    var json = {};
    console.log(json.constructor == Object);//true
    var arr = [];
    console.log(arr.constructor == Array);//true
    var reg = /a/;
    console.log(reg.constructor == RegExp);//true
    var fun = function(){};
    console.log(fun.constructor ==Function);//true
    var error = new Error();
    console.log(error.constructor == Error);//true
    var nul = null;
    console.log(nul.constructor == Object);//报错
    var und = undefined;
    console.log(und.constructor == Object);//报错
    

    从上面的测试中我们可以看到,undefined和null是不能够判断出类型的,并且会报错。因为null和undefined是无效的对象,因此是不会有constructor存在的

    同时我们也需要注意到的是:使用constructor是不保险的,因为constructor属性是可以被修改的,会导致检测出的结果不正确

    function Aaa(){}
    Aaa.prototype.constructor = Aaa;//程序可以自动添加,当我们写个构造函数的时候,程序会自动添加这句代码
    function BBB(){}
    Aaa.prototype.constructor = BBB;//此时我们就修改了Aaa构造函数的指向问题
    console.log(Aaa.construtor==Aaa);//false
    
  4. Object.prototype.toString(可以说不管是什么类型,它都可以立即判断出)

    toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型, 返回的类型

    格式为[object xxx],xxx是具体的数据类型,其中包括:

    String,Number,Boolean,Undefined,Null,Function,Date,Array,RegExp,Error,HTMLDocument,... 基本上所有对象的类型都可以通过这个方法获取到。

     var str = 'hello';
     console.log(Object.prototype.toString.call(str));//[object String]
     var bool = true;
     console.log(Object.prototype.toString.call(bool))//[object Boolean]
     var num = 123;
     console.log(Object.prototype.toString.call(num));//[object Number]
     var nul = null;
     console.log(Object.prototype.toString.call(nul));//[object Null]
     var und = undefined;
     console.log(Object.prototype.toString.call(und));//[object Undefined]
     var oDate = new Date();
     console.log(Object.prototype.toString.call(oDate));//[object Date]
     var json = {};
     console.log(Object.prototype.toString.call(json));//[object Object]
     var arr = [];
     console.log(Object.prototype.toString.call(arr));//[object Array]
     var reg = /a/;
     console.log(Object.prototype.toString.call(reg));//[object RegExp]
     var fun = function(){};
     console.log(Object.prototype.toString.call(fun));//[object Function]
     var error = new Error();
     console.log(Object.prototype.toString.call(error));//[object Error]
    

instanceof和constructor不能跨iframe,上面没有细说,所以下面我们直接上例子喽

例:跨页面判断是否是数组

window.onload = function(){
    var oF = document.createElement('iframe');
    document.body.appendChild( oF );
    var ifArray = window.frames[0].Array;
    var arr = new ifArray();
    console.log( arr.constructor == Array );  	//false
    console.log( arr instanceof Array );        //false
    console.log( Object.prototype.toString.call(arr) == '[object Array]' );  //true
};

从结果中可以看出,constructor和instanceof都没有正确的判断出类型,只有object.prototype.toString.call();正确判断出了

其实面试官也经常喜欢让说一种最简单的判断是数组的方法,记住喽是object.prototype.toString.call()哦!

  • 总结
不同类型的优缺点typeofinstanceofconstructorObject.prototype.toString.call
优点使用简单能检测出引用类型基本能检测所有的类型(除了null和undefined)检测出所有的类型
缺点只能检测出基本类型(出null)不能检测出基本类型,且不能跨iframeconstructor易被修改,也不能跨iframeIE6下,undefined和null均为Object

9. 可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用。

隐式类型转换的场景

  1. 自动转换 Boolean

例如 if 语句 或者其他需要 Boolean 的地方

if (表达式){}
  1. 运算符

在非 Numeber 类型进行数学运算符 - * / 时,会先将非 Number 转换成 Number 类型。 + 运算符要考虑字符串的情况,在操作数中存在字符串时,优先转换成字符串, + 运算符其中一个操作数是字符串的话,会进行连接字符串的操作。

"12" / 2 //6
"12" - 2 //10
"12" * 2 //24
1 + '2' // '12'

+ 操作符的执行顺序是:

  • 当一侧操作数为 String 类型,会优先将另一侧转换为字符串类型。
  • 当一侧操作数为 Number 类型,另一侧为原始类型,则将原始类型转换为 Number 类型。
  • 当一侧操作数为 Number 类型,另一侧为引用类型,将引用类型和 Number 类型转换成字符串后拼接。
  1. 对象

只有在 JavaScript 表达式语句中需要用到数字或字符串时,对象才被隐式转换。 当需要将对象转换为数字时,需要三个步骤

  • 调用 valueOf()。如果结果是原始值(不是一个对象),则将其转换为一个数字。
3*{valueOf:function () { return 5 }} // 15
  • 否则,调用 toString() 方法。如果结果是原始值,则将其转换为一个数字。
3*{toString:function () { return 5 }} // 15
  • 否则,抛出一个类型错误。
3*{toString:function () { return {} }} //TypeError: Cannot convert object to primitive value

如何避免或巧妙应用

  1. 字符串里的数字与数字相加时,可以将Number()对字符串强制类型转换,然后再计算。
  2. 在变量计算前可以先进行类型判断,判断方法可以参考上文。

10. 出现小数精度丢失的原因,JavaScript可以存储的最大数字、最大安全数字,JavaScript处理大数字的方法、避免精度丢失的方法

小数精度丢失的原因

​ 计算机的二进制实现和位数限制有些数无法有限表示。就像一些无理数不能有限表示,如 圆周率 3.1415926...,1.3333... 等。JS 遵循 IEEE 754 规范,采用双精度存储(double precision),占用 64 bit。

JavaScript可以存储的最大数字、最大安全数字

​ 能够表示的最大数字 Number.MAX_VALUE 等于 1.7976931348623157e+308 ,

​ 最大安全数字 Number.MAX_SAFE_INTEGER 等于 9007199254740991

解决方法

​ 对于整数,前端出现问题的几率可能比较低,毕竟很少有业务需要需要用到超大整数,只要运算结果不超过 Math.pow(2, 53) 就不会丢失精度。

​ 对于小数,前端出现问题的几率还是很多的,尤其在一些电商网站涉及到金额等数据。

  • 避免精度丢失

    计算小数时,先乘 100 或 1000,变成整数再运算 如果值超出了安全整数,有一个最新提案,BigInt 大整数,它可以表示任意大小的整数,注意只能表示整数,而不受安全整数的限制

原型和原型链

1. 理解原型设计模式以及JavaScript中的原型规则

javascript的原型规则

1.所有的引用类型(对象,数组,函数)都具有对象特性,既可自由扩展属性(除了null 以外)

var obj = {};
obj.a=100;
var arr = []; 
arr.a=100;
function fn(){};
fn.a = 100;

2.所有的引用类型(对象,数组,函数),都有一个 _proto_属性(隐式原型),属性值是一个普通对象

console.log(obj._proto_)
console.log(arr._proto_)
console.log(fn._proto_)

3.所有的函数,都有一个prototype属性(显式原型),属性值是一个普通对象

console.log(fn.prototype)

4.所有的引用类型(对象,数组,函数),其隐式原型指向构造函数的显式原型( obj.proto === Object.prototype)

console.log(obj._proto_ === Object.Prototype)  // true
console.log(arr._proto_ === Arrray.Prototype)  // true
console.log(fn._proto_ === Function.prtotype)  // true

5.当试图得到一个对象的属性时,如果这个对象本身没有这个属性,那么会去它的 proto (既构造函数的prototype)中去寻找

// 构造函数
function Fn(name){
   this.name = name;
}

// 显示原型
Fn.prototype.alertName = function(){
    console.log(this.name)
}

// 创建实例
var f = new Fn('张三');
f.printName = function(){
    console.log(this.name);
}

f.printName(); // 张三
f.alertName(); // 张三

原型链

当试图得到一个对象obj的某个属性时,如果这个对象本身没有这个属性,那么会去它的 proto (既它的构造函数的prototype)obj._proto_中去寻找;当obj._proto_也没有时,便会在obj.proto.proto(既obj的构造函数的prototype的构造函数的prototype)中去寻找,直到Object.prototype

prototype.png

设计模式

  1. 工厂模式

在函数内创建一个对象,给对象赋予属性及方法再将对象返回

function Person() {
	var People = new Object();
	People.name = 'CrazyLee';
	People.age = '25';
	People.sex = function(){
		return 'boy';
	};
	return People;
}

var a = Person();
console.log(a.name);//CrazyLee
console.log(a.sex());//boy
  1. 构造函数模式

无需在函数内部重新创建对象,而是用this指代

function Person() {
	this.name = 'CrazyLee';
	this.age = '25';
	this.sex = function(){
		return 'boy'
	};
	
}

var a = new Person();
console.log(a.name);//CrazyLee
console.log(a.sex());//boy
  1. 原型模式

函数中不对属性进行定义,利用prototype属性对属性进行定义,可以让所有对象实例共享它所包含的属性及方法。

function Parent() {
	Parent.prototype.name = 'carzy';
	Parent.prototype.age = '24';
	Parent.prototype.sex = function() {
	 var s="女";

    console.log(s);
	}
}

var  x =new  Parent();  
console.log(x.name);      //crazy
console.log(x.sex());       //女
  1. 混合模式

原型模式+构造函数模式。这种模式中,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性

function Parent(){  
	this.name="CrazyLee";  
	this.age=24;  
};
Parent.prototype.sayname=function(){  
	return this.name;  
};

var x =new Parent(); 
console.log(x.sayname());   //Crazy  
  1. 动态原型模式

将所有信息封装在了构造函数中,而通过构造函数中初始化原型,这个可以通过判断该方法是否有效而选择是否需要初始化原型。

function Parent(){  
	this.name="CrazyLee";  
	this.age=24;  
	if(typeof Parent._sayname=="undefined"){     
		Parent.prototype.sayname=function(){  
			return this.name;  
		}  
		Parent._sayname=true;  
	}         
};   

var x =new Parent();  
console.log(x.sayname()); 

2. instanceof的底层实现原理,手动实现一个instanceof

3. 实现继承的几种方式以及他们的优缺点

4. 至少说出一种开源项目(如Node)中应用原型继承的案例

5. 可以描述new一个对象的详细过程,手动实现一个new操作符

6. 理解es6 class构造以及继承的底层实现原理

HTML和CSS

HTML

1. 从规范的角度理解HTML,从分类和语义的角度使用标签

2. 常用页面标签的默认样式、自带属性、不同浏览器的差异、处理浏览器兼容问题的方式

3. 元信息类标签(head、title、meta)的使用目的和配置方法

4. HTML5离线缓存原理

5. 可以使用Canvas API、SVG等绘制高性能的动画

CSS

1. CSS盒模型,在不同浏览器的差异

w3c中的盒子模型的宽*:包括margin+border+padding+width;*

1、width:margin*2+border*2+padding*2+width;

2、height:margin*2+border*2+padding*2+height;

IE中的盒子模型的宽*:包括margin+border+padding+width;*

1、width:margin*2+width;

2、height:margin*2+height;

css可以根据需要选择不同的盒模型:box-siziing

box-sizing: content-box|border-box|inherit;
描述
content-box这是由 CSS2.1 规定的宽度高度行为。宽度和高度分别应用到元素的内容框。在宽度和高度之外绘制元素的内边距和边框。
border-box为元素设定的宽度和高度决定了元素的边框盒。就是说,为元素指定的任何内边距和边框都将在已设定的宽度和高度内进行绘制。通过从已设定的宽度和高度分别减去边框和内边距才能得到内容的宽度和高度。
inherit规定应从父元素继承 box-sizing 属性的值。
div{
    box-sizing:border-box;
    -moz-box-sizing:border-box; /* Firefox */
    -webkit-box-sizing:border-box; /* Safari */
    width:50%;
    float:left;
}

2. CSS所有选择器及其优先级、使用场景,哪些可以继承,如何运用at规则

3. CSS伪类和伪元素有哪些,它们的区别和实际应用

4. HTML文档流的排版规则,CSS几种定位的规则、定位参照物、对文档流的影响,如何选择最好的定位方式,雪碧图实现原理

5. 水平垂直居中的方案、可以实现6种以上并对比它们的优缺点

6. BFC实现原理,可以解决的问题,如何创建BFC

7. 可使用CSS函数复用代码,实现特殊效果

8. PostCSS、Sass、Less的异同,以及使用配置,至少掌握一种

9. CSS模块化方案、如何配置按需加载、如何防止CSS阻塞渲染

10. 熟练使用CSS实现常见动画,如渐变、移动、旋转、缩放等等

11. CSS浏览器兼容性写法,了解不同API在不同浏览器下的兼容性情况

12. 掌握一套完整的响应式布局方案

手写

1. 手写图片瀑布流效果

2. 使用CSS绘制几何图形(圆形、三角形、扇形、菱形等)

3. 使用纯CSS实现曲线运动(贝塞尔曲线)

4. 实现常用布局(三栏、圣杯、双飞翼、吸顶),可是说出多种方式并理解其优缺点

本系列会慢慢更新,博友们有疑问可以提,若文章有啥纰漏也请大牛们指点一下。