js中的闭包
令狐寻欢 2017年10月18日发布
文章同步到github
js的闭包概念几乎是任何面试官都会问的问题,最近把闭包这块的概念梳理了一下,记录成以下文章。
我先列出一些官方及经典书籍等书中给出的概念,这些概念虽然表达的不一样,但是都在对闭包做了最正确的定义和翻译,也帮助大家更好的理解闭包,这阅读起来可能比较模糊,大家往后看,本文通过对多个经典书籍中的例子讲解,相信会让大家能很好的理解js中的闭包。文章开始,我会先铺垫一下闭包的概念和为什么要引入闭包的概念,然后结合例子来说明讲解,并讲解如何使用闭包。
闭包包含自由(未绑定到特定对象)变量;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域) -- 百度百科
函数对象可以通过作用域链互相关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学中成为闭包
闭包是指有权访问另一个函数作用域中的变量的函数。
我引入《深入理解JavaScript系列:闭包(Closures)》文章中的例子来说明,也可以直接去看那篇文章,我结合其他书籍反复读了很多遍此文章才理解清楚。如下:
function testFn() {
var localVar = 10; // 自由变量
function innerFn(innerParam) {
alert(innerParam + localVar);
}
return innerFn;
}
var someFn = testFn();
someFn(20); // 30
一般来说,在函数执行完毕之后,局部变量对象即被销毁,所以innerFn是不可能以返回值形式返回的,innerFn函数作为局部变量应该被销毁才对。
这是当函数以返回值时的问题,另外再看一个当函数以参数形式使用时的问题,还是直接引用《深入理解JavaScript系列》中的例子,也方便大家有兴趣可以直接去阅读那篇文章
var z = 10;
function foo() {
alert(z);
}
foo(); // 10 – 使用静态和动态作用域的时候
(function () {
var z = 20;
foo(); // 10 – 使用静态作用域, 20 – 使用动态作用域
})();
// 将foo作为参数的时候是一样的
(function (funArg) {
var z = 30;
funArg(); // 10 – 静态作用域, 30 – 动态作用域
})(foo);
当函数foo在不同的函数中调用,z该取哪个上下文中的值呢,这就又是一个问题,所以就引入了闭包的概念,也可以理解为定义了一种规则。
看一个《javsScript权威指南》中的一个例子,我稍微做一下修改如下:
var scope = 'global scope';
function checkScope() {
var scope = 'local scope';
return function() {
console.log(scope);
}
}
var result = checkScope();
result(); // local scope checkScope变量对象中的scope,非全局变量scope
分析:
即使匿名函数是在checkScope函数外调用,也没有使用全局变量scope,即是利用了js的静态作用域,当匿名函数初始化时,就创建了自己的作用域链(作用域链的概念这里不做解释,可以参考我的另一篇文章js中的执行栈、执行环境(上下文)、作用域、作用域链、活动对象、变量对象的概念总结,其实当把作用域链理解好了之后,闭包也就理解了), 此匿名函数的作用域链包括checkScope的活动对象和全局变量对象, 当checkScope函数执行完毕后,checkScope的活动对象并不会被销毁,因为匿名函数的作用域链还在引用checkScope的活动对象,也就是checkScope的执行环境被销毁,但是其活动对象没有被销毁,留存在堆内存中,直到匿名函数销毁后,checkScope的活动对象才会销毁,解除对匿名函数的引用将其设置为null即可,垃圾回收将会将其清除,另外当外部对checkScope的自由变量存在引用的时候,其活动对象也不会被销毁
result = null; //解除对匿名函数的引用
注释:
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量
补充:
引用一下《javsScript权威指南》中的补充,帮助大家进一步理解
函数以参数形式使用
当函数以参数形式使用时一般用于利用闭包特性解决实际问题,比如浏览器中内置的方法等,下面我直接引用《深入理解JavaScript系列:闭包(Closures)》中关于闭包实战部分的例子如下:
sort
在sort的内置方法中,函数以参数形式传入回调函数,在sort的实现中调用:
[1, 2, 3].sort(function (a, b) {
... // 排序条件
});
map
和sort的实现一样
[1, 2, 3].map(function (element) {
return element * 2;
}); // [2, 4, 6]
另外利用自执行匿名函数创建的闭包
var foo = {};
// 初始化
(function (object) {
var x = 10;
object.getX = function() {
return x;
};
})(foo);
alert(foo.getX()); // 获得闭包 "x" – 10
先来看一个例子
var fnBox = [];
function foo() {
for(var i = 0; i < 3; i++) {
fnBox[i] = function() {
return i;
}
}
}
foo();
var fn0 = fnBox[0];
var fn1 = fnBox[1];
var fn2 = fnBox[2];
console.log(fn0()); // 3
console.log(fn1()); // 3
console.log(fn2()); // 3
用伪代码来说明如下:
fn0.[[scope]]= {
// 其他变量对象,一直到全局变量对象
父级上下文中的活动对象AO: [data: [...], i: 3]
}
fn1.[[scope]]= {
// 其他变量对象,一直到全局变量对象
父级上下文中的活动对象AO: [data: [...], i: 3]
}
fn2.[[scope]]= {
// 其他变量对象,一直到全局变量对象
父级上下文中的活动对象AO: [data: [...], i: 3],
}
分析:
这是因为fn0、fn1、fn2的作用域链共享foo的活动对象, 而且js没有块级作用域,当函数foo执行完毕的时候foo的活动对象中i的值已经变为3,当fn0、fn1、fn2执行的时候,其最顶层的作用域没有i变量,就沿着作用域链查找foo的活动对象中的i,所以i都为3。
但是这种结果往往不是我们想要的,这时就可以利用认为创建一个闭包来解决这个问题,如下:
var fnBox = [];
function foo() {
for(var i = 0; i < 3; i++) {
fnBox[i] = (function(num) {
return function() {
return num;
}
})(i);
}
}
foo();
var fn0 = fnBox[0];
var fn1 = fnBox[1];
var fn2 = fnBox[2];
console.log(fn0()); // 0
console.log(fn1()); // 1
console.log(fn2()); // 2
用伪代码来说明如下:
fn0.[[scope]]= {
// 其他变量对象,一直到全局变量对象
父级上下文中的活动对象AO: [data: [...], k: 3],
fn0本身的活动对象AO: {num: 0}
}
fn1.[[scope]]= {
// 其他变量对象,一直到全局变量对象
父级上下文中的活动对象AO: [data: [...], k: 3],
fn1本身的活动对象AO: {num: 1}
}
fn2.[[scope]]= {
// 其他变量对象,一直到全局变量对象
父级上下文中的活动对象AO: [data: [...], k: 3],
fn2本身的活动对象AO: {num: 2}
}
分析:
当使用自执行匿名函数创建闭包, 传入i的值赋值给num,由于作用域链是在函数初始化时创建的,所以当每次循环时,函数fn10、fn1、fn2的作用域链中保存了当次循环是num的值, 当fn10、fn1、fn2调用时,是按照本身的作用域链进行查找。
从理论的角度将,由于js作用域链的特性,js中所有函数都是闭包,但是从应用的角度来说,只有当函数以返回值返回、或者当函数以参数形式使用、或者当函数中自由变量在函数外被引用时,才能成为明确意义上的闭包。
最后,我想表达的式,本篇大量引用和罗列了经典的犀牛书《javaScript权威指南》、红宝书《javaScript高级教程》、以及《深入理解JavaScript系列:闭包(Closures)》系列文章中的概念和例子,不为能形成自己的独特见解,只为了能把闭包清晰的讲解出来。笔者是个小菜鸟,能力实在有限,也在学习中,希望大家多多指点,如发现错误,请多多指正。也希望看过此文的朋友能对闭包多一些理解,那我写这篇文章也就值得了。下次面试时就可以告诉面试官什么是闭包了。谢谢。
征服 JavaScript 面试:什么是闭包?
标签: JavaScript js 闭包 函数 对象数据
2016年12月29日 11:12:021105人阅读 评论(0) 收藏 举报
目录(?)[+]
“征服 JavaScript 面试”是我写的一系列文章,来帮助面试者准备他们在面试 JavaScript 中、高级职位中将可能会遇到的一些问题。这些问题我自己在面试中也经常会问。
在我面试时问出的一系列问题里,闭包通常是我问的第一个或最后一个问题。坦白地说,如果你连闭包也弄不明白,你是不会在 JavaScript 的道路上走多远的。
你别东张西望,说的就是你。你真的理解如何构建一个严谨的 JavaScript 应用?你真的理解代码背后发生的事情或者说一个应用程序是如何工作的?我表示怀疑。如果连个闭包问题都搞不清的话,真是有点够呛。
你不仅仅应该了解闭包的机制,更应该了解闭包为什么很重要,以及能够很容易地回答出闭包的几种可能的应用场景。
闭包在 JavaScript 中常用来实现对象数据的私有,在事件处理和回调函数中也常常会用到它,此外还有 偏函数应用(partial applications)和柯里化(currying) ,以及其他函数式编程模式。
我不在乎面试者是否知道“closure”这个单词或者它的专业定义。我只想弄清他们是否理解基本原理。如果他们没有,那么通常意味着这些面试者在构建实际 JavaScript 应用方面并没有很多经验。
如果你不能回答这个问题,你只是个初级开发者。不管你实际上已经干这个多久了。
为了快速理解下面的内容:你想一下能否举出两个闭包的通用场景?
什么是闭包?
简言之, 闭包 是由函数引用其周边状态( 词法环境 )绑在一起形成的(封装)组合结构。在 JavaScript 中,闭包在 每个函数被创建时 形成。
这是基本原理,但为什么我们关心这些?实际上,由于闭包与它的词法环境绑在一起,因此 闭包让我们能够从一个函数内部访问其外部函数的作用域 。
要使用闭包,只需要简单地将一个函数定义在另一个函数内部,并将它暴露出来。要暴露一个函数,可以将它返回或者传给其他函数。
内部函数将能够访问到外部函数作用域中的变量,即使外部函数已经执行完毕。
闭包使用的例子
闭包的用途之一是实现对象的私有数据。数据私有是让我们能够面向接口编程而不是面向实现编程的基础。而面向接口编程是一个重要的概念,有助于我们创建更加健壮的软件,因为实现细节比接口约定相对来说更加容易被改变。
“面向接口编程,别面向实现编程。” 设计模式:可复用面向对象软件的要素
在 JavaScript 中,闭包是用来实现数据私有的原生机制。当你使用闭包来实现数据私有时,被封装的变量只能在闭包容器函数作用域中使用。你无法绕过对象 被授权的方法 在外部访问这些数据。在 JavaScript 中,任何定义在闭包作用域下的公开方法才可以访问这些数据。例如:
[html] view plain copy
在上面的例子里, get() 方法定义在 getSecret() 作用域下,这让它可以访问任何 getSecret() 中的变量,于是它就是一个被授权的方法。在这个例子里,它可以访问参数 secret 。
对象不是唯一的产生私有数据的方式。闭包还可以被用来创建 有状态的函数 ,这些函数的执行过程可能由它们自身的内部状态所决定。例如:
[html] view plain copy
[html] view plain copy
在函数式编程中,闭包经常用于偏函数应用和柯里化。为了说明这个,我们先定义一些概念:
函数应用:一个过程,指将参数传给一个函数,并获得它的返回值。
偏函数应用:一个过程,它传给某个函数其中一部分参数,然后返回一个新的函数,该函数等待接受后续参数。换句话说,偏函数应用是一个函数,它接受另一个函数为参数,这个作为参数的函数本身接受多个参数,它返回一个函数,这个函数与它的参数函数相比,接受更少的参数。偏函数应用 提前赋予 一部分参数,而返回的函数则等待调用时传入剩余的参数。
偏函数应用通过闭包作用域来提前赋予参数。你可以实现一个通用的函数来赋予指定的函数部分参数,它看起来如下:
partialApply(targetFunction: Function, ...fixedArgs: Any[]) =>
functionWithFewerParams(...remainingArgs: Any[])
partialApply 接受一个多参数的函数,以及一串我们想要提前赋给这个函数的参数,它返回一个新的函数,这个函数将接受剩余的参数。
下面给一个例子来说明,假设你有一个函数,求两个数的和:
[html] view plain copy
现在你想要得到一个函数,它能够对任何传给它的参数都加 10,我们可以将它命名为 add10() 。 add10(5) 的结果应该是 15 。我们的 partialApply() 函数可以做到这个:
[html] view plain copy
在这个例子里,参数 10 通过闭包作用域被提前赋予 add() ,从而让我们获得 add10() 。
现在让我们看一下如何实现 partialApply() :
Let’s look at a possible partialApply() implementation:
[html] view plain copy
如你所见,它只是简单地返回一个函数,这个函数通过闭包访问了传给 partialApply() 函数的 fixedArgs 参数。
轮到你来试试了
你用闭包来做什么?如果你有最喜欢的应用场景,举一些例子,在评论中告诉我。
前端 互联网 面试
关注者
11
被浏览
1,996
关注问题写回答
添加评论
分享
邀请回答
查看全部 3 个回答
知乎用户
专栏-技术烂笔头 http://wangjizhi.com
8 人赞同了该回答
闭包真的是javascript学习者绕不过去的一道坎,而且是面试初级甚至中级前端必问的一个面试点。
简单总结下,要点就你自己提取吧。
1.首先明确闭包是函数而不是一个事物或者一个过程。明确了闭包是函数后,接下来就要搞清楚什么样的函数才能称为闭包。
2. 函数执行的时候会先创建一个属于该函数的上下文执行环境,这个上下文包含了作用域在内的相关信息。当一个函数执行完毕后,与该函数相关的上下文环境便会收回。
3.但是当闭包出现的时候,这个该被收回的环境就不会被收回了,因此也就产生了闭包。原因就是因为在这个函数执行的时候,它内部有对不属于这个作用域并且也不属于全局作用域的局部变量的引用。具体看下面的示例:
function outer() {
var a = 1;
function inner() {
console.log(a++);
}
return inner;
}
var retFun = outer();
retFun();//2
retFun();//3
retFun();//4
说明:outer函数嵌套inner函数,outer作用域内的变量a本应在outer执行完之后收回。但是因为outer执行完后返回了inner函数,而inner函数要想成功执行就必须依赖外部变量a。所以,为了不出现问题,此时在outer执行完毕后原本属于outer的执行环境并没有被收回。所以每次执行retFun(也就是内部函数inner)的时候引用的a都是一直存在的。
从上面的示例可以知道,闭包大多出现在嵌套函数内部。当被包裹的函数引用到了他的父级(如果嵌套层数深的话也可能是祖父级)作用域内的变量而且该被包裹函数被当做值return出去的时候,便形成了闭包。
为什么呢?因为函数在执行的时候会创建与该函数有关的执行上下文环境,闭包执行时候的上下文环境很特殊,因为他有引用自身外部作用域的变量,牵扯到了外部函数执行环境。但是理论上来说,外部函数在执行完毕后就应该被收回执行环境,按照这个理论说法的话,这里就会出现问题,因为内部函数执行的时候就会找不到外部变量。为了避免这个问题出现,所以javascript就将外部作用域的变量保存了下来,也就是外部作用域相关信息保存了下来。
所以上面的值一直递增而不是始终不变,因为闭包每次执行都是在引用同一个变量而不是重新生成的,这个变量在闭包第一次执行的时候被保存了起来。
闭包明显可以保存作用域外的变量值,这个特性可以加以利用,但是同时也存在了内存泄漏的风险,因为外部函数执行完后属于该函数的上下文环境没有被收回。
1005浏览 2017-08-30 21:28:38
小字号
JS面向对象知识中,继承是比较难比较抽象的一块内容,而且实现继承有很多种方法,每种方法又各有优缺点,更加的让人奔溃,这需要对面向对象知识中的对象、原型、原型链、构造函数等基础知识掌握透彻,否则《JS高程》里第六章继承也是看不明白的,网上也有N多的文章,看了这么多对继承依然不是很明白,所谓懂得不少道理但依然过不好这一生。
下面我结合自己的理解,和参考了《JS高程》和网上文章,总结一下实现继承的几种方法及优缺点,这篇文章适合出去面试前速记。
借用构造函数继承
function Parent0(){
this.name = "parent0";
this.colors = ["red","blue","yellow"];
}
function Child0(){
Parent0.call( this ); // 或apply
this.type = "child0";
}
第6行,在子类(Child0)中执行父类(Parent0)的构造函数,通过这种调用,把父类构造函数的this
指向为子类实例化对象引用,从而导致父类执行的时候父类里面的属性都会被挂载到子类的实例上去。
new Child0().name; // Parent0
new Child0().colors; // (3) ["red", "blue", "yellow"]
但是通过这种方式,父类原型上的东西是没法继承的,因此函数复用也就无从谈起
Parent0.prototype.sex = "男";
Parent0.prototype.say = function() {
console.log(" Oh,My God! ");
}
new Child0().sex; // undefined
// Uncaught TypeError: (intermediate value).say is not a function
new Child0().say();
缺点:Child1无法继承Parent1的原型对象,并没有真正的实现继承(部分继承)
原型链式继承(借用原型链实现继承)
function Parent1(){
this.name = "parent1";
this.colors = ["red","blue","yellow"];
}
function Child1(){
this.name = "child1";
}
Child1.prototype = new Parent1();
这种方式能否解决借用构造函数继承的缺点呢?来看下面代码,我们依然为父类的原型添加sex属性和say方法:
Parent1.prototype.sex = "男";
Parent1.prototype.say = function() {
console.log(" Oh,My God! ");
}
new Child1().sex; // 男
new Child1().say(); // Oh,My God!
这种方式确实解决了上面借用构造函数继承方式的缺点。
但是,这种方式仍有缺点,我们来看如下代码:
var s1 = new Child1();
s1.colors.push("black");
var s2 = new Child1();
s1.colors; // (4) ["red", "blue", "yellow", "balck"]
s2.colors; // (4) ["red", "blue", "yellow", "balck"]
我们实例化了两个Child1,在实例s1中为父类的colors属性push了一个颜色,但是s2也被跟着改变了。造成这种现象的原因就是原型链上中的原型对象它俩是共用的。
这不是我们想要的,s1
和s2
这个两个对象应该是隔离的,这是这种继承方式的缺点。
组合式继承
这里所谓的组合是指组合借用构造函数和原型链继承两种方式。
function Parent2(){
this.name = "parent2";
this.colors = ["red","blue","yellow"];
}
function Child2(){
Parent2.call(this);
this.type = "child2";
}
Child2.prototype = new Parent2()
注意第6,9行,这种方式结合了借用构造函数继承和原型链继承的有点,能否解决上述两个实例对象没有被隔离的问题呢?
var s1 = new Child2();
s1.colors.push("black");
var s2 = new Child2();
s1.colors; // (4) ["red", "blue", "yellow", "balck"]
s2.colors; // (3) ["red", "blue", "yellow"]
可以看到,s2
和s1
两个实例对象已经被隔离了。
但这种方式仍有缺点。父类的构造函数被执行了两次,第一次是Child2.prototype = new Parent2()
,第二次是在实例化的时候,这是没有必要的。
直接把父类的原型对象赋给子类的原型对象
function Parent3(){
this.name = "parent3";
this.colors = ["red","blue","yellow"];
}
Parent3.prototype.sex = "男";
Parent3.prototype.say = function(){console.log("Oh, My God!")}
function Child3(){
Parent3.call(this);
this.type = "child3";
}
Child3.prototype = Parent3.prototype;
var s1 = new Child3();
var s2 = new Child3();
console.log(s1, s2);
但是,我们来看如下代码:
console.log(s1 instanceof Child3); // true
console.log(s1 instanceof Parent3); // true
可以看到,我们无法区分实例对象s1
到底是由Child3直接实例化的还是Parent3直接实例化的。用instanceof
关键字来判断是否是某个对象的实例就基本无效了。
我们还可以用.constructor
来观察对象是不是某个类的实例:
console.log(s1.constructor.name); // Parent3
从这里可以看到,s1
的构造函数居然是父类,而不是子类Child3,这显然不是我们想要的。
这是继承的最完美方式
function Parent4(){
this.name = "parent4";
this.colors = ["red","blue","yellow"];
}
Parent4.prototype.sex = "男";
Parent4.prototype.say = function(){console.log("Oh, My God!")}
function Child4(){
Parent4.call(this);
this.type = "child4";
}
Child4.prototype = Object.create(Parent4.prototype);
Child4.prototype.constructor = Child4;
Object.create
是一种创建对象的方式,它会创建一个中间对象
var p = {name: "p"}
var obj = Object.create(p)
// Object.create({ name: "p" })
通过这种方式创建对象,新创建的对象obj
的原型就是p
,同时obj
也拥有了属性name
,这个新创建的中间对象的原型对象就是它的参数。
这种方式解决了上面的所有问题,是继承的最完美实现方式。
ES6中继承
Class
可以通过extends
关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
class Parent {
}
class Child1 extends Parent {
constructor(x, y, colors) {
super(x, y); // 调用父类的constructor(x, y)
this.colors = colors;
}
toString() {
return this.colors + ' ' + super.toString(); // 调用父类的toString()
}
}
上面代码中,constructor
方法和toString
方法之中,都出现了super
关键字,它在这里表示父类的构造函数,用来新建父类的this
对象。
子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。如果子类没有定义constructor
方法,这个方法会被默认添加,不管有没有显式定义,任何一个子类都有constructor
方法。
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this
上面(Parent.apply(this)
)。ES6 的继承机制完全不同,实质是先创造父类的实例对象this
(所以必须先调用super
方法),然后再用子类的构造函数修改this
。
前端面试:js的继承实现
转载 2017年03月06日 22:20:09
JS实现继承的几种方式
前言
JS作为面向对象的弱类型语言,继承也是其非常强大的特性之一。那么如何在JS中实现继承呢?让我们拭目以待。
JS继承的实现方式
既然要实现继承,那么首先我们得有一个父类,代码如下:
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
1、原型链继承
核心: 将父类的实例作为子类的原型
function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
特点:
缺点:
推荐指数:★★(3、4两大致命缺陷)
2、构造继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
特点:
缺点:
推荐指数:★★(缺点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);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false
特点:
缺点:
推荐指数:★★
4、拷贝继承
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);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
特点:
缺点:
推荐指数:★(缺点1)
5、组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
特点:
缺点:
推荐指数:★★★★(仅仅多消耗了一点内存)
6、寄生组合继承
核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
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);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
特点:
缺点:
推荐指数:★★★★(实现复杂,扣掉一颗星)
附录代码:
示例一:
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属性值已经变化了。
前端面试之ES6篇(高产似母猪)
云中歌 2017年09月25日发布
这也是前端面试经常询问的问题,经常问你es6出现了哪些新的特性,平时又使用过那些。在编写此教程的时候,第一句话往往就是面试常常问到的地方,然后后面就是他的详细解释,面试要求的内容我会用*标记出来。写技术文档是真的累啊,虽然是看别人的文档,但是你得看很多,而且还得自己总结啊。所以说要是觉得对你有用还是帮我点个star吧https://github.com/skychenbo
1、箭头函数需要注意的地方
2、ES6 let、const
3、set数据结构
4、promise对象的用法,手写一个promise
5、class的理解
6、模版语法的理解
7、rest参数
8、 module体系
箭头函数需要注意的地方
*当要求动态上下文的时候,就不能够使用箭头函数。也就是this的固定化
1、在使用=>定义函数的时候,this的指向是定义时所在的对象,而不是使用时所在的对象
2、不能够用作构造函数,这就是说,不能够使用new命令,否则就会抛出一个错误
3、不能够使用arguments对象
4、不能使用yield命令
这是一道当年很困惑我的一道题不知道你在第一眼能不能看出其结果,this的指向总是让人困扰,但是有了=>以后妈妈再也不用担心你使用this了
class Animal {
constructor(){
this.type = 'animal'
}
says(say) {
setTimeout(function () {
console.log(this.type + 'says' + say)
},1000)
}
}
var animal = new Animal()
animal.says('hi') // undefined says hi
我们再来看看=>的情况
class Animal() {
constructor() {
this.type = 'animal'
}
says(say) {
setTimeout(() => {
console.log(this.type + ' says ' + say)
}, 1000)
}
}
var animal = new Animal()
animal.says('hi') // animal says hi
ES6 let、const
*let是更完美的var,不是全局变量,具有块级函数作用域,大多数情况不会发生变量提升。const定义常量值,不能够重新赋值,如果值是一个对象,可以改变对象里边的属性值
let
1、let声明的变量具有块级作用域
2、let声明的变量不能通过window.变量名进行访问
3、形如for(let x..)的循环是每次迭代都为x创建新的绑定
下面是var带来的不合理场景
var a = []
for (var i = 0; i < i; i++) {
a[i] = function () {
console.log(i)
}
}
a[5]() // 10
在上述代码中,变量i是var声明的,在全局范围类都有效。所以每一次循环,新的i值都会覆盖旧值,导致最后输出都是10
而如果对循环使用let语句的情况,那么每次迭代都是为x创建新的绑定代码如下
var a = []
for (let i = 0; i < i; i++) {
a[i] = function () {
console.log(i)
}
}
a[5]() // 5
当然除了这种方式让数组中的各个元素分别是不同的函数,我们还可以采用闭包和立即函数两种方法
这是闭包的方法
function showNum(i) {
return function () {
console.log(i)
}
}
var a = []
for (var i = 0; i < 5; i++) {
a[i] = showNum(i)
}
这是立即函数的方法
var a = []
for (var i = 0; i < 5; i++) {
a[i] = (function (i) {
return function () {
console.log(i)
}
})(i)
}
a[2]()
Set数据结构
*es6方法,Set本身是一个构造函数,它类似于数组,但是成员值都是唯一的
const set = new Set([1,2,3,4,4])
[...set] // [1,2,3,4]
Array.from(new Set())是将set进行去重
promise对象的用法,手写一个promise
promise是一个构造函数,下面是一个简单实例
var promise = new Promise((resolve,reject) => {
if (操作成功) {
resolve(value)
} else {
reject(error)
}
})
promise.then(function (value) {
// success
},function (value) {
// failure
})
Class的讲解
*class语法相对原型、构造函数、继承更接近传统语法,它的写法能够让对象原型的写法更加清晰、面向对象编程的语法更加通俗
这是class的具体用法
class Animal {
constructor () {
this.type = 'animal'
}
says(say) {
console.log(this.type + 'says' + say)
}
}
let animal = new Animal()
animal.says('hello') // animal says hello
class Cat extends Animal {
constructor() {
super()
this.type = 'cat'
}
}
let cat = new Cat()
cat.says('hello') // cat says hello
可以看出在使用extend的时候结构输出是cat says hello 而不是animal says hello。说明contructor内部定义的方法和属性是实例对象自己的,不能通过extends 进行继承。在class cat中出现了super(),这是什么呢
在ES6中,子类的构造函数必须含有super函数,super表示的是调用父类的构造函数,虽然是父类的构造函数,但是this指向的却是cat
Object.assign 方法
var n = Object.assign(a,b,c)向n中添加a,b,c的属性
模版语法
*就是这种形式${varible},在以往的时候我们在连接字符串和变量的时候需要使用这种方式'string' + varible + 'string'但是有了模版语言后我们可以使用string${varible}string这种进行连接
rest参数
*es6引入rest参数,用于获取函数的多余参数,这样就不需要使用arguments对象了
ex:
function add(...values) {
let sum = 0
for(var val of values) {
sum += val
}
return sum
}
module体系
*历史上js是没有module体系,无法将一个大程序拆分成一些小的程序。在es6之前,有commonJs,AMD两种
CommonJS是如何书写的呢
const animal = require('./content.js')
// content.js
module.exports = 'a cat'
require.js是这样做的
// content.js
define('content.js', function () {
return 'A cat'
})
require(['./content.js'], function (animal) {
console.log(animal) // a cat
})
ES6的语法(在我用的vue中,就使用的是这个)
import animal from './content'
// content.js
export default 'a cat'
es6 import的其他用法
在vue中可以 import animal from './content'
animal这个值可以根据你的喜欢而改变,但是有一个问题就是如果一旦引入的是函数或者变量时,你就必须和content中的名字保持一致,可以参照
import { say, type } from './content'
常用的还有一种写法
import * as content from './content'
这种写法就是表示所有的输出值都在这个对象上
80% 应聘者都不及格的 JS 面试题
发布于 1 年前 作者 wangshijun 6688 次浏览 来自 分享
共 5024 字,读完需 6 分钟,速读需 2 分钟,本文首发于知乎专栏前端周刊。写在前面,笔者在做面试官这 2 年多的时间内,面试了数百个前端工程师,惊讶的发现,超过 80% 的候选人对下面这道题的回答情况连及格都达不到。这究竟是怎样神奇的一道面试题?他考察了候选人的哪些能力?对正在读本文的你有什么启示?且听我慢慢道来
招聘前端工程师,尤其是中高级前端工程师,扎实的 JS 基础绝对是必要条件,基础不扎实的工程师在面对前端开发中的各种问题时大概率会束手无策。在考察候选人 JS 基础的时候,我经常会提供下面这段代码,然后让候选人分析它实际运行的结果:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i);
这段代码很短,只有 7 行,我想,能读到这里的同学应该不需要我逐行解释这段代码在做什么吧。候选人面对这段代码时给出的结果也不尽相同,以下是典型的答案:
只要你对 JS 中同步和异步代码的区别、变量作用域、闭包等概念有正确的理解,就知道正确答案是 C,代码的实际输出是:
2017-03-18T00:43:45.873Z 5
2017-03-18T00:43:46.866Z 5
2017-03-18T00:43:46.868Z 5
2017-03-18T00:43:46.868Z 5
2017-03-18T00:43:46.868Z 5
2017-03-18T00:43:46.868Z 5
接下来我会追问:如果我们约定,用箭头表示其前后的两次输出之间有 1 秒的时间间隔,而逗号表示其前后的两次输出之间的时间间隔可以忽略,代码实际运行的结果该如何描述?会有下面两种答案:
这就要求候选人对 JS 中的定时器工作机制非常熟悉,循环执行过程中,几乎同时设置了 5 个定时器,一般情况下,这些定时器都会在 1 秒之后触发,而循环完的输出是立即执行的,显而易见,正确的描述是 B。
如果到这里算是及格的话,100 个人参加面试只有 20 人能及格,读到这里的同学可以仔细思考,你及格了么?
追问 1:闭包
如果这道题仅仅是考察候选人对 JS 异步代码、变量作用域的理解,局限性未免太大,接下来我会追问,如果期望代码的输出变成:5 -> 0,1,2,3,4,该怎么改造代码?熟悉闭包的同学很快能给出下面的解决办法:
for (var i = 0; i < 5; i++) {
(function(j) { // j = i
setTimeout(function() {
console.log(new Date, j);
}, 1000);
})(i);
}
console.log(new Date, i);
巧妙的利用 IIFE(Immediately Invoked Function Expression:声明即执行的函数表达式)来解决闭包造成的问题,确实是不错的思路,但是初学者可能并不觉得这样的代码很好懂,至少笔者初入门的时候这里琢磨了一会儿才真正理解。
有没有更符合直觉的做法?答案是有,我们只需要对循环体稍做手脚,让负责输出的那段代码能拿到每次循环的 i 值即可。该怎么做呢?利用 JS 中基本类型(Primitive Type)的参数传递是按值传递(Pass by Value)的特征,不难改造出下面的代码:
var output = function (i) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
};
for (var i = 0; i < 5; i++) {
output(i); // 这里传过去的 i 值被复制了
}
console.log(new Date, i);
能给出上述 2 种解决方案的候选人可以认为对 JS 基础的理解和运用是不错的,可以各加 10 分。当然实际面试中还有候选人给出如下的代码:
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i);
细心的同学会发现,这里只有个非常细微的变动,即使用 ES6 块级作用域(Block Scope)中的 let 替代了 var,但是代码在实际运行时会报错,因为最后那个输出使用的 i 在其所在的作用域中并不存在,i 只存在于循环内部。
能想到 ES6 特性的同学虽然没有答对,但是展示了自己对 ES6 的了解,可以加 5 分,继续进行下面的追问。
追问 2:ES6
有经验的前端同学读到这里可能有些不耐烦了,扯了这么多,都是他知道的内容,先别着急,挑战的难度会继续增加。
接着上文继续追问:如果期望代码的输出变成 0 -> 1 -> 2 -> 3 -> 4 -> 5,并且要求原有的代码块中的循环和两处 console.log 不变,该怎么改造代码?新的需求可以精确的描述为:代码执行时,立即输出 0,之后每隔 1 秒依次输出 1,2,3,4,循环结束后在大概第 5 秒的时候输出 5(这里使用大概,是为了避免钻牛角尖的同学陷进去,因为 JS 中的定时器触发时机有可能是不确定的,具体可参见 How Javascript Timers Work)。
看到这里,部分同学会给出下面的可行解:
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(new Date, j);
}, 1000 * j)); // 这里修改 0~4 的定时器时间
})(i);
}
setTimeout(function() { // 这里增加定时器,超时设置为 5 秒
console.log(new Date, i);
}, 1000 * i));
不得不承认,这种做法虽粗暴有效,但是不算是能额外加分的方案。如果把这次的需求抽象为:在系列异步操作完成(每次循环都产生了 1 个异步操作)之后,再做其他的事情,代码该怎么组织?聪明的你是不是想起了什么?对,就是 Promise。
可能有的同学会问,不就是在控制台输出几个数字么?至于这样杀鸡用牛刀?你要知道,面试官真正想考察的是候选人是否具备某种能力和素质,因为在现代的前端开发中,处理异步的代码随处可见,熟悉和掌握异步操作的流程控制是成为合格开发者的基本功。
顺着下来,不难给出基于 Promise 的解决方案(既然 Promise 是 ES6 中的新特性,我们的新代码使用 ES6 编写是不是会更好?如果你这么写了,大概率会让面试官心生好感):
const tasks = [];
for (var i = 0; i < 5; i++) { // 这里 i 的声明不能改成 let,如果要改该怎么做?
((j) => {
tasks.push(new Promise((resolve) => {
setTimeout(() => {
console.log(new Date, j);
resolve(); // 这里一定要 resolve,否则代码不会按预期 work
}, 1000 * j); // 定时器的超时时间逐步增加
}));
})(i);
}
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(new Date, i);
}, 1000); // 注意这里只需要把超时设置为 1 秒
});
相比而言,笔者更倾向于下面这样看起来更简洁的代码,要知道编程风格也是很多面试官重点考察的点,代码阅读时的颗粒度更小,模块化更好,无疑会是加分点。
const tasks = []; // 这里存放异步操作的 Promise
const output = (i) => new Promise((resolve) => {
setTimeout(() => {
console.log(new Date, i);
resolve();
}, 1000 * i);
});
// 生成全部的异步操作
for (var i = 0; i < 5; i++) {
tasks.push(output(i));
}
// 异步操作完成之后,输出最后的 i
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(new Date, i);
}, 1000);
});
读到这里的同学,恭喜你,你下次面试遇到类似的问题,至少能拿到 80 分。
我们都知道使用 Promise 处理异步代码比回调机制让代码可读性更高,但是使用 Promise 的问题也很明显,即如果没有处理 Promise 的 reject,会导致错误被丢进黑洞,好在新版的 Chrome 和 Node 7.x 能对未处理的异常给出 Unhandled Rejection Warning,而排查这些错误还需要一些特别的技巧(浏览器、Node.js)。
追问 3:ES7
既然你都看到这里了,那就再坚持 2 分钟,接下来的内容会让你明白你的坚持是值得的。
多数面试官在决定聘用某个候选人之前还需要考察另外一项重要能力,即技术自驱力,直白的说就是候选人像有内部的马达在驱动他,用漂亮的方式解决工程领域的问题,不断的跟随业务和技术变得越来越牛逼,究竟什么是牛逼?建议阅读程序人生的这篇剖析。
回到正题,既然 Promise 已经被拿下,如何使用 ES7 中的 async await 特性来让这段代码变的更简洁?你是否能够根据自己目前掌握的知识给出答案?请在这里暂停 1 分钟,思考下。
下面是笔者给出的参考代码:
// 模拟其他语言中的 sleep,实际上可以是任何异步操作
const sleep = (timeountMS) => new Promise((resolve) => {
setTimeout(resolve, timeountMS);
});
(async () => { // 声明即执行的 async 函数表达式
for (var i = 0; i < 5; i++) {
await sleep(1000);
console.log(new Date, i);
}
await sleep(1000);
console.log(new Date, i);
})();
总结
感谢你花时间读到这里,相信你收获的不仅仅是用 JS 精确控制代码输出的各种技巧,更是对于前端工程师的成长期许:扎实的语言基础、与时俱进的能力、强大技术自驱力。
深入理解ES6箭头函数的this以及各类this面试题总结
原创 2017年03月11日 19:16:05
ES6中新增了箭头函数这种语法,箭头函数以其简洁性和方便获取this的特性,俘获了大批粉丝儿
它也可能是面试中的宠儿, 我们关键要搞清楚 箭头函数和普通函数中的this
一针见血式总结:
普通函数中的this:
1. this总是代表它的直接调用者(js的this是执行上下文), 例如 obj.func ,那么func中的this就是obj
2.在默认情况(非严格模式下,未使用 'use strict'),没找到直接调用者,则this指的是 window (约定俗成)
3.在严格模式下,没有直接调用者的函数中的this是 undefined
4.使用call,apply,bind(ES5新增)绑定的,this指的是 绑定的对象
箭头函数中的this
箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window; 箭头函数可以方便地让我们在 setTimeout ,setInterval中方便的使用this
下面通过一些例子来研究一下 this的一些使用场景[ 使用最新版 chrome测试 ]
要整明白这些, 我们需要首先了解一下作用域链:
当在函数中使用一个变量的时候,首先在本函数内部查找该变量,如果找不到则找其父级函数,
最后直到window,全局变量默认挂载在window对象下
1.全局变量默认挂载在window对象下
我们仅仅声明了一个全局变量aa,但是打印出window.aa却和aa保持一致,为什么呢?
眼见为实, 我们使用console.dir(window) 打印 window对象看看
我们可以看到在window属性中,看到 aa 属性了;此外,函数也适用于此情况,全局函数也会挂在在window对象下
我们常见的window的属性和方法有: alert, location,document,parseInt,setTimeout,setInterval等,window的属性默认可以省略window前缀!
2.在普通函数中,this指向它的直接调用者;如果找不到直接调用者,则是window
我们来看一些例子
示例1:
结果是: window
原因: test()是一个全局函数,也就是说是挂在window对象下的,所以test()等价于 window.test() ,所以此时的this是window
示例2:
结果是: window
匿名函数,定时器中的函数,由于没有默认的宿主对象,所以默认this指向window
问题: 如果想要在setTimeout/setInterval中使用这个对象的this引用呢?
用一个 变量提前把正确的 this引用保存 起来, 我们通常使用that = this, 或者 _this = this来保存我们需要的this指针!
我们也可以使用 func.bind(this) 给回调函数直接绑定宿主对象, bind绑定宿主对象后依然返回这个函数, 这是更优雅的做法
[javascript] view plain copy
示例3(改变自360面试题):
结果是: 2 4 8 8
<1> 12行代码调用
val变量在没有指定对象前缀,默认从函数中找,找不到则从window中找全局变量
即 val *=2 就是 window.val *= 2
this.val默认指的是 obj.val ;因为 dbl()第一次被obj直接调用
<2>14行代码调用
func() 没有任何前缀,类似于全局函数,即 window.func调用,所以
第二次调用的时候, this指的是window, val指的是window.val
第二次的结果受第一次的影响
3.在严格模式下的this
结果是: undefined
4.箭头函数中的 this
此时的 this继承自obj, 指的是定义它的对象obj, 而不是 window!
示例(多层嵌套的箭头函数):
因为f1定义时所处的函数 中的 this是指的 obj, setTimeout中的箭头函数this继承自f1, 所以不管有多层嵌套,都是 obj
示例(复杂情况: 普通函数和箭头函数混杂嵌套)
结果: 都是 window,因为 箭头函数在定义的时候它所处的环境相当于是window, 所以在箭头函数内部的this函数window
示例(严格模式下的混杂嵌套)
结果都是undefined
说明: 严格模式下,没有宿主调用的函数中的this是undefined!!!所以箭头函数中的也是undefined!
总结:
使用箭头函数,可以让我们解决一些在匿名函数中 this指向不正确的问题; 但是要注意在和普通函数混合的时候,this的指向可能是window !
Y(^o^)Y, 掌握这么多已经足够面试绝大部分关于this的内容了,我们在开发中的应用也没问题了!
如果对大家有所帮助, 我会感到很开心!
推荐阅读:
vue v-model的原理实现一个自定义的表单组件
vue项目按需加载之 require.ensure: http://blog.csdn.net/yangbingbinga/article/details/61417689
前端面试之跨域请求
原创 2017年08月26日 18:06:18
背景
跨域是由浏览器的同源策略引起的,是指页面请求的url地址,必须与浏览器上url地址处于同域上(即域名,端口,协议相同)。这是为了防止某域名下的接口被其他域名下的网页非法调用,是浏览器对javascript施加的安全限制。
一、解决本地(使用文件协议)的跨域请求问题
首先创建一个test项目,在此项目中创建demo2.html文件,在data文件夹下新建一个mydata.json文件用来存储我们要访问的数据。
html中代码很简单,只有一个ajax请求。代码如下:
<script>
$(function(){
//alert(1);
$.ajax({
type:'get',
url:'../data/mydata.json',
success:function(data){
console.log(data);
},
error:function(){
console.log('error');
}
});
})
script>
解决这个问题百度上有好多答案,具体操作:右键浏览器快捷方式,选择属性,在目标中添加–allow-file-access-from-files即可。
这样我们就能访问到数据了。(注意:如果还是报错,就以管理员身份运行浏览器)。
二、解决外网跨域请求问题。
为了模拟真实的网络环境,我将之前创建的test项目放到tomcat的webapp下,然后运行tomcat。同时将html中的ajax访问的url地址改为淘宝免费提供的一个API接口https://suggest.taobao.com/sug?code=utf-8&q=手机,点击这个地址可以获取到相应的json数据。
浏览器的地址是tomcat地址,而请求数据的地址是淘宝的地址,这肯定已经存在跨域问题了。ok,准备好之后,运行,不出我们预料,报错了…
这里我们提供两种解决方法:
第一种,使用jsonp协议, jQuery中的$.ajax方法也直接支持使用该协议进行跨域访问。代码就是在之前的ajax参数中再增加一个dataType:’jsonp’参数。同时我们又增加了一个dataFiter方法,你可以在这个方法内对返回的json数据进行预处理,比如过滤、更改数据等。我增加的目的主要是跟第二种方法做对比,这个后面再说。代码如下:
$.ajax({
type:'get',
dataType:'jsonp',
url:'https://suggest.taobao.com/sug?code=utf-8&q=手机',
//url:'../data/mydata.json',
dataFilter:function(json){
console.log(json);
return json;
},
success:function(data){
console.log(data);
},
error:function(XMLHttpRequest, textStatus, errorThrown){
console.log(XMLHttpRequest.status);
console.log(XMLHttpRequest.readyState);
console.log(textStatus);
}
});
结果就访问到我们需要的数据了:注意打印的undefined是我们上面的dataFilter方法打印的。
第二种,使用jquery提供的一个插件jquery-jsonp,这是下载地址https://github.com/congmo/jquery-jsonp。下载后以后像引入jquery一样在html页面引入。代码如下:
$.jsonp({
type:'get',
url:'https://suggest.taobao.com/sug?code=utf-8&q=手机',
callbackParameter:"callback",
dataFilter:function(json){
console.log(json);
return json;
},
//url:'../data/mydata.json',
success:function(data){
console.log(data);
},
error:function(msg){
console.log(msg);
}
});
看起来跟上面的ajax方法差不多,注意在这里多了callbackParameter:”callback”这行代码,不加是会报错的。结果如下:第一个object对象是dataFilter方法打印的,第二个是我们在success方法中获取的数据。
数据获取到了,跨域问题解决了。最后我们再说一下dataFilter这个方法:
jsonp的dataFilter中的数据直接转换成json对象了,而ajax的dataFilter中获取的却是原生的带callback方法名的json字符串(控制台打印出来的是undefined)。如果需要对返回的数据进行预处理的话,建议使用jquery插件jquery-jsonp的$.jsonp方法。
同源、跨域、jsonp(面试常问)
原创 2017年03月07日 15:29:09
提到跨域,就不得不说一下同源策略,同源策略是浏览器的一种安全策略,也就说a网站不能随便读取b网站的内容,试想一下,如果网站之间都可以随便读取互相的文件,比如一个黑客程序,他利用IFrame把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。
所谓"同源"是指协议、端口号、域名相同,那么"跨域"就可以理解为不同源的网站之间的访问,最常见的应用是当我们调用ajax接口时如果不设置跨域浏览器会报错,这证明使用xmlHttpRequest对象不能发送跨域请求。
有疑惑的小伙伴肯定会问,那我用a标签和script标签请求其他网站,是不是就是跨域了呢?
这里要明白跨域是指在当前域下调用其他域下的东西,而链接则是直接跳转到对方的域下了,跟你之前的域名毫无关系。
如果想从你的网站跨域去另一个网站拿到一些资源,有一个前提条件是另一个网站的服务器支持跨域,这个需要在服务端设置,不同服务端设置方法不一样,这里我们不多说,就看客户端跨域如何解决。
解决跨域最常见方法是jsonp方式,jsonp是jquery给我们封装的一个方法,使用方法如下:
[javascript] view plain copy
上面代码是当我们调用外部的一个接口时,通过设置jquery的ajax方法里面的datatype属性的值为jsonp就可以成功调用这个接口了。
现在当有人问起你如何解决跨域,你说用jsonp,这时候我相信不懂的人一定还是不懂,哈哈,人家会想jsonp是个什么鬼?
这就告诉我们学东西要知其然而知其所以然,也许我们以为script标签只能引用本地文件,却不知script标签也可以发送请求,下面就是jsonp的原理
[html] view plain copy
[php] view plain copy
jsonp跨域的原理
1:使用script 标签发送请求,这个标签支持跨域访问
2:在script 标签里面给服务器端传递一个 callback
3:callback 的值对应到页面一定要定义一个全局函数(为什么是全局?因为服务端接收到callback函数后会返回页面中的script中去找,如果不写在全局作用域中根本找不到)
4:服务端返回的是一个函数的调用。调用的时候会吧数据作为参数包在这个函数里面。
缺点:jsonp只能解决get方式的跨域
CSS3 弹性盒布局模型和布局原理
标签: 弹性盒模型 css3
2017年02月09日 14:00:261652人阅读 评论(0) 收藏 举报
分类:
CSS & CSS3(19)
版权声明:本文为博主原创文章,如需要转载,请标明文章出处,谢谢。 https://blog.csdn.net/u010297791/article/details/54945290
在CSS 3中,CSS Flexible Box模块为一个非常重要的模块,该模块用于以非常灵活的方式实现页面布局处理。
虽然可以使用其他CSS样式属性来实现页面布局处理,但是如果使用CSS Flexible Box模块中定义的弹性盒布局技术,可以根据屏幕尺寸或浏览器窗口尺寸自动调整页面中各局部区域的显示方式,即实现非常灵活的布局处理。
引入弹性盒布局模型的目的是提供一种更加有效的方式来对一个容器中的条目进行排列、对齐和分配空白空间。即便容器中条目的尺寸未知或是动态变化的,弹性盒布局模型也能正常的工作。在该布局模型中,容器会根据布局的需要,调整其中包含的条目的尺寸和顺序来最好地填充所有可用的空间。当容器的尺寸由于屏幕大小或窗口尺寸发生变化时,其中包含的条目也会被动态地调整。比如当容器尺寸变大时,其中包含的条目会被拉伸以占满多余的空白空间;当容器尺寸变小时,条目会被缩小以防止超出容器的范围。弹性盒布局是与方向无关的。在传统的布局方式中,block 布局是把块在垂直方向从上到下依次排列的;而 inline 布局则是在水平方向来排列。弹性盒布局并没有这样内在的方向限制,可以由开发人员自由操作。
在进行详细 解释这个模型之前,我们先了解一下弹性盒模型的几个属性,稍微看一下就好,后面会解释具体的用法。
1)box-orient:用来确定子元素的方向。是横着排还是竖着走。
2)box-direction:用来确定子元素的排列顺序,即是否反转。
3)box-pack:用来确定子元素的左右对齐方式。
4)box-align:用来确定子元素的上下对齐方式。
5)box-flex:用来确定子元素如何分配其剩余元素。子元素的尺寸=盒子的尺寸*子元素的box-flex属性值 / 所有子元素的box-flex属性值的和。
5)box-lines:用来决定子元素是可以换行显示。
6)box-flex-group:用来确定子元素的所属组。
7)box-ordinal-group:用来确定子元素的显示顺序。
接下来开始通过一个示例页面开始学习弹性盒布局。该示例页面中的body元素中的代码如下所示。
[html] view plain copy
示例文字
示例文字
示例文字
示例文字
示例文字
示例文字
示例文字
示例文字
示例文字
示例文字
示例文字
示例文字
为了更清晰看清楚结构,具体的样式代码如下:
[css] view plain copy
页面显示结果如下:
1、使用弹性盒布局 dispaly:box
接下来,对示例页面使用弹性盒布局:
弹性盒布局的指定方法为:对需要布局的元素的容器元素使用display: -moz-box;display: -webkit-box;display: box;样式属性,因为这个属性需要兼容各种浏览器,所以需要写前缀进行兼容性书写。在CSS Flexible Box模块中,该容器元素中的每一个元素均被称为“Flex item”,将该容器元素称为“Flex container”。
弹性盒布局方式与使用float等样式属性进行的布局方式的一个主要区别为,当使用float等样式属性时,需要对容器中每一个元素指定样式属性,当使用弹性盒布局时,只需对容器元素指定样式属性。
接下来,我们首先对所有样式类名为content的div元素使用弹性盒布局,这些div元素的容器元素为id属性值为main的div元素,修改该元素的样式代码如下所示:
[css] view plain copy
在浏览器中打开示例页面,页面中所有样式类名为content的div元素的排列方式被修改为横向排列,如下图所示。
2、设置元素排列顺序 box-direction
可以通过box-direction样式属性的使用来控制容器中所有子元素的排列顺序,可指定值如下所示。
其中normal是默认值,表示按照正常顺序排列。所谓正常顺序,就是我们看书写文字的顺序,从左往右,由上至下,先出现的元素,就上面或是左边。而reverse表示反转。
修改id属性值为main的div元素的样式代码如下所示:
[css] view plain copy
在浏览器中打开示例页面,页面中所有样式类名为content的div元素的排列方式被修改为从容器元素,即id属性值为main的div元素的右端开始横向反向排列,如下图所示。
接下来首先恢复所有样式类名为content的div元素的排列方式为横向正向排列,修改id属性值为main的div元素的样式代码如下所示:
[css] view plain copy
然后对所有样式类名为content的div元素指定box-direction: reverse;样式属性,代码如下所示:
[css] view plain copy
在浏览器中打开示例页面,页面中所有content的div元素的所有section子元素的排列方式被修改为纵向反向排列(不包含section子元素中的section孙元素),如下图所示。
3、设置元素排列方式 box-orient
可以通过box-orient来确定容器中子元素的方向。是横着排还是竖着走。可选的值有:
horizontal | vertical | inline-axis | block-axis | inherit
其中,inline-axis是默认值。且horizontal与inline-axis的表现似乎一致的,让子元素横排;而vertical与block-axis的表现也是一致的,让元素纵列。
然后对所有样式类名为content的div元素指定-moz-box-orient:vertical; -webkit-box-orient:vertical; box-orient:vertical; 样式属性,代码如下显示:
[css] view plain copy
在浏览器中打开示例页面,页面中所有content的div元素的所有section子元素的排列方式被修改为纵向排列(不包含section子元素中的section孙元素),如下图所示。
4、设置元素宽度 box-flex
接下来首先介绍如何设置被横向排列的每一个元素的宽度。
可以通过flex属性值的使用使所有子元素的总宽度等于容器宽度。
接下来通过将所有样式类名为content的div元素的flex属性值设置为1的方法使所有样式类名为content的div元素的总宽度等于容器元素,即id属性值为main的div元素的宽度,代码如下所示。当所有样式类名为content的div元素的flex属性值都被设置为1时,这些div元素的宽度均等。
[css] view plain copy
在浏览器中打开示例页面,所有样式类名为content的div元素的宽度自动增长,这些元素的总宽度等于容器元素,即id属性值为main的div元素的宽度,每一个样式类名为content的div元素的宽度均等,如下图所示。
接下来,我们设置第二个样式类名为content的div元素的box-flex属性值为2,代码如下所示。
[css] view plain copy
在浏览器中打开示例页面,所有样式类名为content的div元素的宽度自动增长,第二个类名为content的div元素 的宽度时其他两个div的2倍,如下图所示。
为了更清晰地计算元素宽度,我们取消所有元素的边框设置及内边距设置,修改后的完整样式代码如下所示。
[css] view plain copy
在浏览器中打开示例页面,第二个样式类名为content的div元素宽度为其他样式类名为content的div元素宽度的两倍,假设这些元素的容器元素,即id属性值为main的div元素的宽度等于600px,则第一个与第三个样式类名为content的div元素宽度的宽度均等于150px,第二个样式类名为content的div元素宽度的宽度等于300px。
5、设置垂直方向上的对齐方式 box-align
box-align决定了垂直方向上的空间利用,也就是垂直方向上的对齐表现。为了便于记忆,我们可以拿来和CSS2中的vertical-align隐射记忆,两者都有”align”,都是都是垂直方向的对齐。
box-align的可选参数有:
start | end | center | baseline | stretch
其中stretch为默认值,为拉伸,也就是父标签高度过高,其孩子元素的高度就多高,start表示顶边对齐,end为底部对齐,center为居中对齐,baseline表示基线(英文字母o,m,n等的底边位置线)对齐。
6、设置垂直方向上的对齐方式box-pack
box-pack决定了父标签水平遗留空间的使用,其可选值有:
start | end | center | justify
就大部分的行为表现来说分别对应text-align属性的值:left | right | center | justify;但是,之所以box-pack不使用”left”, 而是”start”,是因为box-direction属性,这玩意可以反转原本的排列,原本的“左对齐”反转后结果是“右对齐”了,此时”left”显然就词不达意了,所以使用”start”更具有概括性,就是与父标签的起始位置对齐,从而不会产生语义与行为上的困扰。
7、box-flex-group和box-ordinal-group
子元素除了以上说的几个属性外,还有两个属性box-flex-group和box-ordinal-group,因为这两个属性实用性不强,这里就不做详细的说明。目前没有浏览器支持 box-flex-group 属性。
box-ordinal-group用整数值来定义伸缩盒对象的子元素显示顺序。数值越小,位置就越靠前,这不难理解,第一组在最前嘛,随后第二组,第三组… 例如:box-ordinal-group:1的组就会在box-ordinal-group:2的组前面显示。于是,我们可以利用这个属性改变子元素的顺序。