html
Doctype作用?标准模式与兼容模式各有什么区别?
-
声明位于HTML文档的第一行,告知浏览器的解析器用什么文档标准解析这个文档。DOCTYPE不存在或格式不正确会导致文档以兼容模式呈现。
-
标准模式的排版和JS运作模式都是以该浏览器支持的最高标准运行。在兼容模式中,页面以宽松的向后兼容的方式显示,模拟老式浏览器的行为以防止站点无法工作。简单的说,就是尽可能的能显示东西给用户看。
HTML5 为什么只需要写 ?
HTML5 不基于 SGML,因此不需要对DTD进行引用,但是需要doctype来规范浏览器的行为(让浏览器按照它们应该的方式来运行);
而HTML4.01基于SGML,所以需要对DTD进行引用,才能告知浏览器文档所使用的文档类型。
HTML的语义化
语义化是指使用恰当语义的html标签,让页面具有良好的结构与含义;比如<p>标签就代表段落,<article>代表正文内容等等。 语义化的作用有两点:
什么是盒子模型?CSS-标准盒模型和怪异盒模型的区别?哪个css可以改变盒子模型?
css盒子模型 又称为框模型(Box Model),包含了元素内容(content)、内边距(padding)、边框(border)、外边距(margin)几个要素。
区别:当不对doctype进行定义时,会触发怪异模式。
在标准模式下,一个块的总宽度= width + margin(左右) + padding(左右) + border(左右) 在怪异模式下,一个块的总宽度= width + margin(左右)(即width已经包含了padding和border值)
javaScript
什么是闭包?闭包的优缺点
答:闭包是将外部作用域中的局部变量封闭起来的函数对象。被封闭起来的变量与封闭它的函数对象有相同的生命周期。 优点:一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在f1调用后被自动清除。
缺点:
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
地址栏输入一个URL后会发生什么?
参考 【peanutYu96】的文章: juejin.cn/post/684490…
js数据类型
1.基本类型
基本类型主要是: Undefined、Boolean、String、Number、Null、Symbol(ECMAScript 6 新定义)
- 存放在栈中
- 基本类型存储在栈内存中,数据大小确定,内存空间大小可以分配,按值存放,所以可直接访问
- 基本类型的比较是值的比较
2. 引用类型
引用数据类型统称为 Object 对象,主要包括对象、数组、函数、日期和正则
- 存放在堆中, 堆内存中是无序存放
引用类型存放在堆内存中,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况进行特定的分配,例如:
var person1 = {name: 'joj'};
var person2 = {name: 'xiaomi'};
var person3 = {name: 'xiaoyang'};
- 引用类型的比较是引用的比较
每次我们对js中的引用类型进行操作的时候,都是操作其对象的引用(保存在栈内存中的指针),所以比较两个引用类型,看是否指向同一个对象。例如:
var a = [1, 2, 3];
var b = [1, 2, 3];
console.log(a === b); //false
虽然变量a,b表示的是同一个内容,但其在内存中的位置不一样,指向的不是同一个对象,所以不相等。
数据类型的判断
- typeof 返回一个表示数据类型的字符串,返回结果包括:number、boolean、string、symbol、object、undefined、function等7种数据类型,但不能判断null、array等
typeof Symbol(); // symbol 有效
typeof ''; // string 有效
typeof 1; // number 有效
typeof true; //boolean 有效
typeof undefined; //undefined 有效
typeof new Function(); // function 有效
typeof null; //object 无效
typeof [] ; //object 无效
typeof new Date(); //object 无效
typeof new RegExp(); //object 无效
- instanceof 用来判断A是否为B的实例,A instanceof B, 返回 boolean 值。instanceof 用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性,但它不能检测 null 和 undefined
[] instanceof Array; //true
{} instanceof Object;//true
new Date() instanceof Date;//true
new RegExp() instanceof RegExp//true
null instanceof Null//报错
undefined instanceof undefined//报错
- 最准确最常用 Object.prototype.toString.call()
Object.prototype.toString.call('') ; // [object String]
Object.prototype.toString.call(1) ; // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
原型与继承
题目
请用js实现一个类P,包含成员变量a,成员变量b,成员函数sum,sum输出a与b的和,a,b默认值都为0。实现一个类M,M继承自P,在P的基础上增加成员变量c,成员函数sum变成输出a,b,c的和。
题目分析
Js所有的函数都有一个prototype属性,这个属性引用了一个对象,即原型对象,也简称原型。这个函数包括构造函数和普通函数,我们讲的更多是构造函数的原型,但是也不能否定普通函数也有原型,实现继承的方法很多,这里使用原型链和构造继承,即组合继承的方式。
function P(a, b) {
this.a = a || 0;
this.b = b || 0;
this.sum = function() {
return this.a + this.b;
}
}
function M(a, b, c) {
P.call(this, a, b) //继承P类的成员对象
this.c = c; //在自己的构造函数中定义的
this.sum = function() {
return this.a + this.b + this.c;
}
}
M.prototype = new P();
var m = new M(2, 2, 2);
M.sum(); //输出6
js继承的实现方式
既然要实现继承,那么首先我们得有一个父类,代码如下:
//定义一个动物类
function Animal(name, eye, skin) {
//属性
this.name = name || 'Animal';
this.eye = eye;
this.skin = skin;
//实例方法
this.sleep = function() {
console.log(this.name + '正在睡觉');
}
}
//原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
下面给大家列出几种继承方式的实现
原型链继承
实现父类代码在(js继承的实现方式中)
核心: 将父类的实例作为子类的原型
function Cat() {}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
//Test Code
var cat = new Cat();
console.log(cat.name); //cat
console.log(cat.eat('fish')); //cat正在吃:fish
console.log(cat.sleep()); //cat正在睡觉!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
特点:
1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
2. 父类新增原型方法/原型属性,子类都能访问到
3. 简单,易于实现
缺点:
1. 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
2. 无法实现多继承
3. 来自原型对象的引用属性是所有实例共享的
4. 创建子类实例时,无法向父类构造函数传参(即无法像这样var cat=new Cat(hair,eye,skin)传参给父类)
推荐指数:★★(3、4两大致命缺陷)
构造继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Cat(name) {
Animal.call(this);
this.name = name || 'Tom';
}
//Test Code
var cat = new Cat();
console.log(cat.name); //Tom
//console.log(cat.eat('fish')); //报错
console.log(cat.sleep()); //Tom正在睡觉!
console.log(cat instanceof Animal); //false
console.log(cat instanceof Cat); //true
特点:
1. 解决了1中,子类实例共享父类引用属性的问题
2. 创建子类实例时,可以向父类传递参数(可通过Animal.call(this,name,eye,skin)或者Animal.apply(this,[name,eye,skin])实现)
3. 可以实现多继承(call多个父类对象)
缺点:
1. 实例并不是父类的实例,只是子类的实例
2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
推荐指数:★★(缺点3)
实例继承
核心:为父类实例添加新特性,作为子类实例返回
function Cat(name) {
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
}
//Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep()); //Tom正在睡觉!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //false
特点:
不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果
缺点:
实例是父类的实例,不是子类的实例
不支持多继承
推荐指数:★★
拷贝继承
function Cat(name) {
var animal = new Animal();
for (var p in animal) {
Cat.prototype[p] = animal[p];
}
Cat.prototype.name = name || 'Tom';
}
//Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep()); //Tom正在睡觉!
console.log(cat instanceof Animal); //false
console.log(cat instanceof Cat); //true
特点:
支持多继承
缺点:
1. 效率较低,内存占用高(因为要拷贝父类的属性)
2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
推荐指数:★(缺点1)
组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name) {
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
//Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep()); //Tom正在睡觉!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
特点:
1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
2. 既是子类的实例,也是父类的实例
3. 不存在引用属性共享问题
4. 可传参
5. 函数可复用
缺点:
调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
推荐指数:★★★★(仅仅多消耗了一点内存)
寄生组合继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function Cat(name) {
Animal.call(this);
this.name = name || 'Tom';
} (function() {
// 创建一个没有实例方法的类
var Super = function() {};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();
//Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep()); //Tom正在睡觉!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
特点:
堪称完美
缺点:
实现较为复杂
推荐指数:★★★★(实现复杂,扣掉一颗星)
4.9 附录代码
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
//实例引用属性
this.features = [];
}
function Cat(name){
}
Cat.prototype = new Animal();
var tom = new Cat('Tom');
var kissy = new Cat('Kissy');
console.log(tom.name); // "Animal"
console.log(kissy.name); // "Animal"
console.log(tom.features); // []
console.log(kissy.features); // []
tom.name = 'Tom-New Name';
tom.features.push('eat');
//针对父类实例值类型成员的更改,不影响
console.log(tom.name); // "Tom-New Name"
console.log(kissy.name); // "Animal"
//针对父类实例引用类型成员的更改,会通过影响其他子类实例
console.log(tom.features); // ['eat']
console.log(kissy.features); // ['eat']
原因分析:
关键点:属性查找过程
执行tom.features.push,首先找tom对象的实例属性(找不到),
那么去原型对象中找,也就是Animal的实例。发现有,那么就直接在这个对象的
features属性中插入值。
在console.log(kissy.features); 的时候。同上,kissy实例上没有,那么去原型上找。
刚好原型上有,就直接返回,但是注意,这个原型对象中features属性值已经变化了。
原型与原型链
JavaScript 中,万物皆对象!但对象也是有区别的。分为普通对象和函数对象,Object ,Function 是JS自带的函数对象。 怎么区分?其实很简单,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。Function Object 也都是通过 New Function()创建的。
构造函数
构造函数模式的目的就是为了创建一个自定义类,并且创建这个类的实例。构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立的,即实例识别。
构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写。另外就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用。
原型
在JavaScript中,每当定义一个函数数据类型(普通函数、类)时候,都会天生自带一个prototype属性,这个属性指向函数的原型对象,并且这个属性是一个对象数据类型的值。
让我们用一张图表示构造函数和实例原型之间的关系:
原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。
原型链
1. __proto__和constructor
每一个对象数据类型(普通的对象、实例、prototype......)也天生自带一个属性__proto__,属性值是当前实例所属类的原型(prototype)。原型对象中有一个属性constructor, 它指向函数对象。
function Person() {}
var person = new Person()
console.log(person.__proto__ === Person.prototype)//true
console.log(Person.prototype.constructor===Person)//true
//顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
2.何为原型链
在JavaScript中万物都是对象,对象和对象之间也有关系,并不是孤立存在的。对象之间的继承关系,在JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链。
我们可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性;使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
function Person() {}
Person.prototype.a = 123;
Person.prototype.sayHello = function () {
alert("hello");
};
var person = new Person()
console.log(person.a)//123
console.log(person.hasOwnProperty('a'));//false
console.log('a'in person)//true
person实例中没有a这个属性,从 person 对象中找不到 a 属性就会从 person 的原型也就是 person.proto ,也就是 Person.prototype中查找,很幸运地得到a的值为123。那假如 person.__proto__中也没有该属性,又该如何查找?
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层Object为止。Object是JS中所有对象数据类型的基类(最顶层的类)在Object.prototype上没有__proto__这个属性。
console.log(Object.prototype.__proto__ === null) // true
new关键字
假设已经定义了父类Base对象
我们执行如下代码
var obj = new Base();
这样代码的结果是什么,我们在Javascript引擎中看到的对象模型是:
new操作符具体干了什么呢?其实很简单,就干了三件事情。
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
第一行,我们创建了一个空对象obj
第二行,我们将这个空对象的__proto__成员指向了Base函数对象prototype成员对象
第三行,我们将Base函数对象的this指针替换成obj,然后再调用Base函数
注意:new的过程会执行构造函数Base() 再对空对象进行构造
如何实现图片懒加载
图片懒加载的原理很简单,就是我们先设置图片的data-set属性(当然也可以是其他任意的,只要不会发送http请求就行了,作用就是为了存取值)值为其图片路径,由于不是src,所以不会发送http请求。 然后我们计算出页面scrollTop的高度和浏览器的高度之和, 如果图片举例页面顶端的坐标Y(相对于整个页面,而不是浏览器窗口)小于前两者之和,就说明图片就要显示出来了(合适的时机,当然也可以是其他情况),这时候我们再将 data-set 属性替换为 src 属性即可。
同源与跨域
同源
同源策略: 同协议、同域名、同端口
同源策略目的:为了保证用户信息的安全,不被恶意的网站窃取数据
如果是非同源,共有三种行为受到限制:
1、ajax请求
2、dom无法获取
3、cookie、localStorage、indexDB无法读取
虽然这些限制是合理且必需的,但是有时候一些合理地用途也会受到限制
跨域
常见解决跨域方案:
1、webpack的proxyTable方案
在一般项目中都会有webpack对应的开发环境的配置文件:webpack.dev.js,在配置项中加入ProxyTable的配置项即可:
proxy: {
'/api': {
changeOrigin: true,
target: 'http://******.*****.com',
}
}
如果前后端前缀不匹配或者后端前缀不统一的情况,可以增加pathRewrite属性来统一:
proxy: {
'/EntryApp': {
changeOrigin: true,
target: 'http://******.*******.com',
pathRewrite: {"^/EntryApp": "/EntryApp"}
},
}
2. cors
该方案一般由由后端同学配置处理
3. nginx配置
Nginx是一个免费的,开源的高性能的HTTP和反向代理服务器。
nginx.conf是主配置文件,有若干个部分组成,每一部分都用{}区分。主要包括:
- main:nginx的全局配置,对全局生效
- events:影响nginx服务器或与用户的网络连接
- http:可以嵌套多个server,配置代理,缓存,日志等
- server:配置虚拟主机的相关参数,一个http可以有多个server
nginx解决跨域的基本方法是在sever中配置proxy_pass:
// 前端服务的域名为 fe.**.com
// 后端服务的域名为 dev.**.com
server {
listen: 80,
server_name: fe.**.com,
location / {
proxy_pass dev.**.com
}
}
根据实际需求,还可以添加一些其他的指令,比如:
- proxy_connect_timeout:nginx从接受请求至连接到上游服务器的最长等待时间
- proxy_cookie_domain:替代上游服务器的set_cookie头的domain属性
- proxy_cookie_path:替代上游服务器的set_cookie头的path
- proxy_set_header:重写发送到上游服务器头的内容,也可以通过将某个头部的值设置为空字符串,而不发送某个头部的方式发放实现
4.jsonp
基本思路:网页通过添加一个 <script> 元素,向服务器请求JSON数据,并且该请求的查询字符串有一个callback 参数,用来指定回调函数的名字,这种做法不受同源政策限制; 服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
提问: JSONP 为什么不支持 POST?
- 因为 JSONP 是通过动态创建 script标签实现的
- 创建
<script>只能发送 get 请求
cookie 和 session
会话跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端记录信息确定用户身份。
cookie相关参数:
- Name:Cookie 的名称
- Value:Cookie 的值
- Domain:Cookie 的域
域名只能为一个,不能有多个。但是可以设置域名等级,例如,domain 为 A.study.com,那么只有A.study.com能获取到这个 Cookie;domain 为 .study.com,那么 A.study.com 以及B.study.com 都能获取到这个 Cookie。
- Path:路径
默认为/,你也可以设置为/login,那么/login可以获取到/login以及/下的 Cookie,而/只能获取到/下的 Cookie。
- Expires/Max-age:Cookie的过期时间
默认为一次会话,关闭浏览器一会后就消失,也可以自己设置。
- Size:Cookie 的大小
- HttpOnly:JS脚本是否可以获取(默认可以获取,为false)
若设置为 true,JS 脚本将无法获取 Cookie,能有效的防止XSS攻击。
- Secure:JS脚本是否可以获取(默认可以获取,为false)
若为 true,那么 Cookie 只能在HTTPS连接中传输;若为 false,HTTP、HTTPS连接都行。
- SameSite:限制第三方cookie
有3个值:Strict/Lax/None。chrome51新增,chrome80+强制执行。
- Strict: 仅允许发送同站点请求的的cookie;
- Lax: 仅get请求跨站(chrome 80+之后的默认值)。
- None: 任意发送cookie,但是chrome 80+之后,设置为None,需要同时设置Secure,意味着网站必须采用https
Cookie 与 webStorage 的区别
| Cookie | sessionStorage | localStorage | |
|---|---|---|---|
| 有效期 | 默认会话(关闭浏览器后消失,也可设置时间) | 会话(关闭浏览器一会后消失) | 始终有效 |
| 作用域 | 同域(端口号和协议可以不同,后面会说明) | 当前页面 | 同源 |
| 大小 | 4kb 左右(各浏览器不同) | 5mb(各浏览器不同) | 5mb(各浏览器不同) |
| 兼容性 | 用户可禁用 | ie8+ | ie8+ |
session
session是一种服务度机制,类似散列表结构来存储用户数据
浏览器第一次向客户端发送请求时,服务器会自动生成一个session和sessionid
sessionid唯一标识这个session
服务器响应时把sessionid发送给浏览器
浏览器第二次向服务器发送请求时就会携带这个sessionid
服务器通过这个id找到对应的session获取用户数据
浅拷贝与深拷贝
原生ajax 封装
<script type="text/javascript">
function ajax (options) {
// 存储的是默认值
var defaults = {
type: 'get',
url: '',
data: {},
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
success: function () {},
error: function () {}
};
// 使用options对象中的属性覆盖defaults对象中的属性
Object.assign(defaults, options);
// 创建ajax对象
var xhr = new XMLHttpRequest();
// 拼接请求参数的变量
var params = '';
// 循环用户传递进来的对象格式参数
for (var attr in defaults.data) {
// 将参数转换为字符串格式
params += attr + '=' + defaults.data[attr] + '&';
}
// 将参数最后面的&截取掉
// 将截取的结果重新赋值给params变量
params = params.substr(0, params.length - 1);
// 判断请求方式
if (defaults.type == 'get') {
defaults.url = defaults.url + '?' + params;
}
// 配置ajax对象
xhr.open(defaults.type, defaults.url);
// 如果请求方式为post
if (defaults.type == 'post') {
// 用户希望的向服务器端传递的请求参数的类型
var contentType = defaults.header['Content-Type']
// 设置请求参数格式的类型
xhr.setRequestHeader('Content-Type', contentType);
// 判断用户希望的请求参数格式的类型
// 如果类型为json
if (contentType == 'application/json') {
// 向服务器端传递json数据格式的参数
xhr.send(JSON.stringify(defaults.data))
}else {
// 向服务器端传递普通类型的请求参数
xhr.send(params);
}
}else {
// 发送请求
xhr.send();
}
// 监听xhr对象下面的onload事件
// 当xhr对象接收完响应数据后触发
xhr.onload = function () {
// xhr.getResponseHeader()
// 获取响应头中的数据
var contentType = xhr.getResponseHeader('Content-Type');
// 服务器端返回的数据
var responseText = xhr.responseText;
// 如果响应类型中包含applicaition/json
if (contentType.includes('application/json')) {
// 将json字符串转换为json对象
responseText = JSON.parse(responseText)
}
// 当http状态码等于200的时候
if (xhr.status == 200) {
// 请求成功 调用处理成功情况的函数
defaults.success(responseText, xhr);
}else {
// 请求失败 调用处理失败情况的函数
defaults.error(responseText, xhr);
}
}
}
ajax({
type: 'post',
// 请求地址
url: 'http://localhost:3000/responseData',
success: function (data) {
console.log('这里是success函数');
console.log(data)
}
})
</script>
http Content-Type 知多少
参考 【潇湘待雨】的文章: juejin.cn/post/684490…
http与https
react面试题
参考文章: juejin.cn/post/684490…
vue 面试题
JS线程、Event Loop、事件循环、任务队列、宏任务
参考文章: juejin.cn/post/684490…