黑夜键盘手

黑夜键盘手 查看完整档案

重庆编辑吉林大学  |  计算机-软件应用 编辑商社电器  |  前端工程师 编辑 blog.csdn.net/qq_26455367 编辑
编辑

路虽远,无所畏。

个人动态

黑夜键盘手 赞了文章 · 2019-09-03

go 学习笔记之万万没想到宠物店竟然催生出面向接口编程?

到底是要猫还是要狗

在上篇文章中,我们编撰了一则简短的小故事用于讲解了什么是面向对象的继承特性以及 Go 语言是如何实现这种继承语义的,这一节我们将继续探讨新的场景,希望能顺便讲解面向对象的接口概念.

为了照顾到没有看过上一节文章的读取,这里再简述一下上节文章关于买宠物的故事,如需详细了解,请自行翻阅历史文章进行查看.

A: 猫是一种宠物,淘气可爱会卖萌,看家本领抓老鼠,偶尔还会喵喵喵.
B: 狗是一种宠物,忠实听话能看家,嗅觉灵敏会破案,一言不合汪汪汪.
C: 我想要买一个宠物,文能卖萌,武可退敌,明个一早给我送来吧!

于是,第二天,A和B各自带着自己的宠物来拜见C,并附上各自的理由,说的头头是道,C总觉得有些哪里不对,可一时间又无言反驳,只能悻悻收下了猫和狗,白白多花了一份钱!

go-oop-inheritance-one-pet.jpeg

这则故事很简单,但同时也暴露出一个问题,那就是在这场交易中,卖家实际上亏了,明明只是想买一个宠物,结果却买了两个!

当然,在上篇文中最后也给出了解决方案,那就是将猫和狗进行抽象封装,共性的部分提取成宠物,个性的部分才是猫和狗.

如此一来,顾客买宠物时要么买的是猫,要么面对是狗,具体买的是什么宠物是由顾客自己根据各自宠物的个性决定的,一定程度上解决了交易不公平的问题.

让我们再简单回忆一下继承的实现过程,回忆的过程中不妨思考一下继承有没有没能解决的问题?

  • 宠物默认自带能文能武技能
type Pet struct {

}

func (p *Pet) Skill() {
    fmt.Println("能文能武的宠物")
}
  • 猫是宠物,还是能抓老鼠的宠物.
type Cat struct {
    p *Pet
}

func (c *Cat) Catch() {
    fmt.Println("老鼠天敌喵喵喵")
}
  • 狗是宠物,还是能认路导航的宠物.

type Dog struct {
    p *Pet
}

func (d *Dog) Navigate() {
    fmt.Println("自带导航汪汪汪")
}

某一天,C要能文能武的宠物,最好还可以顺便抓个老鼠,于是C选择了喵喵喵!

func TestExtendInstance(t *testing.T) {
    p := new(Pet)
    c := new(Cat)
    c.p = p

    // 老鼠天敌喵喵喵
    c.Catch()
    // 能文能武的宠物
    c.p.Skill()
}

过了一阵子,C觉得猫除了抓老鼠别的什么都不会,别人遛狗,我遛猫?

于是,想要一种能自带导航功能的宠物,毫无疑问的是,选择了狗.

func TestExtendInstance(t *testing.T) {
    p := new(Pet)
    d := new(Dog)
    d.p = p

    // 自带导航汪汪汪
    d.Navigate()
    // 能文能武的宠物
    d.p.Skill()
}

上述示例,简而言之就是通过组合的方式实现了面向对象中的继承特性,解决了猫和狗除了是宠物还是自己的问题.

猫狗随便是宠物就行

面对猫和狗两种宠物,顾客犯了选择困难症,于是第一次全盘照收买下了两种宠物,吃了一次哑巴亏.

后来在市场监督的介入下,利用面向对象的继承特性,用 Go 语言实现了猫和狗的个性化与宠物的共性化,从此像C一样的顾客再也不会面临选择困难症,每一次都要根据独特的需求,最终选择某一种宠物,要么是猫,要么是狗.

不知过了多久,这种相安无事的场景最终被一群急性子的顾客所打破,这一天宠物市场一大早就来一大批人,一上来就吵吵嚷嚷说快给我们一批宠物,我们要作为抽奖活动的奖品,一定要快一点!

go-oop-interface-hurry.jpeg

谁知道销售人员不紧不慢地说: "别着急,我们这里的宠物有很多种,有猫,有狗,有兔子,有金鱼,有乌龟,有蜗牛..."

"别整那些虚头巴脑的,我只要宠物,赶紧给我宠物就行,别尽扯没用的",顾客吵吵说.

果然是一群急性子的顾客,还没等销售人员介绍完各个宠物的差异性亮点直接被打断了.

宠物市场吵吵闹闹引来了市场监督人员的注意,顾客和商家均向官方诉苦,期望能给出一个解决办法!

市场监督人员心想: 商家和顾客原本和谐相处的,今天怎么会吵闹起来?

仔细听了事情来龙去脉,双方都没有过错,看来还真的是市场赶不上实际需求的变化,真得尽快研究新的解决办法才行啊!

go-oop-interface-issue-new.jpg

"冷静一下,你们的意见我们这边已经知道了,这样吧,给我们三天的时间,我们一定会想出一个万全之策,到时候再公布新的交易规则,现在我宣布暂时关闭交易,省的再惹出不必要的争端!"

原本吵吵嚷嚷的市场顿时冷却了不少,毕竟谁也不敢违抗市场老大的命令,众人只得悻悻而去,期待三天后的重新开市.

视角切换到市场监督大会上,主席首先开始发言:"各位,现在市场面临的问题想必大家都有所耳闻吧,我们已经郑重承诺,三天后必须给市场一个答复,时间紧,任务重,大家要集思广益,一起解决这个难题!"

go-oop-interface-issue-meeting.jpg

"现在的顾客到底是怎么了,连自己到底想要什么都搞不清楚,还急冲冲地跑来买宠物,自己都不知道要买啥,鬼才知道呢!",资历老练的继承经理抱怨道.

"经理说得对,他们自己都不知道到底想要啥宠物,怎么能埋怨商家太罗里吧嗦呢?人家那么卖力介绍宠物的特点,不也是帮助顾客更好的选择嘛!",发言的是继承经理的小弟.

"..."

"咳咳,我理解大家的心情,继承项目组确实在解决宠物问题上立下了很大功劳,大家为他们抱不平也是情理之中的事情,过去的就让他过去吧!当务之急,还是要解决现实问题!",主席首先安抚前几位激动情绪,又挑出重点提醒在场的各位回归到主题的讨论上,不要再揪住过去的功劳簿.

"我觉得,心急顾客的真正需求只是想要一种宠物,而不再关注宠物的种类,管他是猫是狗,只要是宠物就行.所以我们应该提供一种新的机制,对外宣传时只说这是宠物,至于这种宠物到底是猫还是狗,都可以!"

"猫和狗明明已经是宠物了啊,难道不可以直接卖给顾客吗?为啥还要提供新机制?"

"猫和狗虽然是宠物,但对于用户来说,这种宠物有点浪费了,用户实际使用到可能只是宠物的功能,并不会用到也不能用到具体宠物的功能,所以对用户来说,这就是一种浪费."

"哦哦,明白了,这就像是顾客需要的宠物是能卖萌的,是要送给女朋友作为礼物的,并不关心这个宠物能不能抓老鼠.所以对于抓老鼠的技能就是没用的,而买家却要为抓老鼠的技能额外买单,这对于买家来说并不公平!"

经过一番激烈的讨论,大家基本上达成一致,先前存在的继承模型确实有些不足,不能适应快速变化的市场,过于强调差异性而非共性.

这样就导致无法满足急性子顾客批量购买的需求,所以需要提供类似于继承那种抽象的概念来表达某种约定,只要满足这种约定的动物就是宠,不管是猫还是狗,哪怕是玩具也行!

go-oop-interface-pet-toy.jpeg

让继承变得更加抽象

透过现象看本质,从纷繁冗杂的事务中抽象出精简的模型是各个编程语言都必不可少的一个环节,Go 语言当然也不例外.

面向对象编程中的继承概念表达是一种上下级的抽象关系,也就是说某一个封装对象是从属于特定上级的封装对象,默认拥有该上级的行为方法,这里的上级概念就是父类就是对所有子类共性的抽象实现.

当研究的问题就是具体的子类实现时,此时使用继承的概念,语义上比较清晰,子类只需要关注自己的特性,共性部分由父类去完成,这种思路也是非常自然的,猫是猫的同时也是一种宠物.

go-oop-inheritance-one-cat.jpeg

但是当我们研究的问题不再关注具体的子类实现而是着眼于父类的共性时,此时如果再提供具体的子类实现当然也能用,但是杀鸡焉用牛刀?

明明我仅仅需要一滴水,你却给了我整个海洋!

go-oop-interface-water.jpeg

本来,真正需要的可能只是父类的某一个方法,你却提供给我一个具体的子类实现,这个子类不但拥有目标方法还有很多的其他方法.

既然有这么多的附加价值,你说浪费不浪费,销售时可不得涨价吗,这样不相当于捆绑销售了嘛!

所以,我们需要对继承的概念进一步抽象,使这种抽象达到一种极致状态以至于只存在非常少量的行为方法,凡是继承自这种极致抽象的子类都是它的子民.

为了之后讨论方便,业界将这种抽象到极致的继承关系称之为接口,虽然看似只是称呼的改变,但实际上思维方式上已经发生了翻天覆地的变化.

继承的概念是描述子类和父类的关系,子类继承自父类,关注点在子类,共性部分完全由父类实现,子类自然拥有这些行为能力.

而接口的概念衍生于继承,只不过是这种抽象程度已经达到了一种不能再抽象的地步,所有子类都要有一个最终的父类,这个父类拥有最公共性的行为方法,所以这种极致的抽象也就无法体现出子类的共性行为的具体表现.此时这种极致的抽象没有太大的意义,是一种非常非常宽泛的概念,等于什么都没说,所以也适合绝大部分封装对象.

所以,干脆取消了极致抽象中对于行为共性的实现,转而仅仅定义共性的行为,具体这种行为到底如何表现,完全由具体子类自行决定.

这样做有两个显而易见的好处,一是解决了太宽泛概念等于没说的尴尬,同时保留了对共性行为的描述.二是将控制权转移到具体的子类实现,实现了体制内的个性化!

所以这种专业名词的转变背后是思维方式的转变,而接口更是很好地描述了这种转变的语义.

回忆一下生活总随处可见的 USB 数据线,对于计算机来说,对外暴露的是 USB 插口,行为描述是只要插入就能连接到电脑,能够同电脑进行沟通交流,这种交流可能是传递数据,也可能是连接电源等等不同的行为表现.

go-oop-interface-usb.jpg

基于接口设计,USB 数据线提供了访问电脑的能力,一端连着电脑,另一端连着手机,双方进行数据交换.
有线鼠标的数据线也提供了访问电脑的能力,实现鼠标的左击还是又击都能反馈到电脑.

诸如此类的案例不胜枚举,生活中不缺少计算机哲学,缺少的只是我们的思考.

所以,如果让我来给这种机制进行命名的话,我可能会将其称呼为插口,意思是只要能适配指定的插口,那么就说满足插口要求,对外暴露的抽象概念是插口,真正的实现可能是数据线或者工具等.

当然,这只是我的一厢情愿,因为面向对象中这种机制叫做接口,满足接口的规范叫做实现了接口.

接口这种概念显得比较专业,提出这个概念的人估计也是厉害人物,基本上所有的面向对象语言中都采用了接口的概念,即使不是面向对象语言但支持面向对象编程风格的 Go 语言也采用了接口概念.

由此可见,接口的概念应该是通俗易懂,可移值性比较强的,获得了相当高的认可度.

除了面向对象编程风格外,与接口相关的编程风格中还有一种叫做面向接口编程,这个会在以后的文章中继续分享这封面的内容.

个人理解封装和继承的概念,讲的就是面向对象编程,关注点在于具体的对象以及对象之间的层次关系.

而接口的出现则是另外一种维度的思考,当关注点不再是具体的子类而是抽象的父类,这种情况下则根据实际情况抽象出了接口的概念,由此看出,面向对象编程中高内聚部分说的是封装和继承,低耦合则是接口和多态.

所以面向接口编程在应用而生,由此可见,不同的应用场景关注点不同,面向对象和面向接口也并不是互斥关系,是互补关系.

在未来的某种需求继续发生改变时,可能还会产生新的概念,进而提出新的一套理论,到时候是面向需求编程还是面向思维编程亦或是面向搜索编程,那就就不得而知了.

聪明的读者,你们有什么看法呢?

如何设计又怎么实现

市场监督大会散会后,继承小组接受了设计接口的任务挑战.大会之所以推举继承小组领头,是因为与会人员一致认为继承小组在处理抽象概念上十分擅长,相信设计出接口这种机制也是可以的.

继承小组深感此次任务责任重大,任重而道远,一定要设计出接口概念才能不辜负参会人员的认可和领导的厚爱.

go-oop-interface-dashing.jpg

于是,继承小组内部在一起开了个会,会上大家畅所欲言谈谈自己的看法.

小王: "我觉得这种接口的概念是抽象的终极状态,我们可能没办法一下子到达终点,但是按照现有的理论应该可以逐步逼近终点."
小李: "我也是有类似的感觉,抽象到什么程度才是终点呢?拿什么判定这个抽象程度呢?猫和狗到宠物的过程是一种抽象过程,我们先前也是基于此过程提出了继承的概念,解决了重复定义的问题.现在应该沿着这种思路继续抽象,直到小王说的那种接口概念."
小张: "从猫和狗抽象到宠物,是封装对象的演进过程,顾客需要的不是具体的猫和狗,而是宠物.但是这个宠物直觉上感觉和原来继承中实现的宠物还是有点不一样啊?"
小王: "我也有同感,这次的宠物必须具备某种能力,只要是满足这种能力的,管他是猫还是狗或者是别的什么蜥蜴蟑螂的都是顾客眼中的宠物.所以这种宠物更加单一化,并不在乎有没有其他能力."

...

大家你一言我一语的讨论了好长时间,最终在项目经理的引导整理下有了有了初步的思路.

  • 接口是一种抽象,这种抽象可能并不关注父类本身的全部能力,只在于关注的能力.
  • 普通的抽象父类既有行为的约束还顺便实现了该行为,但抽象到接口这种程度时是否实现并不在乎,但必须要有行为的约束.
  • 接口本身的语义是一种行为约束,满足这种约束行为的具体对象可能会有很多,同时这些具体对象可能也满足其他接口约束.
  • 接口约束变化时,满足接口约束的具体子类到底要不要随之变化?如果需要的话,有道理,如果不需要的话,也有道理.

"等一下,我有疑问?你怎么一会说需要,一会又说不需要,这不自相矛盾了吗?",大家几乎不约而同举手示意经理.似乎早就料到这帮小子搞不懂其中缘由,经理故弄玄虚地回应说:"嗯嗯,我就知道你们会有疑惑,下面容我谈一下我的看法,你们听听看.”

go-oop-interface-expert.jpg

如果站在接口的定义者角度上看问题,一旦发布了接口规范,子类肯定会屁颠屁颠满足接口约束,于是对外暴露时都是接口那一套理论,忽略了自己的特色.

统一了接口规范这种情况对于接口设计者最为方便,所有的控制权全部掌握在自己手中,一道命令即可号令群雄,莫敢不从,如若不从,轻则千夫所指,重则驱逐出境!

对于接口设计者来说,这些实现了接口的对象并没有什么不同,地球离了谁照样自转,随时随地想换就换.

但是对于接口的实现类来说,只要一收到天子诏令,立马无条件停下手上的活,熬夜加班也要满足新的接口规范,敢怒不敢言,除非是不想混了,哪怕怠慢了一步也会引发巨大的动荡!

所以说接口更改时,具体的实现类必须要随之改变以实现新的接口规范约束.

go-oop-interface-designer.jpeg

如果站在接口的使用者角度上看问题,是否实现接口应该是我的地盘我做主,是自主决定的事情,管你接口是否更改,老子爱实现就实现,不乐意实现就不实现!你奈我何?我的王国我当家,尊你敬你你才是国王,把我们惹恼了,所谓的联合王国到时候只剩你这么一个孤家寡人去吧!

所以说接口更改时,具体的实现类不需要随之更改,想不想满足新的接口规范完全在于自己,并不是强迫的,不必立即实现新的接口规范.

go-oop-interface-impler.jpeg

真的是公说公有理婆说婆有理,既然如此,那么问题来了,Go 语言选择是哪一种?其他主流的编程语言又是选择哪一种的呢?

先说其他主流的编程语言,这类编程语言大多是站在接口设计者角度出发,控制欲特别强,一言不合就报错,接口更新了实现类必须同步更新,违令者杀无赦!

这样有优点也有缺点,优点是皇帝一声令下,天下臣民莫敢不从,屡教不敢者,千夫所指,王国崩溃也不是没有可能!
正是这种优点,换另外一种角度看就是缺点了,俗话说天高皇帝远,圣旨虽下但还没传达到边境要塞,那边监察御史就上奏你一本,说你怠政目无尊上,引发帝国动荡,罪大恶极,理应凌迟处死!

你说冤不冤,不管是朝令夕改还是焕然一新的改革,凡是曾经实现过接口的类都要实时更新,否则后果不堪设想.

真的是成也萧何败萧何,控制欲太强有利有弊.

go-oop-interface-xiaohe.jpg

所以,Go 与众不同,选择了另一种思路来解决问题,放弃中央集权转向分封制,将权力下放给地方.

名义上还是由国王制定统一标准,由地方负责自主实施,具体如何实现标准完全是诸侯国自己的事情,万一哪天国王需要使用统一标准时,实现了该标准的诸侯王国都可以无障碍使用.

即使以后接口规范有变,旧的接口不再适合新时代要求,国王只需要制定了一套新的标准,昭告天下后,当诏令传到地方时,地方可以根据新的规范更新自己的实现类,万一消息闭塞或者不愿意立即更新,也没关系,王国不会崩溃,只不过需要使用新规范时,没有实现接口的地方自然是不能使用的.

go-oop-interface-kingdom.jpg

因此,不论是集中制还是民主制,接口的规范都是自顶向下实施的,不同之处在于底下的人因各种原因没有实现新的接口规范时,集中制会直接崩溃而民主制依旧正常运行,仅此而已.

下面就演示一下两种思路的实现方式.

  • java 等传统的面向对象语言

卖家首先定义到底什么是宠物这种接口.

public interface Pet {
    void actingCute();
}

喵喵喵,人家能卖萌,就是宠物嘛,为啥还非得证明一下啊!

public static class Cat implements Pet {
    @Override
    public void actingCute() {
        System.out.println("喵星人喵喵喵来卖萌");
    }
}
这里说的证明一下猫是宠物,指的是必须使用关键字 implements 声明实现了宠物的接口,颁发了合格证才算是宠物,否则就不是.

汪星人说,这年头自带卖萌天赋的猫咪都要通过专业认证才算是宠物,我也乖乖去认证宠物吧!

public static class Dog implements Pet {
    @Override
    public void actingCute() {
        System.out.println("汪星人汪汪汪来卖萌");
    }
}

第二天,市场上又来了一群急性子的买家,一上来就要买宠物,管他是猫还是狗,并不在乎,只要是宠物就行.

public static void main(String[] args) {
    Pet p;
    
    p = new Cat();

    // 喵星人喵喵喵来卖萌
    p.actingCute();

    p = new Dog();

    // 汪星人汪汪汪来卖萌
    p.actingCute();
}

终于送走了这批顾客,卖家也舒了一口气,默默念叨着,市场监督那帮人真牛逼,竟然设计出接口的方案,只要是宠物,别管是猫还是狗,随便给一个都行,给这帮人点个赞!

  • go 等非传统非面向对象语言

首先定义接口规范,宠物一定要能卖萌,不然怎么讨得女神欢心?

type Pet interface {
    ActingCute()
}

喵喵喵说我会卖萌啊,那我就是宠物啦!

type Cat struct {

}

func (c *Cat) ActingCute() {
    fmt.Println("喵星人喵喵喵来卖萌")
}

汪汪汪说我也会卖萌,我也要给女神当宠物!

type Dog struct {

}

func (d *Dog) ActingCute() {
    fmt.Println("汪星人汪汪汪来卖萌")
}

既然你们都会卖萌,对于直男来说这就够了,随便拿一个就行了,快点准备送礼物啦!

func SendGift(p Pet) {
    p.ActingCute()
}

于是乎,既然买家并不在乎到底是猫还是狗,那就卖给他一个猫好了,于是小伙子打包了宠物准备送给女神.

func TestActingCute(t *testing.T) {
    var p Pet
    p = new(Cat)

    // 喵星人喵喵喵来卖萌
    SendGift(p)
}

第二天,女神打来电话说,你不知道对猫毛过敏的吗,送的啥破礼物!哼!

可怜的小伙子跑去宠物店找卖家算账,气冲冲地质问卖家,卖家一脸毫不在意的样子,笑嘻嘻的说,小伙子想不想将功补过啊,这一次保准你能获得女神青睐.

只见,卖家这一次找来了一条宠物狗,打打包还放到原来的包装盒递给你小伙子.

func TestActingCute(t *testing.T) {
    var p Pet
    p = new(Dog)

    // 汪星人汪汪汪来卖萌
    SendGift(p)
}

我擦,还是原来的配方,有点担心,一样的包装这一次真的能讨得女神欢心,原谅自己吗?

go-oop-inheritance-one-dog.jpeg

亲爱的读者,你们说呢,同样的配方不一样的味道,女神会原谅自己吗?

同一个问题思路不同

不论是站在设计者角度上解决抽象问题还是站在使用者角度思考,两者的解决方案没有高低优劣之分,选用好恰当的应用场景就是最好的解决方案.

只不过这种选择往往不是开发者能左右的事情,因为这种底层的语言级别框架设计属于缔造者的工作,他们一旦觉得了一种模式,语言使用者很难改变,我们唯一能做的就是理解并使用罢了!

当站在接口设计者角度上时,接口的定义和具体实现类的关系就好比是集中制,皇帝一声令下,不管身处何处,天下臣民皆惟命是从,如有懒政懈怠者,千夫所指,立马崩溃.

当站在接口实现者角度上时,此时接口的设计者和具体实现者的关系是松耦合的,犹如分封制,国王一声令下,诸侯国可以听从差遣也可以抗旨不遵,对于整个王国而言并不会造成颠覆性混乱,诸侯国和国王更像是一种契约精神而不是隶属服从关系.

Go 语言中的接口采用的就是后一种松耦合的关系,接口设计者和接口实现者是松耦合的,实现的关系也是隐式的,这也是另一种理论"鸭子模型"的体现.

go-oop-interface-dock.jpg

好了,本文主要介绍了为什么要有接口设计的需求以及接口设计是怎么思考的,并简单介绍了 Go 是如何实现这种模型的.

下一节我们将真正开始介绍 Go 语言关于接口的设计,顺便讲解面向对象最后一个知识点---多态.

如果本文对你理解面向对象有所帮助,欢迎你的的转发分享,如果文章有描述不当之处,希望你能留言告诉我,谢谢你的阅读.

雪之梦技术驿站.png

查看原文

赞 2 收藏 0 评论 0

黑夜键盘手 赞了文章 · 2019-03-28

git 入门教程之忽略文件

忽略文件

"并不是所有的牛奶都叫特仑苏",在版本控制系统中也有相似的表达,那就是"并不是所有的文件都需要提交".

有的是因为没必要提交,比如日志文件,系统缓存文件等,有的是因为不能提交,比如个人隐私文件,付费文档等.

正常来说,这些文件都是不应该被提交到版本库,倘若一不留神提交到版本库,要么泄露机密信息,要是造成经济损失,要么对团队其他人工作造成不便.

有鉴于此,我们应该寻求一种机制来规避事故的发生,在 git 版本控制系统中一般有三种不同的解决方案.

最常用也是最简单的当属 .gitignore 文件,不过先不要着急,我们先了解一下忽略原则和配置规则.

忽略文件的基本原则

  • 忽略操作系统自动生成的文件,保持不同操作系统的纯粹性和整洁度.
  • 忽略工具软件自动生成的文件,避免因个性化配置而产生的工作障碍.
  • 忽略个人隐私配置文件,除非你愿意承担公开隐私所带来的潜在风险.
目标: 只提交必要文件,忽略无用文件,尽可能考虑多种情况,不给他人制造麻烦.

忽略文件的配置规则

一行记录代表一条规则,配置规则仅针对尚未被跟踪的文件清单.

# 忽略 `*.a` 文件
*.a
# 忽略 `*.A` 文件,但 `somefile.A` 除外.
*.A
!somefile.A

# 忽略 `*.b` 和 `*.B` 文件
*.[bB]
# 忽略 `*.c` 和 `*.C` 文件,但 `somefile.C` 除外.
*.[cC]
!somefile.C

# 只忽略 `somepath/` 目录(包括该目录下所有文件),但不忽略 `somepath` 文件
somepath/
# 只忽略 `somepath/` 一级子目录下 `*.txt`,但不忽略 `somepath/sub/*.txt` 文件
somepath/*.txt

# 忽略 `somepath` 文件和 `somepath` 目录
somepath
# 只忽略 `somepath` 文件,但不忽略 `somepath/` 目录
somepath
!somepath/

# 只忽略当前目录下的 `somepath` 文件和目录,但不忽略子目录的 `somepath`
/somepath
说明: # 开头表示注释,! 紧跟某规则之后表示增加例外情况

在线示例和帮助文档

提供两个不错的在线示例,可以参考下在什么场景应该忽略哪些文件以及如何编写忽略规则.

运行 git help ignore 命令查看帮助文档

三种设置方式

git 设置忽略文件有三种方式,如下:

  • 全局配置文件(~/.gitignore),执行 git config --global core.excludesfile ~/.gitignore 命令后适用于所有的版本库.
  • 远程配置文件($PWD/.gitignore),编辑 .gitignore 文件后适用于远程和本地版本库.
  • 本地配置文件($PWD/.git/info/exlude),编辑 $PWD/.git/info/exlude 文件后适用于本地版本库.

最常用方式

三种设置方式中,第二种最为常见,另外两种大致一样,重点在于配置文件如何编写.

创建 .gitignore 文件

参考在线示例以及基本语法编写自定义忽略规则

# General
.DS_Store
.AppleDouble
.LSOverride

# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db

提交 .gitignore 文件

忽略文件规则配置完毕后,需要将该文件提交到版本库,这样在其他电脑上也能应用相同的忽略规则.

# 添加 `.gitignore` 
git add .gitignore

# 提交 `.gitignore` 
git commit -m "add .gitignore"

# 上传 `.gitignore`
git push origin master

验证忽略效果

新建 .gitignore 文件中已忽略的文件,运行 git status 命令,如果提示 working directory clean,那么说明忽略文件的配置已经生效,如果工作区不干净,很遗憾,忽略文件配置可能并未生效,需要检查下哪里配置错了.

运行 git check-ignore 命令检查是哪个配置规则写错了,从而我们能够更正相应的配置规则.

查看原文

赞 3 收藏 3 评论 0

黑夜键盘手 关注了专栏 · 2019-03-28

雪之梦技术驿站

你在互联网的路上匆匆而来,雪之梦技术驿站助你满载而去

关注 17

黑夜键盘手 发布了文章 · 2019-03-13

一对一职业辅导我有心得,响应两会对职业教育的号召

”职业教育“在本次十三届全国人民代表大会第二次会议上单独做出了审议。
随着互联网的快速发展,越来越多的培训机构/培训企业踊跃出现,想吃IT这碗饭的人越来越多,大学生在校园转型学计算机应用的也越来越多。在这里要情调的是本次会议上对大学和培训机构单独做出了说明,说什么呢?无论是培训还是大学培养出来的人,绝大部分都无法匹配市场和企业的用人需求,因为在未来的几年,职业教育很有可能走向定制化人才培养路线。

在这之前我已经在做这件事情,从2018年的下半年至今,已经有十几个人从我这里提升和学习Web前端开发,他们都来自于不同的领域,有培训机构出来的,有在职工作的,有建筑行业的,但都有一个共同点那就是"求专",他们之前了解的 培训的都只是皮毛,在校园和培训机构里面学到的知识根本原因上脱离了企业岗位招聘需求,学不到重点/学习周期长/学费高/有问题找不到人解决,慢慢的也就从入门到放弃,这是软件开发行业一个很旁边的现象。

针对这点我现在在业余之际专门投入Web前端开发一对一教学的事业上去,现代IT技术更新迭代的速度这么快,等你从学校和培训机构出来,技术都大辩天了,学习并不是缺资料和却时间,真正的是缺一个好方法 一个引路人。

欢迎大家关注我 联系我,在你技术瓶颈期的时候我可以帮你,在你零基础想转行做Web前端开发的时候,我可以帮你,不要再去被培训机构坑了,下面是我的联系方式:
QQ: 672365397

查看原文

赞 0 收藏 0 评论 0

黑夜键盘手 发布了文章 · 2019-03-13

重庆知名互联网前端开发工程师一对一带徒弟!

职业教育“在本次十三届全国人民代表大会第二次会议上单独做出了审议。
随着互联网的快速发展,越来越多的培训机构/培训企业踊跃出现,想吃IT这碗饭的人越来越多,大学生在校园转型学计算机应用的也越来越多。在这里要情调的是本次会议上对大学和培训机构单独做出了说明,说什么呢?无论是培训还是大学培养出来的人,绝大部分都无法匹配市场和企业的用人需求,因为在未来的几年,职业教育很有可能走向定制化人才培养路线。

在这之前我已经在做这件事情,从2018年的下半年至今,已经有十几个人从我这里提升和学习Web前端开发,他们都来自于不同的领域,有培训机构出来的,有在职工作的,有建筑行业的,但都有一个共同点那就是"求专",他们之前了解的 培训的都只是皮毛,在校园和培训机构里面学到的知识根本原因上脱离了企业岗位招聘需求,学不到重点/学习周期长/学费高/有问题找不到人解决,慢慢的也就从入门到放弃,这是软件开发行业一个很旁边的现象。

针对这点我现在在业余之际专门投入Web前端开发一对一教学的事业上去,现代IT技术更新迭代的速度这么快,等你从学校和培训机构出来,技术都大辩天了,学习并不是缺资料和却时间,真正的是缺一个好方法 一个引路人。

欢迎大家关注我 联系我,在你技术瓶颈期的时候我可以帮你,在你零基础想转行做Web前端开发的时候,我可以帮你,不要再去被培训机构坑了,下面是我的联系方式:
QQ: 672365397

查看原文

赞 0 收藏 0 评论 0

黑夜键盘手 赞了文章 · 2019-02-22

2019前端面试题汇总(主要为Vue)

毕业之后就在一直合肥小公司工作,没有老司机、没有技术氛围,在技术的道路上我只能独自摸索。老板也只会画饼充饥,前途一片迷茫看不到任何希望。于是乎,我果断辞职,在新年开工之际来到杭州,这里的互联网公司应该是合肥的几十倍吧。。。。
刚来3天,面试了几家公司,有些规模比较小,有些是创业公司,也有些已经发展的不错了;今天把最近的面试题目做个汇总,也给自己复个盘,由于我的技术栈主要为Vue,所以大部分题目都是Vue开发相关的。

1. 谈谈你对MVVM开发模式的理解

MVVM分为Model、View、ViewModel三者。
Model 代表数据模型,数据和业务逻辑都在Model层中定义;
View 代表UI视图,负责数据的展示;
ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;
ModelView 并无直接关联,而是通过 ViewModel 来进行联系的,ModelViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。
这种模式实现了 ModelView 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom

2. Vue 有哪些指令?

v-html、v-show、v-if、v-for等等

3. v-if 和 v-show 有什么区别?

v-show 仅仅控制元素的显示方式,将 display 属性在 block 和 none 来回切换;而v-if会控制这个 DOM 节点的存在与否。当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。

4. 简述Vue的响应式原理

当一个Vue实例创建时,vue会遍历data选项的属性,用 Object.defineProperty 将它们转为 getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。
每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。

5. Vue中如何在组件内部实现一个双向数据绑定?

假设有一个输入框组件,用户输入时,同步父组件页面中的数据
具体思路:父组件通过 props 传值给子组件,子组件通过 $emit 来通知父组件修改相应的props值,具体实现如下:

import Vue from 'vue'

const component = {
  props: ['value'],
  template: `
    <div>
      <input type="text" @input="handleInput" :value="value">
    </div>
  `,
  data () {
    return {
    }
  },
  methods: {
    handleInput (e) {
      this.$emit('input', e.target.value)
    }
  }
}

new Vue({
  components: {
    CompOne: component
  },
  el: '#root',
  template: `
    <div>
      <comp-one :value1="value" @input="value = arguments[0]"></comp-one>
    </div>
  `,
  data () {
    return {
      value: '123'
    }
  }
})

可以看到,当输入数据时,父子组件中的数据是同步改变的:
Image 1.png

Image 2.png

我们在父组件中做了两件事,一是给子组件传入props,二是监听input事件并同步自己的value属性。那么这两步操作能否再精简一下呢?答案是可以的,你只需要修改父组件:

template: `
    <div>
      <!--<comp-one :value1="value" @input="value = arguments[0]"></comp-one>-->
      <comp-one v-model="value"></comp-one>
    </div>
  `

v-model 实际上会帮我们完成上面的两步操作。

6. Vue中如何监控某个属性值的变化?

比如现在需要监控data中,obj.a 的变化。Vue中监控对象属性的变化你可以这样:

watch: {
      obj: {
      handler (newValue, oldValue) {
        console.log('obj changed')
      },
      deep: true
    }
  }

deep属性表示深层遍历,但是这么写会监控obj的所有属性变化,并不是我们想要的效果,所以做点修改:

watch: {
   'obj.a': {
      handler (newName, oldName) {
        console.log('obj.a changed')
      }
   }
  }

还有一种方法,可以通过computed 来实现,只需要:

computed: {
    a1 () {
      return this.obj.a
    }
}

利用计算属性的特性来实现,当依赖改变时,便会重新计算一个新值。

7. Vue中给data中的对象属性添加一个新的属性时会发生什么,如何解决?

示例:

<template>
  <div>
    <ul>
      <li v-for="value in obj" :key="value">
        {{value}}
      </li>
    </ul>
    <button @click="addObjB">添加obj.b</button>
  </div>
</template>
<script>
export default {
  data () {
    return {
      obj: {
        a: 'obj.a'
      }
    }
  },
  methods: {
    addObjB () {
      this.obj.b = 'obj.b'
      console.log(this.obj)
    }
  }
}
</script>
<style></style>

点击button会发现,obj.b 已经成功添加,但是视图并未刷新:
Image 3.png

Image 4.png

原因在于在Vue实例创建时,obj.b并未声明,因此就没有被Vue转换为响应式的属性,自然就不会触发视图的更新,这时就需要使用Vue的全局api $set():

addObjB () {
      // this.obj.b = 'obj.b'
      this.$set(this.obj, 'b', 'obj.b')
      console.log(this.obj)
    }

$set()方法相当于手动的去把obj.b处理成一个响应式的属性,此时视图也会跟着改变了:
Image 5.png

8. delete和Vue.delete删除数组的区别

delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。
Vue.delete直接删除了数组 改变了数组的键值。

    var a=[1,2,3,4]
    var b=[1,2,3,4]
    delete a[1]
    console.log(a)
    this.$delete(b,1)
    console.log(b)

Image 6.png

Image 7.png

9.如何优化SPA应用的首屏加载速度慢的问题?

  • 将公用的JS库通过script标签外部引入,减小app.bundel的大小,让浏览器并行下载资源文件,提高下载速度;
  • 在配置 路由时,页面和组件使用懒加载的方式引入,进一步缩小 app.bundel 的体积,在调用某个组件时再加载对应的js文件;
  • 加一个首屏 loading 图,提升用户体验;

10. 前端如何优化网站性能?

  1. 减少 HTTP 请求数量

在浏览器与服务器进行通信时,主要是通过 HTTP 进行通信。浏览器与服务器需要经过三次握手,每次握手需要花费大量时间。而且不同浏览器对资源文件并发请求数量有限(不同浏览器允许并发数),一旦 HTTP 请求数量达到一定数量,资源请求就存在等待状态,这是很致命的,因此减少 HTTP 的请求数量可以很大程度上对网站性能进行优化。

    • CSS Sprites:国内俗称 CSS 精灵,这是将多张图片合并成一张图片达到减少 HTTP 请求的一种解决方案,可以通过 CSS background 属性来访问图片内容。这种方案同时还可以减少图片总字节数。
    • 合并 CSS 和 JS 文件:现在前端有很多工程化打包工具,如:grunt、gulp、webpack等。为了减少 HTTP 请求数量,可以通过这些工具再发布前将多个 CSS 或者 多个 JS 合并成一个文件。
    • 采用 lazyLoad:俗称懒加载,可以控制网页上的内容在一开始无需加载,不需要发请求,等到用户操作真正需要的时候立即加载出内容。这样就控制了网页资源一次性请求数量。
    1. 控制资源文件加载优先级

    浏览器在加载 HTML 内容时,是将 HTML 内容从上至下依次解析,解析到 link 或者 script 标签就会加载 href 或者 src 对应链接内容,为了第一时间展示页面给用户,就需要将 CSS 提前加载,不要受 JS 加载影响。
    一般情况下都是 CSS 在头部,JS 在底部。

    1. 利用浏览器缓存
      浏览器缓存是将网络资源存储在本地,等待下次请求该资源时,如果资源已经存在就不需要到服务器重新请求该资源,直接在本地读取该资源。
    2. 减少重排(Reflow)
      基本原理:重排是 DOM 的变化影响到了元素的几何属性(宽和高),浏览器会重新计算元素的几何属性,会使渲染树中受到影响的部分失效,浏览器会验证 DOM 树上的所有其它结点的 visibility 属性,这也是 Reflow 低效的原因。如果 Reflow 的过于频繁,CPU 使用率就会急剧上升。

    减少 Reflow,如果需要在 DOM 操作时添加样式,尽量使用 增加 class 属性,而不是通过 style 操作样式。

    1. 减少 DOM 操作
    2. 图标使用 IconFont 替换

    11. 网页从输入网址到渲染完成经历了哪些过程?

    大致可以分为如下7步:

    1. 输入网址;
    2. 发送到DNS服务器,并获取域名对应的web服务器对应的ip地址;
    3. 与web服务器建立TCP连接;
    4. 浏览器向web服务器发送http请求;
    5. web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址);
    6. 浏览器下载web服务器返回的数据及解析html源文件;
    7. 生成DOM树,解析css和js,渲染页面,直至显示完成;

    12. jQuery获取的dom对象和原生的dom对象有何区别?

    js原生获取的dom是一个对象,jQuery对象就是一个数组对象,其实就是选择出来的元素的数组集合,所以说他们两者是不同的对象类型不等价。

    • 原生DOM对象转jQuery对象:
    var box = document.getElementById('box');
    var $box = $(box);
    • jQuery对象转原生DOM对象:
    var $box = $('#box');
    var box = $box[0];

    13. jQuery如何扩展自定义方法

    (jQuery.fn.myMethod=function () {
           alert('myMethod');
    })
    // 或者:
    (function ($) {
            $.fn.extend({
                 myMethod : function () {
                      alert('myMethod');
                 }
            })
    })(jQuery)

    使用:

    $("#div").myMethod();

    目前来看公司面试的问题还是比较基础的,但是对于某些只追求会用并不研究其原理的同学来说可能就没那么容易了。所以大家不仅要追求学习的广度,更要追求深度。
    OK,希望自己能早日拿到心仪的offer.

    参考:
    浅谈网站性能之前端性能优化

    查看原文

    赞 529 收藏 391 评论 47

    黑夜键盘手 发布了文章 · 2019-01-17

    WEB前端一对一/零基础/瓶颈期都需要的干货,都来瞧一瞧!

    资料一堆,不知道如何选?没有规划,进度慢?不知重点,费时间?无人请教,卡几天?就业迷茫,谁指点?是应该找个老师了!!有Lea,你可以全速前进。
    联系QQ:672365397
    知名企业前端工程师,闲暇之余带徒弟,非机构。

    查看原文

    赞 0 收藏 0 评论 0

    黑夜键盘手 评论了文章 · 2018-12-11

    快速提升前端技能,挑战年薪15万不是梦!

    我是一名平凡的前端开发工程师。
    今天我要表达的立场是:拒绝培训机构,拥抱快速更新的前端!
    上面这句话陈述的意思是,现代培训机构的收费高、学习周期长、学不到终点,我相信绝大部分伙计都深有体会,总之就一个字“坑”。
    在18年的下半年,偶然的一次群聊中发现有好多外行和培训机构出来的人想要从事前端开发工作,于是我的故事也就开始了,本人目前带了有十多个徒弟,大部分都是培训机构出身,有点基础的半个月左右就能带出来,知识体系包括:各种UI框架 jQuery jQuery系列插件、Vue Vue常用基础组建封装、Vue-axios封装、Git版本控制器、Charles抓包工具等。这些知识体系学出来在一二线城市拿到10k以上问题不大,我是一对一教,本地的可以现场教,外地的通过aTeamviwer,一次2个小时,周末除外,不要问我时间怎么这么短,学习不是用时长来衡量质量,问题也不是靠熬夜就能纠结的。

    查看原文

    黑夜键盘手 发布了文章 · 2018-12-11

    快速提升前端技能,挑战年薪15万不是梦!

    我是一名平凡的前端开发工程师。
    今天我要表达的立场是:拒绝培训机构,拥抱快速更新的前端!
    上面这句话陈述的意思是,现代培训机构的收费高、学习周期长、学不到终点,我相信绝大部分伙计都深有体会,总之就一个字“坑”。
    在18年的下半年,偶然的一次群聊中发现有好多外行和培训机构出来的人想要从事前端开发工作,于是我的故事也就开始了,本人目前带了有十多个徒弟,大部分都是培训机构出身,有点基础的半个月左右就能带出来,知识体系包括:各种UI框架 jQuery jQuery系列插件、Vue Vue常用基础组建封装、Vue-axios封装、Git版本控制器、Charles抓包工具等。这些知识体系学出来在一二线城市拿到10k以上问题不大,我是一对一教,本地的可以现场教,外地的通过aTeamviwer,一次2个小时,周末除外,不要问我时间怎么这么短,学习不是用时长来衡量质量,问题也不是靠熬夜就能纠结的。

    查看原文

    赞 1 收藏 0 评论 1

    黑夜键盘手 赞了文章 · 2017-12-19

    ajax跨域,这应该是最全的解决方案了

    前言

    从刚接触前端开发起,跨域这个词就一直以很高的频率在身边重复出现,一直到现在,已经调试过N个跨域相关的问题了,16年时也整理过一篇相关文章,但是感觉还是差了点什么,于是现在重新梳理了一下。

    个人见识有限,如有差错,请多多见谅,欢迎提出issue,另外看到这个标题,请勿喷~

    题纲

    关于跨域,有N种类型,本文只专注于ajax请求跨域(,ajax跨域只是属于浏览器"同源策略"中的一部分,其它的还有Cookie跨域iframe跨域,LocalStorage跨域等这里不做介绍),内容大概如下:

    • 什么是ajax跨域

      • 原理
      • 表现(整理了一些遇到的问题以及解决方案)
    • 如何解决ajax跨域

      • JSONP方式
      • CORS方式
      • 代理请求方式
    • 如何分析ajax跨域

      • http抓包的分析
      • 一些示例

    什么是ajax跨域

    ajax跨域的原理

    ajax出现请求跨域错误问题,主要原因就是因为浏览器的“同源策略”,可以参考

    浏览器同源政策及其规避方法(阮一峰)

    CORS请求原理

    CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

    基本上目前所有的浏览器都实现了CORS标准,其实目前几乎所有的浏览器ajax请求都是基于CORS机制的,只不过可能平时前端开发人员并不关心而已(所以说其实现在CORS解决方案主要是考虑后台该如何实现的问题)。

    关于CORS,强烈推荐阅读 
    跨域资源共享 CORS 详解(阮一峰)

    另外,这里也整理了一个实现原理图(简化版):

    如何判断是否是简单请求?

    浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。只要同时满足以下两大条件,就属于简单请求。

    • 请求方法是以下三种方法之一:HEAD,GET,POST
    • HTTP的头信息不超出以下几种字段:

      • Accept
      • Accept-Language
      • Content-Language
      • Last-Event-ID
      • Content-Type(只限于三个值application/x-www-form-urlencoded、 multipart/form-data、text/plain)

    凡是不同时满足上面两个条件,就属于非简单请求。

    ajax跨域的表现

    说实话,当初整理过一篇文章,然后作为了一个解决方案,但是后来发现仍然有很多人还是不会。无奈只能耗时又耗力的调试。然而就算是我来分析,也只会根据对应的表现来判断是否是跨域,因此这一点是很重要的。

    ajax请求时,如果存在跨域现象,并且没有进行解决,会有如下表现:(注意,是ajax请求,请不要说为什么http请求可以,而ajax不行,因为ajax是伴随着跨域的,所以仅仅是http请求ok是不行的)

    注意:具体的后端跨域配置请看题纲位置。

    第一种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且The response had HTTP status code 404

    出现这种情况的原因如下:

    • 本次ajax请求是“非简单请求”,所以请求前会发送一次预检请求(OPTIONS)
    • 服务器端后台接口没有允许OPTIONS请求,导致无法找到对应接口地址

    解决方案: 后端允许options请求

    第二种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且The response had HTTP status code 405

    这种现象和第一种有区别,这种情况下,后台方法允许OPTIONS请求,但是一些配置文件中(如安全配置),阻止了OPTIONS请求,才会导致这个现象

    解决方案: 后端关闭对应的安全配置

    第三种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且status 200

    这种现象和第一种和第二种有区别,这种情况下,服务器端后台允许OPTIONS请求,并且接口也允许OPTIONS请求,但是头部匹配时出现不匹配现象

    比如origin头部检查不匹配,比如少了一些头部的支持(如常见的X-Requested-With头部),然后服务端就会将response返回给前端,前端检测到这个后就触发XHR.onerror,导致前端控制台报错

    解决方案: 后端增加对应的头部支持

    第四种现象:heade contains multiple values '*,*'

    表现现象是,后台响应的http头部信息有两个Access-Control-Allow-Origin:*

    说实话,这种问题出现的主要原因就是进行跨域配置的人不了解原理,导致了重复配置,如:

    • 常见于.net后台(一般在web.config中配置了一次origin,然后代码中又手动添加了一次origin(比如代码手动设置了返回*))
    • 常见于.net后台(在IIS和项目的webconfig中同时设置Origin:*)

    解决方案(一一对应):

    • 建议删除代码中手动添加的*,只用项目配置中的即可
    • 建议删除IIS下的配置*,只用项目配置中的即可

    如何解决ajax跨域

    一般ajax跨域解决就是通过JSONP解决或者CORS解决,如以下:(注意,现在已经几乎不会再使用JSONP了,所以JSONP了解下即可)

    JSONP方式解决跨域问题

    jsonp解决跨域问题是一个比较古老的方案(实际中不推荐使用),这里做简单介绍(实际项目中如果要使用JSONP,一般会使用JQ等对JSONP进行了封装的类库来进行ajax请求)

    实现原理

    JSONP之所以能够用来解决跨域方案,主要是因为 <script> 脚本拥有跨域能力,而JSONP正是利用这一点来实现。具体原理如图

    实现流程

    JSONP的实现步骤大致如下(参考了来源中的文章)

    • 客户端网页网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制

      function addScriptTag(src) {
        var script = document.createElement('script');
        script.setAttribute("type","text/javascript");
        script.src = src;
        document.body.appendChild(script);
      }
      
      window.onload = function () {
        addScriptTag('http://example.com/ip?callback=foo');
      }
      
      function foo(data) {
        console.log('response data: ' + JSON.stringify(data));
      };                      
          

      请求时,接口地址是作为构建出的脚本标签的src的,这样,当脚本标签构建出来时,最终的src是接口返回的内容

    • 服务端对应的接口在返回参数外面添加函数包裹层
    foo({
      "test": "testData"
    });                     
    • 由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。

    注意,一般的JSONP接口和普通接口返回数据是有区别的,所以接口如果要做JSONO兼容,需要进行判断是否有对应callback关键字参数,如果有则是JSONP请求,返回JSONP数据,否则返回普通数据

    使用注意

    基于JSONP的实现原理,所以JSONP只能是“GET”请求,不能进行较为复杂的POST和其它请求,所以遇到那种情况,就得参考下面的CORS解决跨域了(所以如今它也基本被淘汰了)

    CORS解决跨域问题

    CORS的原理上文中已经介绍了,这里主要介绍的是,实际项目中,后端应该如何配置以解决问题(因为大量项目实践都是由后端进行解决的),这里整理了一些常见的后端解决方案:

    PHP后台配置

    PHP后台得配置几乎是所有后台中最为简单的,遵循如下步骤即可:

    • 第一步:配置Php 后台允许跨域
    <?php
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept');
    //主要为跨域CORS配置的两大基本信息,Origin和headers
    • 第二步:配置Apache web服务器跨域(httpd.conf中)

    原始代码

    <Directory />
        AllowOverride none
        Require all denied
    </Directory>

    改为以下代码

    <Directory />
        Options FollowSymLinks
        AllowOverride none
        Order deny,allow
        Allow from all
    </Directory>

    Node.js后台配置(express框架)

    Node.js的后台也相对来说比较简单就可以进行配置。只需用express如下配置:

    app.all('*', function(req, res, next) {
        res.header("Access-Control-Allow-Origin", "*");
        res.header("Access-Control-Allow-Headers", "X-Requested-With");
        res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
        res.header("X-Powered-By", ' 3.2.1')
            //这段仅仅为了方便返回json而已
        res.header("Content-Type", "application/json;charset=utf-8");
        if(req.method == 'OPTIONS') {
            //让options请求快速返回
            res.sendStatus(200); 
        } else { 
            next(); 
        }
    });

    JAVA后台配置

    JAVA后台配置只需要遵循如下步骤即可:

    • 第一步:获取依赖jar包

      下载 cors-filter-1.7.jarjava-property-utils-1.9.jar 这两个库文件放到lib目录下。(放到对应项目的webcontent/WEB-INF/lib/下)

    • 第二步:如果项目用了Maven构建的,请添加如下依赖到pom.xml中:(非maven请忽视)
    <dependency>
        <groupId>com.thetransactioncompany</groupId>
        <artifactId>cors-filter</artifactId>
        <version>[ version ]</version>
    </dependency>

    其中版本应该是最新的稳定版本,CORS过滤器

    • 第三步:添加CORS配置到项目的Web.xml中( App/WEB-INF/web.xml)
    <!-- 跨域配置-->    
    <filter>
            <!-- The CORS filter with parameters -->
            <filter-name>CORS</filter-name>
            <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
            
            <!-- Note: All parameters are options, if omitted the CORS 
                 Filter will fall back to the respective default values.
              -->
            <init-param>
                <param-name>cors.allowGenericHttpRequests</param-name>
                <param-value>true</param-value>
            </init-param>
            
            <init-param>
                <param-name>cors.allowOrigin</param-name>
                <param-value>*</param-value>
            </init-param>
            
            <init-param>
                <param-name>cors.allowSubdomains</param-name>
                <param-value>false</param-value>
            </init-param>
            
            <init-param>
                <param-name>cors.supportedMethods</param-name>
                <param-value>GET, HEAD, POST, OPTIONS</param-value>
            </init-param>
            
            <init-param>
                <param-name>cors.supportedHeaders</param-name>
                <param-value>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value>
            </init-param>
            
            <init-param>
                <param-name>cors.exposedHeaders</param-name>
                <!--这里可以添加一些自己的暴露Headers   -->
                <param-value>X-Test-1, X-Test-2</param-value>
            </init-param>
            
            <init-param>
                <param-name>cors.supportsCredentials</param-name>
                <param-value>true</param-value>
            </init-param>
            
            <init-param>
                <param-name>cors.maxAge</param-name>
                <param-value>3600</param-value>
            </init-param>
    
        </filter>
    
        <filter-mapping>
            <!-- CORS Filter mapping -->
            <filter-name>CORS</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>

    请注意,以上配置文件请放到web.xml的前面,作为第一个filter存在(可以有多个filter的)

    • 第四步:可能的安全模块配置错误(注意,某些框架中-譬如公司私人框架,有安全模块的,有时候这些安全模块配置会影响跨域配置,这时候可以先尝试关闭它们)

    JAVA Spring Boot配置

    20171230补充

    仅列举简单的全局配置

    @Configuration
    public class CorsConfig {
    
        private CorsConfiguration buildConfig() {
            CorsConfiguration corsConfiguration = new CorsConfiguration();
            
            // 可以自行筛选
            corsConfiguration.addAllowedOrigin("*");
            corsConfiguration.addAllowedHeader("*");
            corsConfiguration.addAllowedMethod("*");
            
            return corsConfiguration;
        }
    
        @Bean
        public CorsFilter corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            
            source.registerCorsConfiguration("/**", buildConfig());
            
            return new CorsFilter(source);  
        }
    }

    新建配置,然后添加Configuration注解即可配置成功

    PS:这一部分方法是收录的,没有亲身实践过,但根据反馈,理论上可行

    NET后台配置

    .NET后台配置可以参考如下步骤:

    • 第一步:网站配置

    打开控制面板,选择管理工具,选择iis;右键单击自己的网站,选择浏览;打开网站所在目录,用记事本打开web.config文件添加下述配置信息,重启网站

    请注意,以上截图较老,如果配置仍然出问题,可以考虑增加更多的headers允许,比如:

    "Access-Control-Allow-Headers":"X-Requested-With,Content-Type,Accept,Origin"            
    • 第二步:其它更多配置,如果第一步进行了后,仍然有跨域问题,可能是:

      • 接口中有限制死一些请求类型(比如写死了POST等),这时候请去除限 制
      • 接口中,重复配置了Origin:*,请去除即可
      • IIS服务器中,重复配置了Origin:*,请去除即可

    代理请求方式解决接口跨域问题

    注意,由于接口代理是有代价的,所以这个仅是开发过程中进行的。

    与前面的方法不同,前面CORS是后端解决,而这个主要是前端对接口进行代理,也就是:

    • 前端ajax请求的是本地接口
    • 本地接口接收到请求后向实际的接口请求数据,然后再将信息返回给前端
    • 一般用node.js即可代理

    关于如何实现代理,这里就不重点描述了,方法和多,也不难,基本都是基于node.js的。

    搜索关键字node.js,代理请求即可找到一大票的方案。

    OPTIONS预检的优化

    Access-Control-Max-Age: 

    这个头部加上后,可以缓存此次请求的秒数。

    在这个时间范围内,所有同类型的请求都将不再发送预检请求而是直接使用此次返回的头作为判断依据。

    非常有用,可以大幅优化请求次数

    如何分析ajax跨域

    上述已经介绍了跨域的原理以及如何解决,但实际过程中,发现仍然有很多人对照着类似的文档无法解决跨域问题,主要体现在,前端人员不知道什么时候是跨域问题造成的,什么时候不是,因此这里稍微介绍下如何分析一个请求是否跨域:

    抓包请求数据

    第一步当然是得知道我们的ajax请求发送了什么数据,接收了什么,做到这一步并不难,也不需要fiddler等工具,仅基于Chrome即可

    • Chrome浏览器打开对应发生ajax的页面,F12打开Dev Tools
    • 发送ajax请求
    • 右侧面板->NetWork->XHR,然后找到刚才的ajax请求,点进去

    示例一(正常的ajax请求)

    上述请求是一个正确的请求,为了方便,我把每一个头域的意思都表明了,我们可以清晰的看到,接口返回的响应头域中,包括了

    Access-Control-Allow-Headers: X-Requested-With,Content-Type,Accept
    Access-Control-Allow-Methods: Get,Post,Put,OPTIONS
    Access-Control-Allow-Origin: *

    所以浏览器接收到响应时,判断的是正确的请求,自然不会报错,成功的拿到了响应数据。

    示例二(跨域错误的ajax请求)

    为了方便,我们仍然拿上面的错误表现示例举例。

    这个请求中,接口Allow里面没有包括OPTIONS,所以请求出现了跨域、


    这个请求中,Access-Control-Allow-Origin: *出现了两次,导致了跨域配置没有正确配置,出现了错误。

    更多跨域错误基本都是类似的,就是以上三样没有满足(Headers,Allow,Origin),这里不再一一赘述。

    示例三(与跨域无关的ajax请求)

    当然,也并不是所有的ajax请求错误都与跨域有关,所以请不要混淆,比如以下:

    比如这个请求,它的跨域配置没有一点问题,它出错仅仅是因为request的Accept和response的Content-Type不匹配而已。

    更多

    基本上都是这样去分析一个ajax请求,通过Chrome就可以知道了发送了什么数据,收到了什么数据,然后再一一比对就知道问题何在了。

    写在最后的话

    跨域是一个老生常谈的话题,网上也有大量跨域的资料,并且有不少精品(比如阮一峰前辈的),但是身为一个前端人员不应该浅尝而止,故而才有了本文。

    漫漫前端路,望与诸君共勉之!

    附录

    招聘软广

    阿里巴巴钉钉商业化团队大量hc,高薪股权。机会好,技术成长空间足,业务也有很大的发挥空间!

    还在犹豫什么,来吧!!!

    社招(P6~P7)

    职责和挑战

    1. 负责钉钉工作台。工作台是帮助企业实现数字化管理和协同的门户,是拥有亿级用户量的产品。如何保障安全、稳定、性能和体验是对我们的一大挑战。
    2. 负责开放能力建设。针对纷繁的业务场景,提供合理的开放方案,既要做到深入用户场景理解并支撑业务发展,满足企业千人千面、千行千面的诉求,又要在技术上保障用户的安全、稳定和体验。需要既要有技术抽象能力、平台架构能力,又要有业务的理解和分析能力。
    3. 开放平台基础建设。保障链路的安全和稳定。同时对如何保障用户体验有持续精进的热情和追求。

    职位要求

    1. 精通HTML5、CSS3、JS(ES5/ES6)等前端开发技术
    2. 掌握主流的JS库和开发框架,并深入理解其设计原理,例如React,Vue等
    3. 熟悉模块化、前端编译和构建工具,例如webpack、babel等
    4. (加分项)了解服务端或native移动应用开发,例如nodejs、Java等
    5. 对技术有强追求,有良好的沟通能力和团队协同能力,有优秀的分析问题和解决问题的能力。

    前端实习

    面向2021毕业的同学

    1. 本科及以上学历,计算机相关专业
    2. 熟练掌握HTML5/CSS3/Javascript等web前端技术
    3. 熟悉至少一种常用框架,例如React、vue等
    4. 关注新事物、新技术,有较强的学习能力,有强烈求知欲和进取心
    5. 有半年以上实际项目经验,大厂加分

    image.png

    image.png

    内推邮箱

    lichun.dlc@alibaba-inc.com

    简历发我邮箱,必有回应,符合要求直接走内推!!!

    一对一服务,有问必答!

    也可加我微信了解更多:a546684355

    参考资料

    博客

    初次发布2017.03.22于个人博客

    查看原文

    赞 546 收藏 2037 评论 38

    认证与成就

    • SegmentFault 讲师
    • 获得 5 次点赞
    • 获得 8 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 7 枚铜徽章

    擅长技能
    编辑

    开源项目 & 著作
    编辑

    (゚∀゚ )
    暂时没有

    注册于 2017-07-14
    个人主页被 164 人浏览