闭包
什么是闭包?
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)
关键词:函数,周围状态引用,捆绑,引用包围,组合
闭包的用途
闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
function init(){ //外层函数,引用包围开始
let name = 'jack'
function getName(){ //内层函数
console.log(name) // 访问外层函数的变量
}
getName()
} //引用包围结束
init()
复制代码
闭包的优点(使用闭包的原因)
-
模拟私有化方法
-
将函数与其所操作的某些数据(环境)关联起来
var Counter = (function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
})();
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */
复制代码
Counter.increment
,Counter.decrement
和 Counter.value
三个函数所共享词法环境。三个函数只可以通过Counter
访问,三个函数和privateCounter
关联
闭包的缺点
-
闭包在处理速度和内存消耗方面对脚本性能具有负面影响。
-
循环中创建闭包中,可能会出现直接取到循环最后的值(在for循环中使用 var 定义变量例子)
call()、apply()、bind() 的用法分别是什么?
-
call() 方法允许为不同的对象分配和调用属于一个对象的函数/方法。
- 语法
function.call(thisArg, arg1, arg2, …)
- 用 call方法调用父构造函数
function Product(name, price) { this.name = name; this.price = price; console.log() } function Food(name, price) { Product.call(this, name, price); this.category = 'food'; } function Toy(name, price) { Product.call(this, name, price); this.category = 'toy'; } var cheese = new Food('feta', 5).name; // expected output: "feta" var fun = new Toy('robot', 40).price; // expected output: 40 //Food 和 Toy 都有了 this.name = name;this price = price 复制代码
- 使用 call 方法调用匿名函数
var animals = [ { species: 'Lion', name: 'King' }, { species: 'Whale', name: 'Fail' } ]; for (var i = 0; i < animals.length; i++) { (function(i) { this.print = function() { console.log('#' + i + ' ' + this.species + ': ' + this.name); } this.print(); }).call(animals[i], i); } 复制代码
这个匿名函数的主要目的是给每个数组元素对象添加一个 print 方法,这个 print 方法可以打印出各元素在数组中的正确索引号。
- 使用 call 方法调用函数并且指定上下文的 ‘this’
function greet() { var reply = [this.animal, 'typically sleep between', this.sleepDuration].join(' '); console.log(reply); } var obj = { animal: 'cats', sleepDuration: '12 and 16 hours' }; greet.call(obj); // cats typically sleep between 12 and 16 hours 复制代码
注意:当使用 call 方法调用调用函数并且不指定第一个参数(argument),
this
的值将会被绑定为全局对象。 -
使用apply()方法, 你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。
- 语法
func.apply(thisArg, [argsArray])
-
对于一些需要写循环以便历数组各项的需求,我们可以用apply完成以避免循环。
我们可以使用push将元素追加到数组中。由于push接受可变数量的参数,所以也可以一次追加多个元素。
但是,如果push的参数是数组,它会将该数组作为单个元素添加,而不是将这个数组内的每个元素添加进去,因此我们最终会得到一个数组内的数组。如果不想这样呢?concat符合我们的需求,但它并不是将元素添加到现有数组,而是创建并返回一个新数组。 然而我们需要将元素追加到现有数组……那么怎么做好?难道要写一个循环吗?别当然不是!
var array = ['a', 'b']; var elements = [0, 1, 2]; array.push.apply(array, elements); console.info(array); // ["a", "b", 0, 1, 2] 复制代码
注意:call()方法的作用和 apply() 方法类似,区别就是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组。
- bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用
当需要调用一个函数内部的函数(内部函数访问了其外部函数的变量),我们需要最终结果内部函数仍可以访问外部函数的变量,但此时内部函数已经剥离出外部函数,就需要使用bind()
方法,将外部函数作为参数传入到内部函数中。
this.x = 9; // 在浏览器中,this 指向全局的 "window" 对象
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
//等同于
var retrieveX = function () { return this.x }
retrieveX();
// 返回 9 - 因为函数是在全局作用域中调用的
// 创建一个新函数,把 'this' 绑定到 module 对象
// 新手可能会将全局变量 x 与 module 的属性 x 混淆
var boundGetX = retrieveX.bind(module); //重新进行关联
boundGetX(); // 81
复制代码
常见HTTP状态码
- 2xx 成功,操作被成功接收并处理
-
200 OK 请求成功。成功的含义取决于HTTP方法。
-
204 No Content 服务器成功处理了请求,但不需要返回任何实体内容,并且希望返回更新了的元信息。
-
206 Partial Content 服务器已经成功处理了部分 GET 请求。(断点续传或者分段下载)
-
- 3xx 重定向,需要进一步的操作以完成请求
-
301 Moved Permanently 请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
-
302 Found 请求的资源现在临时从不同的 URI 响应请求。
-
303 See Other 对应当前请求的响应可以在另一个 URI 上被找到,而且客户端应当采用 GET 的方式访问那个资源。
-
304 Not Modified 如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码。
-
- 4xx 客户端错误,请求包含语法错误或无法完成请求
-
400 Bad Request 语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。请求参数有误。
-
401 Unauthorized 当前请求需要用户验证。该响应必须包含一个适用于被请求资源的 WWW-Authenticate 信息头用以询问用户信息。
-
403 Forbidden 服务器已经理解请求,但是拒绝执行它。
-
404 Not Found 请求失败,请求所希望得到的资源未被在服务器上发现。
-
405 Method Not Allowed 请求行中指定的请求方法不能被用于请求相应的资源。
-
- 5xx 服务器错误,服务器在处理请求的过程中发生了错误
-
500 Internal Server Error 服务器遇到了不知道如何处理的情况。
-
501 Not Implemented 此请求方法不被服务器支持且无法被处理。只有
GET
和HEAD
是要求服务器支持的,它们必定不会返回此错误代码。 -
502 Bad Gateway 此错误响应表明服务器作为网关需要得到一个处理这个请求的响应,但是得到一个错误的响应。
-
503 Service Unavailable 服务器没有准备好处理请求。 常见原因是服务器因维护或重载而停机。 请注意,与此响应一起,应发送解释问题的用户友好页面。
-
数组去重
array = [1,5,2,3,4,2,3,1,3,4]
//方法一
function unique(array) {
return [...new Set(array)];
}
//方法一的简化
let unique = (a) => [...new Set(a)]
//方法2
let temp_arr = []
function unique(arr){
arr.map(item =>{
if( temp_arr.indexOf(item) === -1){
temp_arr.push(item)
}
})
return temp_arr
}
复制代码
缺陷:当数组中存在NaN
时,方法2就存在漏洞,因为Array.indexOf
使用的是 ===
来进行判断,众所周知 NaN === NaN
返回false
事件委托、阻止默认事件、阻止事件冒泡
什么是事件委托?
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件的代理\委托(delegation)。
阻止默认事件
event.preventDefault();
复制代码
阻止事件冒泡
event.stopPropagation();
复制代码
JS中的继承
原型链继承
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subproperty = false;
}
// 这里是关键,创建SuperType的实例,并将该实例赋值给SubType.prototype
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
var instance = new SubType();
console.log(instance.getSuperValue()); // true
复制代码
class 继承
class Rectangle{
constructor(width, height){
this.width = width
this.height = height
}
area (){
return this.width*this.height
}
}
class Square extends Rectangle{
constructor(length){
super(length,length)
this.name = 'square'
}
// 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
}
const rectangle = new Rectangle(10, 20);
console.log(rectangle.area()); //200
const square = new Square(10);
console.log(square.area()); //100
复制代码
数组排序
function sort(numbers){
if ( numbers.length > 2 ){
let tempMin = min(numbers)
let indexMin = numbers.indexOf(tempMin)
numbers.splice(indexMin,1)
return [tempMin].concat(sort (numbers) )
} else{
return numbers[0] < numbers[1] ? numbers : numbers.reverse
}
}
function min (numbers) {
if ( numbers.length > 2){
return min(
[ numbers[0], min( numbers.slice(1) ) ]
)
}
else {
return Math.min.apply(null, numbers)
}
}
console.log(sort( [2,1,5,3,8,4,9,5]) )
//[1,2,3,4,5,5,8,9]
复制代码
Promise
Preomise 是一个对象,它代表着一个异步操作最终的成功或者失败,并有效解决回调地狱
创建一个 new Promise
想要某个函数拥有promise
功能,只需让其返回一个promise
即可。
function myAsyncFunction(url) {
return new Promise((resolve, reject) => {
....
});
};
复制代码
Promise.prototype.then()
then()
方法返回一个 Promise
。它最多需要有两个参数:Promise
的成功和失败情况的回调函数。
var p1 = new Promise((resolve, reject) => {
resolve('成功!');
// or
// reject(new Error("出错了!"));
});
p1.then(value => {
console.log(value); // 成功!
}, reason => {
console.error(reason); // 出错了!
});
复制代码
Promise.all()
Promise.all
方法接收一个可iterable
的promise
(注:Array,Map,Set都属于ES6的iterable类型)输入,并且只返回一个Promise
实例,那个输入的所有promise
的resolve
回调的结果是一个数组。这个Promise
的resolve
回调执行是在所有输入的promise
的resolve
回调都结束,或者输入的iterable
里没有promise
了的时候。它的reject
回调执行是,只要任何一个输入的promise
的reject
回调执行或者输入不合法的promise
就会立即抛出错误,并且reject
的是第一个抛出的错误信息。(来源:MDN)
太长了,总结下:
Promise.all()方法接收一个可迭代(Array,Map,Set都属于ES6的iterable类型)的preomise,返回一个Premise
实例,输入的所有promise的resolve回调的结果组成了一个数组,返回的Premise
实例当输入的所有promise的resolve回调结束才resolve
,遇到输入的promise出现reject
,就立即抛出的错误信息。
都是resolve
的情况
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [3, 1337, "foo"]
});
复制代码
含有reject
的情况
var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'one');
});
var p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'two');
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'three');
});
var p4 = new Promise((resolve, reject) => {
setTimeout(resolve, 4000, 'four');
});
var p5 = new Promise((resolve, reject) => {
reject('reject');
});
Promise.all([p1, p2, p3, p4, p5]).then(values => {
console.log(values);
}, reason => {
console.log(reason)
});
//From console:
//"reject"
//You can also use .catch
Promise.all([p1, p2, p3, p4, p5]).then(values => {
console.log(values);
}).catch(reason => {
console.log(reason)
});
//From console:
//"reject"
复制代码
Promise.race()
Promise.race(iterable)
方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
简单来说就是第一状态传递,
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "one");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "two");
});
Promise.race([p1, p2]).then(function(value) {
console.log(value); // "two"
// 两个都完成,但 p2 更快
});
var p3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "three");
});
var p4 = new Promise(function(resolve, reject) {
setTimeout(reject, 500, "four");
});
Promise.race([p3, p4]).then(function(value) {
console.log(value); // "three"
// p3 更快,所以它完成了
}, function(reason) {
// 未被调用
});
var p5 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "five");
});
var p6 = new Promise(function(resolve, reject) {
setTimeout(reject, 100, "six");
});
Promise.race([p5, p6]).then(function(value) {
// 未被调用
}, function(reason) {
console.log(reason); // "six"
// p6 更快,所以它失败了
});
复制代码
跨域
同源政策
同源= 协议+域名+端口
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
什么是跨域
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url
不同即为跨域
JSONP 跨域
利用 <script>
标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。
JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
//请求端
<script src="http://localhost:9090/api"></script>
//服务端
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/api') {
....
}
})
.listen(9090, () => {
console.log(9090)
})
复制代码
CORS 跨域
通过设置响应头来解决:Access-Control-Allow-Origin:
//服务端
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/api') {
response.statusCode = 200
response.setHeader("Access-Control-Allow-Origin", "http://xxxx.com");
}
})
.listen(9090, () => {
console.log(9090)
})
复制代码
每日一语:时间会风化你的意志,用自我肯定去维护它。