前端基础知识整理汇总(上)
HTML页面的生命周期有以下三个重要事件:
DOMContentLoaded —— 浏览器已经完全加载了 HTML,DOM 树已经构建完毕,但是像是
// 带有src属性的元素不应该在标签之间包含额外的js代码,即使包含,只会下载并执行外部文件,内部代码也会被忽略。
与嵌入式js代码一样, 在解析外部js文件时,页面的处理会暂时停止。
改变脚本行为的方法
1. defer: 立即下载,延迟执行
加载和渲染后续文档元素的过程将和脚本的加载并行进行(异步),但是脚本的执行会在所有元素解析完成之后。脚本总会按照声明顺序执行。
在DOMContentLoaded事件之前执行。2. async: 异步脚本
加载和渲染后续文档元素的过程将和脚本的加载与执行并行进行(异步)。但是
async
在下载完毕后的执行会阻塞HTML的解析。脚本加载后马上执行,不能保证异步脚本按照他们在页面中出现的顺序执行。
一定会在load事件之前执行,可能会在DOMContentLoaded事件之前或之后执行。区别:
meta META标签是HTML标记HEAD区的一个关键标签,它提供的信息虽然用户不可见,但却是文档的最基本的元信息。除了提供文档字符集、使用语言、作者等网页相关信息外,还可以设置信息给搜索引擎,目的是为了SEO(搜索引擎优化)。
HTML 元素表示那些不能由其它 HTML 元相关(meta-related)元素((
、, 后台代码:
$callback = $_GET['callback'];//得到回调函数名
$data = array('a','b','c');//要返回的数据
echo $callback.'('.json_encode($data).')';//输出
?>
CORS - 跨域资源共享
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
CORS有两种请求,简单请求和非简单请求。只要同时满足以下两大条件,就属于简单请求。
请求方法是以下三种方法之一:HEAD,GET,POST
HTTP的头信息不超出以下几种字段:Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type【只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain】,没有自定义的HTTP头部。
简单请求
浏览器:把客户端脚本所在的域填充到Origin header里,向其他域的服务器请求资源。
服务器:根据资源权限配置,在响应头中添加Access-Control-Allow-Origin Header,返回结果。
浏览器:比较服务器返回的Access-Control-Allow-Origin Header和请求域的Origin。如果当前域已经得到授权,则将结果返回给页面。否则浏览器忽略此次响应。
网页:收到返回结果或者浏览器的错误提示。
对于简单的跨域请求,只要服务器设置的
Access-Control-Allow-Origin
Header和请求来源匹配,浏览器就允许跨域。服务器端设置的`Access-Control-Allow-Methods
和Access-Control-Allow-Headers
对简单跨域没有作用。非简单请求
浏览器:先向服务器发送一个OPTIONS预检请求,检测服务器端是否支持真实请求进行跨域资源访问,浏览器会在发送OPTIONS请求时会自动添加Origin Header 、Access-Control-Request-Method Header和Access-Control-Request-Headers Header。
服务器:响应OPTIONS请求,会在responseHead里添加
Access-Control-Allow-Methods
head。这其中的method的值是服务器给的默认值,可能不同的服务器添加的值不一样。服务器还会添加Access-Control-Allow-Origin
Header和Access-Control-Allow-Headers
Header。这些取决于服务器对OPTIONS请求具体如何做出响应。如果服务器对OPTIONS响应不合你的要求,你可以手动在服务器配置OPTIONS响应,以应对带预检的跨域请求。在配置服务器OPTIONS的响应时,可以添加Access-Control-Max-Age head
告诉浏览器在一定时间内无需再次发送预检请求,但是如果浏览器禁用缓存则无效。浏览器:接到OPTIONS的响应,比较真实请求的method是否属于返回的
Access-Control-Allow-Methods
head的值之一,还有origin, head
也会进行比较是否匹配。如果通过,浏览器就继续向服务器发送真实请求, 否则就会报预检错误:请求来源不被options响应允许,请求方法不被options响应允许或请求中有自定义header不被options响应允许。服务器:响应真实请求,在响应头中放入
Access-Control-Allow-Origin
Header、Access-Control-Allow-Methods
和Access-Control-Allow-Headers
Header,分别表示允许跨域资源请求的域、请求方法和请求头,并返回数据。浏览器:接受服务器对真实请求的返回结果,返回给网页
网页:收到返回结果或者浏览器的错误提示。
Access-Control-Allow-Origin
在响应options请求和响应真实请求时都是有作用的,两者必须同时包含要跨域的源。Access-Control-Allow-Methods
和Access-Control-Allow-Headers
只在响应options请求时有作用。携带cookie
在 CORS 跨域中,浏览器并不会自动发送 Cookie。对于普通跨域请求只需服务端设置,而带cookie跨域请求前后端都需要设置。
浏览器,对于跨域请求,需要设置
withCredentials
属性为 true。服务端的响应中必须携带Access-Control-Allow-Credentials: true
。除了
Access-Control-Allow-Credentials
之外,跨域发送 Cookie 还要求Access-Control-Allow-Origin
不允许使用通配符。否则浏览器将会抛出The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*'
错误。事实上不仅不允许通配符,而且只能指定单一域名。计算 Access-Control-Allow-Origin
既然
Access-Control-Allow-Origin
只允许单一域名, 服务器可能需要维护一个接受 Cookie 的 Origin 列表, 验证Origin
请求头字段后直接将其设置为Access-Control-Allow-Origin
的值。在 CORS 请求被重定向后Origin
头字段会被置为null
, 此时可以选择从Referer
头字段计算得到Origin
。具体实现
服务器端的响应头配置
Access-Control-Allow-Origin 可以设置为*
,表示可以与任意域进行数据共享。// 设置服务器接受跨域的域名
"Access-Control-Allow-Origin": "http://127.0.0.1:8080",
// 设置服务器接受跨域的请求方法
'Access-Control-Allow-Methods': 'OPTIONS,HEAD,DELETE,GET,PUT,POST',
// 设置服务器接受跨域的headers
'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type',
// 设置服务器不用再次预检请求时间
'Access-Control-Max-Age': 10000,
// 设置服务器接受跨域发送Cookie
'Access-Control-Allow-Credentials': true
document.domain
此方案仅限主域相同,子域不同的跨域应用场景。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
栗子:
在父页面 http://xxx.com/a.html 中设置document.domain
在子页面http://xxx.com/b.html 中设置document.domain
window.postMessage
window.postMessage是html5的功能,是客户端和客户端直接的数据传递,既可以跨域传递,也可以同域传递。
postMessage(data, origin)方法接受两个参数:
data:html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin:协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
栗子:
假如有一个页面,页面中拿到部分用户信息,点击进入另外一个页面,另外的页面默认是取不到用户信息的,你可以通过window.postMessage把部分用户信息传到这个页面中。(需要考虑安全性等方面。)发送消息:
// 弹出一个新窗口
var domain = 'http://haorooms.com';
var myPopup = window.open(`${domain}/windowPostMessageListener.html`,'myWindow');
// 发送消息
setTimeout(function(){
var message = {name:"站点",sex:"男"};
console.log('传递的数据是 ' + message);
myPopup.postMessage(message, domain);
}, 1000);
接收消息:
// 监听消息反馈
window.addEventListener('message', function(event) {
// 判断域名是否正确
if (event.origin !== 'http://haorooms.com') return;
console.log('received response: ', event.data);
}, false);
如下图,接受页面得到数据
如果是使用iframe,代码应该这样写:
// 捕获iframe
var domain = 'http://haorooms.com';
var iframe = document.getElementById('myIFrame').contentWindow;
// 发送消息
setTimeout(function(){
var message = {name:"站点",sex:"男"};
console.log('传递的数据是: ' + message);
iframe.postMessage(message, domain);
},1000);
接收数据并反馈信息:
// 响应事件
window.addEventListener('message',function(event) {
if(event.origin !== 'http://haorooms.com') return;
console.log('message received: ' + event.data, event);
event.source.postMessage(event.origin);
}, false);
几个比较重要的事件属性:
source – 消息源,消息的发送窗口/iframe。
origin – 消息源的URI(可能包含协议、域名和端口),用来验证数据源。
data – 发送方发送给接收方的数据。window.name
原理:
window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的。栗子:
在子页面(b.com/data.html) 设置window.name:/* b.com/data.html */
在父页面(a.com/app.html)中创建一个iframe,把其src指向子页面。在父页面监听iframe的onload事件,获取子页面数据:
/* a.com/app.html */
http-proxy-middleware
http-proxy-middleware
用于把请求代理转发到其他服务器的中间件。
安装:npm install http-proxy-middleware --save-dev
配置如下:module.exports = {
devServer: {
contentBase: path.resolve(__dirname, 'dev'),
publicPath: '/',
historyApiFallback: true,
proxy: {
// 请求到 '/device' 下的请求都会被代理到target:http://target.com中
'/device/*': {
target: 'http://target.com',
secure: false, // 接受运行在https上的服务
changeOrigin: true
}
}
}
}
使用如下:
fetch('/device/space').then(res => {
// 被代理到 http://target.com/device/space
return res.json();
});
// 使用的url 必须以/开始 否则不会代理到指定地址
fetch('device/space').then(res => {
// http://localhost:8080/device/space 访问本地服务
return res.json();
});
nginx反向代理
反向代理(Reverse Proxy)方式是指以代理服务器来接受客户端的连接请求,然后将请求转发给内部网络上的服务器;并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。
反向代理服务器对于客户端而言它就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理 的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将获得的内容返回给客户端,就像这些内容 原本就是它自己的一样。
模块化 AMD/CMD/CommonJs都是JS模块化开发的标准,目前对应的实现是RequireJS,SeaJs, nodeJs;
CommonJS:服务端js
CommonJS 是以在浏览器环境之外构建 javaScript 生态系统为目标而产生的写一套规范,主要是为了解决 javaScript 的作用域问题而定义的模块形式,可以使每个模块它自身的命名空间中执行。
实现方法:模块必须通过 module.exports 导出对外的变量或者接口,通过 require() 来导入其他模块的输出到当前模块的作用域中;
主要针对服务端(同步加载文件)和桌面环境中,node.js 遵循的是 CommonJS 的规范;CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。
require()用来引入外部模块;
exports对象用于导出当前模块的方法或变量,唯一的导出口;
module对象就代表模块本身。
// 定义一个module.js文件
var A = () => console.log('我是定义的模块');
// 1.第一种返回方式
module.exports = A;
// 2.第二种返回方式
module.exports.test = A
// 3.第三种返回方式
exports.test = A;
// 定义一个test.js文件【这两个文件在同一个目录下】
var module = require("./module");
//调用这个模块,不同的返回方式用不同的方式调用
// 1.第一种调用方式
module();
// 2.第二种调用方式
module.test();
// 3.第三种调用方式
module.test();
// 执行文件
node test.js
AMD:异步模块定义【浏览器端js】
AMD 是 Asynchronous Module Definition 的缩写,意思是异步模块定义;采用的是异步的方式进行模块的加载,在加载模块的时候不影响后边语句的运行。主要是为前端 js 的表现指定的一套规范。
实现方法:通过define方法去定义模块,通过require方法去加载模块。
define(id?,dependencies?,factory)
: 它要在声明模块的时候制定所有的依赖(dep),并且还要当做形参传到factory中。没什么依赖,就定义简单的模块(或者叫独立的模块)require([modules], callback)
: 第一个参数[modules],是需加载的模块名数组;第二个参数callback,是模块加载成功之后的回调函数主要针对浏览器js,requireJs遵循的是 AMD 的规范;
// module1.js文件, 定义独立的模块
define({
methodA: () => console.log('我是module1的methodA');
methodB: () => console.log('我是module1的methodB');
});
// module2.js文件, 另一种定义独立模块的方式
define(() => {
return {
methodA: () => console.log('我是module2的methodA');
methodB: () => console.log('我是module2的methodB');
};
});
// module3.js文件, 定义非独立的模块(这个模块依赖其他模块)
define(['module1', 'module2'], (m1, m2) => {
return {
methodC: () => {
m1.methodA();
m2.methodB();
}
};
});
//定义一个main.js,去加载这些个模块
require(['module3'], (m3) => {
m3.methodC();
});
// 为避免造成网页失去响应,解决办法有两个,一个是把它放在网页底部加载,另一个是写成下面这样:
// async属性表明这个文件需要异步加载,避免网页失去响应。
// IE不支持这个属性,只支持defer,所以把defer也写上。
// data-main属性: 指定网页程序的主模块
// 控制台输出结果
我是module1的methodA
我是module2的methodB
CMD:通用模块定义【浏览器端js】
CMD 是 Common Module Definition 的缩写,通过异步的方式进行模块的加载的,在加载的时候会把模块变为字符串解析一遍才知道依赖了哪个模块;
主要针对浏览器端(异步加载文件),按需加载文件。对应的实现是seajs
AMD和CMD的区别
对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible(尽可能的懒加载,也称为延迟加载,即在需要的时候才加载)。
CMD 推崇依赖就近,AMD 推崇依赖前置。
// CMD
define(function(require, exports, module) {
var a = require('./a');
a.doSomething();
// ...
var b = require('./b'); // 依赖可以就近书写
b.doSomething();
// ...
})
// AMD
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething();
// ...
b.doSomething();
//...
})
import和require区别
import和require都是被模块化使用。
require
是CommonJs的语法(AMD规范引入方式),CommonJs的模块是对象。import
是es6的一个语法标准(浏览器不支持,本质是使用node中的babel将es6转码为es5再执行,import会被转码为require),es6模块不是对象。require
是运行时加载整个模块(即模块中所有方法),生成一个对象,再从对象上读取它的方法(只有运行时才能得到这个对象,不能在编译时做到静态化),理论上可以用在代码的任何地方。import
是编译时调用,确定模块的依赖关系,输入变量(es6模块不是对象,而是通过export命令指定输出代码,再通过import输入,只加载import中导的方法,其他方法不加载),import具有提升效果,会提升到模块的头部(编译时执行)
export和import可以位于模块中的任何位置,但是必须是在模块顶层,如果在其他作用域内,会报错(es6这样的设计可以提高编译器效率,但没法实现运行时加载)。
require
是赋值过程,把require的结果(对象,数字,函数等),默认是export的一个对象,赋给某个变量(复制或浅拷贝)。import
是解构过程(需要谁,加载谁)。
require/exports:
// require: 真正被require出来的是来自module.exports指向的内存块内容
const a = require('a') //
// exports: 只是 module.exports的引用,辅助module.exports操作内存中的数据
exports.a = a
module.exports = a
import/export:
// import
import a from 'a';
import { default as a } from 'a';
import * as a from 'a';
import { fun1,fun2 } from 'a';
// export
export default a;
export const a = 1;
export functon a { ... };
export { fun1, fun2 };
http和https Http:
超文本传输协议(Http,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。设计Http最初的目的是为了提供一种发布和接收HTML页面的方法。它可以使浏览器更加高效。Http协议是以明文方式发送信息的,如果黑客截取了Web浏览器和服务器之间的传输报文,就可以直接获得其中的信息。
Https:
是以安全为目标的Http通道,是Http的安全版。Https的安全基础是SSL。SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。SSL协议可分为两层:SSL记录协议(SSL Record Protocol),它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。SSL握手协议(SSL Handshake Protocol),它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
HTTP与HTTPS的区别
1、HTTP是超文本传输协议,信息是明文传输,HTTPS是具有安全性的SSL加密传输协议。
2、HTTPS协议需要ca申请证书,一般免费证书少,因而需要一定费用。
3、HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样。前者是80,后者是443。
4、HTTP连接是无状态的,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,安全性高于HTTP协议。https的优点
尽管HTTPS并非绝对安全,掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击,但HTTPS仍是现行架构下最安全的解决方案,主要有以下几个好处:
1)使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;
2)HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
3)HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。
4)谷歌曾在2014年8月份调整搜索引擎算法,并称“比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的排名将会更高”。Https的缺点
1)Https协议握手阶段比较费时,会使页面的加载时间延长近。
2)Https连接缓存不如Http高效,会增加数据开销,甚至已有的安全措施也会因此而受到影响;
3)SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗。
4)Https协议的加密范围也比较有限。最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。遍历方法 for
在for循环中,循环取得数组或是数组类似对象的值,譬如arguments和HTMLCollection对象。
不足:
在于每次循环的时候数组的长度都要去获取;
终止条件要明确;
foreach(),map()
两个方法都可以遍历到数组的每个元素,而且参数一致;
forEach(): 对数组的每个元素执行一次提供的函数, 总是返回undefined;
map(): 创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。返回值是一个新的数组;var array1 = [1,2,3,4,5];
var x = array1.forEach((value,index) => {
console.log(value);
return value + 10;
});
console.log(x); // undefined
var y = array1.map((value,index) => {
console.log(value);
return value + 10;
});
console.log(y); // [11, 12, 13, 14, 15]
for in
经常用来迭代对象的属性或数组的每个元素,它包含当前属性的名称或当前数组元素的索引。
当遍历一个对象的时候,变量 i 是循环计数器 为 对象的属性名, 以任意顺序遍历一个对象的可枚举属性。对于每个不同的属性,语句都会被执行。
当遍历一个数组的时候,变量 i 是循环计数器 为 当前数组元素的索引不足:
for..in循环会把某个类型的原型(prototype)中方法与属性给遍历出来.
const array = ["admin","manager","db"];
array.color = 'red';
array.prototype.name= "zhangshan";
for(var i in array){
if(array.hasOwnProperty(i)){
console.log(array[i]); // admin,manager,db,color
}
}
// hasOwnProperty(): 对象的属性或方法是非继承的,返回true
for … of
迭代循环可迭代对象(包括Array,Map,Set,String,TypedArray,arguments 对象)等等。不能遍历对象。只循环集合本身的元素
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
a.name = 'array';
for (var x of a) {
console.log(x); //'A', 'B', 'C'
}
for (var x of s) {
console.log(x);//'A', 'B', 'C'
}
for (var x of m) {
console.log(x[0] + '=' + x[1]);//1='x',2='y',3='z'
}
继承 // 定义一个动物类
function Animal(name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
原型链继承
核心: 将父类的实例作为子类的原型。
function Dog(age) {
this.age = age;
}
Dog.protoType = New Animal();
Dog.prototype.name = 'dog';
const dog = new Dog(12);
console.log(dog.name);
console.log(dog.eat('age'));
console.log(dog instanceof Animal); //true
console.log(dog instanceof Dog); //true
new 创建新实例对象经过了以下几步:
1.创建一个新对象
2.将新对象的_proto_指向构造函数的prototype对象
3.将构造函数的作用域赋值给新对象 (也就是this指向新对象)
4.执行构造函数中的代码(为这个新对象添加属性)
5.返回新的对象// 1. 创建一个新对象
var Obj = {};
// 2. 将新对象的_proto_指向构造函数的prototype对象
Obj._proto_ = Animal.prototype();
// 3. 执行构造函数中的代码(为这个新对象添加属性)
Animal.call(Obj);
// 4. 返回新的对象
return Obj;
特点:
1.实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性
2.非常纯粹的继承关系,实例是子类的实例,也是父类的实例
3.父类新增原型方法/原型属性,子类都能访问到缺点:
1.新实例无法向父类构造函数传参。
2.继承单一。
3.所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
4.要想为子类新增原型上的属性和方法,必须要在new Animal()
这样的语句之后执行,不能放到构造器中构造函数继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Dog(name) {
Animal.apply(this, 'dog');
this.name = name;
}
const dog = new Dog();
console.log(dog.name);
console.log(dog.eat('age'));
console.log(dog instanceof Animal); //false
console.log(dog instanceof Dog); //true
重点:用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
特点:
1.只继承了父类构造函数的属性,没有继承父类原型的属性。
2.解决了原型链继承缺点1、2、3。
3.可以实现多继承,继承多个构造函数属性(call多个)。
4.在子实例中可向父实例传参。缺点:
1.能继承父类构造函数的属性。
2.无法实现构造函数的复用。(每次用每次都要重新调用)
3.每个新实例都有父类构造函数的副本,臃肿。
4.实例并不是父类的实例,只是子类的实例组合继承(原型链继承和构造函数继承)(常用)
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){
Animal.call(this, name);
this.name = name;
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
重点:结合了两种模式的优点,传参和复用
特点:
1.可以继承父类原型上的属性,可以传参,可复用。
2.每个新实例引入的构造函数属性是私有的。
3.既是子类的实例,也是父类的实例缺点:
调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。原型式继承
重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
特点:
类似于复制一个对象,用函数来包装。缺点:
1.所有实例都会继承原型上的属性。
2.无法实现复用。(新实例属性都是后面添加的)寄生式继承
重点:就是给原型式继承外面套了个壳子。
优点:没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。
缺点:没用到原型,无法复用。寄生组合式继承(常用)
寄生:在函数内返回对象然后调用
组合:
1、函数的原型等于另一个实例。
2、在函数中用apply或者call引入另一个构造函数,可传参。function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
(function(){
// 创建一个没有实例方法的类
var Super = function(){};
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
})();
var cat = new Cat();
Cat.prototype.constructor = Cat; // 需要修复下构造函数
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
重点:修复了组合继承的问题。
本文完〜