web基础
作为一个web开发者,基于HTML、CSS和JS的web基础知识点,不管是初级码农,还是骨灰级专家,在面试时都可能问到。
一个完整的js实现包括啥?
ECMAscript, DOM, BOM
DOCTYPE有什么作用?
用于声明文档使用哪种规范,告诉浏览器使用哪个版本的HTML规范来渲染文档。
行内、块级、空元素各有哪些?
- 行内元素:a span img input select
- 块级元素:div ul ol li dl dt dd h1 p
- 空元素:br hr link meta
标签中src和href的区别?
- src 指向外部资源的位置,请求src资源时会将其下载并应用到文档内;
- href 指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,用于超链接。
HTML5有哪些优势?
- 更多的语义化标签:header、footer、nav、hgroup、article、section、time等;
- 对媒体支持:使用audio和video标签避免先前以插件的方式播放音频、视频带来的麻烦;
- Canvas绘图:实现在HTML页面中绘制图形和图像,且所有的绘图内容都是使用js来控制的;
- SVG绘图技术:矢量图技术,可以无限缩放;
- 实时通信:HTML5提供了对Web Sockets的支持;
- Geolocation: 地理定位,使用浏览器获得客户端所在的地理坐标;
- 客户端本地存储:会话级客户端存储sessionStorage和跨会话级客户端存储localStorage;
- 拖放API:在HTML页面中实现GUI程序中的“拖”和“放”操作,提供了七个新事件;
- 文件离线储存:浏览器就会根据manifest文件的内容下载相应的资源,并进行离线存储。
为什么要重置 浏览器 的css默认属性?
兼容问题,不同浏览器对一些标签的CSS默认值是不同的,如果没对CSS初始化会出现浏览器之间的页面显示差异。
CSS如何计算选择器优先?
- 相同权重,定义最近者为准:行内样式 > 内部样式 > 外部样式;
- 含外部载入样式时,后载入样式覆盖其前面的载入的样式和内部样式;
- 选择器优先级: 行内样式[1000] > id[100] > class[10] > Tag[1];
- 在同一组属性设置中,!important 优先级最高,高于行内样式;
- 继承得到的样式的优先级最低。
介绍使用过的 CSS 预处理器?
- CSS 预处理器的原理:为 CSS 增加了一些编程的特性(变量、逻辑判断、函数等),开发时使用这种方式进行页面的样式设计,部署时再编译成普通的 CSS 文件;
- 使用 CSS 预处理器的好处:可以使 CSS 更加简洁、适应性更强、可读性更佳,无需考虑兼容性;
- 最常用的 CSS 预处理器语言包括:Sass(SCSS)和 LESS, stylus。
var const let 的区别?
- 初始值:const 声明的变量必须设置初始值,且不能重复赋值。
- 重复定义:const 和 let 不支持重复定义
- const,let 支持块级作用域,有效避免变量覆盖
- 变量提升:const 和 let 必须先声明再使用,不支持变量提升
原始类型有哪几种?
在 JS 中,原始类型有:
- boolean
- null
- undefined
- number
- string
- symbol
其特点有:
- 原始类型存储的都是值,是没有函数可以调用的,比如 null.toString(), '100'.toString() 可以使用是因为它被强制转换成了 String 内置对象的类型,所以能调用 toString 函数。
- JS 的 number 类型是浮点类型的,存在 0.1 + 0.2 !== 0.3 的问题。
null 是对象嘛?
- 在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object,这成了 JS 的一个悠久 Bug。
谈谈 typeof ?
- typeof 对于原始类型来说,除了 null 都可以显示正确的类型:
typeof undefined // 'undefined'
typeof null // 'object'
typeof 0 // 'number'
typeof '0' // 'string'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
- typeof 对于引用类型来说,除了函数都会显示 object:
typeof [] // 'object'
typeof {} // 'object'
typeof JSON.parse; // 'function'
可以看到,单独使用 typeof 并不能准确判断变量的类型,要想判断一个变量的类型,需要结合多种手段。
如何判断一个变量是否为数组?
- 内置数组对象上 Array.isArray() 方法;
- instanceof 此方法返回一个布尔值;
- Array.prototype.isPrototypeOf(): 用于指示对象是否存在于一个对象的原型链中,如果存在返回true,反之返回false;
- 构造函数 constructor: variable.constructor.toString().indexOf("Array");
- Object.prototype.toString.call(variable).indexOf('Array'): 调用toString时会将this对象的[[class]]属性值拿到,而这个属性值就是该对象的真实类型。
this几种不同的使用场景?
- 在构造函数中使用(构造函数本身)
- 作为对象属性时使用(调用属性的对象)
- 作为普通函数时使用(window)
- call,apply,bind(执行的第一个参数)
函数声明与函数表达式的区别?
- 在Javscript中,解析器在向执行环境中加载数据时,对函数声明和函数表达式并非是一视同仁的,解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);
- 至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解析执行。
谈谈闭包
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的常见方式为在一个函数内部创建另一个函数。
闭包存在的意义主要是让我们可以间接访问函数内部的变量,弥补JS设计上不够安全的缺陷。
最典型的例子就是如下:
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), i * 1000);
}
我们期待是打印出0,1,2,3,4,最后却打印出了 5个5,这是因为 setTimeout 是个异步函数,会先把循环全部执行完毕再打印,此时 i 就是 5了。
解决办法有很多,比如使用ES6的let替代var,实现块级作用域,兼容性最好的还是使用闭包:
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(() => console.log(j), j* 1000);
)(i)
}
使用立即执行函数将 i 传入函数内部,这个时候值就被固定在了参数 j 上面不会改变,当下次执行 打印 的闭包函数时,就可以使用外部函数的变量 j,从而达到目的。
谈谈浅拷贝?
浅拷贝主要是为了解决使用赋值方式复制引用类型的数据时,改变新变量的值,也会影响原有变量的情况,比如:
let x = {
a: 0
};
let y = x;
y.a = 6
console.log(x.a) // 6
这是因为使用赋值复制时,JS引擎没有分配新内存,新变量指向的还是原变量所存指针的地址,解决这个问题可以使用浅拷贝:
- 通过 Object.assign 来实现:
let x = {
a: 0
};
let y = Object.assign({}, x);
y.a = 6
console.log(x.a) // 0
- 通过展开运算符 ... 来实现:
let x = {
a: 0
};
let y = { ...x };
y.a = 6
console.log(x.a) // 0
谈谈深拷贝
为何会有深拷贝,这是因为浅拷贝不能解决对象属性中包含对象属性的问题,
let x = {
a: 0,
b: {
c: 5
}
};
let y = { ...x };
y.b.c = 6
console.log(x.b.c) // 6
解决这个问题的方式我们称为深拷贝:
- 使用JSON.parse(JSON.stringify()) :有忽略 undefined和symbol、不能序列化函数及不能解决循环引用的局限性;
- 使用 MessageChannel:要求所拷贝的对象含有内置类型且不包含函数;
实现项目中的深拷贝函数,需要考虑好多种边界情况,比如原型链如何处理、DOM 如何处理等,一般都使用 lodash 的深拷贝函数,这里我们只是实现简易版应付面试:
const deepClone = obj => {
let newObj = Array.isArray(obj) ? [...obj] : { ...obj }
Reflect.ownKeys(newObj).forEach(key => {
newObj[key] = (typeof obj === 'object' || typeof obj === 'function') && obj !== null ? deepClone(obj[key]) : obj[key];
})
return newObj;
}
const x = {
a: 0,
b: {
c: 5
}
};
const y = deepClone(x);
y.b.c = 6
console.log(x.b.c) // 0
谈谈组合继承?
组合继承,指的是将原型链和借用构造函数的技术组合到一起, 避免了原型链和借用构造函数的缺点,是JS中最常用的继承模式:
- 思路是使用原型链实现对原型方法的继承,而通过借用构造函数来实现对实例属性的继承。
- 既通过在原型上定义方法实现了函数的复用,又能够保证每个实例都有它自己的属性。
function SuperType(name) {
this.name=name;
this.colors=["red", "blue", "green"];
}
SuperType.prototype.sayName=function() {
alert(this.name);
};
function SubType(name, age) {
//借用构造函数实现对实例属性的继承
SuperType.call(this,name);
this.age=age;
}
//继承方法 使用原型链实现
SubType.prototype=new SuperType();
SubType.prototype.constructor=SubType;
subType.prototype.sayAge=function() {
alert(this.age);
};
const instance1=new SubType("mary", 22);
instance1.colors.push("black");
alert(instance1.colors); //red,blue,green,black
instance1.sayName(); //mary
instance1.sayAge(); //22
const instance2=new SubType("greg", 25);
alert(instance2.colors); //red,blue,green
instance2.sayName(); //greg
instance2.sayAge(); //25
小结
对于工作3年左右的JS工程师,以上的问题,我在几年前的面试过程中基本都被问到,近两年因为看机会基本都是架构师或者负责人,很少会问到。