HTML页面的生命周期
HTML页面的生命周期有以下三个重要事件:
DOMContentLoaded —— 浏览器已经完全加载了 HTML,DOM 树已经构建完毕,但是像是 和样式表等外部资源可能并没有下载完毕。
load —— 浏览器已经加载了所有的资源(图像,样式表等)。
beforeunload —— 当用户即将离开当前页面(刷新或关闭)时触发。正要去服务器读取新的页面时调用,此时还没开始读取;
unload —— 在用户离开页面后触发。从服务器上读到了需要加载的新的页面,在即将替换掉当前页面时调用。
每个事件都有特定的用途:
DOMContentLoaded —— DOM 加载完毕,所以 JS 可以访问所有 DOM 节点,初始化界面。
load —— 附加资源已经加载完毕,可以在此事件触发时获得图像的大小(如果没有被在 HTML/CSS 中指定)
beforeunload —— 该事件可用于弹出对话框,提示用户是继续浏览页面还是离开当前页面。
unload —— 删除本地数据localstorage等
DOMContentLoaded 由 document 对象触发。使用 addEventListener 来监听它:
document.addEventListener("DOMContentLoaded", () => {});
DOMContentLoaded 和脚本
当浏览器在解析 HTML 页面时遇到了 标签,将无法继续构建DOM树(UI 渲染线程与 JS 引擎是互斥的,当 JS 引擎执行时 UI 线程会被挂起),必须立即执行脚本。所以
DOMContentLoaded
有可能在所有脚本执行完毕后触发。
外部脚本(带 src
的)的加载和解析也会暂停DOM树构建,所以 DOMContentLoaded
也会等待外部脚本。带 async
的外部脚本,可能会在DOMContentLoaded事件之前或之后执行。带 defer
的脚本肯定会在在DOMContentLoaded事件之前执行。
DOMContentLoaded 与样式表
外部样式表并不会阻塞 DOM 的解析,所以 DOMContentLoaded
并不会被它们影响。
window
对象上的 load
事件在所有文件包括样式表,图片和其他资源下载完毕后触发。
window.addEventListener('load', function(e) {...});
window.onload = function(e) { ... };
当窗口即将被卸载(关闭)时, 会触发该事件。此时页面文档依然可见, 且该事件的默认动作可以被取消。beforeunload在unload之前执行,它还可以阻止unload的执行。
// 推荐使用
window.addEventListener('beforeunload', (event) => {
// Cancel the event as stated by the standard.
event.preventDefault();
// Chrome requires returnValue to be set.
event.returnValue = '关闭提示';
});
window.onbeforeunload = function (e) {
e = e || window.event;
// 兼容IE8和Firefox 4之前的版本
if (e) {
e.returnValue = '关闭提示';
}
// Chrome, Safari, Firefox 4+, Opera 12+ , IE 9+
return '关闭提示';
};
用户离开页面的时候,window
对象上的 unload
事件会被触发,无法阻止用户转移到另一个页面上。
// 推荐使用
window.addEventListener("unload", function(event) { ... });
window.onunload = function(event) { ... };
document.readyState
表示页面的加载状态,有三个值:
loading
加载 —— document仍在加载。
interactive
互动 —— 文档已经完成加载,文档已被解析,但是诸如图像,样式表和框架之类的子资源仍在加载。
complete
—— 文档和所有子资源已完成加载。 load
事件即将被触发。
可以在 readystatechange
中追踪页面的变化状态:
document.addEventListener('readystatechange', () => {
console.log(document.readyState);
});
Script标签:向HTML插入JS的方法
属性 | 值 | 描述 |
---|---|---|
async | async | 立即下载脚本(仅适用于外部脚本)。 |
charset | charset | 表示通过src属性指定的代码的字符集 |
defer | defer | 表示脚本可以延迟到文档完全被解析和显示之后再执行(仅适用于外部脚本)。 |
language | script(已废弃) | 表示编写代码使用的脚本语言。用 type 属性代替它。 |
src | URL | 规定外部脚本文件的 URL。 |
xml:space | preserve | 规定是否保留代码中的空白。 |
type | text/xxx | language的替换属性,表示编写代码使用的脚本语言的内容类型,也称为MIME属性。 |
没有 defer
或 async
,所有'字符串,如果必须出现,必须使用转义标签‘\’ alert('<\/script>');
}
包含在// 带有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是一个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请求时有作用。
在 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
此方案仅限主域相同,子域不同的跨域应用场景。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
栗子:
在父页面 http://xxx.com/a.html 中设置document.domain
document.domain = 'xxx.com';//设置成主域
function test(){
alert(document.getElementById('iframe').contentWindow);
//contentWindow 可取得子窗口的 window 对象
}
在子页面http://xxx.com/b.html 中设置document.domain
document.domain = 'xxx.com';
//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
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)的生命周期内,窗口载入的所有的页面都是共享一个window.name,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的。
栗子:
在子页面(b.com/data.html) 设置window.name:
/* b.com/data.html */
window.name = 'I was there!';
// 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右
// 数据格式可以自定义,如json、字符串
在父页面(a.com/app.html)中创建一个iframe,把其src指向子页面。在父页面监听iframe的onload事件,获取子页面数据:
/* a.com/app.html */
var iframe = document.createElement('iframe');
iframe.src = 'http://b.com/data.html';
function iframelLoadFn() {
var data = iframe.contentWindow.name;
console.log(data);
// 获取数据以后销毁iframe,释放内存;这也保证了安全(不被其他域frame js访问)。
iframeDestoryFn();
}
function iframeDestoryFn() {
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
if (iframe.attachEvent) {
iframe.attachEvent('onload', iframelLoadFn);
} else {
iframe.onload = iframelLoadFn;
}
document.body.appendChild(iframe);
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();
});
反向代理(Reverse Proxy)方式是指以代理服务器来接受客户端的连接请求,然后将请求转发给内部网络上的服务器;并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。
反向代理服务器对于客户端而言它就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理 的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将获得的内容返回给客户端,就像这些内容 原本就是它自己的一样。
模块化
AMD/CMD/CommonJs都是JS模块化开发的标准,目前对应的实现是RequireJS,SeaJs, nodeJs;
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 是 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 是 Common Module Definition 的缩写,通过异步的方式进行模块的加载的,在加载的时候会把模块变为字符串解析一遍才知道依赖了哪个模块;
主要针对浏览器端(异步加载文件),按需加载文件。对应的实现是seajs
对于依赖的模块,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都是被模块化使用。
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循环中,循环取得数组或是数组类似对象的值,譬如arguments和HTMLCollection对象。
不足:
在于每次循环的时候数组的长度都要去获取;
终止条件要明确;
两个方法都可以遍历到数组的每个元素,而且参数一致;
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]
经常用来迭代对象的属性或数组的每个元素,它包含当前属性的名称或当前数组元素的索引。
当遍历一个对象的时候,变量 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
迭代循环可迭代对象(包括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