
JavaScript专家、技术讲师 关注前端开发和移动端开发
在前端工程化系列[05] Yeoman脚手架使用入门这边文章中,对Yeoman的使用做了简单的入门介绍,这篇文章我们将接着探讨Yeoman这个脚手架工具内部的核心机制,主要包括以下内容 Yeoman脚手架工具的价值讨论 generator[生成器]的内部结构 generator[生成器]的项目模板 Yeoman脚手架工具的核心运转机制 Yeoman 的主要组装流程 Yeoman这样的脚手架工具解决了什么问题? 所有新事物都不是凭空产生的,它们的出现总有某些内在的驱动力。一项新技术,一个新工具的出现更是如此。不知道从什么时候开始起,我接触新事物新技术以及某些工具的时候,总愿意多花点时间想一想它出现的原因是什么?因为时间、精力等等这些东西都很宝贵,IT从业人员对这些资源尤其敏感,所以新技术或者新工具的出现我认为有几种情况: 已有的技术或工具存在缺陷,作者们靠自己的才学推出更完美的替代方案 已有的技术或工具无法解决既定的需求,作者们探索出解决问题的技术方案 纯粹闲的蛋疼(这种情况一般比较少见) 现在,我们来研究下Yeoman的价值,或者说Yeoman出现的意义是什么?Yeoman的出现解决了什么样的问题? 我们假设有这样的开发场景:公司的开发团队,基于某些特定的技术栈已经完成了项目A的开发和上线等工作,项目A的基本情况如下 技术栈:JavaScript + HTML + CSS + Bootstrap + jQuery工作流:npm(包管理工具) + bower(下载器) + grunt版本管理工具:Git 项目整体目录结构(简化后) . ├── Gruntfile.js ├── bower.json ├── node_modules │ ├── abbrev ··· │ └── xtend ├── package-lock.json ├── package.json ├── build │ ├── css │ │ └── style.min.css │ └── js │ ├── index.js │ └── index.min.js ├── dist └── src ├── css │ └── style.css ├── index.html ├── js │ └── index.js ├── libs │ ├── bootstrap │ └── jquery └── template 说明:上面的目录中src为代码的工作目录,bulid为构建后目录,dist为发布目录。 因为项目A已经上线发布,现在公司要求着手开展新的项目B,经过需求评审和技术选型后,新项目B采用的工作流和项目A保持一致,技术栈在原有的基础上尝试使用TypeScript来处理脚本部分引入Vue框架,其它部分保持不变。我们发现项目A和项目B它们的结构基本上是一致的(比如项目的目录就够,都需要拥有Gruntfile.js和package.json等文件),但是有些部分又不太一样,比如package.json文件中的项目名称、开发依赖等。 这个时候,我们在对项目B进行初始化的方式可以尝试以下操作方式: 方案① 从0开始创建目录结构,集成工作流配置开发环境 方案② 从项目A中拷贝目录结构和固定文件,对于不同的部分一个个修改 如果我们采用方案① 你会发现这个过程你在初始化项目A的时候就已经做过了,是重复性的工作,毫无技术含量但是又费时费力。如果我们采用方案② 你会发现要修改的文件有些多,每个文件要改的字段也比较多,而且容易遗漏总是调不通会出现各种问题,心烦意乱。 如果你会使用Yeoman脚手架工具的话,那么对于上面的开发场景你就会多一个方案③,在使用方案③来初始化项目B的时候,你只需要动动手指在终端中输入$ yo 生成器名称再使用交互方式简单配置某些特定值,初始化的工作就完成了。这就是Yeoman的价值所在,初始化项目的时候你不必再把自己沉入到琐碎重复无技术成长的费力工作中,也不必总是像个机器人般进入到拷贝-粘贴-修改这样无止境的循环中。脚手架工具是那么的简单直接和高效,你甚至可以省出点加班的时间来看世界杯了 : ) 我知道有一些杠精要出来喷了。“解决这种初始化问题不用搞的这么复杂,我完全可以把项目结构和固定不变的部分抽取出来托管到gitHub仓库,要初始化项目的时候 $ git clone一下不就好了吗?” 说的很有道理,但是clone下来的仓库虽然结构和必要文件已经准备好了,但很多文件是不是还得修改?那你会顶回来“难道使用Yeoman初始化就不需要修改了吗?”当然也要修改,不过就算是修改那改起来也很有趣味还So快! Yeoman使用交互式的方式来对项目文件中需要灵活处理的部分进行配置,这部分内容我们称为组装指令,具体再文章的后面会进行讲解。 另外,如果新项目的整体结构以及技术选型和已有的项目很不一样,那你抽取后交由git管理的仓库就没用了,因为八字不合啊。使用Yeoman就没用这样的顾虑,在Yeoman-generator列表有好几千现成的generator供你选择,总有一款适合你!!! 我要求太太…太高,实在谁也看不上?没关系,generator这家伙还可以私人订制,你完全可以根据自己的需求来定制需要的generator,你一高兴甚至还能把它发布到社区造福全人类。 Yeoman-generator的内部结构 搞清楚 generator的价值所在和应用场景之后,我们就可以开始谈论generator相关的话题了,前面介绍过Yeoman脚手架工具的作用是帮助我们依据特定的技术栈需求来初始化项目,在安装了yo工具之后,只需要在终端中使用类似$ yo generator--xx的命令先安装对应的generator然后再$ yo xx搭建即可。至于如何找到匹配当前技术选型的generator,可以去官网的generator列表搜索,这些生成器中有很大一部分来自于对应框架的作者或者Yeoman官方团队,质量有保证且更新很及时。当然,我们也可以创建自己的generator并发布。关于如何创建自己的generator,我们放到另一篇文章Yeoman脚手架生成器创建来解决。 简单说Yeoman做的工作其实就是根据当前的生成器(generator)来复制固定的项目模板文件到新项目中,而新项目中的某些文件需要配置,这部分工作由安装时候的交互式指令来完成(相当于传递参数给模板文件)。 需要注意的是,Yeoman的设计仅仅只提供了一小部分核心的API,而真正繁重的初始化工作是交给每个具体的generator来完成的。 generator主要由组装指令和项目模板两部分组成。 组装指令 Yeoman generator中的generators/app/index.js文件是整个生成器的核心部分,该文件用于告知Yeoman该如何来组织并搭建项目,我们可以在该文件中设置初始化项目时必要的安装提示和选项来让用户选择,以及每个文件应该如何复制和修改,是否需要加载依赖和Node包等内容。 项目模板 项目模板包括初始化项目需要的所有必须文件。这些文件又可以简单的划分为固定文件、灵活文件、可选文件和依赖文件。所谓固定文件就是在每个初始项目中都一模一样的文件,譬如index.js、style.css等文件,在具体处理的时候这些文件只需要简单复制即可。灵活文件指的是那些需要根据用户选择来做简单修改然后才能复制的文件,譬如index.html文件(title等信息需根据用户输入来指定)。对于可选文件来说,它们并不是必须的,譬如某些基础框架有的项目中需要,有的项目中也许并不需要,这部分文件的处理方式需要交给用户来决定。 项目模板文件的类别 前面已经介绍过了Yeoman生成器的组成部分主要是组装指令和项目模板。对于整个Yeman脚手架工具来说,项目模板这部分就相当于是搭建脚手架需要用到的原材料,而组装指令用来决定和控制所有的具体行动是什么。 现在我们开始深入的来讨论项目模板这部分内容,需要先明白的是“能够满足所有需求的万能的项目模板是不存在的”。因为这世界上每个项目组,每个产品甚至每个人的需求(要求)都各有不同。所以,在实践中你必须要对当前项目的需求和采用的技术栈有深入的理解,这样你才能知道目标项目的目录结构会是什么样的? 哪些文件是必不可少的。 如果你的项目和采用的技术栈比较大众化,那么搜索一个合适的generator基本就能满足需求,拿来主义即可。如果你的项目不管结构还是所采用的技术看上去都那么的非凡和特别,那么就多花一点点时间创建个自己的generator吧,如果你需要处理多个这样的项目,那就更应该了。在创建或者理解generator的时候,我们可以根据前面对项目模板文件的划分情况来区别对待不同的文件。 固定文件 固定文件是在每个项目中初始内容都一样的必要文件。 比如我们可能总是会把代码的结构划分为src、build和dist三个目录,在src目录下面拥有js、css和lib文件目录,index.js和style.css等文件。这些文件都是必要的,刚开始的时候可能是空的或者只有几行简单的代码。这些文件的特点是,在使用组装指令操作(通常是复制-移动)这些文件的时候,不需要对它们进行任何的修改。 灵活文件 灵活文件和固定文件差不多,也是初始化项目所必须的,但不同的项目中这些文件的内容也会稍有不同,这些不同之处可能很细微(比如仅仅是名字、协议这些),也可能差异巨大。比如,我们常用的构建工作流中的bower.json和package.json文件,它们是必不可少的,但是它们都需要当前项目的项目名称和协议等信息才能正常工作。像这样的灵活文件还有index.html,在这个文件中的title标签中应该使用当前项目的名称。 灵活文件中的部分内容需要在安装该生成器的时候,由用户交互式配置输入的信息来进行设定。 可选文件 可选文件并不是搭建初始化项目时所必须的文件,如果没有那么没关系,如果有那似乎更好。这些一般在用户交互式配置的时候,以是否题的方式交由用户决定,譬如是否使用less 是否安装Bootstrap等。 依赖文件 依赖文件指的是某些常用的框架、插件或者是Node模块,这些文件并不需要你在项目模板文件中提供,然后通过组装指令去一个个复制。因为基本上成熟的项目中都会使用既定的工作流(主要包括依赖和包的下载、项目的自动化构建等),所以我们完全只需要在package.json或者bower.json等文件中设置好依赖即可,然后在组装指令的相关代码中通过this.installDependencies()类似的代码来调用npm或者是bower执行install命令即可。 Yeoman脚手架运转的核心机制 当您为项目准备好(搜索或自己创建)合适的generator之后,就可以用它们来搭建项目了。generator的执行需要在终端中使用yo命令来操作。yo是Yeoman的核心命令,主要用来连接生成器和项目结构。我们可以把yo命令理解为generator的执行器,它知道怎么找到对应的generator,也知道该如何执行它们。 注意:yo基于NodeJS且需要在任何文件目录中使用,所以在安装yo命令的时候应该使用-g来进行全局安装。安装过程请参考:Yeoman脚手架使用入门。 在使用yo命令行工具和生成器来初始化项目之前,需要先把指定的生成器(generator)下载安装到本地(如果是自己创建的生成器,那么可以通过$ npm link命令以软连接的方式生成一个全局的npm包,我的是mac OSX系统,生成的npm包会保存在/usr/local/lib/node_modules/路径,如果使用的是别人发布的generator,那么请使用$ npm install -g generator-xxx的方式来安装)。 这里需要注意的是yo命令行工具主要负责前期工作,在使用的时候它主要检查当前安装的generator有哪些,指定的generator是否能够正常工作,如果能,那么它就会调用generator的组装指令,把剩下部分的工作交接给generator来完成。generator接管项目的组装流程之后,会按app/index.js中的要求来处理文件的复制等工作。 下面给出脚手架工具初始化项目时的核心流程。 这里对yo的主要命令进行简单说明 $ yo 执行该命令的时候,yo会搜索并列出所有本地可用的生成器$ yo 生成器名称 比如对于generator-typescript生成器,那么执行的命令就是$ yo typescript。该命令会先检查enerator-typescript生成器是否可用。如果可用,那么就接着以 ①交互式配置 ② 写入文件 ③ 下载安装依赖的顺序来执行组装指令。 Yeoman的主要组装流程 组装指令是用来让Yeoman创建项目所需文件的一系列具体的命令(代码)。典型的组装流程分为三个步骤: ① 交互式配置。这个步骤通过向用户提问或直接输入配置信息来完成模板传参。② 写入文件。把项目模板中的指定文件复制到新项目的指定目录中。③ 安装依赖。下载并安装所有保存在bower.json和package.json文件中的依赖和Node模块。 ① 交互式配置 Yeoman在执行生成器的时候,首先会执行安装提示以交互式的方式来询问用户,目的是为了获取生成器所需要的一些参数,比如项目的名称、作者、使用的开原协议以及是否安装和使用某些组件等。 这部分功能,需要使用到inquirer包,这个包的作用是生成选项来让用户选择。下面给出代码示例: prompting() { const prompts = [ { type : 'input', name : 'appName', message : '请输入项目名称:', default : this.appname //appname是内置对象,代表工程名,这里就是ys }, { type : 'input', name : 'appAuthor', message : '请输入作者姓名:', default : '文顶顶' }, { type: 'list', name: 'appLicense', message: '请选择使用的license:', choices: ['MIT', 'ISC', 'Apache-2.0', 'AGPL-3.0'] }, { type : 'confirm', name : 'isIncludeBootstrap', message : '是否需要使用bootStrap框架?', default : false }, ]; return this.prompt(prompts).then(props => { // To access props later use this.props.someAnswer; this.props = props; }); 我们可以看到在代码中,这些交互式配置都由prompts来进行维护,prompts是一个对象数组,数组中的每个元素对象就代表着一个具体的安装提示,在使用yo命令运行该生成器的时候,它的执行情况如下: _-----_ ╭──────────────────────────╮ | | │ 欢迎使用 │ |--(o)--| │ generator-wen! │ `---------´ │ Author:文顶顶 │ ( _´U`_ ) ╰──────────────────────────╯ /___A___\ / | ~ | __'.___.'__ ´ ` |° ´ Y ` ? 请输入项目名称: wendingdingTest ? 请输入作者姓名: 文顶顶 ? 请选择使用的license: Apache-2.0 ? 是否需要使用bootStrap框架? (y/N) yes prompts中的每个对象元素就代表着一个安装提示,上面代码一共提供了四个安装提示。每个对象中的type属性用于表明交互的类型,其中输入项目名称和作者姓名是input型的,表示接收用户的输入,相当于填空题。选择使用的license是list型的,它提供了多个选项供用户选择,您可以认为这种类型是单选题。是否需要使用bootStrap框架是confirm型的,默认为false,如果需要安装那么需要输入YES,这相当于是非题。 交互式配置这部分可以根据项目的实际情况来设置prompts中的对象元素,除上面介绍的这些类型外,您还可以通过查看inquirer.js的文档来获取更多内容。 交互式配置过程中用户做出的所有选择和输入都会被保存到this.props对象中,可以通过访问this.props.isIncludeBootstrap属性来确定是否需要安装Bootstrap。 message属性保存是每一条安装提示的提示信息。name属性是最重要的属性之一,它作为key用来访问用户的选择结果。default属性保存的是默认值,即当用户跳过当前安装提示的时候,name对应的value值将使用default中保存的默认值来设置。 ② 写入文件 写入文件这个过程会把项目模板复制到指定的目录中,如果是固定文件那么就直接拷贝,如果是灵活文件那么还需要把某些参数传递给指定的模板文件。这个过程在代码中由writing() 函数体现,另外系统还提供了两个函数(fs.copyTpl和fs.copy)用来执行具体的操作。 writing() { mkdirp("build"); //创建build文件目录 mkdirp("dist"); //创建dist文件目录 mkdirp("src/template"); //创建src/template文件目录 //传递参数this.props.appName渲染index.html文件 //把项目模板中的index.html文件复制到新项目的src路径下 this.fs.copyTpl( this.templatePath('index.html'), this.destinationPath('src/index.html'), {appName: this.props.appName} ); //把项目模板中的style.css文件复制到新项目的src/css路径下 this.fs.copy( this.templatePath('css/style.css'), this.destinationPath('src/css/style.css') ); //...... } fs.copy方法会把指定文件复制到目标路径。fs.copyTpl方法会先传递参数给模板文件,经过模板引擎处理后再进行复制。 ③ 下载和安装依赖 这个阶段做的事情非常简单,就是调用npm或者是bower来下载并安装依赖和相关的node模块。Yeoman提供了几个对应的方法来处理这个过程。 this.npmInstall()使用Npm来安装package.json中的依赖和模块,相当于在终端中输入$ npm install指令。 this.bowerInstall()使用Bower来安装bower.json中的依赖和模块,相当于在终端中输入$ bower install指令。 this.installDependencies()调用Bower和Npm并且安装package.json和bower.json中依赖的所有模块,相当于先后调用了npmInstall和bowerInstall方法。 最后,为了帮助更好的理解Yeoman组装流程的三个阶段,给出下面的示意图。
Yeoman是一款流行的前端的脚手架工具。 脚手架工具可以用来快速的自动生成项目的必要文件和基础文件结构。Yeoman使用的内建命令为yo,同时它自己也是一个包管理工具和自动化任务工具,它基于特定的模板来初始化项目。 考虑这样的开发场景:现在我们需要开始一个全新的前端项目,通常需要先处理项目的文件结构,创建包括img、JavaScript、CSS 等静态资源的文件夹,如果团队开发,可能还需要添加 .gitignore忽略文件以及.editorconfig、.eslintrc、package.json、Gruntfile.js以及README.md等配置文件。 如果你进行的多个前端项目,它们的技术选型差不多(比如都是:jQuery + grunt + Vue + Bootstrap),你会发现这些项目的整体文件结构是相同的。我们在初始化项目的时候,当然可以从0开始搭建,也可以直接把旧项目的结构和相关文件拷贝过来,这其实都是些重复性没有技术含量的工作,而Yeoman 的作用就是减少这些重复性的工作,通过调用 Yeoman 生态圈中的现成的生成器(generator)即可自动生成项目初始化所需要的文件结构、配置文件等。所以简单来说,Yeoman 是一个用于初始化项目的模版工具,用完就可以扔在一边了。 关于Yeoman的更多信息可以参考Yeoman官网和Github托管仓库。 Yeoman的安装和使用 用于初始化项目的模板被称为生成器(generator), 在开源社区中已经有众多现成的generator可以供我们使用(可以在生成器列表页使用关键字搜索)。在开始项目的时候,我们可以先搜寻是否有匹配当前项目技术栈的生成器,如果有的话直接用就好了,如果找不到合适的generator,那么可以考虑自己来写一个Yeoman生成器,甚至通过很简单的方式我们就可以把自己写的生成器发布出来造福社区。 这篇文章并不包含自己创建Yeoman生成器的内容,我只是想简单介绍下Yeoman的特征,以及如何使用Yeoman的生成器来初始化项目这个部分。 环境准备 安装yeoman之前,你需要先安装以下环境 Node.js 6或更高版本 npm 3或更高版本(通常安装Node的时候默认安装) Git版本控制工具 点击NodeJS官网选择对应系统和版本根据提示完成NodeJS的安装,我们可以通过在终端输入下面的命令来检查Node和npm的安装是否成功。 $ node --version && npm --version 有些 Node 版本可能安装的是旧版本的 npm,你可以通过以下命令来更新npm $ npm install -g npm@latest Git的安装过程请自行百度(OSX 默认安装),您可以通过以下命名来检查Git $ git --version 安装Yeoman 通过下面的命令来安装Yeoman并检查是否安装成功,当前最新版本为2.0.1,-g表示全局安装。 $ npm install -g yo$ yo --version generator-typeScript初始化项目示例 接下来我们将选择一个生成器(这里以typescript为例)来演示初始化项目的操作,Yeoman将会根据对应的生成器替我们创建好package.json和bower.json等文件,然后自动安装依赖。 ① 新建 mytodo 文件夹,生成器生成的脚手架文件会放在这个文件夹中。 $ mkdir mytodo && cd mytodo ② 根据项目技术栈需求到官网列表搜索合适的生成器。③ 通过npm来安装指定的generator。 $ npm install -g generator-typescript ④ typescript生成器安装完成后,使用yo命令来开始。 $ yo typescript 下面给出终端处理的具体细节: wendingding:Blog wendingding$ mkdir mytodo && cd mytodo wendingding:mytodo wendingding$ npm install -g generator-typescript npm WARN deprecated npmconf@2.1.3: this package has been reintegrated into npm and is now out of date with respect to npm > spawn-sync@1.0.15 postinstall /usr/local/lib/node_modules/generator-typescript/node_modules/spawn-sync > node postinstall > yo@1.8.5 postinstall /usr/local/lib/node_modules/generator-typescript/node_modules/yo > yodoctor Yeoman Doctor Running sanity checks on your system Global configuration file is valid NODE_PATH matches the npm root Node.js version No .bowerrc file in home directory No .yo-rc.json file in home directory npm version Everything looks all right! + generator-typescript@0.3.0 added 608 packages in 138.302s wendingding:mytodo wendingding$ yo typescript _-----_ | | ╭──────────────────────────╮ |--(o)--| │ Let's make some awesome │ `---------´ │ typescript project! │ ( _´U`_ ) ╰──────────────────────────╯ /___A___\ / | ~ | __'.___.'__ ´ ` |° ´ Y ` I will include JSHint and Editorconfig by default. ? First off, how would you like to name this project? wendingdingDemo ? Where should it be compiled to? app/build ? Where should your typescript go? app/src create package.json create app/src/index.ts create app/src/app.ts create tslint.json create gulpfile.js create test/test-greeting.js create test/test-load.js create README.md create .editorconfig create .jshintrc I'm all done. Running npm install && bower install for you to install the required dependencies. If this fails, try running the command yourself. 按上面的步骤在终端中执行对应命令,我们就可以得到一个基于基于typescript模板生成的初始化项目了,下面列出该项目的目录结构: wendingding:mytodo wendingding$ tree . ├── README.md ├── app │ ├── build │ └── src │ ├── app.ts │ └── index.ts ├── gulpfile.js ├── package.json ├── test │ ├── test-greeting.js │ └── test-load.js └── tslint.json 4 directories, 8 files 最后,根据Yeoman终端中的提示通过$ npm install && bower install命令来安装必要的依赖即可。
在前端工程化系列[02]-Grunt构建工具的基本使用和前端工程化系列[03]-Grunt构建工具的运转机制这两篇文章中,我们对Grunt以及Grunt插件的使用已经有了初步的认识,并探讨了Grunt的主要组件以及它的运转机制,这篇文章是Grunt使用的进阶教程,主要输出以下内容: Grunt项目的自定义任务 Grunt任务的描述和依赖 Grunt多目标任务和选项 Grunt项目任务模板配置 Grunt自动化构建和监听 3.1 Grunt自定义任务 在使用Grunt的时候,可以先到Grunt官网的插件列表搜索是否有适合自己项目的Grunt插件,如果有那么建议直接使用,如果没有那么开发者可以尝试自定义任务或者是自己创建对应的插件。Grunt的插件其实就是一些封装好的任务(Task),没有什么稀奇的,Grunt支持自定义任务,而且方式非常简单。 如果我们需要定义一个任务,向控制台里输出字符串信息,那么在package.json文件、Gruntfile文件已经创建且grunt本地依赖已安装的前提下,如下编辑Gruntfile文件即可: //包装函数 module.exports = function (grunt) { //(1)自定义任务(一) //向控制台输出:hello 文顶顶 //第一个参数:任务的名称(Task) //第二个参数:具体的任务内容 grunt.registerTask("hello",function () { grunt.log.writeln("hello 文顶顶"); }); //(2)自定义任务(二) grunt.registerTask("football",function () { grunt.log.writeln("皇家马德里: how are you!"); grunt.log.writeln("尤文图斯: how old are you!"); }); }; 终端输入命令执行任务,可以单个执行,也可以一起执行,下面给出具体执行情况 wendingding:02-Grunt_Test wendingding$ grunt hello Running "hello" task hello 文顶顶 Done. wendingding:02-Grunt_Test wendingding$ grunt football Running "football" task 皇家马德里: how are you! 尤文图斯: how old are you! Done. wendingding:02-Grunt_Test wendingding$ grunt hello football Running "hello" task hello 文顶顶 Running "football" task 皇家马德里: how are you! 尤文图斯: how old are you! Done. 通过上面的代码我们可以看到,自定义任务非常简单,只需要调用grunt对象的registerTask方法即可,其中第一个参数是Task的名称,第二个参数是回调函数用来存放具体的任务(比如这里是打印输出)。 在自定义任务中,我们用到了grunt.log.writeln函数,这是Grunt提供的众多内置方法之一,作用是向控制台输出消息并换行。同类型的方法还有grunt.log.error()、grunt.log.subhead()等方法,大家可以到官网API文档自行查看。 Grunt项目在具体使用的时候,通常是自定义Task + Grunt插件相结合的形式,我们来看下面这段代码: //包装函数 module.exports = function (grunt) { //(1)自定义任务(一) 任务名称 hello grunt.registerTask("hello",function () { grunt.log.writeln("hello 文顶顶"); }); //(2)自定义任务(二) 任务名称 football grunt.registerTask("football",function () { grunt.log.writeln("皇家马德里: how are you!"); grunt.log.writeln("尤文图斯: how old are you!"); }); //(2) 插件的处理 //使用步骤: //[1] 先把对应的插件下载和安装到本地的项目中 $ npm install grunt-contrib-concat --save-dev //[2] 对插件(任务)进行配置 grunt.initConfig //[3] 加载对应的插件 loadNpmTasks //[4] 注册任务 grunt.registerTask //[5] 通过grunt命令执行任务 //配置插件相关信息 grunt.initConfig({ "concat":{ "dist":{ "src":["src/demo_one.js","src/demo_two.js","src/demo_three.js"], "dest":"dist/index.js" } } }); //加载插件 grunt.loadNpmTasks("grunt-contrib-concat"); //注册任务(一):把hello \ football \ concat 这三个Task注册为default的Task //当执行$ grunt 或者是$ grunt default的时候,会顺序执行者三个任务! grunt.registerTask("default",["hello","football","concat"]); //注册任务(二) grunt.registerTask("customTask",["hello","football"]); }; 3.2 任务描述和依赖 对于上面的Gruntfile文件,如果在终端输入$ grunt或者$ grunt default 命令则依次执行hello football和concat三个任务,输入$ grunt customTask则一次执行hello football 自定义任务。 设置任务描述 随着项目复杂性的增加,Grunt任务也会越来越多,而任务(Task)的可用性、用途以及调用方法可能会变得难以追踪。所幸,我们可以通过给任务设定相应的描述信息来解决这些问题。 要给任务设置描述信息非常简单,只需要在调用registerTask方法的时候多传递一个参数即可(作为第二个参数传递),我们可以把一个具体的字符串描述信息作为函数的参数传递。 这里,我们修改上面示例代码中football任务部分的代码,并任务设置描述信息。 grunt.registerTask("football","17-18赛季 欧冠八分之一决赛抽签场景",function () { grunt.log.writeln("皇家马德里: how are you!"); grunt.log.writeln("尤文图斯: how old are you!"); }); 此时,在终端中输入$ grunt --help命令就能够看到当前Grunt项目中可用的Task,以及相应的描述信息了,关键信息如下。 Available tasks hello Custom task. football 17-18赛季 欧冠八分之一决赛抽签场景 concat Concatenate files. * default Alias for "hello", "football", "concat" tasks. customTask Alias for "hello", "football" tasks. 任务依赖 在复杂的Grunt工作流程中,很多任务之间往往存在依赖关系,比如js代码的语法检查和压缩这两个任务,压缩任务需要依赖于语法检查任务,它们在执行的时候存在一定的先后关系,这种情况我们称之为任务依赖。 我们可以在注册任务的时候,刻意指定这种依赖关系,他们更多的是以一种特定的先后顺序执行。如果是自定义任务,也可以通过grunt.task.requires()方法来设定这种任务间的依赖关系。 module.exports = function (grunt) { //注册两个自定义任务 /* * 第一个参数:Task的名称 * 第二个参数:任务的描述信息 * */ grunt.registerTask("hi","描述信息:这是一个打招呼的任务",function () { grunt.log.ok("hi 文顶顶"); }); grunt.registerTask("hello","任务的描述次信息:这是一个简单问候任务",function () { //设置任务依赖:表明当前的任务在执行的时候需要依赖于另外一个任务 //必须先执行hi这个任务,才能执行hello这个任务 grunt.task.requires("hi"); console.log("Nice to meet you!"); }); }; 上面的代码中定义了hi和hello两个任务,其中hello这个Task需要依赖于hi的执行,如果直接执行hello,那么会打印任务依赖的提示信息,具体的执行情况如下。 wendingding:05-Grunt项目任务的描述和依赖 wendingding$ grunt hello Running "hello" task Warning: Required task "hi" must be run first. Use --force to continue. Aborted due to warnings. wendingding:05-Grunt项目任务的描述和依赖 wendingding$ grunt hi hello Running "hi" task >> hi 文顶顶 Running "hello" task Nice to meet you! Done. 3.3 Grunt多目标任务和Options选项 理解多目标Task Grunt中的多目标任务(multi-task)是相对于基本任务而言的,多目标任务几乎是Grunt中最复杂的概念。它的使用方式非常灵活,其设计的目的是可以在当个项目中支持多个Targets目标[可以认为是多种配置]。当任务在执行的时候,可以一次性执行全部的Target也可以指定某一特定的Target执行。 module.exports = function (grunt) { //(1) 配置Task,给Task设置多个Target grunt.config("hello", { "targetA":{ "des":"Nice to meet you!" }, "targetB":{ "des":"how are you?" }, }); //(2) 自定义任务 任务的名称为hello //第一个参数:Task名称 //第二个参数:任务的描述信息 //第三个参数:具体要执行的任务 grunt.registerMultiTask("hello","描述信息:打招呼",function () { grunt.log.ok("hello 文顶顶"); grunt.log.writeln("this.target:",this.target); grunt.log.writeln("this.data:",this.data); }); }; 代码说明 通过观察可以发现,我们通过grunt.registerMultiTask方法创建了支持多任务(Target)操作的自定义任务hello,主要任务就是输出“hello 文顶顶”消息以及打印当前的target和data值。然后通过grunt.config方法来给hello这个Task设定了两个Target,分别是targetA和targetB。 在上面的代码中,我们引用了this.target和this.data这两个属性,回调函数中的this指向的是当前正在运行的目标对象。执行targetA这个选项的时候,打印的this对象如下: { nameArgs: 'hello:targetA', name: 'hello', args: [], flags: {}, async: [Function], errorCount: [Getter], requires: [Function: bound ], requiresConfig: [Function], options: [Function], target: 'targetA', data: { des: 'Nice to meet you!' }, files: [], filesSrc: [Getter] } 目前为止,我们一直在谈论Task(任务)和Target(目标),大家可能懵逼了,不禁要问它们之间到底是什么关系? 私以为可以简单的类比一下,假设现在有一个任务就是中午吃大餐,而具体吃什么大餐,可以灵活安排多个方案进行选择,比如方案A吃西餐,方案B吃中餐,方案C吃日本料理。等我们真正到了餐馆要开吃的时候,可以选择方案A吃西餐或者是方案B吃中餐,甚至中餐、西餐和日本料理全端上桌也未尝不可。 Task指的是整个任务,在这个例子中就是要吃大餐,Target指的是任务中的某一种可行方案,也就是方案A、方案B和方案C,吃大餐这个Task中我们配置了三个Target。定义任务的目的是为了执行,在执行Task的时候,我们可以选择执行某个或某几个指定的Target(目标),这样的处理方式无疑更强大而且操作起来更加的灵活。 多目标任务的执行 运行多目标Task的时候,有多种方式选择。 ① 让Task按照指定的target运行。$ grunt TaskName:targetName ② 让Task把所有的target都运行一次。$ grunt TaskName 下面列出示例代码的具体执行情况 wendingding:05-Grunt项目任务的描述和依赖 wendingding$ grunt hello Running "hello:targetA" (hello) task >> hello 文顶顶 this.target: targetA this.data: { des: 'Nice to meet you!' } Running "hello:targetB" (hello) task >> hello 文顶顶 this.target: targetB this.data: { des: 'how are you?' } Done. wendingding:05-Grunt项目任务的描述和依赖 wendingding$ grunt hello:targetA Running "hello:targetA" (hello) task >> hello 文顶顶 this.target: targetA this.data: { des: 'Nice to meet you!' } Done. wendingding:05-Grunt项目任务的描述和依赖 wendingding$ grunt hello:targetB Running "hello:targetB" (hello) task >> hello 文顶顶 this.target: targetB this.data: { des: 'how are you?' } Done. 如果在Gruntfile文件中,调用了grunt.registerTask方法来注册自定义任务,那么可以通过TaskName:targetName的来方式直接指定任务的Target //注册任务 [给hello起一个别名] grunt.registerTask("helloTargetA",["hello:targetA"]); 在终端中,输入$ grunt helloTargetA 命令将会执行hello这个Task中的targetA选项。 多目标任务的Options选项 在对多目标的任务进行配置的时候,任何存储在options选项下面的数据都会被特殊的处理。 下面列出一份Gruntfile文件中的核心代码,并以多种方式执行,通过这份代码能够帮助我们理解多目标任务的Options选项配置。 //包装函数 module.exports = function (grunt) { //(1) 配置Task相关信息 /* * 第一个参数:Task的名称 * 第二个参数:任务的描述信息 * */ grunt.initConfig({ "hi": { /*对整个任务中所有target的配置项 全局配置*/ options:{ "outPut":"array" }, targetA:{ arrM:["targetA_1","targetA_2","targetA_3"] }, targetB:{ options:{ "outPut":"json" }, arrM:["targetB_1","targetB_2","targetB_3"] }, targetC:{ arrM:["targetC_1","targetC_2","targetC_3"] } } }); //(2) 自定义任务 Task名称为hi //第一个参数:Task名称 //第二个参数:任务的描述信息 //第三个参数:具体要执行的任务 grunt.registerMultiTask("hi","描述次信息:这是一个打招呼的任务",function () { console.log("任务当前执行的target: "+this.target); console.log("任务当前执行的target对应的数据: \n"); var objT = this.options(); if (objT.outPut === "array") { console.log("输出数组:\n"); console.log(this.data.arrM); }else if (objT.outPut === "json") { console.log("输出JSON数据:\n"); console.log(JSON.stringify(this.data.arrM)); } }); //(1) 相关的概念 Task(任务-hi) | target(目标) //(2) 任务的配置:任务中可以配置一个或者是多个目标 调用config //(3) 复合任务的执行(多任务-多target) // 001 grunt TaskName 把当前Task下面所有的目标操作都执行一遍 // 002 grunt TaskName:targetName 执行当前Task下面的某一个指定的目标 grunt.registerTask("default",["hi"]); }; 具体的执行情况 wendingding:06-Grunt项目多任务和options wendingding$ grunt Running "hi:targetA" (hi) task 任务当前执行的target: targetA 任务当前执行的target对应的数据: 输出数组: [ 'targetA_1', 'targetA_2', 'targetA_3' ] Running "hi:targetB" (hi) task 任务当前执行的target: targetB 任务当前执行的target对应的数据: 输出JSON数据: ["targetB_1","targetB_2","targetB_3"] Running "hi:targetC" (hi) task 任务当前执行的target: targetC 任务当前执行的target对应的数据: 输出数组: [ 'targetC_1', 'targetC_2', 'targetC_3' ] Done 代码说明 上面的代码中定义了一个多目标任务,Task的名称为hi,该Task有三个target目标选项,分别是targetA、targetB和targetC。在任务配置相关代码中,全局的options配置项中outPut属性对应的值为array,表示具体的目标任务在执行的时候以数组的形式输出。 我们看到在targetB目标中重写了options选项中的outPut属性为json,当终端执行$ grunt命令的时候,会依次执行所有三个target目标选项,而targetA和targetC以数组格式来输出内容,targetB则以json格式来输出内容。 Grunt多目标任务以及选项使得我们可以针对不同的应用环境,以不同的方式来运行同一个Task。可以利用这一点,我们完全能够定义Task为不同的构建环境创建不同的输出目标。 说明 this.options()方法用于获取当前正在执行的目标Task的options配置选项 3.4 Grunt项目任务配置模板 Grunt项目中配置模板的简单使用 在Grunt项目中,我们可以使用<% %>分隔符的方式来指定模板,当Task读取自己配置信息的时候模板的具体内容会自动扩展,且支持以递归的方式展开。 在通过<%= ... %>在向模板绑定数据的时候,我们可以直接传递配置对象中的属性或调用grunt提供的方法,模板中属性的上下文就是当前的配置对象。 下面,我们通过Gruntfile文件中的一段核心代码来展现配置模板的使用情况。 module.exports = function (grunt) { //(1) 创建并设置grunt的配置对象 //配置对象:该对象将作为参数传递给grunt.config.init方法 var configObj = { concat: { target: { //src:["src/demo1.js","src/demo2.js"] src: ['<%= srcPath %>demo1.js', '<%= srcPath %>demo2.js'], //dest:["dist/2018_05_21_index.js"] dest: '<%= targetPath %>', }, }, srcPath:"src/", destPath:"dist/", targetPath:"<%= destPath %><%= grunt.template.today('yyyy_mm_dd_') %>index.js" }; //(2) 调用init方法对任务(Task)进行配置 // grunt.config.init 方法 === grunt.initConfig方法 grunt.config.init(configObj); //(3) 加载concat插件 grunt.loadNpmTasks("grunt-contrib-concat"); //(4) 注册Task grunt.registerTask("default",["concat"]); }; 上面这段代码对concat插件代码合并Task进行了配置,使用到了模板技术。该任务把src目录下的demo1和demo2两个js文件合并到dist目录下并命名为2018_05_21_index.js文件。 Grunt项目中导入外部的数据 在向模板绑定数据的时候,常见的做法还会导入外部的数据,并把导入的数据设置为配置对象的指定属性值。比如在开发中常常需要用到当前Grunt项目的元信息,包括名称、版本等,这些数据常通过调用grunt.file.readJSON方法加载package.json文件的方式获取。下面给出代码示例: //包装函数 module.exports = function (grunt) { //设置(demoTask和concat)Task的配置信息 grunt.config.init({ //从package.json文件中读取项目的元(基本)信息 pkg:grunt.file.readJSON("package.json"), //demoTask的配置信息 demoTask :{ banner:"<%=pkg.name%> -- <%=pkg.version%>" }, //concat的配置信息 concat:{ options:{ stripBanners:true, banner:'/*项目名称:<%=pkg.name%> 项目版本:<%=pkg.version%> 项目的作者:<%=pkg.author%> 更新时间:<%=grunt.template.today("yyyy-mm-dd")%>*/\n' }, target:{ src:["src/demo1.js","src/demo2.js"], dest:'dist/index.js' } } }); //自定义Task 任务的名称为demoTask grunt.registerMultiTask("demoTask",function () { console.log("执行demo任务"); //表示调用config方法来读取demoTask里面的banner属性并输出 console.log(grunt.config("demoTask.banner")); }); //从node_modules目录中加载concat插件 //注意:需要先把插件下载到本地 npm install grunt-contrib-concat --save-dev grunt.loadNpmTasks("grunt-contrib-concat"); //注册任务 grunt.registerTask("default",["demoTask","concat"]); }; 如果在终端输入$ grunt命令执行,那么demoTask任务将会输出grunt_demo -- 1.0.0打印消息,而concat任务则把两个js文件合并到dist目录下面的index.js文件并添加注释信息。 wendingding$ grunt Running "demoTask:banner" (demoTask) task 执行demo任务 grunt_demo -- 1.0.0 Running "concat:target" (concat) task Done. wendingding:07-Grunt项目模板配置 wendingding$ cat dist/index.js /*项目名称:grunt_demo 项目版本:1.0.0 项目的作者:文顶顶 更新时间:2018-05-21*/ console.log("demo1"); console.log("demo2"); 说明 grunt.file.readJSON方法用于加载JSON数据,grunt.file.readYAML方法用于加载YAML数据。 3.5 Grunt自动化构建和监听 到这里,基本上就可以说已经熟练掌握Grunt了。上文我们在进行代码演示的时候,不论是自定义任务还是Grunt插件使用的讲解都是片段性的,支离破碎的,Grunt作为一款自动化构建工具,自动化这三个字到现在还没有体现出来。 顾名思义,自动化构建的意思就是能够监听项目中指定的文件,当这些文件发生改变后自动的来执行某些特定的任务。 否则的话,每次修改文件后,都需要我们在终端里面输入对应的命令来重新执行,这顶多能算半自动化是远远不够的。 下面给出一份更全面些的Gruntfile文件,该文件中使用了几款常用的Grunt插件(uglify、cssmin、concat等)来搭建自动化构建项目的工作流。点击获取演示代码 //包装函数 module.exports = function (grunt) { // 项目配置信息 grunt.config.init({ pkg:grunt.file.readJSON("package.json"), //代码合并 concat:{ options:{ stripBanners:true, banner:'/*项目名称:<%=pkg.name%> 项目版本:<%=pkg.version%> 项目的作者:<%=pkg.author%>' +' 更新时间:<%=grunt.template.today("yyyy-mm-dd")%>*/\n' }, target:{ src:["src/demo1.js","src/demo2.js"], dest:'dist/index.js' } }, //js代码压缩 uglify:{ target:{ src:"dist/index.js", dest:"dist/index.min.js" } }, //css代码压缩 cssmin:{ target:{ src:"src/index.css", dest:"dist/index.min.css" } }, //js语法检查 jshint:{ target:['Gruntfile.js',"dist/index.js"], options:{ jshintrc:".jshintrc" } }, //监听 自动构建 watch:{ target:{ files:["src/*.js","src/*.css"], //只要指定路径的文件(js和css)发生了变化,就自动执行tasks中列出的任务 tasks:["concat","jshint","uglify","cssmin"] } } }); //通过命令行安装插件(省略...) //从node_modules路径加载插件 grunt.loadNpmTasks("grunt-contrib-concat"); grunt.loadNpmTasks("grunt-contrib-uglify"); grunt.loadNpmTasks("grunt-contrib-cssmin"); grunt.loadNpmTasks("grunt-contrib-jshint"); grunt.loadNpmTasks("grunt-contrib-watch"); //注册任务:在执行$ grunt命令的时候依次执行代码的合并|检查|压缩等任务并开启监听 grunt.registerTask("default",["concat","jshint","uglify","cssmin","watch"]) }; 当在终端输入$ grunt命令的时候,grunt会执行以下任务 ① 合并src/demo1.js和src/demo2.js文件并命名为index.js保存到dist目录② 按照既定的规则对Gruntfile.js和index.js文件来进行语法检查③ 压缩index.js文件并命名为index.min.js保存在dist目录④ 压缩src/index.css文件并保存到dist/index.min.css⑤ 开启监听,如果src目录下面的js文件或css文件被更改则重新构建 关于监听插件grunt-contrib-watch的更多用法建议查看使用文档
在前端工程化系列[02]-Grunt构建工具的基本使用这篇文章中,已经对Grunt做了简单的介绍,此外,我们还知道了该如何来安装Grunt环境,以及使用一些常见的插件了,这篇文章主要介绍Grunt的核心组件和运转机制。 Grunt是一套前端自动化构建工具,可以帮助我们简化开发中需要反复处理的任务,甚至可以实现自动构建等功能。 Grunt拥有数量庞大的插件,这些插件能够帮助我们处理开发中遇到的绝大多数构建任务,比如代码的预编译、压缩、代码检查、单元测试等。但为什么在终端输入Grunt相关命令,就能够执行对应的任务,Grunt到底是怎么运转的?这些知识对于深入研究Grunt非常重要,下面我们从Grunt运转的组件和运转机制两方面来展开讨论。 2.1 Grunt的核心组件 node和npm Grunt项目基于Node.js,Grunt和相关的插件都通过 npm 安装并管理。 Grunt-cli Grunt命令行用于调用与Gruntfile文件在同一目录中的 Grunt模块,通过-g参数把Grunt命令行安装到全局环境中,这样的话在所有文件目录中都可以调用grunt相关的命令。 在命令行中运行Grunt 相关命令时(比如 $grunt default),内部会根据node提供的require系统查找来当前目录中安装的 Grunt,如果找到那么加载,并把加载的grunt作为参数传递到Gruntfile文件中,然后执行指定的任务。 Task Task就是任务的意思,grunt支持自定义任务,也支持使用现成的插件任务。比如向控制台输出一句问候这可以被认为是一个Task,对所有的js文件进行压缩这也是一个Task,通常任务(Task)都是可配置的。 Grunt本地依赖 安装了grunt命令行不等于就安装了grunt,这只是让我们拥有了在命令行中使用grunt相关命令的能力,对于每个需要使用grunt的工程,仍然需要为其配置grunt本地依赖。 Grunt插件(Plugins) Grunt插件是一系列能够用于不同项目的可配置任务的集合。Grunt插件通常以npm包的形式发布。Grunt官网的插件列表列出了所有可用的Grunt插件,截止当前的插件数量为6,393个,其中带有contrib前缀的插件由Grunt官方开发和维护。 package.json文件 package.json文件用于被npm存储项目的元数据,以便将此项目发布为npm模块。我们可以在此文件中列出项目依赖的Grunt和Grunt插件,保存在devDependencies(开发依赖)配置字段内,我们可以通过$ npm install命令来加载该文件中的所有依赖项。 Gruntfile.js文件 Gruntfile文件是Grunt项目中最核心的文件,该文件同package.json文件一起存放在项目的根目录中,主要用来配置或定义任务(task)并加载Grunt插件。标准的grunt项目中必须拥有package.json和Gruntfile这两个文件。 node_modules文件夹 node_modules文件目录存放着从远程仓库下载的grunt以及所有相关的grunt插件。 2.2 Grunt的运转机制 上面给出了Grunt项目中各主要组件的关系图示,是根据个人的理解绘制的,所以可能并非完全准确,但基本上已经能够说清楚Grunt的运转机制了。 我们在使用Grunt作为项目构建工具的时候,所做的事情大概可以分成三块:准备、配置、执行。 ① 准备阶段 准备阶段主要进行以下操作 node环境的安装、npm的安装(在安装node的时候默认安装) grunt-cli命令行的安装(通过$ npm install -g grunt-cli命令) 创建package.json文件(手动创建或通过$ npm init命令交互式创建) 配置grunt本地依赖(通过$ npm install grunt --save-dev下载grunt到项目) 安装需要的grunt插件(通过$ npm install grunt-contrib-xx --save-dev命令把需要的插件下载到node_modules目录) ② 配置阶段 配置阶段主要就是创建和编辑Gruntfile文件,在该文件中接收grunt参数并配置Task,注册Task。Task简单说就是任务的意思,我们可以自定义任务,也可以直接使用现成的、一些其他优秀开发者定义好并打包为node模块发布的任务(其实就是grunt插件)。 一般来说,我们总是通过grunt为我们提供的grunt.initConfig方法来对Task(插件)进行配置,如果是该Task是Grunt插件那么还需要先从node_modules目录中加载。 如果对多个Task的执行有指定的顺序或者依赖关系,那么我们可以通过grunt.registerTask方法来注册Task。 ③ 执行阶段 在执行阶段,通过在命令行中输入$ grunt task名称的方式来执行指定的任务。 执行Task的时候,可以单个执行,例如: $ grunt taskName1$ grunt taskName2 也可以用单条命令执行多个Task,每个Task都将按照参数的传入顺序依次执行,例如: $ grunt taskName1 taskName2 在使用构建工具的时候,这些Task具体怎么执行,执行的顺序等并非是固定不变的,需要结合特定的需求来特殊处理。如果总是有一组Task需要按顺序执行,一般可以使用grunt.registerTask方法来给这组Task设置个别名,这一组的Task以数组的形式传递。 例如:要依次执行js文件的合并、语法检查、代码压缩、css代码压缩等任务,则配置好相关Task后可以像下面这样来设置。 grunt.registerTask("customTask",["concat","jshint","uglify","cssmin"]); 要执行这组任务的时候,直接执行$ grunt customTask命令即可。
本文主要介绍前端开发中常用的构建工具Grunt,具体包括Grunt的基本情况、安装、使用和常见插件的安装、配置和使用等内容。 1.1 Grunt简单介绍 Grunt是一套前端自动化构建工具。对于需要反复重复的任务(如压缩、编译、单元测试等),自动化构建工具可以减轻并简化我们的工作。我们只需要在 Gruntfile 文件中正确配置好要处理的任务,任务运行器就会自动帮我们完成大部分工作。 Grunt的优点 Grunt拥有庞大的生态系统,并且一直在增长。 Grunt支持我们自己创作插件并发布。 由于Grunt拥有数量庞大的插件,所以几乎任何的任务都可以利用Grunt来自动完成,你也可以根据自己项目的特点来创作合适的插件发布。 Grunt的工作方式 Grunt为开发者提供了一个工具包,用于创建命令行程序来执行项目构建过程中的重复性任务,比如压缩js代码、编译Sass样式等。Grunt不仅仅能创建简单任务以解决特定工程遇到的特定需求,还能将任务打包为可复用的插件。这些插件可以被发布、分享,使用以及被其他人进行改进。 Grunt的运转依赖于四个核心的组件:分别是Gruntfile、Tasks 、Plugins以及任务配置。 ① Gruntfile Gruntfile指的是在项目根目录下面名为Gruntfile.js的Node模块。该文件使得我们可以加载Grunt插件,创建自定义任务,并根据项目需求对它们进行配置。 Grunt每次运行时的首要任务都是接受该模块发出的指令。 ② Tasks Tasks作为Grunt的基本构建模块,它实际上是由Grunt的registerTask()方法注册的具名函数。 ③ Plugins Plugins是一系列能够用于不同项目的可配置任务的集合。 ④ 任务配置 Grunt强调配置优先,任务和插件的功能都可以通过配置文件进行定制,以适应不同工程的需求。这种代码和配置相分离的特性,使开发者能够创造出高复用的插件。 相关参考 现在最新版本 v1.0.2其它构建工具 gulp、webpack、fis3等 Grunt官网Grunt官网(中文)Grunt相关的插件列表 1.2 Grunt的安装 Grunt和相关的插件都通过 npm 安装并管理。 Grunt基于Node.js,安装之前要先安装Node.js。 Node.js的安装 ① 打开Node.js官网找到Download选项,选择对应的版本下载。② 下载之后,根据对应的提示进行安装即可。③ 安装完成之后,可以通过$ node --version和$ npm --version命令查看是否安装成功。 wendingding:~ wendingding$ node --version v8.9.3 wendingding:~ wendingding$ npm --version 5.5.1 wendingding:~ wendingding$ ️Grunt依赖于nodejs的v0.8.0及以上版本; 安装注意点 ️奇数版本号的 Node.js 被认为是不稳定的开发版;️ 需确保当前环境中所安装的 npm 已经是最新版本($ npm update -g npm) 安装Grunt命令行 注意 在使用Grunt之前,需要先安装Grunt命令行到全局环境中。 安装命令:$ npm install -g grunt-cli 安装完之后,可以通过$ grunt命令来验证Grunt命令行是否安装完成并生效,命令行中的-g表示全局安装。 具体的执行情况 wendingding:~ wendingding$ npm install -g grunt-cli /usr/local/bin/grunt -> /usr/local/lib/node_modules/grunt-cli/bin/grunt + grunt-cli@1.2.0 added 16 packages in 9.289s wendingding:~ wendingding$ grunt grunt-cli: The grunt command line interface (v1.2.0) Fatal error: Unable to find local grunt. If you're seeing this message, grunt hasn't been installed locally to your project. For more information about installing and configuring grunt, please see the Getting Started guide: http://gruntjs.com/getting-started Grunt命令行的作用 Grunt命令行用于调用与Gruntfile在同一目录中 Grunt。每次运行Grunt 时,都会根据node提供的require()系统查找本地安装的 Grunt(因此我们可以在项目的任意子目录中运行grunt) ,如果找到一份本地安装的 Grunt,命令行就将其加载,并传递Gruntfile中的配置信息,然后执行指定的任务。 1.3 Grunt的安装和使用 1.3.1 Grunt使用的基本步骤 Grunt使用的基本步骤 ① 生成package.json和Gruntfile.js文件② 命令行安装项目中需要用到的插件③ 编辑Gruntfile文件定义Task并进行配置④ 命令行以grunt task的方式执行任务 1.3.2 Grunt的安装 接下来,我们通过一个完整的Grunt案例来介绍Grunt的常规使用方法。首先创建的对应的项目文件目录,这里命名为Grunt_demo文件夹,然后创建package.json文件和Gruntfile.js文件并进行相关配置,安装相应的插件并执行Task。 ① 创建package.json文件 创建package.json文件有两种方式,一种是直接创建然后以json格式的字段来进行配置,第二种是通过执行npm install来创建,推荐通过命令行的方式来创建。 直接创建package.json文件 wendingding:~ wendingding$ mkdir Grunt_Demo wendingding:~ wendingding$ cd Grunt_Demo/ wendingding:Grunt_Demo wendingding$ PWD /Users/文顶顶/Grunt_Demo wendingding:Grunt_Demo wendingding$ touch package.json wendingding:Grunt_Demo wendingding$ open package.json $ mkdir Grunt_Demo 表示创建文件夹命令行说明 $ cd Grunt_Demo/ 表示进入文件目录 $ PWD 表示查看当前路径 $ touch package.json 表示创建package.json文件 $ open package.json 表示使用记事本打开文件并编辑 wendingding:Grunt_Demo wendingding$ open package.json wendingding:Grunt_Demo wendingding$ cat package.json { "name":"Grunt_Demo", "version":"1.0.0", "dependencies":{} } 创建好package.json文件后,可以根据需要添加内容字段到文件中。该json文件中最基本字段主要有name、version和dependencies,其中name和version对应的是Grunt项目的名称和版本,而dependencies字段中则列出该项目的依赖。$ cat package.json 表示查看文件内容 package.json文件用于被npm存储项目的元数据,以便将此项目发布为npm模块。我们可以在此文件中列出项目依赖的Grunt和Grunt插件,保存在devDependencies(开发依赖)配置段内。 初始化命令创建package.json文件 除手动创建外,我们还能够通过命令行来进行初始化操作,会以交互的方式来生成一个包含基本配置信息的package.json文件。 初始化命令:$ npm init 下面列出具体的命令行执行情况 wendingding:Grunt_Demo wendingding$ npm init This utility will walk you through creating a package.json file. It only covers the most common items, and tries to guess sensible defaults. See `npm help json` for definitive documentation on these fields and exactly what they do. Use `npm install <pkg>` afterwards to install a package and save it as a dependency in the package.json file. Press ^C at any time to quit. package name: (grunt_demo) version: (1.0.0) description: entry point: (index.js) test command: git repository: keywords: author: license: (ISC) About to write to /Users/文顶顶/Grunt_Demo/package.json: { "name": "grunt_demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } Is this ok? (yes) yes wendingding:Grunt_Demo wendingding$ cat package.json { "name": "grunt_demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } package.json文件注意点在执行npm init命令创建基本package.json文件的时候,可以设置名称、版本、依赖等选项,如果不设置直接回车表示以默认(建议)的方式来进行配置。 package.json应当放置于项目的根目录中,并同项目源代码一起管理。 如果在根目录中运行npm install命令,那么将依据package.json列出的依赖项来自动安装适当版本的依赖。 ② 创建Gruntfile文件 Gruntfile文件是Grunt项目中最核心的文件,可以被命名为 Gruntfile.js 或者是Gruntfile.coffee,该文件同package.json文件一起存放在项目的根目录中,主要用来配置或定义任务(task)并加载Grunt插件。 标准的grunt项目中必须拥有package.json和Gruntfile这两个文件。 wendingding:Grunt_Demo wendingding$ touch Gruntfile.js wendingding:Grunt_Demo wendingding$ tree -L 2 . ├── Gruntfile.js └── package.json 0 directories, 2 files ③ 安装Grunt $ tree -L 2 表示以树状图的方式列出当前目录下面的二级文件结构,具体使用可以参考网络编程系列 Mac系统中Tree的使用 在创建Grunt项目的过程中,我们可以通过$ npm install <module> --save-dev模式的命令来安装Grunt和Grunt插件。该命令在安装的同时,会自动将其添加到package.json文件的devDependencies 配置段中。 接下来我们演示安装Grunt最新版本到项目目录中,并将其添加到devDependencies内。 命令行:$ npm install grunt --save-dev wendingding:Grunt_Demo wendingding$ npm install grunt --save-dev npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN grunt_demo@1.0.0 No description npm WARN grunt_demo@1.0.0 No repository field. + grunt@1.0.2 added 94 packages in 33.833s 命令行执行完毕之后,会发现package.json的配置段中信息发生了变更,在devDependencies配置项中增加了grunt字段和对应的版本信息。 wendingding:Grunt_Demo wendingding$ cat package.json { "name": "grunt_demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { "grunt": "^1.0.2" } } 项目的根目录中增加了node_modules文件中,该目录列出了必要的依赖文件,下面给出文件结构。 1 wendingding:Grunt_Demo wendingding$ tree -L 2 2 . 3 ├── Gruntfile.js 4 ├── node_modules 5 │ ├── abbrev 6 │ ├── ansi-regex 7 │ ├── ansi-styles 8 │ ├── argparse 9 │ ├── array-find-index 10 │ ├── async 11 │ ├── balanced-match 12 │ ├── brace-expansion 13 │ ├── builtin-modules 14 │ ├── camelcase 15 │ ├── camelcase-keys 16 │ ├── chalk 17 │ ├── coffeescript 18 │ ├── colors 19 │ ├── concat-map 20 │ ├── currently-unhandled 21 │ ├── dateformat 22 │ ├── decamelize 23 │ ├── error-ex 24 │ ├── escape-string-regexp 25 │ ├── esprima 26 │ ├── eventemitter2 27 │ ├── exit 28 │ ├── find-up 29 │ ├── findup-sync 30 │ ├── fs.realpath 31 │ ├── get-stdin 32 │ ├── getobject 33 │ ├── glob 34 │ ├── graceful-fs 35 │ ├── grunt 36 │ ├── grunt-known-options 37 │ ├── grunt-legacy-log 38 │ ├── grunt-legacy-log-utils 39 │ ├── grunt-legacy-util 40 │ ├── has-ansi 41 │ ├── hooker 42 │ ├── hosted-git-info 43 │ ├── iconv-lite 44 │ ├── indent-string 45 │ ├── inflight 46 │ ├── inherits 47 │ ├── is-arrayish 48 │ ├── is-builtin-module 49 │ ├── is-finite 50 │ ├── is-utf8 51 │ ├── isexe 52 │ ├── js-yaml 53 │ ├── load-json-file 54 │ ├── lodash 55 │ ├── loud-rejection 56 │ ├── map-obj 57 │ ├── meow 58 │ ├── minimatch 59 │ ├── minimist 60 │ ├── nopt 61 │ ├── normalize-package-data 62 │ ├── number-is-nan 63 │ ├── object-assign 64 │ ├── once 65 │ ├── parse-json 66 │ ├── path-exists 67 │ ├── path-is-absolute 68 │ ├── path-type 69 │ ├── pify 70 │ ├── pinkie 71 │ ├── pinkie-promise 72 │ ├── read-pkg 73 │ ├── read-pkg-up 74 │ ├── redent 75 │ ├── repeating 76 │ ├── resolve 77 │ ├── rimraf 78 │ ├── safer-buffer 79 │ ├── semver 80 │ ├── signal-exit 81 │ ├── spdx-correct 82 │ ├── spdx-exceptions 83 │ ├── spdx-expression-parse 84 │ ├── spdx-license-ids 85 │ ├── sprintf-js 86 │ ├── strip-ansi 87 │ ├── strip-bom 88 │ ├── strip-indent 89 │ ├── supports-color 90 │ ├── trim-newlines 91 │ ├── underscore.string 92 │ ├── validate-npm-package-license 93 │ ├── which 94 │ └── wrappy 95 ├── package-lock.json 96 └── package.json 97 98 91 directories, 3 files 1.3.3 Grunt插件的安装和使用 至此,Grunt项目的基本配置以及Grunt的安装已经完成,在开发中使用Grunt主要是用Grunt相关的一些插件来实现特定的功能。Grunt的生态中提供了非常丰富的插件,我们可以直接在官方搜索查看,接下来给大家介绍几个在前端项目构建中常用到的插件。 文件合并插件concat的安装和使用 concat插件的地址:https://github.com/gruntjs/grunt-contrib-concat concat插件安装命令:$ npm install grunt-contrib-concat --save-dev --save-dev参数表示插件安装完成后,记录相关信息到package.json文件中的devDependencies配置项。 下面列出具体的执行情况 1 wendingding:Grunt_Demo wendingding$ npm install grunt-contrib-concat --save-dev 2 npm WARN grunt_demo@1.0.0 No description 3 npm WARN grunt_demo@1.0.0 No repository field. 4 5 + grunt-contrib-concat@1.0.1 6 added 2 packages in 3.165s 7 wendingding:Grunt_Demo wendingding$ cat package.json 8 { 9 "name": "grunt_demo", 10 "version": "1.0.0", 11 "description": "", 12 "main": "index.js", 13 "scripts": { 14 "test": "echo \"Error: no test specified\" && exit 1" 15 }, 16 "author": "", 17 "license": "ISC", 18 "devDependencies": { 19 "grunt": "^1.0.2", 20 "grunt-contrib-concat": "^1.0.1" 21 } 22 } 插件安装完成后,在项目的node_modules文件目录会新增加grunt-contrib-concat模块。接下来我们通过编辑Gruntfile文件来定义和配置Task。 在项目的根目录中创建src文件夹,在该文件夹下面创建两个示例的js文件,分别为demo_one.js和demo_two.js demo_one.js文件的内容 1 //声明demoOne函数并执行 2 function demoOne() { 3 console.log("demoOne.js文件中的内容"); 4 } 5 demoOne(); demo_two.js文件的内容 1 //声明demoTwo函数并执行 2 function demoTwo() { 3 console.log("demoTwo.js文件中的内容"); 4 } 5 demoTwo(); 编辑Gruntfile文件定义和配置Task 接下来我们需要编辑Gruntfile文件,在该文件中告诉grunt具体的任务(Task)是什么,以及这些任务(Task)应该如何执行,下面给出示例代码 1 //包装函数,规定所有的代码都需要写在该函数内部 2 module.exports = function (grunt) { 3 4 //项目配置信息 5 grunt.initConfig({ 6 //表示从package文件中加载json数据,并保存到pkg属性中 7 pkg:grunt.file.readJSON("package.json"), 8 //concat任务的配置信息 9 "concat":{ 10 dist: { 11 //把src目录下面的demo_one和demo_two文件合并成demo.js文件保存到dist目录 12 src: ['src/demo_one.js', 'src/demo_two.js'], 13 dest: 'dist/demo.js', 14 } 15 } 16 17 }) 18 19 //加载包含concat任务的插件 20 grunt.loadNpmTasks("grunt-contrib-concat"); 21 22 //设置默认执行的任务列表 23 grunt.registerTask("default",["concat"]); 24 }; 上面的示例代码主要由三部分组成:配置任务相关代码 + 加载插件相关代码 + 注册任务相关代码,所有的代码都需要写在module.exports这个包装函数内部,grunt作为包装函数的参数传递。 代码说明 这里代码中的pkg部分并非必要,loadNpmTasks方法用于从node_modules中加载对应的插件,registerTask方法表示把concat这个任务加入到默认的任务队列中(该行代码并非必需),如果不写该行代码则可以直接以$ grunt concat的方式执行合并任务。当然也可以通过registerTask方法来给Task注册个别名,然后通过$ grunt 别名指令来执行该Task。 当前的目录结构如下(注:省略node_modules目录细节) wendingding:Grunt_Demo wendingding$ tree -L 1 . ├── Gruntfile.js ├── node_modules ├── package-lock.json ├── package.json └── src ├── demo_one.js └── demo_two.js 不同插件的使用方式可能也不尽相同,插件的具体用法请参考对应的文档说明。通过编辑Gruntfile文件指定任务的配置项、加载插件并注册任务后,就可以通过命令行来执行Task了。 执行Task 执行Task的命令行:$ grunt 或者是$ grunt default 或者是$ grunt concat命令行输出结果 wendingding:Grunt_Demo wendingding$ grunt default Running "concat:dist" (concat) task Done. Task执行结束后,src目录下面的demo_one.js和demo_two.js两个文件会被合并成demo.js文件并保存到dist目录下,如果指定的目录不存在那么将会直接创建。 压缩插件uglify和cssmin的安装和使用 创建新的文件目录Grunt_Test来演示javaScript的压缩插件uglify以及CSS的压缩插件cssmin的使用,创建好文件目录之后,同样通过$ npm init初始化命令来生成基础的package.json文件。 先安装grunt到本地的项目中,具体命令如下: $ npm install grunt --save-dev 然后下载需要用到的对应插件到本地的项目中,具体命令如下 : $ npm install grunt-contrib-uglify --save-dev 表示安装uglify插件 $ npm install grunt-contrib-cssmin --save-dev 表示cssmin插件 上面的命令行执行完毕后,grunt就会把两个压缩插件下载到node_modules文件目录下,可以通过在该目录下查找grunt-contrib-uglify和grunt-contrib-cssmi文件进行验证。 --save--dev参数会把下载记录更新到package.json文件中的devDependencies字段。 "devDependencies": { "grunt": "^1.0.2", "grunt-contrib-cssmin": "^2.2.1", "grunt-contrib-uglify": "^3.3.0" } 为了演示压缩插件的具体使用,下面我们在项目根目录下创建index.js文件,并新建style文件夹,并在该目录下创建index.css文件,具体的目录结构如下: . ├── node_modules │ ├── ...(省略) │ ├── grunt-contrib-cssmin │ ├── grunt-contrib-uglify ├── package-lock.json ├── package.json └── src ├── index.js └── style └── index.css index.js文件内容为 1 /** 2 * Created by wendingding on 18/5/19. 3 */ 4 5 var a = 123; 6 var b = "文顶顶"; 7 function sum(a,b) { 8 return a + b; 9 } 10 11 (function (c) { 12 console.log("______" + c); 13 })(window); index.css文件内容为 body{ background: red; } *{ margin: 0; padding: 0; list-style: none; } 接下来我们创建并编辑Gruntfile文件,通过特定的代码定义和配置Task。 1 //包装函数 2 module.exports = function (grunt) { 3 4 var app = { 5 src:"src/", 6 dist:"dist/" 7 }; 8 9 //(1) 项目配置信息 10 //说明:initConfig方法等价于grunt.config.init()方法; 11 grunt.initConfig({ 12 //定义js文件压缩Task: 表示把src目录下面的index.js文件压缩到dist目录中的index.min.js 13 "uglify":{ 14 target:{ 15 src:app.src + "index.js", 16 dest:app.dist + "index.min.js" 17 } 18 }, 19 //定义css文件压缩Task: 表示把src/style目录中的index.css文件压缩到dist目录中的index.min.css 20 "cssmin":{ 21 target:{ 22 src:app.src + "style/index.css", 23 dest:app.dist + "index.min.css" 24 } 25 } 26 }); 27 28 //(2) 加载对应的插件 29 grunt.loadNpmTasks("grunt-contrib-uglify"); 30 grunt.loadNpmTasks("grunt-contrib-cssmin"); 31 32 //(3) 注册任务 33 //002 注册任务的第一种方式 34 //① 这种方式可以不写任何注册任务相关的代码 35 //② 我们可以通过$ grunt uglify和$ grunt cssmin命令来分别执行这两个Task 36 //③ 支持以$ grunt uglify cssmin的方式来依次执行多个Task 37 38 //002 注册任务的第二种方式 39 //① 这种方式相当于给每个任务都起一个Task名称,通过$ grunt task名称的方式执行 40 //② 执行命令 $ grunt uglifyTask 表示执行js文件的压缩操作 41 //③ 执行命令 $ grunt cssminTask 表示执行css文件的压缩操作 42 //④ 执行命令 $ grunt cssminTask uglifyTask 表示先执行css文件的压缩,再执行js文件的压缩 43 //grunt.registerTask("uglifyTask","uglify"); 44 //grunt.registerTask("cssminTask","cssmin"); 45 46 //003 注册任务的第三种方式 47 // ① 这种方式把多个任务添加到default任务队列中,执行$ grunt default的时候,所有的Task依次执行 48 // ② 执行命令为 $ grunt default 或者是$ grunt 因为default可以被省略 49 // grunt.registerTask("default",["uglify","cssmin"]); 50 }; 根据任务注册的不同方式来执行Task,下面分别给出三种方式的执行命令 方式(1)先执行$ grunt cssmin再执行 $ grunt uglify,或者通过$ grunt cssmin uglify命令来依次执行多个任务。 方式(2)先执行$ grunt cssminTask再执行 $ grunt uglifyTask,或者通过$ grunt cssminTask uglifyTask命令来依次执行多个任务。 方式(3)通过$ grunt或者是$ grunt default命令来依次执行多个任务。 wendingding:Grunt_Test wendingding$ grunt default Running "uglify:target" (uglify) task >> 1 file created 174 B → 93 B Running "cssmin:target" (cssmin) task >> 1 file created. 89 B → 57 B Done. 当两个任务执行完毕后,项目中会创建dist目录,该目录中新增两个文件分别对应压缩版的js文件和压缩版的css文件,新的目录结构如下。 . ├── node_modules │ ├── ...(省略) │ ├── grunt-contrib-cssmin │ ├── grunt-contrib-uglify ├── package-lock.json ├── package.json ├── dist │ ├── index.min.js │ └── index.min.css └── src ├── index.js └── style └── index.css 上文列出了代码合并插件concat和压缩插件uglify|uglify的安装和基本使用过程,grunt生态系统拥有数量庞大的高质量插件群体,无法一一介绍,可以到Grunt相关的插件列表页面-中文或Grunt相关的插件列表页面-官网自行查看。 Grunt插件使用总结 创建package.json文件(简单配置)和Gruntfile文件($ npm init) 通过命令行把Grunt下载和安装到本地项目中($ npm install grunt --save-dev) 通过命令行把Grunt插件下载和安装到本地项目中($ npm install grunt-contrib-xxx) 在Gruntfile文件中对Grunt插件的Task进行配置(grunt.initConfig) 在Gruntfile文件中通过代码来加载对应的插件(grunt.loadNpmTasks) 在Gruntfile文件中通过代码来注册任务(grunt.registerTask) 在命令行中通过grunt + 任务名的方式来执行Task或加入到default队列以grunt命令执行。 获取更多、更专业的IT技能,请猛戳~小码哥教育︎ Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·Coder_文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
本文主要介绍前端开发中常用的包管理工具Bower,具体包括Bower的基本情况、安装、使用和常见命令等内容,最后还介绍了依赖树管理的常见方式以及Bower采用的策略并进行了比较。 1.1 关于Bower Bower是一款优秀的包管理器,它由Twitter公司开发,支持以命令行的方式来对包进行搜索、下载、更新和卸载。 模块或组件指独立完整的模块,可以是应用的一部分或者是扩展,依赖可以是jQuery或backbone这样的库,也可以像Bootstrap这样的UI框架或者是UI组件。 包英文(package)模块或组件的另一种叫法。 依赖一个模块为了满足独立完整原则所必须的其他模块,依赖提供了这个模块所需要的功能,如果没有这个功能,那么这个组件就无法工作。例如我们认为jQuery-ui这个组件依赖于jQuery。 Bower的优点 专为前端开发设计,几乎所有的主流前端库都可以使用该工具。 约束松散,使用简单。 依赖树扁平更适合Web应用开发。 官网参考:https://bower.io/ 1.2 Bower的安装 在安装bower之前,必须确认你已经安装了Node.js和Git。 安装Bower 使用npm来安装Bower,-g表示全局安装 $ npm install -g bower 查看Bower版本 Bower安装完成后就能在命令行中直接使用bower命令了,可以通过下面的命令行来查看当前的版本。 $ bower -v 或者是$ bower -version 查看帮助信息 使用help命令来查看帮助信息。 $ bower -help Usage: bower <command> [<args>] [<options>] Commands: cache Manage bower cache(管理缓存信息) help Display help information about Bower(显示关于Bower的帮助信息) home Opens a package homepage into your favorite browser info Info of a particular package(显示特定包的详细信息) init Interactively create a bower.json file(交互式创建bower.json文件) install Install a package locally(安装包到本地) link Symlink a package folder(在本地bower库建立一个项目链接) list List local packages - and possible updates(列出本地包以及可能的更新) login Authenticate with GitHub and store credentials(Github身份认证) lookup Look up a single package URL by name(根据包名查询包的URL) prune Removes local extraneous packages(删除项目没有用到的包) register Register a package(注册一个包) search Search for packages by name(通过名称来搜索包) update Update a local package(更新项目的包) uninstall Remove a local package(移除项目的包) unregister Remove a package from the registry(注销包) version Bump a package version(列出版本信息) Options:... 1.3 Bower的使用 初始化操作 在桌面创建新的文件夹,用来演示Bower的使用。先使用命令行进入到文件夹路径,然后使用下面的命令来对Bower进行初始化操作。 $ bower init 根据提示来交互式的设置基本项,初始化操作完成之后,会在文件夹的根目录中创建一个bower.json文件,里面包含一些基本信息。 { "name": "bowerDemo", "authors": [ "wendingding" ], "description": "Nothing", "main": "", "license": "MIT", "homepage": "wendingding.com", "private": true, "ignore": [ "**/.*", "node_modules", "bower_components", "test", "tests" ] } 安装指定的包 尝试执行下面的命令行,来把jQuery框架安装到当前项目中。 $ bower install --save jquery 命令行中的save参数会把包记录保存到bower.json文件中,install命令会把jQuery框架下载到bower_components目录中,该目录文件是保存所有组件和依赖的地方。当执行install命令的时候,Bower会从仓库中获取到jQuery组件的信息然后和bower.json文件中的信息进行比较,如果jQuery没有安装,那么就会默认安装最新版本,如果已经安装了,则bower会自动停止并提示。 具体的执行情况如下: 1 bogon:Demo wendingding$ bower install --save jquery 2 bower invalid-meta for:/Users/文顶顶/Desktop/Demo/bower.json 3 bower invalid-meta The "name" is recommended to be lowercase, can contain digits, dots 4 bower cached https://github.com/jquery/jquery-dist.git#3.3.1 5 bower validate 3.3.1 against https://github.com/jquery/jquery-dist.git#* 6 bower install jquery#3.3.1 7 8 jquery#3.3.1 bower_components/jquery 命令执行完毕后,Bower会在根目录下生成bower_components文件夹,并把下载好的包(jQuery框架)放在这个文件夹里面。此时目录结构如下: . ├── bower.json └── bower_components └── jquery ├── AUTHORS.txt ├── LICENSE.txt ├── README.md ├── bower.json ├── dist ├── external └── src 查看bower.json文件会发现,此时更新了包和对应的版本信息 "dependencies": { "jquery": "^3.3.1" } 1.4 Bower的常见命令Bower在执行安装操作的时候,主要做了如下操作: ① 检查项目目录中的bower.json文件,以确定包是否已经安装了。② 如果指定的包没有安装,那么Bower就会检查Bower仓库中是否存在名称对应的包③ 如果Bower仓库中存在指定的包,那么就下载最新版本到本地④ 把下载好的包的文件添加到项目目录中,并且更新bower.json文件(包名和版本) 安装指定包 1 $ bower install # 通过 bower.json 文件安装 2 $ bower install jquery # 通过在github上注册的包名安装 3 $ bower install desandro/masonry # GitHub短链接 4 $ bower install http://example.com/x.js # URL路径 5 $ bower install git://github.com/user/package.git #Github上的 .git 想要下载安装的包可以是GitHub上的短链接、.git 、一个URL路径等。 搜索指定的包 $ bower search jquery 如果我们在使用框架或者是框架插件的时候,记不住或者是不确定包的名字,则可以尝试先通过关键字搜索,bower会列出包含关键字的所有可用包。 安装包的指定版本 $ bower install --save jquery#1.8.0 Bower通过#号来确定需要下载的版本,如果没有指定版本,则Bower自动帮我们下载最新的。所以,如果需要下载特定版本的包,可以在安装命令中使用#号来声明。 如果要安装指定的版本,也可以先在bower.json文件中对dependencies选项进行配置,然后执行$ bower install或者是$ bower update命令。 查看已安装的包 $ bower list$ bower list --paths 具体的执行情况如下 1 wendingding$ bower list 2 bower check-new Checking for new versions of the project dependencies... 3 demo /Users/文顶顶/Desktop/Test 4 ├── jquery#3.3.1 5 └─┬ jquery-ui#1.12.1 6 └── jquery#3.3.1 7 wendingding$ bower list --paths 8 jquery: bower_components/jquery/dist/jquery.js 9 jquery-ui': bower_components/jquery-ui/jquery-ui.js 卸载指定的包 list命令可以查看项目中当前下载过的包,并提供最新版本号。paths命令可以查看当前下载过的所有包在项目中的对应路径,在其它工具中需要声明/配置前端依赖包地址的时候该命令可能会比较有用。 $ bower uninstall jquery 卸载本地项目中已经安装的jQuery框架。 更新指定的包 $ bower update jquery 更新包的过程和安装包的过程差不多,区别在于更新的时候会使用新文件替换旧文件,上面的命令强制安装最新版本的jQuery框架。 查看指定包的详细信息 $ bower info jquery 通过info指令可以查看指定包的详情,还指令会列出对应的git仓库地址,以及所有可用的版本。 1.5 Bower安装有依赖的包 通常当我们使用Bower命令(bower install --save xxx)来安装指定包的时候,Bower会查找包对应的git仓库地址然后下载到本地并在bower.json文件中记录版本等信息。但有的包在使用的时候可能存在依赖关系,比如ember这个包需要依赖于jQuery框架。 使用Bower安装有依赖包的两种方式 先安装该包的依赖项(这里为jQuery),再安装指定的包(这里为ember) 直接安装(这里为ember) 方式 ① 先安装依赖,再安装指定包 $ bower install --save jquery$ bower install --save ember$ bower list Test /Users/文顶顶/Desktop/Test ├─┬ ember#2.18.2 (latest is 3.0.0-beta.2) │ └── jquery#3.3.1 └── jquery#3.3.1 直接安装指定包,会自动安装依赖(推荐) 方式 ② $ bower install --save ember bower invalid-meta for:/Users/文顶顶i/Desktop/Test/bower.json bower cached https://github.com/components/ember.git#2.18.2 bower validate 2.18.2 against https://github.com/components/ember.git#* bower cached https://github.com/jquery/jquery-dist.git#3.3.1 bower validate 3.3.1 against https://github.com/jquery/jquery-dist.git#>=1.7.0<4.0.0 bower install ember#2.18.2 bower install jquery#3.3.1 ember#2.18.2 bower_components/ember └── jquery#3.3.1 jquery#3.3.1 bower_components/jquery` $ bower list列出项目中已经安装的所有包和依赖关系 ...... Test /Users/文顶顶/Desktop/Test └─┬ ember#2.18.2 (latest is 3.0.0-beta.2) └── jquery#3.3.1 该命令行在安装ember包的时候,发现需要依赖于jQuery框架,就会自动下载对应版本的jQuery框架并安装到本地项目中。 1.6 依赖树管理 组件(包)并非总是相互独立的,有些组件(包)在使用的时候需要依赖于另外一些组件(包),就像上文提到的ember需要依赖于jQuery,jQuery-ui需要依赖于jQuery一样,我们尝试使用下面的图示来描述这种关系。 上面的图示揭示了包(组件)与包(组件)之间的依赖关系,非常简单容易理解。在实际中,每个组件可能都有多个依赖,而这些依赖自己可能还有别的依赖,或者多个不同组件都依赖于某个指定的组件,因此在处理的时候可能会非常复杂。为了理清楚这复杂的关系,依赖管理工具会把所有的依赖构成一颗Dependency Tree(依赖树)。 依赖树主要有三种 嵌套依赖树 扁平依赖树 混合依赖树 注意:在构造依赖树的时候,组件(包)必须要有唯一的标识,该标识由组件的名称和版本号构成,也就是说jQuery1.7.3和jQuery3.3.1是两个不同的组件。 嵌套依赖树 基本理念:每个组件各自都有自己的依赖,而不会共用一个依赖。主要问题:项目中会产生同一组件的多个副本,且可能有多个版本的组件共存比较混乱。 扁平依赖树 基本理念:保证每个组件在项目只有一个版本,没有任何其它的副本。主要问题:容易产生冲突,如果两个组件需要同一个依赖的不同版本,就会导致出错,必须要用户自己来解决冲突,决定具体使用哪个依赖并解决潜在的不一致性。 混合依赖树 基本理念:使用最高效的办法来管理一个组件的不同版本。主要特点:混合依赖树是扁平依赖树和嵌套依赖树的折中方案,如果一个组件的依赖已经安装了,而且版本也兼容,那么就不必再次下载安装,只要指向已安装的那个组件即可,如果版本不兼容的话,则下载安装并版本兼容的组件。 1.7 Bower依赖树管理和冲突处理 Bower作为专为前端开发者设计的依赖管理工具,是完全基于扁平依赖树的。上文介绍了扁平依赖树在处理的时候要求保证每个组件在项目只有一个版本,没有任何其它的副本,优缺参半。那既然如此,Bower为什么不使用更高效的混合依赖树? Bower采用扁平依赖树管理的原因 (1)代码体积对于前端开发非常重要,而扁平树管理可以实现组件最(少)小化。(2)所有组件默认都在浏览器的全局作用域中运行,不同版本的同名组件会产生冲突。 因为Bower采用了扁平依赖树的方式来处理,所以在使用的时候容易产生冲突,这种依赖管理方式要求开发者注重组件的版本兼容和依赖关系。接下来,我们简单演示Bower使用过程中会出现冲突的情况。 冲突的产生和处理 ① 安装jQuery框架的1.2.3版本 $ bower install --save jquery#1.2.3 执行情况 1 bower invalid-meta for:/Users/文顶顶/Desktop/Test/bower.json 2 bower invalid-meta The "name" is recommended to be lowercase, can contain... 3 bower not-cached https://github.com/jquery/jquery-dist.git#1.2.3 4 bower resolve https://github.com/jquery/jquery-dist.git#1.2.3 5 bower download https://github.com/jquery/jquery-dist/archive/1.2.3.tar.gz 6 bower extract jquery#1.2.3 archive.tar.gz 7 bower deprecated Package jquery is using the deprecated component.json 8 bower resolved https://github.com/jquery/jquery-dist.git#1.2.3 9 bower install jquery#1.2.3 ② 安装ember组件并解决冲突 $ bower install --save ember 具体的执行情况 1 bogon:Test wendingding$ bower install --save ember 2 bower invalid-meta for:/Users/文顶顶/Desktop/Test/bower.json 3 bower invalid-meta The "name" is recommended to be lowercase, can contain digits, dots, dashes 4 bower cached https://github.com/components/ember.git#2.18.2 5 bower validate 2.18.2 against https://github.com/components/ember.git#* 6 bower cached https://github.com/jquery/jquery-dist.git#3.3.1 7 bower validate 3.3.1 against https://github.com/jquery/jquery-dist.git#>= 1.7.0 < 4.0.0 8 9 Unable to find a suitable version for jquery, please choose one by typing one of the numbers below: 10 1) jquery#1.2.3 which resolved to 1.2.3 and is required by Test 11 2) jquery#>= 1.7.0 < 4.0.0 which resolved to 3.3.1 and is required by ember#2.18.2 12 13 Prefix the choice with ! to persist it to bower.json 14 15 ? Answer 2 16 bower install jquery#3.3.1 17 bower install ember#2.18.2 18 19 jquery#3.3.1 bower_components/jquery 20 21 ember#2.18.2 bower_components/ember 22 └── jquery#3.3.1 说明:我们先把jQuery的1.2.3版本安装到了项目中,然后又通过Bower来安装ember,而ember组件需要依赖于jQuery框架,这里有个关键信息就是ember要求依赖的jQuery框架版本范围为1.70 ~ 4.0.0和本地已经安装的jQuery 1.2.3冲突,Bower并不会自己处理这个问题而是抛出一个异常,把选择权交给用户,由用户来选择使用哪种方案。 通过命令行的打印,我们看到Bower为我们提供了两个可选项,第一个选项是保留本地已经安装的1.2.3版本,第二个选项是保存ember所依赖的版本,这里显示了依赖需要的版本范围jQuery#>=1.70<4.0.0和最终会决定(resolved)的版本(3.3.1)。上面的示例中,冲突产生后我们输入2,选择安装jQuery的3.3.1版本。 ③ 查看项目已安装的组件信息 $ bower list 具体的组件和依赖结构 1 Test /Users/文顶顶/Desktop/Test 2 ├─┬ ember#2.18.2 (latest is 3.0.0-beta.2) 3 │ └── jquery#3.3.1 4 └── jquery#3.3.1 incompatible with 1.2.3 (1.2.3 available, latest is 3.3.1) 冲突处理完后,项目中原本下载安装好的jQuery1.2.3版本被重新下载的3.3.1版本替代。 1.8 Bower自定义组件目录 默认情况下,所有的依赖包都被下载保存到bower_components文件路径,如果想要把依赖包下载到自己指定的目录,使用.bowerrc文件配合bower.json就可以实现。 在项目根目录创建.bowerrc文件,使用json格式来设置文件路径。{ "directory": "指定路径" }保存好以后,执行bower install命令,就会把bower.json配置好的相关组件全部下载到指定的路径中。 命令行参考 1 bogon:Test wendingding$ touch .bowerrc 2 bogon:Test wendingding$ vim .bowerrc 3 bogon:Test wendingding$ cat .bowerrc 4 { 5 "directory": "app/xxx/" 6 } 7 bogon:Test wendingding$ cat bower.json 8 { 9 "name": "Test", 10 "authors": [ 11 "flowerField <18681537032@163.com>" 12 ], 13 "description": "", 14 "main": "", 15 "license": "MIT", 16 "homepage": "", 17 "ignore": [ 18 "**/.*", 19 "node_modules", 20 "bower_components", 21 "test", 22 "tests" 23 ], 24 "dependencies": { 25 "jquery": "^3.3.1" 26 } 27 } 28 bogon:Test wendingding$ bower install 29 bower invalid-meta for:/Users/文顶顶/Desktop/Test/bower.json 30 bower invalid-meta The "name" is recommended to be lowercase, can contain digits... 31 bower cached https://github.com/jquery/jquery-dist.git#3.3.1 32 bower validate 3.3.1 against https://github.com/jquery/jquery-dist.git#^3.3.1 33 bower install jquery#3.3.1 34 35 jquery#3.3.1 app/xxx/jquery 36 bogon:Test wendingding$ tree -L 3 37 . 38 ├── app 39 │ └── xxx 40 │ └── jquery 41 └── bower.json 获取更多、更专业的IT技能,请猛戳~小码哥教育︎ Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·Coder_文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
本文输出和JSON有关的以下内容 JSON和javaScript JSON的语法介绍 JSON的数据类型 JSON和XMLHTTPRequest JSON的序列化和反序列化处理 1.1 JSON和javaScript JSON是一种数据交换格式。 JSON的全称是JavaScript Object Notation,翻译为JavaScript对象表示法。JSON的这个全称,无疑让很多人既兴奋又困惑,兴奋的人直接认为这就是JavaScript中的对象,困惑的人觉察出JSON数据和JavaScript对象好像有些不一样。接下来我们先谈一谈JSON数据和JavaScript的关系。 诚然,从JSON的全称可以看出JSON和JavaScript语言必定有种某种神秘关联,至少能够确定的是JSON的命名确实来源于JavaScript这门语言。 JSON基于JavaScript对象字面量,但JSON本身是一种数据交换格式,因此它是独立于语言的。JSON全称为JavaScript对象表示法,在理解的时候可以认为JSON ==> JavaScript && 对象 && 表示法 JavaScript我们知道是一门动态脚本语言,那么对象表示法是什么? 对象是面向对象编程语言中一种常见的数据类型,表示键值对的集合,那么表示法是什么? 表示法:是指一个可以表示诸如数字或单词等数据的字符系统。 JSON起源于JavaScript(灵感来源于JavaScript的对象语法),但真正重要的是具体的表示法本身。JSON不仅独立于语言,而且使用了一种在许多编程语言中能够找到共同元素的表达方式。基于这种简洁的表达方式,JSON迅速成为一种流行的数据交换格式。目前,客户端和服务器端在进行数据通信的时候,常见的数据格式就是JSON和XML。 1.2 JSON的语法介绍 1.2.1 JSON的语法 JSON因为基于JavaScript的字面量,所以我们先来看下JavaScript字面量的样子,下面给出简单的代码示例,描述了一个书对象。 1 var book = { 2 name:"声名狼藉者的生活", 3 price:42.00, 4 author:"福柯", 5 press:"北京大学出版社", 6 read:function () { 7 console.log("我的书名为:声名狼藉者的的生活,作者为福柯...."); 8 } 9 }; 顺便贴出一个简短的JSON数据 { "name":"声名狼藉者的生活", "price":42.00, "author":"福柯", "press":"北京大学出版社", "content":["a","b","c",123] } 我们可以对比下上面的JavaScript对象和JSON数据,会发现它们的结构和语法形式很像,都是键值对的集合,接下来我们做更详细的说明。JSON数据在表达上和对象保持一致,但因为数据交换格式的核心是数据,所以JSON并不会保存函数等信息。JSON数据所基于的JavaScript对象字面量单纯指对象字面量以及其属性的语法表示。 JSON的主要语法特点 ① 以键值对的方式来保存数据② 标准的JSON数据的key必须要使用双引号包裹③ { } 用于表示和存放对象,[ ] 用于表示和存放数组数据 JSON数据的读取,在读取JSON的时候 { 表示开始读取对象,} 表示对象读取结束 [ 表示开始读取数组,] 表示数组读取结束 :用于分隔键值对中的key和value , 用于分隔对象中的多个键值对或者是数组中的多个元素 JavaScript对象字面量中的key可以使用单引号,可以使用双引号,可以不必加上引号包裹,但是在JSON中,所有的key必须要加上双引号。 1.2.2 JSON的验证和格式化工具 下面列出一些能够对JSON数据进行校验和格式化的在线地址https://jsonlint.com/http://tool.oschina.net/codeformat/jsonhttps://jsonformatter.curiousconcept.com/ 1.2.3 JSON文件和MIME类型 在开发中我们经常需要处理大量的JSON数据,JSON这种数据交换格式可以作为独立的文件存在于文件系统中,文件扩展名为 .json JSON的MIME类型是application/json, 详细信息请参考IANA官网维护的所有媒体类型列表。 1.3 JSON的数据类型 JSON中(作为value值)的数据类型包括对象、字符串、数字、布尔值、null和数组六种。 ① 字符串JSON中的字符串可以由任何的Unicode字符构成,字符串的两边必须被双引号包裹。需要注意的是:虽然在JavaScript语言中字符串可以使用单引号来包裹,但是在JSON中的字符串必须使用双引号包裹。 如果字符串中存在以下特殊字符,那么需要在它们的前面加上一个反斜线(\)来进行转义。 - " 双引号 - \ 反斜线 - \/ 正斜线 - \b 退格符 - \f 换页符 - \t 制表符 - \n 换行符 - \r 回车符 - \u 后面跟16进制字符 ② 数字JSON中的数字可以是整数、小数、负数或者是指数。 ③ 布尔类型JSON数据仅仅支持小写形式的布尔类型值:true 和 false。 ④ null类型JSON中没有undefined这种数据类型,它使用null表示空,并且必须小写。在JavaScript语言中,var obj = null 表示把obj这个对象清空,它和undefined不太一样,null表示什么都没有,undefined表示未定义。 ⑤ 对象类型对象类型是使用逗号分隔的键值对的集合,使用大括号({})裹。 ⑥ 数组类型数组类型是元素的集合,每个元素都可以是字符串、数字、布尔值、对象或者数组中的任何一种。元素与元素之间使用逗号隔开,所有的元素被方括号([])包裹,建议数组中所有的元素都应该是相同数据类型的。 1.4 JSON和XMLHTTPRequest 在前端开发中有一种发送网络请求的技术Ajax,它可以实现异步处理网络通信而不刷新页面。 Ajax的全称为Asynchronous JavaScript and XML,即异步的JavaScript和XML。我们知道JSON的定位是轻量级的数据交互格式,客户端在和服务器端进行网络通信的时候,服务器端返回给我们的数据大多数是JSON或者是XML。也就是说JSON数据在Ajax网络通信中可能扮演重要的角色,那什么Ajax不叫异步的JSON而叫做异步的XML呢? 答案是:因为刚提出这种网络请求技术的时候,XML相比JSON更流行。 在Ajax网络请求中用到的核心对象XMLHTTPRequest也是如此,其实这个对象命名中包含XML也仅仅是因为对于当时而言,XML是网络请求中最常用的数据交换格式。如果放在今天,那么它们的名字应该叫做AjaJ(Asynchronous JavaScript and JSON)和JSONHTTPRequest更合适一些。 1.5 JavaScript中JSON数据的序列化和反序列化处理 在网络请求中,如果服务器返回给我们的数据是JSON数据,那么为了方便对数据的操作,通常我们在网络请求成功拿到JSON数据之后会先对JSON数据进行反序列化操作。在前端开发中,早期的JSON解析基本上由eval函数来完成,ECMAScript5对解析JSON的行为进行了规范,定义了全局对象JSON。目前IE8+、FireFox 3.5+、Opera 10.5、Safari 4+和Chrome等浏览器均支持原生的JSON全局对象。 JSON数据的处理主要涉及到两方面:序列化处理和反序列化处理 1.5.1 使用eavl函数来处理JSON数据 eavl函数说明 JavaScript语言中eavl函数可以把字符串转换为js的代码并且马上执行,使用情况和Function构造函数用法类型。 eval("var a = 123;"); console.log(a + 1); //输出结果为124 因为从某种程度上来讲,json其实是JavaScript语言的严格子集,所以我们可以直接通过eval函数来对json数据进行解析。需要注意的是,使用eavl函数来对json数据结构求值存在风险,因为可能会执行一些恶意代码。 eavl函数解析JSON 服务器返回给前端的json数据可能是{...}形式的,也可能是[...]形式的,分别对应js中的对象和数组。如果是{...}形式的,那么在解析的时候,如果直接以eval(json)的方式处理会报错,因为js中不允许直接写{name:”zs”}类似的语句。遇到这种结构的json数据,通常我们有两种方式进行处理:① 包装成表达式 ② 赋值给变量。 1 2 //001 [...] 格式的json数据 3 var arrJson= '[{"name":"zs","age":18},{"name":"lisi","age":28}]'; 4 var jsonArr = eval(arrJson); 5 6 //002 {...} 格式的json数据 7 var objJson = `{"name":"wendingding","age":18,"contentAbout":["JavaScript","CSS","HTML"],"car":{"number":"粤A6666","color":"red"}}`; 8 9 //eval(json); 错误的演示:报错 10 //处理方式(1):以拼接的方式赋值给变量 11 eval("var jsonObj1 = " + objJson); 12 //处理方式(2):包装成表达式 13 var jsonObj2 = eval("(" + objJson +")"); 14 15 //打印转换后得到的数组|对象 16 console.log(jsonArr); 17 console.log(jsonObj1); 18 console.log(jsonObj2); 1.5.2 使用JSON全局对象来处理JSON数据 JSON全局对象拥有两个方法:stringify()和parse(),其中parse方法用于把json数据反序列化为原生的js,stringify方法用于把js对象序列化为json字符串。 parse方法的使用语法:JSON.parse(jsonString,[fn]) 参数说明 第一个参数:jsonString为要解析的json字符串第二个参数:fn是一个可选参数,该参数为函数类型,接收两个参数,分别是每个键值对的key和value。 1 2 //json字符串 3 var objJson = `{"name":"wendingding","age":18,"contentAbout":["JavaScript","CSS","HTML"],"car":{"number":"粤A6666","color":"red"}}`; 4 5 //把json字符串转换为js数组 6 var arrJson= '[{"name":"zs","age":18},{"name":"lisi","age":28}]'; 7 8 //把json字符串转换为js对象 9 var jsonObj = JSON.parse(objJson); 10 var jsonArr = JSON.parse(arrJson); 11 console.log(jsonObj); 12 console.log(jsonArr); 13 14 //演示parse方法中函数参数的使用 15 function fn(key, value) { 16 if (key === "name") { 17 return value + "++" //在原有value值的基础上拼接++字符串 18 } else if (key === "age") { 19 return undefined //如果返回undefined,则表示删除对应的键值对 20 } else { 21 return value //正常返回对应的value值 22 } 23 } 24 console.log(JSON.parse(objJson, fn)); stringify方法使用说明 语法:JSON.stringify(Obj,[fn|arr],[space]) 参数说明 第一个参数:Obj为要进行序列化操作的JavaScript对象第二个参数:过滤器,可以是函数或者是一个数组第三个参数:是否在生成的json字符串中保留缩进,用于控制缩进的字符 1 //js中的普通对象 2 var obj = { 3 name:"zs", 4 age:18, 5 friends:["小霸王","花仙子","奥特曼"], 6 other:undefined, 7 showName:function () { 8 console.log(this.name); 9 } 10 11 }; 12 13 //把js中的对象转换为json字符串 14 //注意: 15 //001 如果键值对中存在value值为undefined的数据,那么会被跳过 16 //002 对象中的方法以及该对象的原型成员数据在进行转换的时候,会被有意忽略 17 console.log(JSON.stringify(obj)); 18 19 //控制缩进,该参数的值可以是数字也可以是字符串,自动换行 20 //001 如果是字符串那么会把对应的字符拼接在键值对前面,超过10个字符的省略 21 //002 如果是数字那么会设置对应的缩进,最多为10,超过则默认为10 22 console.log(JSON.stringify(obj, null, 4)); 23 console.log(JSON.stringify(obj, null, "@@")); 24 25 //过滤器(数组):表示只处理key为name和age这两个键值对 26 console.log(JSON.stringify(obj, ["name","age"])); 27 28 //过滤器(函数): 29 function fn(key,value) { 30 if (key === "age") 31 { 32 return value + 20; 33 }else if (key === "name") 34 { 35 return undefined; //过滤掉key为name这个键值对 36 }else 37 { 38 return value; 39 } 40 } 41 console.log(JSON.stringify(obj,fn)); JSON数据总结 JSON全称是JavaScript Object Notation基于JavaScript,是JavaScript的子集。 JSON虽然是JavaScript的子集,但并不从属于JavaScript,它独立于语言。 JSON是用来表示和传输数据的格式,比XML更轻量级,现已成为web数据交换的事实标准。 JSON的优势在于其可以方便的把JSON字符串数据转换为对应的对象,比XML更方便且数据更小。 JSON语法可以表示:字符串、数值、布尔值、null、对象和数组6种类型的值,不支持undefined。 JSON中的”键”区别于JavaScript,必须要加上双引号。 JSON解析可以使用传统的eval函数,或ECMAScript5推出的全局对象来处理。 参考资料 JSON官网:http://json.org/JSON维基百科:https://en.wikipedia.org/wiki/JSONJSON作者简介:https://en.wikipedia.org/wiki/Douglas_CrockfordJSON必知必会:https://book.douban.com/subject/26789960/JavaScript高级程序设计:https://book.douban.com/subject/10546125/ 获取更多、更专业的IT技能,请猛戳~小码哥教育︎ Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·Coder_文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
[04]-javaScript的原型链 本文旨在花很少的篇幅讲清楚JavaScript语言中的原型链结构,很多朋友认为JavaScript中的原型链复杂难懂,其实不然,它们就像树上的一串猴子。 1.1 理解原型链 JavaScript中几乎所有的东西都是对象,我们说数组是对象、DOM节点是对象、函数等也是对象,创建对象的Object也是对象(本身是构造函数),那么有一个重要的问题:对象从哪里来? 这是一句废话,对象当然是通过一定方式创建出来的,根据实际类型不同,对象的创建方式也千差万别。比如函数,我们可以声明函数、使用Function构造函数创建等,比如数组,我们可以直接通过var arr = [] 的方式创建空数组,也可以通过new Array的方式创建,比如普通的对象,我们可以字面量创建、使用内置构造函数创建等等,花样太多了,以至于我们学习的时候头昏脑涨、不得要领。 其实,归根结底所有“类型”的对象都可以认为是由相应构造函数创建出来的。 函数由Function构造函数实例化而来,普通对象由Object构造函数实例化而来,数组对象由Array构造函数实例化而来,至于Object | Array | Function等他们本身是函数,当然也有自己的构造函数。 理解了上面一点,那么接下来我们在理解原型链的时候就会容易得多。 请看刺激的推导过程 前提 所有对象都由构造函数实例化而来,构造函数默认拥有与之相关联的原型对象 ① 构造函数的原型对象也是对象,因此也有自己的构造函数 ② 构造函数原型对象的构造函数,也有与之相关连的原型对象 ③ 构造函数原型对象的原型对象(__proto__)也有自己的构造函数,其也拥有关联的原型对象 以上就形成了一种链式的访问结构,是为原型链。 其实构造函数也是对象,所以构造函数本身作为对象而言也有自己的构造函数,而这个构造函数也拥有与之相关联的原型对象,以此类推。那么,这就是另一条原型链了。综上,我们可以得出原型链并不孤单的结论。 1.2 原型链结构 现在我们基本上把原型链的由来说清楚了,那么接下来通过具体的代码来分析原型链的整体结构。 示例代码 1 //01 自定义构造函数Person和Animal 2 function Person() {} 3 function Animal() {} 4 //02 使用构造函数创建实例对象 5 var p1 = new Person(); 6 var p2 = new Person(); 7 var a = new Animal(); 8 //03 创建数组对象 9 var arrM = ["demoA","demoB"]; 上面的代码非常简单,其中p1,p2和a它们是自定义构造函数的实例化对象。其次,我们采用快捷方式创建了arrM数组,arrM其实是内置构造函数Array的实例化对象。另外,Person和Animal这两个构造函数其实是Function构造函数的实例对象。理解以上几点后,我们就可以来看一下这几行代码对应的原型链结构图了。 原型链结构图说明: ① 因为复杂度关系,arrM对象的原型链结构图单独给出。 ② Object.prototype是所有原型链的顶端,终点为null。 验证原型链相关的代码 1 //[1] 验证p1、p2的原型对象为Person.prototype 2 // 验证a 的原型对象为Animal.prototype 3 console.log(p1.__proto__ == Person.prototype); //true 4 console.log(p2.__proto__ == Person.prototype); //true 5 console.log(a.__proto__ == Animal.prototype); //true 6 //[2] 获取Person.prototype|Animal.prototype构造函数 7 // 验证Person.prototype|Animal.prototype原型对象为Object.prototype 8 // 先删除实例成员,通过原型成员访问 9 delete Person.prototype.constructor; 10 delete Animal.prototype.constructor; 11 console.log(Person.prototype.constructor == Object); //true 12 console.log(Animal.prototype.constructor == Object); //true 13 console.log(Person.prototype.__proto__ == Object.prototype); //true 14 console.log(Animal.prototype.__proto__ == Object.prototype); //true 15 //[3] 验证Person和Animal的构造函数为Function 16 // 验证Person和Animal构造函数的原型对象为空函数 17 console.log(Person.constructor == Function); //true 18 console.log(Animal.constructor == Function); //true 19 console.log(Person.__proto__ == Function.prototype); //true 20 console.log(Animal.__proto__ == Function.prototype); //true 21 //[4] 验证Function.prototype的构造函数为Function 22 console.log(Function.prototype.constructor == Function); //true 23 //[5] 验证Function和Object的构造函数为Function 24 console.log(Function.constructor == Function); //true 25 console.log(Object.constructor == Function); //true 26 //[6] 验证Function.prototype的原型对象为Object.prototype而不是它自己 27 console.log(Function.prototype.__proto__ == Object.prototype);//true 28 //[7] 获取原型链的终点 29 console.log(Object.prototype.__proto__); //null 下面贴出数组对象的原型链结构图 验证数组对象原型链结构的代码示例 1 //[1] 验证arrM的构造函数为Array 2 //方法1 3 console.log(arrM.constructor == Array); //true 4 //方法2 5 console.log(Object.prototype.toString.call(arrM)); //[object Array] 6 //[2] 验证Array的构造函数为Function 7 console.log(Array.constructor == Function); //true 8 //[3] 验证Array构造函数的原型对象为Function.prototype(空函数) 9 console.log(Array.__proto__ == Function.prototype); //true 10 //[4] 验证Array.prototype的构造函数为Object,原型对象为Object.prototype 11 delete Array.prototype.constructor; 12 console.log(Array.prototype.constructor == Object); //true 13 console.log(Array.prototype.__proto__ == Object.prototype); //true 1.3 原型链的访问 原型链的访问规则对象在访问属性或方法的时候,先检查自己的实例成员,如果存在那么就直接使用,如果不存在那么找到该对象的原型对象,查找原型对象上面是否有对应的成员,如果有那么就直接使用,如果没有那么就顺着原型链一直向上查找,如果找到则使用,找不到就重复该过程直到原型链的顶端,此时如果访问的是属性就返回undefined,方法则报错。 1 function Person() { 2 this.name = "wendingding"; 3 } 4 Person.prototype = { 5 constructor:Person, 6 name:"自来熟", 7 showName:function () { 8 this.name.lastIndexOf() 9 } 10 }; 11 var p = new Person(); 12 console.log(p.name); //访问的是实例成员上面的name属性:wendingding 13 p.showName(); //打印wendingding 14 console.log(p.age); //该属性原型链中并不存在,返回undefined 15 p.showAge(); //该属性原型链中并不存在,报错 概念和访问原则说明 实例成员:实例对象的属性或者是方法 原型成员:实例对象的原型对象的属性或者是方法 访问原则:就近原则 1.4 getPrototypeOf、isPrototypeOf和instanceof Object.getPrototypeOf方法用于获取指定实例对象的原型对象,用法非常简单,只需要把实例对象作为参数传递,该方法就会把当前实例对象的原型对象返回给我们。说白了,Object的这个静态方法其作用就是返回实例对象__proto__属性指向的原型prototype。 1 //01 声明构造函数F 2 function F() {} 3 //02 使用构造函数F获取实例对象f 4 var f = new F(); 5 //03 测试getPrototypeOf方法的使用 6 console.log(Object.getPrototypeOf(f)); //打印的结果为一个对象,该对象是F相关联的原型对象 7 console.log(Object.getPrototypeOf(f) === F.prototype); //true 8 console.log(Object.getPrototypeOf(f) === f.__proto__); //true isPrototypeOf方法用于检查某对象是否在指定对象的原型链中,如果在,那么返回结果true,否则返回结果false。 1 //01 声明构造函数Person 2 function Person() {} 3 //02 获取实例化对象p 4 var p = new Person(); 5 //03 测试isPrototypeOf的使用 6 console.log(Person.prototype.isPrototypeOf(p)); //true 7 console.log(Object.prototype.isPrototypeOf(p)); //true 8 var arr = [1,2,3]; 9 console.log(Array.prototype.isPrototypeOf(arr)); //true 10 console.log(Object.prototype.isPrototypeOf(arr)); //true 11 console.log(Object.prototype.isPrototypeOf(Person));//true 上述代码的原型链① p–>Person.prototype –>Object.prototype –>null② arr–>Array.prototype –>Object.prototype –>nullObject.prototype因处于所有原型链的顶端,故所有实例对象都继承于Object.prototype instanceof运算符的作用跟isPrototypeOf方法类似,左操作数是待检测的实例对象,右操作数是用于检测的构造函数。如果右操作数指定构造函数的原型对象在左操作数实例对象的原型链上面,则返回结果true,否则返回结果false。 1 //01 声明构造函数Person 2 function Person() {} 3 //02 获取实例化对象p 4 var p = new Person(); 5 //03 测试isPrototypeOf的使用 6 console.log(p instanceof Person); //true 7 console.log(p instanceof Object); //true 8 //04 Object构造函数的原型对象在Function这个实例对象的原型链中 9 console.log(Function instanceof Object); //true 10 //05 Function构造函数的原型对象在Object这个实例对象的原型链中 11 console.log(Object instanceof Function); //true 注意:不要错误的认为instanceof检查的是该实例对象是否从当前构造函数实例化创建的,其实它检查的是实例对象是否从当前指定构造函数的原型对象继承属性。 我们可以通过下面给出的代码示例来进一步理解 1 //01 声明构造函数Person 2 function Person() {} 3 //02 获取实例化对象p 4 var p1 = new Person(); 5 //03 测试isPrototypeOf的使用 6 console.log(p1 instanceof Person); //true 7 //04 替换Person默认的原型对象 8 Person.prototype = { 9 constructor:Person, 10 showInfo:function () { 11 console.log("xxx"); 12 } 13 }; 14 //05 重置了构造函数原型对象之后,因为Person 15 console.log(p1 instanceof Person); //false 16 //06 在Person构造函数重置了原型对象后重新创建实例化对象 17 var p2 = new Person(); 18 console.log(p2 instanceof Person); //true 19 //==> 建议开发中,总是先设置构造函数的原型对象,之后在创建实例化对象 贴出上面代码的原型链结构图(部分) 1.5 原型链相关的继承 继承是面向对象编程的基本特征之一,JavaScript支持面向对象编程,在实现继承的时候,有多种可行方案。接下来,我们分别来认识下原型式继承、原型链继承以及在此基础上演变出来的组合继承。 原型式继承基本写法 1 //01 提供超类型|父类型构造函数 2 function SuperClass() {} 3 //02 设置父类型的原型属性和原型方法 4 SuperClass.prototype.info = 'SuperClass的信息'; 5 SuperClass.prototype.showInfo = function () { 6 console.log(this.info); 7 }; 8 //03 提供子类型 9 function SubClass() {} 10 //04 设置继承(原型对象继承) 11 SubClass.prototype = SuperClass.prototype; 12 SubClass.prototype.constructor = SubClass; 13 var sub = new SubClass(); 14 console.log(sub.info); //SuperClass的信息 15 sub.showInfo(); //SuperClass的信息 贴出原型式继承结构图 提示 该方式可以继承超类型中的原型成员,但是存在和超类型原型对象共享的问题 原型链继承 实现思想 核心:把父类的实例对象设置为子类的原型对象 SubClass.prototype = new SuperClass();问题:无法为父构造函数(SuperClass)传递参数 原型链继承基本写法 1 //01 提供超类型|父类型 2 function SuperClass() { 3 this.name = 'SuperClass的名称'; 4 this.showName = function () { 5 console.log(this.name); 6 } 7 } 8 //02 设置父类型的原型属性和原型方法 9 SuperClass.prototype.info = 'SuperClass的信息'; 10 SuperClass.prototype.showInfo = function () { 11 console.log(this.info); 12 }; 13 //03 提供子类型 14 function SubClass() {} 15 //04 设置继承(原型对象继承) 16 var sup = new SuperClass(); 17 SubClass.prototype = sup; 18 SubClass.prototype.constructor = SubClass; 19 var sub = new SubClass(); 20 console.log(sub.name); //SuperClass的名称 21 console.log(sub.info); //SuperClass的信息 22 sub.showInfo(); //SuperClass的信息 23 sub.showName(); //SuperClass的名称 贴出原型链继承结构图 组合继承 实现思想 ① 使用原型链实现对原型属性和方法的继承② 通过伪造(冒充)构造函数来实现对实例成员的继承,并且解决了父构造函数传参问题 组合继承基本写法 1 //01 提供超类型|父类型 2 function SuperClass(name) { 3 this.name = name; 4 this.showName = function () { 5 console.log(this.name); 6 } 7 } 8 //02 设置父类型的原型属性和原型方法 9 SuperClass.prototype.info = 'SuperClass的信息'; 10 SuperClass.prototype.showInfo = function () { 11 console.log(this.info); 12 }; 13 //03 提供子类型 14 function SubClass(name) { 15 SuperClass.call(this,name); 16 } 17 //(1)获取父构造函数的实例成员 Person.call(this,name); 18 //(2)获取父构造函数的原型成员 SubClass.prototype = SuperClass.prototype; 19 SubClass.prototype = SuperClass.prototype; 20 SubClass.prototype.constructor = SubClass; 21 var sub_one = new SubClass("zhangsan"); 22 var sub_two = new SubClass("lisi"); 23 console.log(sub_one); 24 console.log(sub_two); 最后,贴出实例对象sub_one和sub_two的打印结果 Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
[03]-javaScript原型对象 引用: javaScript是一门基于原型的语言,它允许对象通过原型链引用另一个对象来构建对象中的复杂性,JavaScript使用原型链这种机制来实现动态代理。当试图去引用某一个属性时,它会遍历整个原型链,直到最后的节点。JavaScript专家编程·P24 1.1 原型对象说明 在JavaScript中除了基本数据类型外的其它数据都是对象类型,包括对象、函数、数组等,它们跟原型对象密不可分。 JavaScript语言中有一个非常重要的概念,叫做原型对象,理解原型对象是进一步理解这门语言的基础,因为它是一门基于原型的语言,也因为所有的代码几乎都和原型对象有关,接下来我们先了解下原型对象是什么。 原型对象 在上一篇文章JavaScript系列 [02]-javaScript对象探析中,我们介绍了使用自定义构造函数创建对象的方式,在构造函数被创建出来的时候,系统会默认帮构造函数创建并关联一个Object类型的新对象,我们称该对象就是这个构造函数的原型对象,构造函数的原型对象默认是一个空对象。 1.2 原型对象的性质 构造函数相关联的原型对象的成员(属性和方法),可以被使用该构造函数创建出来的对象访问,即以自定义构造函数方式创建出来的所有实例对象,都自动拥有和共享该构造函数原型对象中的所有属性和方法(想一想为什么空对象可以使用toString方法,所有的数组都可以使用push等方法)。 代码示例 1 //01 声明构造函数Person 2 function Person(name) { 3 this.name = name; 4 } 5 //02 打印构造函数相关联的原型对象 6 console.log(Person.prototype); //Objec类型的空对象 7 //03 给构造函数原型对象添加方法 8 Person.prototype.showName = function () { 9 console.log(this.name); 10 }; 11 //04 使用构造函数创建实例对象 12 var p = new Person("文顶顶"); 13 p.showName(); //文顶顶 14 console.log(p); 代码说明 上面的代码先提供了Person构造函数,该函数声明后,我们通过Person.prototype访问其原型对象打印得到一个Object类型的空对象,说明所有的构造函数创建后默认拥有prototype属性,即构造函数默认有一个相关联的原型对象(Object类型空对象)。 随后我们通过对象的动态特性给Person的原型对象添加了showName方法,通过打印结果可以验证构造函数的实例化对象(p)可以访问其原型对象上面的成员。 通过对代码和其运行结果分析,我们不难得出构造函数(Person)、原型对象(Person.prototype)、实例对象(p)之间的关系图,如下。 代码说明 ① 实例对象p由Person构造函数实例化而来。 ② Person构造函数可以通过prototype属性访问其原型对象。 ③ 实例对象p可以通过proto属性访问其构造函数的原型对象(可以简称为p的原型对象,我们在说原型对象的时候,应该先确定主语是构造函数还是实例对象,如果主语是构造函数,那么指的是构造函数.prototype,如果主语是实例对象,那么指的是创建该实例对象的构造函数相关联的原型对象,表示为实例对象.proto)。 ④ 原型对象(Person.prototype)可以通过constructor(构造器)属性来访问其关联的构造函数,无法访问实例对象。 下面贴出上面代码更详细的原型结构关系图。 原型对象的访问 1 //获取原型对象的方式 2 //01 构造函数访问原型对象:构造函数.prototype 3 console.log(Person.prototype); 4 //02 构造函数的实例对象访问原型对象:实例对象.__proto__ 5 console.log(p.__proto__); 6 console.log(p.__proto__ == Person.prototype); 7 //03 通过Object.getPrototypeOf方法传递实例对象作为参数访问 8 console.log(Object.getPrototypeOf(p)); 总结一下,原型对象的访问方式如下 ① 构造函数.prototype ② 实例对象.__proto__ ③ Object.getPrototypeOf(实例对象) 原型对象总结 所有的对象都拥有__proto__属性,函数既拥有prototype属性又拥有__proto__属性。 对象的__proto__属性指向其构造函数相关联的原型对象(函数的__proto__属性也一样,指向其构造函数Function相关的原型对象,是一个空函数)。 函数的prototype属性指向默认相关联的原型对象(函数和构造函数本质无差别)。 __proto__是一个非标准属性,即ECMAScript中并不包含该属性,这只是某些浏览器为了方便开发人员开发和调试而提供的一个属性,不具备通用性。建议在调试的时候可以使用该属性,但不能出现在正式的代码中,开发中可以使用Object.getPrototypeOf方法来替代。 1.3 设置原型对象 所谓设置原型对象就是给构造函数的原型对象添加成员(属性和方法),具体的方式有两种 ① 利用对象的动态特性设置② 替换原有的原型对象 代码示例 1 //01 声明构造函数Person 2 function Person(name,age) { 3 this.name = name; 4 this.age = age || 18; 5 } 6 //02 给构造函数原型对象添加方法 7 //设置原型对象的第一种方法 8 Person.prototype.showName = function () { 9 console.log("姓名 "+this.name); 10 }; 11 Person.prototype.showAge = function () { 12 console.log("年龄 "+this.age); 13 }; 14 //04 使用构造函数创建实例对象 15 var p1 = new Person("文顶顶"); 16 p1.showName(); //姓名 文顶顶 17 p1.showAge(); //年龄 18 18 var p2 = new Person("章飞一绝",99); 19 p2.showName(); //姓名 章飞一绝 20 p2.showAge(); //年龄 99 像上面代码这样直接利用对象的动态特性来设置原型对象,在原有原型对象的基础上添加属性和方法非常简单,但是如果要添加的方法或属性比较多,那么冗余代码会比较多,这种情况推荐直接替换原有的原型对象。 1 //01 声明构造函数Person 2 function Person(name,age) { 3 this.name = name; 4 this.age = age || 18; 5 } 6 //02 给构造函数原型对象添加方法 7 //设置原型对象的第二种方法:直接替换原先的原型对象 8 Person.prototype = { 9 constructor:Person, 10 showName:function () { 11 console.log("姓名 "+this.name); 12 }, 13 showAge:function () { 14 console.log("年龄 "+this.age); 15 } 16 }; 17 //04 使用构造函数创建实例对象 18 var p = new Person("文顶顶"); 19 p.showName(); //姓名 文顶顶 20 p.showAge(); //年龄 18 21 console.log(p.constructor); //Person函数 注意 如果是直接替换原型对象,那么需要修正构造器属性,让constructor指向构造函数。说明 因为替换的时候其实是用字面量的方式重新创建了新的对象,该对象作为Object构造函数的原型对象,内部没有constructor属性。这个时候如果要通过实例对象(比如p)的构造器属性判断其类型,那么会先在p身上找,没有则查找原型对象发现也没有,最终得到的Object.prototype身上的构造器属性,结果为Object 。 Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
[02]-javaScript对象探析 题记:多年前,以非常偶然的方式关注了微信公众号“面向对象”,本以为这个公众号主要以分享面向对象编程的干货为主,不料其乃实实在在的猿圈相亲平台。通过查看公开资料,发现该公众号俨然是在以自己的方式来帮助广大单身程序猿们通往一条真真正正的面向对象编程之路,相对而言,编程和语言特性这些破事又算得了什么呢,先“找到对象”似乎才是更高层面的话题。这篇文章,我们不谈如何”面向对象”,只着力于JavaScript中对象特征、创建以及访问。本文作为纯粹的技术文章,和“面向对象”公众号利益无关。 1.1 javaScript中对象的个人档案 javaScript是一门基于弱类型、支持面向对象编程且基于原型继承的脚本语言。 对象说明 在JavaScript语言中,对象是可变的键值对集合(或者称为属性的容器),通过对象我们可以方便的管理一组变量和函数。 用通俗的话来说,对象其实就是一堆变量和函数的集合。只是“定义”在对象中的变量,我们称为属性,“定义”在对象中的函数,我们称之为方法。 数据类型和对象 JavaScript中的数据类型可以分为基本数据类型和复杂数据类型。其中基本数据类型有:数字(number)、字符串(string)、布尔值(boolean)、undefined值和null。 复杂数据类型可以简单理解为对象类型。在JavaScript中,数组是对象、函数是对象、正则表达式是对象,对象自然也是对象,它们在使用typeof 运算符时得到的结果为object,即对象类型。 typeof对函数运算的结果为function,对null运算的结果为object,但这被认为是一个错误。 JavaScript语言中的对象是无类型的,对象可以拥有属性和方法,属性和方法都是以key-value的方式存储的。我们可以笼统的把方法和属性统一归类为属性(因为方法名其实和属性名没有任何本质的区别,属性和方法的分类方式只是对它们存储的内容进行人为区分),所以对象其实就是键值对的集合。 对象通过引用来传递,它们永远不会被复制。 对象的检查 ① 可以使用typeof来对对象类型进行简单的检查,但需要注意排除null的情况。 ② 在开发中,经常会用到hasOwnProperty方法来过滤对象原型成员。 1.2 javaScript中对象的获取方式 JavaScript中对象的创建有多种方式,根据特定的应用场景,我们可以选择不同的更合适的方式来创建对象,简单可以归纳为以下情况: ① 字面量的方式创建对象② 内置构造函数创建对象③ 封装工厂函数创建对象④ 定义构造函数创建对象⑤ 调用系统方法创建对象 下面分别对上面列出的这些方式进行逐一介绍。 字面量的方式创建对象 基本写法 1 var bookObj = { 2 name:"声名狼藉者的生活", 3 price:42.00, 4 author:"福柯", 5 press:"北京大学出版社", 6 read:function () { 7 console.log("我的书名为:声名狼藉者的的生活,作者为福柯...."); 8 } 9 }; 上面的代码中通过字面量的方式创建了bookObj对象,该对象拥有name、price、author和press属性,还拥有read方法。 对象字面量提供了一种非常方便的创建新对象的表示方法,一个对象就是包围在{}中的N(N>=0)个键值对。对象字面量可以出现在任何允许表达式出现的地方。 内置构造函数创建对象 JavaScript中的内置构造函数主要有: String Number Boolean Date Array Function Object RegExp 注意:(String Number Boolean 是区别于string number boolean的基本包装类型) 基本写法 1 var book = new Object(); 2 book.name = "声名狼藉者的生活"; 3 book.price = 42.00; 4 book.author = "福柯"; 5 book.press = "北京大学出版社"; 6 book.read = function () { 7 console.log("我的书名为:声名狼藉者的的生活,作者为福柯...."); 8 }; 这种写法相对字面量创建方式而言不够简洁和直观,而且本身的代码复用性不好,不推荐。 工厂函数创建对象 工厂函数方式创建对象其本质是对内置构造函数创建对象的过程进行了封装,适用于大规模“批量生产”同类型的对象。基本写法 1 //提供工厂函数 2 function createBookNew (name,price,author,press) { 3 var book = new Object(); 4 book.name = name; 5 book.price = price; 6 book.author = author; 7 book.press = press; 8 book.read = function () { 9 console.log("我的书名为:"+book.name+",作者为"+book.author+"...."); 10 }; 11 return book; 12 } 13 //使用工厂函数来创建对象 14 var book1 = createBookNew("声名狼藉者的的生活","42.00","福柯","北京大学出版社"); 15 var book2 = createBookNew("人性的枷锁","49.00","毛姆","华东师范大学出版社"); 16 var book3 = createBookNew("悟空传","28.00","今何在","湖南文艺出版社"); 17 //打印对象的属性,调用对象的方法 18 console.log(book1.name); 19 console.log(book2.name); 20 console.log(book3.name); 21 book1.read(); 22 book2.read(); 23 book3.read(); 总结工厂函数创建对象的实现过程 ① 提供一个创建对象的函数(参数)② 在函数内使用new 关键字和构造器创建对象③ 设置对象的属性和方法④ 返回加工过的对象 自定义构造函数创建对象 基本写法 1 //提供构造函数 2 function CreateBook (name,price,author,press) { 3 //使用new调用该构造函数时,默认在内部会先创建Object类型的实例对象 4 //并把该对象关联到当前构造函数的原型对象上,并把函数内的this绑定到该对象 5 //通过this来给实例对象设置属性和方法 6 this.name = name; 7 this.price = price; 8 this.author = author; 9 this.press = press; 10 this.read = function () { 11 console.log("我的书名为:"+this.name+",作者为"+this.author+"...."); 12 }; 13 //默认总是把新创建的实例对象返回 14 } 15 //使用new + 函数名() 的方式来调用 16 var bookObj = new CreateBook("声名狼藉者的的生活","42.00","福柯","北京大学出版社"); 构造函数和普通函数没有本质区别,约定使用new调用的构造函数的首字母应该大写。构造函数的作用在于完成对象的初始化,对象的创建等工作由new关键字完成,组合使用。 工厂函数和构造函数创建对象过程简单对象 ① 函数的首字母大写(用于区别构造函数和普通函数) ② 创建对象的过程是由new关键字实现 ③ 在构造函数内部会自动的创建新对象,并赋值给this指针 ④ 自动返回创建出来的对象 构造函数调用方式的返回值 ① 如果在构造函数中没有显示的return,则默认返回的是新创建出来的对象 ② 如果在构造函数中显示的return,则依照具体的情况处理 return 的是对象类型数据,则直接返回该对象 return 的是null或其他基本数据类型数据,则返回新创建的对象(即this) 提示 在开发中我们通过把自定义构造函数和原型对象结合在一起使用,这样可以充分的利用JavaScript原型链继承的特性并解决方法的共享问题。下面给出基本的代码示例: 1 //(1)提供Person构造函数 2 function Person(name) {} 3 //(2)替换Person默认的原型对象 4 Person.prototype ={ 5 //修正构造器属性 Object --> Person 6 constructor:Person, 7 //提供实例对象的初始化方法 8 init:function(name,age){ 9 this.name = name || "默认的姓名"; 10 this.age = age || 18; 11 }, 12 //所有实例对象都需要访问的原型方法 13 showName:function () { 14 console.log(this.name); 15 } 16 }; 17 //(3)使用new来调用构造函数以创建实例对 18 var p = new Person(); 19 //(4)调用init方法对实例对象进行初始化操作 20 p.init("文顶顶",20); 使用Object.create方法创建对象 ES5提供了Object.create法来创建一个新对象,该方法在使用的时候会把传入的指定对象连接为新对象的原型对象。 语法Object.create(proto, [propertiesObject]) 参数说明第一个参数proto:新创建对象的原型对象。第二个参数propertiesObject:可选的参数,如果没有指定为 undefined,则表示要添加到新创建对象的可枚举属性信息,存放对象的属性描述符以及相应的属性名称。 如果传入的参数为null,则创建出来的空对象不会继承Object原型成员,没有基础方法。如果传入的参数为Object.prototype,那么创建出来的对象等同于{}空对象。 代码示例 1 //01 字面量方式创建对象obj 2 var obj = {name:"wendingding",age:18}; 3 //02 使用Object.create方法来创建新对象 4 var o = Object.create(obj); 5 //o是一个空对象,o.__proto__指向obj对象 6 console.log(o); 7 //wendingding 访问原型对象obj上面的name属性 8 console.log(o.name); 9 //03 测试传入null的情况 10 var o1 = Object.create(null); 11 //打印结果为空对象,No properties 该对象身上没有任何成员 12 console.log(o1); 13 //04 测试传入Object.prototype的情况 14 var o2 = Object.create(Object.prototype); 15 //o2 是空对象,等价于{} 16 console.log(o2); 1.3 javaScript中对象的操作 我们知道对象可以简单理解为键值对的集合,通过前面的阅读我们已经了解到如何创建对象,接下来我们接着探讨对象内部键值对的相关操作。 对象属性和方法的访问方式:点语法或者是[]语法。对象属性和方法的操作方式:对象的操作方式和数据的操作方式保持一致,可以简单总结为增删改查和遍历操作。 代码示例 1 //00 提供obj对象 2 //通过字面量方式创建obj对象,该对象现在拥有name属性和showName方法 3 //因使用字面量方式创建,所有obj的原型对象(__proto__)指向object.prototype 4 var obj = { 5 name:"wendingding", 6 showName:function () { 7 console.log(this.name); 8 } 9 }; 10 //01 添加属性或方法 11 //a 使用点语法来为obj对象添加age属性和showAge方法 12 obj.age = 18; 13 obj.showAge = function (){ 14 console.log(this.age); 15 }; 16 //b 使用中括号语法来为obj对象添加age属性和showAge方法 17 obj["class-name"] = 41; 18 obj["showClassName"] = function () { 19 console.log(this["class-name"]); 20 }; 21 //02 修改属性或方法 22 //如果对象的属性已经存在,那么设置该属性的时候表示修改 23 obj.age = 20; 24 obj.showAge = function (){ 25 console.log("年龄" + this.age); 26 }; 27 //03 查询属性或者调用方法 28 console.log(obj.name); //wendingding 29 console.log(obj["age"]); //20 30 obj.showName(); //wendingding 31 obj["showName"](); //注意,不推荐使用这种方法 32 //04 删除对象中的属性或方法 33 //语法形式:delete 对象.属性 | delete 对象[属性] 34 delete obj.name; 35 delete obj["showName"]; 36 console.log(obj); 37 //05 对象的遍历 38 for (key in obj) 39 { 40 console.log(key, obj["key"]); 41 } delete关键字说明 01 具体使用: (1) 可以用来删除对象中指定的属性 (2) 用来删除没有使用var关键字声明的变量 delete 变量名|window.变量 02 使用注意点 (1) 关键字在使用的时候有返回值,true|false 删除成功返回true (2) 删除对象中不存在的属性,返回true (3) 使用var声明的变量不能被直接删除 (4) 不能删除使用var声明的全局变量但是可以删除直接添加在window上面的属性 (5) 如果删除数组中的元素(数组也是对象)则数组中对应的元素内容被替换为undefined,索引保留。 对象属性说明 对象的属性名可以是任意字符串,包括空字符。 对象的属性名如果是合法的JavaScript标识符,则不必强制要求使用双引号括住属性名。 属性值可以是任何值(包括undefined)。 如果对象的属性名并非合法标识符,则建议使用[]语法来访问对象。 标识符规范:字母开头,后面跟1个或多个下划线、数字或字母。 Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
[01]-javaScript函数基础 1.1 函数的创建和结构 函数的定义:函数是JavaScript的基础模块单元,包含一组语句,用于代码复用、信息隐蔽和组合调用。 函数的创建:在javaScript语言中,可以说函数是其最重要也最成功的设计。我们可以通过三种方式来创建函数。 ① 函数声明② 字面量方式创建③ 使用Function构造函数创建 代码示例 1 //01 函数声明 2 //函数名称为:f1,a和b为该函数的形式参数(形参) 3 function f1(a,b) { 4 return a + b; 5 } 6 //02 字面量创建函数 7 //使用字面量创建匿名函数并赋值给f2,可以通过f2来调用,a和b为该函数的形式参数(形参) 8 var f2 = function (a,b) { 9 return a + b; 10 }; 11 //03 构造函数创建 12 //f3函数为Function这个构造函数的实例化对象,如果不传递参数,那么创建出来的函数没有任何用处。 13 //在创建实例对象的时候我们可以通过参数列表的方式来指定f3的结构。 14 //构造函数的参数中最后一个参数为函数体的内容,其余均为函数的形参。 15 var f3 = new Function("a","b","return a + b"); 16 //函数的调用 17 console.log(f1(1,2)); //3 18 console.log(f2(1,2)); //3 19 console.log(f3(1,2)); //3 函数的结构函数的一般表现形式为: 1 //函数声明 2 function fn(n1,n2) { 3 //函数体的内容 4 return n1 + n2; //返回值 5 } 通常,函数包括四个部分:(1)保留字,function。(2)函数名,这里为fn。(3)圆括号以及包围在圆括号中的一组参数。(4)包括在花括号中的一组语句。 函数名可以被省略(称为匿名函数),函数名可用于函数调用或者是递归调用,另外函数名可以被调试器和开发工具识别。 函数声明时的参数为形参,可以有多个,多个参数之间使用逗号进行分隔。形参将在函数调用的时候被定义为函数中的局部变量,[注意]形参并不会像普通变量一样被初始化为undefined,它们的值根据函数调用时传入的实际参数值设置。另外,函数调用的时候并不会对实参的类型进行检查。 函数体是一组语句,它们在函数被调用的时候执行。函数执行完毕后,会返回一个值。 函数的调用:函数声明后可以通过()运算符来进行调用,JavaScript语言中,只有函数可以被调用。当函数被调用的时候,如果存在参数传递,那么会把实参的值传递给形参,并按照从上到下的顺序逐条执行函数体内部的代码。 1.2 函数和对象的关系 JavaScript中的函数本质上就是对象。在使用typeof 关键字对数据进行类型检查的时候,得到的结果可能会让我们产生错觉。 1 var o = {}; 2 var f = function () {}; 3 console.log(typeof o); //object 4 console.log(typeof f); //function 实际上,函数和对象没有质的区别,函数是特殊的对象。 函数的特殊性① 函数可以被()运算符调用[最重要]。② 函数可以创建独立的作用域空间。③ 函数拥有标配的prototype属性。 因为函数本身就是对象,所以在代码中函数可以像对象一样被使用,凡是对象可以出现的地方函数都可以出现。 函数可以拥有属性和方法。 函数可以保存在变量、对象和数组中。 函数可以作为其它函数的参数(称为函数回调)。 函数可以作为函数的返回值进行返回。 函数和对象的原型链结构我们可以通过下面列出的简单示例代码来分析对象的原型链结构。 1 //字面量方式创建普通的对象 2 var o = {name:"文顶顶",age:"18"}; 3 //关于普通对象的结构研究 4 console.log("① 打印o对象\n",o); 5 console.log("② 打印o.__proto__\n",o.__proto__); 6 console.log("③ 打印o.__proto__ === Object.prototype\n",o.__proto__ === Object.prototype) 7 console.log("④ 打印o.constructor\n",o.constructor); 8 console.log("⑤ 打印o.constructor === Object\n",o.constructor === Object); 通过对该代码的运行和打印分析,可以得到下面的图示。 我们也可以使用同样的方式来分析函数对象的原型链结构。 1 //使用构造函数Function 来创建函数(对象) 2 var f = new Function("a","b","return a + b"); 3 //调用函数,证明该函数是合法可用的 4 console.log(f(2, 3)); //得到打印结果5 5 //关于函数对象的结构研究 6 console.log("① 打印函数对象\n",f); 7 console.log("② 打印f.__proto__\n",f.__proto__); 8 console.log("③ 打印f.__proto__===Function.prototype\n",f.__proto__===Function.prototype); 9 console.log("④ 打印f.constructor\n",f.constructor); 10 console.log("⑤ 打印f.constructor === Function\n",f.constructor === Function); 11 //注意 12 console.log(f.hasOwnProperty("constructor")); //检查constructor是否为函数f的实例成员(false) 13 console.log(f.__proto__.hasOwnProperty("constructor")); //true 顺便贴出研究Function原型结构的代码 1 //说明:下面三行代码表明Function的原型对象指向一个空函数 2 console.log(Function.prototype); //ƒ () { [native code] } 是一个空函数 3 console.log(typeof Function.prototype); //function 4 console.log(Object.prototype.toString.call(Function.prototype)); //[object Function] 5 //检查Function.prototype的原型链结构 6 //Function.prototype是一个空函数,是一个对象,而对象均由构造函数实例化产生 7 //检查Function.prototype的构造函数以及原型对象 8 console.log(Function.prototype.constructor === Function); 9 //注意:按照一般逻辑实例对象的__proto__(原型对象)应该指向创建该实例对象的构造函数的原型对象 10 //即此处应该表现为Function.prototype.__proto__--->Function.prototype.constructor.prototype 11 //似乎可以得出推论:Function.prototype.__proto__ === Function.prototype == 空函数 但这是错误的 12 console.log(Function.prototype.__proto__ === Object.prototype); 通过对函数对象原型结构的代码探索,可以得到下图的原型链结构图(注:原型链并不完整) 函数的其它隐藏细节 ① 函数天生的prototype属性 每个函数对象在创建的时候会随配一个prototype属性,即每个函数在创建之后就天生拥有一个与之相关联的原型对象,这个关联的原型对象中拥有一个constructor属性,该属性指向这个函数。简单描述下就是: function f(){ //......} //声明函数 //函数声明被创建后,默认拥有prototype属性--->原型对象(空对象) 这里需要注意的是,很多人容易被自己的经验误导,认为新创建的函数对象身上除prototype实例属性外,还拥有constructor这个实例属性,因为我们经常看到f.constructor类似的代码,其实这里使用的constructor属性是从原型链中获取的,其实是f构造函数关联原型对象上面的属性,即Function.prototype.constructor。 备注:在ECMAScript标准中函数创建相关章节有这样一句话:NOTE A prototype property is automatically created for every function, to allow for the possibility that the function will be used as a constructor.解释了给新创建函数添加prototype属性的意义在于便于该函数作为构造函数使用。 ② 函数何以能够被调用 我们已经理解了函数本身就是对象,但又区别于普通对象,最大的区别在于函数可以被调用,()被称为调用运算符。 ️ 函数可以被调用的原因在于JavaScript创建一个函数对象时,会给该对象设置一个“调用”属性。当JavaScript调用一个函数时,可以理解为调用该函数的“调用”属性。 1.3 函数的调用和this参数 函数名后面跟上()表明这是一个函数调用。调用运算符:是跟在任何产生一个函数值的表达式之后的一对圆括号。圆括号内可以包含N(N>=0)个用逗号分隔开的表达式,每个表达式产生一个参数值。每个参数值被赋予函数声明时定义的形式参数名。 函数的调用JavaScript中有四种调用函数的模式 ① 对象方法调用模式② 普通函数调用模式③ 构造函数调用模式③ 上下文的调用模式 除了声明函数时定义的形参外,每个函数还接收两个附加的参数,分别是this和arguments。其中arguments用于存储函数调用时接收到的实际参数,this的值则取决于函数的调用模式,下面分别讲解。 普通函数调用模式 当函数并不作为其他对象的属性,直接使用调用运算符来调用时,我们认为它使用普通函数调用模式。 1 <script> 2 //01 声明函数fn 3 function fn() { 4 console.log(this); 5 } 6 //02 以普通函数调用模式来调用fn函数 7 fn(); //this被绑定到全局对象window 8 </script> 备注:在我们看来上面的调用方式非常简单清楚,而且this的指向也没有任何问题。但JSON的作者Douglas Crockford指出这是JavaScript语言设计上的一个错误。因为把this直接绑定给全局变量的方式没有考虑到函数作为内部函数(在其它函数内部声明的函数)使用过程中需要共享外部对象访问权的问题。他指出正确的语言设计应该是,当内部函数被调用时,函数内的this应该和外部函数的this保持一致,即这个this应该被绑定到外部函数的this变量。无疑,这值得思考和讨论。 对象方法调用模式 对象是键值对的集合,对象可以拥有属性和方法。当函数被保存为对象的属性时,我们称之为方法。对象的方法需要通过对象.方法()或者是对象[方法]()的方式进行调用。以对象方法的模式来对函数进行调用,函数内部的this被绑定给该对象。 1 //01 字面量的方式创建对象 2 //02 o对象中拥有name属性和showName方法 3 var o = { 4 name:"文顶顶", 5 showName:function () { 6 console.log(this); 7 console.log(this.name); //文顶顶 8 }}; 9 //03 以对象方法调用模式来调用showName函数 10 o.showName(); //this被绑定到o对象 ️ this到对象的绑定发生在方法调用的时候。 构造函数调用模式 构造函数:如果一个函数创建出来之后,我们总是希望使用new 前缀来调用它,那这种类型的函数就被称为构造函数。构造函数和普通函数本质上没有任何区别,开发者总是约定以函数名首字母大写的方式来人为进行区分。 如果以构造函数的方式来调用函数,那么在调用时,默认会创建一个连接到该构造函数原型对象上面的新对象,同时让this绑定到该新对象上。 1 //01 声明构造函数Person 2 function Person() { 3 console.log(this); 4 } 5 //02 以构造函数的方式来调用Person 6 new Person(); //this被绑定到Person的实例化对象 ️ 构造函数调用方式也会改变函数中return语句的行为,如果显示的return语句后面跟着的不是对象类型的数据,那么默认返回this绑定的新对象。 上下文的调用模式 上下文的调用模式,即使用apply或则call方法来调用函数。因为JavaScrip是一门函数式的面向对象编程语言,所有JavaScript中的函数本质上是对象,也因此函数也可以拥有方法。使用上下文模式对函数进行调用的时候,函数内部的this根据参数传递的情况进行绑定。 1 //声明函数f 2 function f(a,b) { 3 console.log(a, b, a+b); 4 console.log(this); //使用上下文模式调用时,this被绑定给o对象 5 console.log(this.name); //wendingding 6 } 7 //字面量的方式创建对象 8 var o = {name:"wendingding"}; 9 //使用apply和call方法来调用函数 10 f.apply(o, [1,2]); 11 f.call(o,3,4); 12 console.log(f.hasOwnProperty("apply")); //false 13 console.log(Function.prototype.hasOwnProperty("apply"));//true apply和call方法调用函数,函数内部的this绑定给第一个参数。 apply和call方法定义于Function的原型对象上,所以所有的函数都可访问。 apply和call方法作用基本相同,参数传递的形式有所差别。 1.4 函数的参数(arguments) 函数调用时,会完成实际参数对形式参数的赋值工作。当实际参数的个数和形式参数的个数不匹配时,并不会导致运行错误。 如果实际参数的数量过多,那么超出的那些参数会被忽略。如果实际参数的数量不足,那么缺失的那些参数会被设置为undefined。JavaScript在进行函数调用时不会对参数进行任何的类型检查。 在函数的内部,我们总是可以获得一个免费配送的arguments参数。arguments用于接收函数调用时传入的实际参数,它被设计成一个类似于数组的结构,拥有length属性,但因为它不是一个真正的数组所以不能使用任何数组对应的方法。 arguments参数的存在,使得我们可以编写一些无须指定形参个数的函数。下面提供一份示例代码用于对传入的所有参数进行累加计算。 1 <script> 2 function sum() { 3 var sum = 0; 4 var count = arguments.length; 5 for (var i = 0; i < count; i++) { 6 sum += arguments[i]; 7 } 8 return sum; 9 } 10 console.log(sum(1, 2, 3, 4, 5, 6, 7, 8, 89)); //125 11 </script> 1.5 函数的返回值 函数的调用:调用一个函数会暂停当前代码的执行,把控制权和参数传递给正被调用的函数。当一个函数被调用的时候,它会先根据实际参数来对函数的形式参数进行初始化,然后从函数体中的第一个语句开始执行并遇到关闭函数体的 } 时结束。然后把控制权交还给调用该函数的上下文。 函数的返回值:函数体中return语句可以用来让函数提前返回。当retun语句被执行时,函数会立即返回而不再执行余下的语句,return语句后面跟上返回的具体数据,可以是任意类型(包括函数)。 函数总是会有一个返回值,如果没有使用return语句指定,那么将总是返回undefined。 函数的返回值还和它的调用方式有关系,如果使用new也就是也构造函数的方式来调用,若函数体中没有通过return语句显示的返回一个对象类型的数据,则默认返回this(新创建的实例对象)。 ️ JavaScript不允许在return关键字和表达式之间换行。 Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
第八章 jQuery框架Ajax模块 8.1 jQuery框架中的Ajax简介 Ajax技术的核心是XMLHTTPRequest对象,该对象是Ajax实现的关键,发送异步请求、接收服务器端的响应以及执行回调等操作都是通过XMLHTTPRequest对象来完成的。 jQuery框架对Ajax操作进行了封装,在jQuery框架的Ajax模块中提供了很多方法用于网络编程。我们主要从Ajax网络请求、Ajax事件以及序列化等方面讲解。 8.2 jQuery框架中的Ajax网络请求 jQuery框架中为我们提供的发送网络请求方法主要有: load方法 ajax方法 get方法 post方法 getScript方法 getJSON方法 ① load方法 语法 jQ.load(url,[data],[callback])参数 url 资源的请求路径 data 发送请求时提交的参数,支持查询字符串和JSON对象格式 callback 加载完成的回调函数,3形参为:响应体 + 状态 + 请求对象 说明该方法请求远程的资源,并插入到选中的jQ实例对象中。 注意 默认发送GET请求,如果传递参数(JSON对象)则发送POST请求。 支持对返回的数据进行筛选 代码示例 1 //01 直接加载文件中的数据 2 //默认发送的是GET请求 3 //$("#demo").load("php/test.html"); 4 //02 加载文件中的数据,获取其中的某一部分(进行筛选) 5 //$("#demo").load("php/test.html span"); 6 //参数在请求体中进行传递 7 $("#demo").load("php/test.html span",{"namme":"zs"}); ② ajax方法 语法 $.ajax(url,[settings]) | $.ajax(settings) 常用参数说明 url 资源的请求路径 data 发送请求时提交的参数,支持查询字符串和JSON对象格式 async 是否异步发送网络请求 cache 是否进行缓存处理 context 指定回调函数中的this指针 dataType 预期服务器返回的数据类型 timeout 请求的超时时间 beforeSend 请求发送前执行的回调函数 complete 请求完成后执行的回调函数 error 请求失败执行的回调函数 success 请求成功执行的回调函数 说明该方法是jQuery框架中最底层的Ajax实现,用于发送网络请求。注意 最简单的情况下,$.ajax()可以不带任何参数直接使用。 所有的选项都可以通过$.ajaxSetup()函数来进行全局设置。 代码示例 1 $.ajax({ 2 "url":"php/03-ajax.php", //设置请求路径 3 "type":"get", //设置请求方法,不区分大小写 4 "success":function (res,status,xhr) { 5 //请求成功的回调 6 $("#demo").html(res); //获取响应状态码 7 console.log(status); //获取请求的状态 8 console.log(xhr); //获取请求对象本身 9 console.log(this); //获取上下文 10 }, 11 "error":function (res) { 12 //请求失败的回调函数 13 console.log("失败"); 14 console.log(res); 15 }, 16 //"data":"name=ls" //参数:查询字符串形式 17 "data":{"name":"ls"}, //参数:JSON对象形式 18 "timeout":10, //设置请求超时的时间 19 statusText:timeout 20 "context":obj, //设置回调函数中this的指向 21 "complete":function (res) { 22 console.log("请求完成"); 23 console.log(res); 24 } 25 }); ③ get和post方法 语法[1]$.get(url,[data],[callback],[type])[2]$.post(url,[data],[callback],[type]) 参数 url 资源的请求路径 data 发送请求时提交的参数 callback 请求成功的回调函数 type 服务器端返回内容的格式:包括xml、html、json、script等 GET和POST对比 GET请求参数跟在URL后,POST请求参数作为请求体发送。 GET请求对参数大小有限制,而POST请求理论上不受限制。 GET请求的数据会被浏览器缓存,存在严重的安全性问题。 服务器端读取数据的方式不同。在PHP中,区分为\$_GET和\$_POST。 代码示例 1 //发送请求获取服务器返回的文本,把div的内容替换掉 2 //第一个参数:url请求路径(必传) 3 //第二个参数:参数 支持两种形式[查询字符串][JSON对象] 4 //第三个参数:success(response,status,xhr) 5 // 请求成功的回调 6 // response:服务器返回的响应体 7 // status:状态说明[success-error] 8 // xhr:请求对象 9 //第四个参数:预期返回的数据类型:json|script|jsonP等 10 $.get("php/03-get.php",{"param1":"value1"}, 11 function (response,status,xhr) { 12 console.log(response); 13 console.log(status); 14 console.log(xhr); 15 }) 16 //注意点:GET请求请求路径一样会缓存,POST请求不会缓存 17 $.post("php/04-post.php",{"param1":"value1"}, 18 function (response,status,xhr) { 19 console.log(response); 20 console.log(status); 21 console.log(xhr); 22 }) ④ getScript和getJson方法 jQuery框架提供了getScript和getJson方法来直接加载js文件和JSON数据 语法 [1] $.getScript(url,[callBack])[2] $.getJson(url,[callBack]) 说明 getScript方法用于加载js文件,并自动执行。 getJson方法用于加载JSON数据。 代码示例 1 $.getScript("test.js", function(){ 2 //加载完成后执行的回调函数 3 alert("加载并执行JS文件"); 4 }); 8.3 jQuery框架中的Ajax事件方法 jQuery框架中除了提供上述方法来发送网络请求外,还提供了一些事件方法来对调用Ajax方法过程中的HTTP请求进行精细控制。通过jQuery提供的一些自定义全局函数,能够为各种与Ajax相关的事件注册回调函数。jQuery的Ajax全局事件方法如下: [1] ajaxStart(callBack) =>检测到网络请求开始发送会触发,1次[2] ajaxStop(callBack) =>检测到网络请求结束会触发,1次[3] ajaxSend(callBack) =>检测到网络请求开始发送会触发,N次[4] ajaxComplete(callBack)=>检测到网络请求结束会触发,N次[5] ajaxError(callBack) => 网络请求失败会触发[6] ajaxSuccess(callBack) => 网络请求成功会触发 1 $(document).ajaxStart(function () { 2 console.log("第一个已经开始+++++"); 3 $("#demoID").show(1000); 4 }) 5 $(document).ajaxStop(function () { 6 console.log("最后一个已经结束+++++"); 7 $("#demoID").hide(1000); 8 }) 9 $(document).ajaxSend(function () { 10 console.log("开始发送网络请求___"); 11 }) 12 $(document).ajaxComplete(function () { 13 console.log("发送网络请求完成___"); 14 }) 15 $.ajax({ //发送网络请求--A 16 "url":"php/06-other.php", 17 "type":"GET", 18 "success":function (res,status,xhr) { 19 console.log("网络请求成功--1"); 20 } 21 }) 22 $.ajax({ //发送网络请求--B 23 "url":"php/06-other.php", 24 "type":"GET", 25 "success":function (res,status,xhr) { 26 console.log("网络请求成功--2"); 27 } 28 }) 29 $(document).ajaxError(function () { 30 console.log("请求失败"); 31 }) 32 $(document).ajaxSuccess(function () { 33 console.log("请求成功"); 34 }) 8.4 jQuery框架中的序列化方法 在开发的时候,经常需要把表单中的数据作为网络请求的参数,如果一个一个的获取再拼接成查询字符串那么相当的麻烦,jQuery框架中为我们提供了两个对应的方法,可以方便解决该需求。 serialize方法能够将DOM元素内容序列化为查询字符串。serializeArray方法可以将DOM元素序列化后返回JSON格式的数据。 代码示例 1 <body> 2 <form> 3 <input type="text" name="username" id="demo1"> 4 <input type="text" name="password" id="demo2"> 5 </form> 6 <button>按钮</button> 7 <script> 8 $("button").click(function () { 9 $.ajax({ 10 "url":"php/07-get.php", 11 //"data":"username="+$("#demo1").val()+"&password=" +$("#demo2").val(), 12 "data":$("form").serialize(), 13 "success":function (res) { 14 console.log(res); 15 } 16 }) 17 //把表单中的key-value按照固定的格式拼接为查询字符串:username=lisi&password=abcd 18 console.log($("form").serialize()); 19 console.log($("form").serializeArray()); 20 //[{"username":"zhangsan"},{"password":"123"}]; 21 }) 22 </script> 23 </body> Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
第七章 jQuery框架的选择器 jQuery框架继承和优化了JavaScript访问DOM对象的特性,我们使用jQuery框架提供的api可以更加方便的操作DOM对象。 7.1 创建DOM节点 使用JavaScript原生方式创建DOM节点并添加到页面中的代码示例: 1 //01 创建DOM节点 2 var oDiv = document.createElement("div"); 3 //02 设置DOM节点的内容 4 oDiv.innerText = "测试的DIV标签"; 5 //03 把节点添加到页面中 6 document.body.appendChild(oDiv); 使用jQuery框架创建DOM节点并添加的代码示例: 1 //01 创建DOM节点 2 var oDom = $("<div></div>"); 3 //02 设置DOM节点的内容 4 oDom.text("测试的div标签"); 5 //02 把DOM节点添加到页面中 6 $("body").append(oDom); 更简单的创建及添加方式:$("body").append($("<div>我是测试的div标签</div>")); 说明 【1】jQuery框架简化了DOM操作,直接使用jQuery构造函数$()来创建标签,在创建标签的时候只需要把HTML字符串传递给函数,jQuery框架会根据参数的内容来创建标签并包装成一个jQ实例对象返回。 【2】要明白jQuery框架的DOM操作本身是对JavaScript原生方式进行的封装,所以相对原生的DOM操作而言效率更低。 7.2 插入DOM节点 jQuery框架中提供了多个插入DOM节点的方法,我们可以通过调用这些方法方便的实现节点的插入操作。 在JavaScript原生的DOM操作中,我们通常使用appendChild和insertBefore方法来实现插入操作,下面的具体的代码示例。 1 <body> 2 <div>我是div标签1</div> 3 <div>我是div标签2</div> 4 <script> 5 //appendChild方法使用 6 //01 创建p标签 7 var oP = document.createElement("p"); 8 oP.innerHTML = "我是p标签"; 9 //02 获取页面中第一个div标签 10 var oDiv1 = document.getElementsByTagName("div")[0]; 11 //03 使用appendChild方法添加 12 //把p标签插入到oDiv1标签内容的后面 13 oDiv1.appendChild(oP); 14 //insertBefore方法使用 15 var oDiv2 = document.getElementsByTagName("div")[1]; 16 //把p标签插入到oDiv2标签内容的前面 17 oDiv2.insertBefore(oP,oDiv2.firstChild); 18 </script> 19 </body> jQuery框架中为我们提供了四个方法来提供对应的功能,它们分别是:[1]append方法:向每个匹配的元素内部追加内容。[2]appendTo方法:把所有匹配的元素追加到另一个指定的元素集合中,和append方法相反。[3]prepend方法:向每个匹配的元素内部前置内容。[4]prependTo方法:把所有匹配的元素前置到另一个指定的元素集合中,和append方法相反。 插入方法的代码示例 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="jQuery框架/jquery-2.0.0.js"></script> 7 </head> 8 <body> 9 <div class="cur">我是div1</div> 10 <div>我是div2</div> 11 <div>我是div3</div> 12 <ul> 13 <li>我是第1个li</li> 14 <li>我是第2个li</li> 15 <li>我是第3个li</li> 16 <li>我是第4个li</li> 17 <li>我是第5个li</li> 18 </ul> 19 <button>按钮</button> 20 <script> 21 $(function () { 22 $("button").click(function () { 23 //appendTo:把页面中所有的li标签都插入到所有的div标签内容的后面 24 //$("li").appendTo($("div")); 25 //append:把页面中所有的div标签都插入到所有的li标签内容的后面 26 //$("li").append($("div")); 27 //prependTo:把页面中所有的li标签都插入到所有的div标签内容的前面 28 //$("li").prependTo($("div")); 29 //prepend:把页面中所有的div标签都插入到所有的li标签内容的前面 30 //$("li").prepend($("div")); 31 }) 32 }) 33 </script> 34 </body> 35 </html> jQuery框架中还提供了多个外部插入内容的方法,它们分别是:[1]after方法:在每个匹配的元素之后插入内容。[2]before方法:在每个匹配的元素之前插入内容。[3]insertAfter方法:把所有匹配的元素插入到另一个指定的元素集合的后面。[4]insertBefore方法:把所有匹配的元素插入到另一个指定的元素集合的前面。 7.3 删除DOM节点 JavaScript原生的DOM操作中可以使用removeChild方法来删除指定的节点以及其包含的所有子节点,并返回这些删除的内容。 jQuery框架中定义了3个删除内容的方法:它们分别是remove()、empty()和detach()。 remove方法能够将匹配的元素从DOM中删除。empty方法用来清空元素包含的内容,该方法没有参数。detach方法能够将匹配的元素从DOM中分离出来。 注意 [1] 删除和清空是两个概念,清空操作执行后该标签还存在。 [2] detach方法和remove方法差不多,但detach方法能够保存所有jQuery数据与被移走的元素相关联,所有绑定在元素上的事件、附加的数据等都会保存下来。如果您在移走一个元素不久后,又需要将该元素重新插入DOM,那么推荐使用detach方法。 7.4 复制和替换DOM节点 ① 节点的复制 在JavaScript原生方式操作DOM节点时,可以使用cloneNode方法来复制节点,具体的语法如下: 语法:nodeObject.cloneNode(include_all)参数: include_all参数本身为布尔类型的值。 如果为true,那么将会复制原有的节点以及所有的子节点。 如果为false,那么紧紧复制节点本身。 jQuery框架中,使用clones方法来复制节点,具体的语法如下: 语法:jQ.clone([widthDataAndEvents],[deepWithDataAndEvents])参数:clone方法的两个参数都是可选的布尔值,如果不传递则默认全部为false。 widthDataAndEvents参数表示是否复制该节点的事件处理数据。 deepWithDataAndEvents参数表示是否复制子元素的事件处理数据。 ② 节点的替换 在原生的DOM操作中,可以使用replaceChild方法来替换节点。语法:nodeObject.replaceChild(new_node,old_node)参数说明:new_node为指定的新节点,old_node为被替换的节点。如果替换成功,那么会返回被替换的节点,如果替换失败,那么会返回null。 jQuery框架中定义了replaceWith和replaceAll方法来替换节点。 [1] replaceWith方法 语法:jQ.replaceWith(newContent)说明:replaceWith方法能够将所有匹配的元素都替换成指定的HTML或者是DOM元素。示例:$("p").replaceWith("<div>我是DIV标签<div>") [2] replaceAll方法 语法:jQ.replaceAll(selector)说明:replaceAll方法和replaceWith是一对相反的操作。 Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
第六章 jQuery框架事件处理 JavaScript以事件驱动来实现页面的交互,其核心是以消息为基础,以事件来驱动。虽然利用传统的JavaScript事件处理方式也能够完成页面交互,但jQuery框架增加并扩展了基本的事件处理机制,jQuery框架提供了更加优雅的事件处理语法,并极大的增强了事件处理能力。 6.1 事件处理简单说明 jQuery框架在JavaScript的基础上进一步封装了不同类型的事件模型,形成了一套更强大和优雅的“jQuery事件模型”。 jQuery中的事件模型表现出以下特征: ① 使用DOM事件模型中标准的事件类型名称。② 统一了事件处理中的各种方法。③ 允许为每个元素的每个事件类型建立多个处理程序。④ 统一了事件对象的传递方法并规范了事件对象的常用属性和方法。⑤ 为事件管理和操作提供了统一的方法。 6.2 绑定事件 在jQuery中,我们可以有多种方式来为标签绑定事件,可以简单的区分为专用方法绑定事件和快捷方法绑定事件。 ① 快捷方法绑定事件 jQuery框架中定义了24个快捷方法来为标签绑定特定类型的事件,这些方法和二级事件模型中的事件类型对应,名称相同。 具体的快捷方法如下: blur() 当元素失去焦点时发生 blur 事件 change() 当元素的值发生改变时,会发生 change 事件 click() 当点击元素时,会发生 click 事件 dbclick() 当双击元素时,会发生 dblclick 事件 error() 当元素遇到错误(没有正确载入)时,发生 error 事件 focus() 当元素获得焦点时,发生 focus 事件 focusin() 当元素获得焦点时,发生 focusin 事件(包括子元素) focusout() 当元素失去焦点时,发生 focusout事件(包括子元素) keydown() 当按键被按下时,发生 keydown 事件 keyup() 当按键被松开时,发生 keyup 事件 keypress() 当按键被按下时,发生 keypress事件(响应每个字符) mouseenter()当鼠标指针穿过元素时,会发生 mouseenter 事件 mouseleave()当鼠标指针离开元素时,会发生 mouseleave 事件 mouseover() 当鼠标指针位于元素上方时,会发生 mouseover 事件 mouseout() 当鼠标指针从元素上移开时,会发生 mouseout 事件 mousedown() 当鼠标进入元素,并按下按键时,会发生mousedown事件 mouseup() 当在元素上放松鼠标按钮时,会发生 mouseup 事件 mousemove() 当鼠标在指定的元素中移动时,会发生 mousemove 事件 resize() 当调整浏览器窗口的大小时,发生 resize 事件 scroll() 当用户滚动指定的元素时,会发生 scroll 事件 select() 当文本被选择时,会发生 select 事件 submit() 当提交表单时,会发生 submit 事件(表单) load() 当指定的元素(及子元素)已加载时,会发生load事件 unload() 当用户离开页面时,会发生 unload 事件(1.8-) ② 专用方法绑定事件 jQuery中可以使用四种专用方法来绑定事件,分别是bind方法、live方法、delegate方法和on方法,每个版本各有区别,建议使用on方法。 补充说明 bind方法适用于所有的版本,1.7+ 推荐使用on方法来代替。 live方法适用于 1.9- 的版本,1.9+ 版本使用on方法来代替。 delegate方法适用于1.4.2 + 的版本。 on方法适用于1.7+ 的版本,1.7+ 用于替代bind和live方法。 on方法为指定的元素添加一个或者是多个事件,并规定这些事件发生时指定的函数。 on方法的语法:on(eventType,childselector,data,function) 参数说明: eventType:必传参数,指定事件的类型如click等。childselector:可选参数,用于事件委托。data:可选参数,设计需要传递的数据。function:必传参数,事件发生时,执行的函数。 示例代码 1 //【1】使用快捷方法来给按钮添加点击事件 2 $("button").click(function () { 3 console.log("点击了按钮---1"); 4 }); 5 $("button").click(function () { 6 console.log("点击了按钮---2"); 7 }); 8 //【2】使用on方法来给按妞添加点击事件 9 $("button").on("click",{name:"wendingding"},function (event) 10 { 11 console.log("点击了按钮----on"); 12 console.log(event.data.name); 13 }) 扩展:one方法的使用one方法是on方法中的一种特殊使用方式,由one方法绑定的事件在执行一次响应之后就会失效。其设计思路是:在事件处理函数的内部注销当前事件 扩展:事件委托说明事件委托是开发中常见的绑定事件方式,参考代码如下。 1 //思考:如何能够找到所有的span标签(已经存在的 + 尚未创建的) 2 //第一个参数:事件的类型 3 //第二个参数:给谁添加事件 4 //第三个参数:事件发生的回调函数 5 $("div").on("click","span",function () { 6 console.log("点击了标签"); 7 }) 6.3 注销事件 有时候我们需要把一些元素的绑定事件注销,可以使用off方法来注销事件。注销事件的方法和注册事件的方法是相反的操作,参数和用法基本相同。 off方法的使用示例 1 //注销button标签上面的所有点击事件 2 $("button").off("click"); 3 //注销button标签上面指定的鼠标移入事件,fn为绑定移入事件时的函数 4 $("button").off("mouseenter",fn); 6.4 事件对象 在注册事件的时候,event对象实例将作为第一个参数传递给事件的回调函数,这和DOM事件模型是完全相同的。另外,jQuery统一了IE事件模型和DOM事件模型中event对象属性和方法的用法,使其符合DOM标准事件模型的规范。 在事件处理函数(回调函数)中,我们可以获取事件对象的相关信息。 1 $("button").on("click",{name:"zs"},function (event) { 2 console.log("点击了按钮----2"); 3 //获取事件的类型 4 console.log(event.type); 5 //获取目标对象 6 console.log(event.target); 7 //获取被省略的对象 8 console.log(event.data); 9 }) 6.5 事件冒泡 事件冒泡的简单解释:如果某个标签的事件被触发,那么该标签父标签上被注册的相同类型事件也会被触发,并且会依次一直冒泡到顶端。 1 <html lang="en"> 2 <head> 3 <meta charset="UTF-8"> 4 <title>Title</title> 5 <script src="js/jquery-3.2.1.js"></script> 6 <style> 7 .box1{ 8 width: 300px; 9 height: 300px; 10 background: red; 11 } 12 .box2{ 13 width: 200px; 14 height: 200px; 15 background: green; 16 } 17 .box3{ 18 width: 100px; 19 height: 100px; 20 background: yellow; 21 } 22 </style> 23 </head> 24 <body> 25 <script> 26 $(function () { 27 $(".box1").click(function () { 28 console.log("点击了box1"); 29 }) 30 $(".box2").click(function (e) { 31 console.log("点击了box2"); 32 //e.stopPropagation(); //return false; 33 }); 34 $(".box3").click(function () { 35 console.log("点击了box3"); 36 }) 37 }) 38 </script> 39 <div class="box1"> 40 <div class="box2"> 41 <div class="box3"></div> 42 </div> 43 </div> 44 </body> 45 </html> 阻止事件冒泡的两种方式:【1】在回调函数中返回false。【2】调用事件对象的stopPropagation方法。 6.6 触发事件和默认行为 默认行为 默认行为:页面中的一些标签常常存在默认的行为,比如表单的submit事件类型,如果该类型的事件被触发,则会导致表单的提交;比如a标签存在跳转网页连接的默认行为等。如果需要在事件被触发的时候,阻止标签默认的行为,可以考虑在处理函数内部调用事件对象的preventDefault()方法。 触发事件 触发事件:页面中标签的事件都是在特定条件下发生的,所以不同类型的事件触发时间其实无法预测。但有的时候,我们可能需要控制事件发生的时机。这时候,可以考虑使用trigger()或者是triggerHandler()方法来触发事件。 语法说明:trigger(type),[data]triggerHandler(type),[data] 参数说明:type参数表示事件的类型,以字符串的形式传递。data参数是可选的,利用该参数可以向事件的回调函数传递额外的数据。 代码示例: 1 $(".box3").trigger("click"); 2 $("input").triggerHandler("click"); trigger和triggerHandler方法的对比 ① triggerHandler方法不会触发标签的默认事件。② triggerHandler方法只会触发jQ实例对象集合中第一个元素的事件回调。③ triggerHandler方法返回的是事件回调函数的返回值,而非jQ对象。 Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
第五章 jQuery框架动画特效 5.1 jQuery动画特效说明 jQuery框架中为我们封装了众多的动画和特效方法,只需要调用对应的动画方法传递合适的参数,就能够方便的实现一些炫酷的效果,而且jQuery框架还支持自定义各种动画效果。 jQuery中的动画效果主要有以下方法 ① 显示和隐藏动画② 展开和收起动画③ 淡入和淡出动画④ 自定义动画效果 5.2 显示和隐藏动画 jQuery框架中为我们提供了专门的方法来控制让标签显示或者是隐藏。标签的显示和隐藏在开发中相对来说是比较常见的操作,如果使用原生的JavaScript代码来控制标签的显示或者是隐藏,那么我们主要通过控制该标签的display属性值来实现。 jQuery中控制标签显示和隐藏的动画方法 ① show() 控制让标签显示的动画方法② hide() 控制让标签隐藏的动画方法③ toggle()控制让标签显示|隐藏效果切换的动画方法 ex:以原生的方法来控制标签的显示或隐藏 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <!--....--> 5 <style> 6 #demoID{ 7 width: 200px; 8 height: 50px; 9 background: red; 10 } 11 </style> 12 <script src="jquery-3.1.1.js"></script> 13 </head> 14 <body> 15 <div id="demoID"></div> 16 <script> 17 //控制标签显示或隐藏的方式(1) 18 //document.getElementById("demoID").style.display = "none"; 19 //document.getElementById("demoID").style.display = "block"; 20 //控制标签显示或隐藏的方式(2) 21 //$("#demoID").css("display","none"); 22 $("#demoID").css("display","block"); 23 </script> 24 </body> 25 </html> 显示和隐藏动画方法语法 $("selector").show(speed,callBack)$("selector").hide(speed,callBack) 参数说明: 第一个参数:可选的参数。speed表示执行动画的速度,该速度可以使用系统默认提供的值,也可以自己以数字的形式传入。 系统默认提供的值有:“slow”、“normal”、“fast”,对应的速度分别为0.6秒、0.4秒和0.2秒。 自己以数字的形式传递则:传递如1000|3000类似的值,单位为毫秒,如果传递1000表示动画的执行速度为1秒。 第二个参数:可选的参数。callBack为动画完成时执行的回调函数,该函数每个元素执行一次。 切换动画方法语法 调用方式[1] => $("selector").toggle()调用方式[2] => $("selector").toggle(speed,callBack) 方法参数说明: (1)调用方式[1],不传递参数。切换当前元素(标签)的可见状态,如果当前元素的状态为不可见,则切换为可见,如果当前元素的状态为可见,则切换为不可见。 (2)调用方式[2],第一个可选的参数为动画执行的速度,第二个可选的参数为动画执行完后执行的回调函数。 总结 show方法和hide方法用来控制标签的显示或者是隐藏,内部的实现逻辑是同时改变标签的宽度、高度和透明度。 5.3 展开和收起动画 jQuery框架中,为我们提供实现滑动效果的方法,slideDown和slideUp方法分别可以用来控制标签展开和收起。 展开和收起动画方法语法 slideDown(speed,callBack);slideUp(speed,callBack)slideToggle(speed,callBack) 方法解释 slideDown方法的功能是设置让指定标签的高度从顶部向底部增加,以呈现出一种展开的动画效果,元素的其他属性不会发生任何变化。 sldeUp方法的功能是设置让指定标签的高度从底部向顶部减小,仅仅改变标签的高度,其他的属性并不会改变。 slideToggle方法用来切换所选择元素的高度,如果当前标签的高度不为0,那么调用该方法后就会把标签的高度过渡为0,实现收起的效果,否则就实现展开的效果。 注意:无论是展开还是收起的动画方法,他们的动画效果都仅仅只会改变(增加或减小)标签的高度。 参数说明 第一个参数:动画执行的时间,同show方法。第二个参数:动画执行完毕的回调函数,可以省略,同show方法。 代码示例 1 <body> 2 <div>我是div</div> 3 <button>展开</button> 4 <button>收起</button> 5 <button>切换</button> 6 <script> 7 $(function () { 8 $("button").eq(0).click(function () { 9 $("div").slideDown(2000,function () { 10 console.log("展开"); 11 }); 12 }) 13 $("button").eq(1).click(function () { 14 $("div").slideUp(2000); 15 }) 16 $("button").eq(2).click(function () { 17 $("div").slideToggle(1000,function () { 18 alert("切换动画指定完毕") 19 }); 20 }) 21 }) 22 </script> 23 </body> 5.4 淡入和淡出动画 jQuery框架中还提供了淡入和淡出的动画方法,这两个方法分别是fadeIn和fadeOut。 淡入和淡出动画方法语法 fadeIn(speed,callBack);fadeOut(speed,callBack)fadeToggle(speed,callBack)fadeTo(speed,opactity,callBack) 方法说明: 淡入淡出方法的内部实现原理是控制标签的透明度,通过改变选中标签的透明度来实现淡入和淡出的动画效果,并不修改其他的属性。 fadeIn()方法的作用是淡入动画,即改变标签的透明度让标签慢慢的显示出来。 fadeOut()方法的作用是淡出动画,即改变标签的透明度让标签慢慢的消失(透明度为0)。 fadeToggle()方法的作用是切换动画,如果当前标签的透明度不为0,那么就执行淡出动画,否则就执行淡入动画。 fadeTo()方法的作用是淡入到,即使用动画效果实现将标签的透明度设置为指定的值。 备注:标签透明度的取值范围为0.0~1.0。 参数说明: speed参数:动画执行的速度,单位为毫秒。callBack参数:动画执行完毕的回调函数,可选。opacity参数:指定的标签透明度(0.0~1.0)。 代码示例 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="JS/jquery-3.2.1.js"></script> 7 <style> 8 div{ 9 width: 400px; 10 height: 200px; 11 background: red; 12 } 13 </style> 14 </head> 15 <body> 16 <div>我是div</div> 17 <button>淡入</button> 18 <button>淡出</button> 19 <button>切换</button> 20 <button>惦记我</button> 21 <script> 22 //fadeIn:淡入 23 //fadeOut:淡出 24 //fadeToggle切换 25 //fadeTo:淡入淡出到...0.5 26 $(function () { 27 //01 获取页面中指定的按钮,添加点击事件 28 $("button:eq(0)").click(function () { 29 //02 点击按钮后,获取页面中的div标签,设置动画 30 //第一个参数:速度 31 //第二个参数:回调 32 $("div").fadeIn(2000,function () { 33 alert("显示完成"); 34 }); 35 }) 36 $("button:eq(1)").click(function () { 37 $("div").fadeOut(1000,function () { 38 alert("淡出") 39 }); 40 }) 41 $("button:eq(2)").click(function () { 42 $("div").fadeToggle(); 43 }) 44 $("button:eq(3)").click(function () { 45 //第一个参数:速度 46 //第二个参数:目标值 47 //第三个参数:回调 48 $("div").fadeTo(1000,0.5,function () { 49 alert("执行动画完毕") 50 }); 51 }) 52 }) 53 </script> 54 </body> 55 </html> 5.5 自定义动画 jQuery框架中本身已经为我们封装好了一些简单的控制标签宽高、透明度相关的方法,如显示和隐藏、展开和收起、淡入和淡出,除了这些方法之外,jQuery还为我们提供了animate()方法,允许我们自定义动画效果,通过一些设置我们可以实现更加复杂的动画效果, 自定义动画的语法 animate(params,speed,easing,callBack) 参数说明:第一个参数:params是一个对象。在该对象中以键值对的方式来要控制的属性样式和对应的值表示。 第二个参数:speed速度,可以是默认字符串中的某个(“slow” “normal” “fast”)或者是自定义的数字。 第三个参数:easing为动画插件使用的可选参数,用来控制动画的表现效果,通常有linear和swing等固定值。 第四个参数:动画执行完毕后的回调函数。 代码示例 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 div{ 8 width: 200px; 9 height: 50px; 10 background: red; 11 } 12 </style> 13 </head> 14 <body> 15 <!-- 16 animate({},timer,fn) 17 第一个参数:目标对象 18 第二个参数:速度 19 第三个参数:回调函数 20 --> 21 <div>我是个好人</div> 22 <button> 23 喜欢我就点我吧 24 </button> 25 <button> 26 喜欢我就点我吧2 27 </button> 28 <script src="js/jquery-3.2.1.js"></script> 29 <script> 30 $(function () { 31 $("button:eq(0)").click(function () { 32 $("div").animate({ 33 width:"+=50", 34 height:"+=100" 35 },1000); 36 }) 37 $("button:eq(1)").click(function () { 38 //01 直接设置目标值:速度和回调函数可以被省略 39 // $("div").animate({ 40 // width:400, 41 // height:100 42 // },2000,function () { 43 // alert("执行完毕"); 44 // }) 45 //02 第二种用法 46 // $("div").animate({ 47 // width:"+=50", 48 // height:"+=100" 49 // },1000); 50 //03 动画切换(如果有值那么就设置为0,否则就恢复) 51 $("div").animate({ 52 width:"toggle", 53 height:"+=100" 54 },1000); 55 }) 56 }) 57 </script> 58 </body> 59 </html> 动画队列、动画停止和动画延迟 动画队列:如果某个标签身上要调用多个动画相关方法,即需要展示多个动画效果,那么所有的这些动画效果并不会同一时刻发生,而是需要在队列中排队,然后按照队列中动画效果的顺序依次展现。 动画停止:在执行动画的时候,可以通过stop()方法来停止动画。停止动画的语法为: stop(clearQueue,gotoEnd) 方法和参数说明: stop方法的功能是停止指定标签中正在执行的动画,其中第一个参数clearQueue为可选参数,传递一个布尔类型的值,表示是否停止正在执行的动画。第二个参数gotoEnd也是可选参数,传递一个布尔类型的值,表示是否立即完成正在执行的动画。 动画延迟:设置一个延时的值来推迟后续队列中动画的执行,可以传递延迟的具体时间,单位为毫秒。 代码示例01 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 div{ 8 width: 200px; 9 height: 200px; 10 background: yellow; 11 } 12 </style> 13 </head> 14 <body> 15 <div> 16 </div> 17 <button>按钮</button> 18 <script src="js/jquery-3.2.1.js"></script> 19 <script> 20 $(function () { 21 $("button").click(function () { 22 $("div").animate({ 23 width:"50px" 24 }) 25 .animate({ 26 height:50 27 }) 28 .delay(2000) 29 .animate({ 30 height:300 31 }) 32 .animate({ 33 width:400 34 }) 35 }) 36 }) 37 </script> 38 </body> 39 </html> 代码示例02 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 div{ 8 width: 50px; 9 height: 50px; 10 background: green; 11 } 12 </style> 13 </head> 14 <body> 15 <div> 16 </div> 17 <button>按钮</button> 18 <script src="js/jquery-3.2.1.js"></script> 19 <script> 20 $(function () { 21 //01 当页面加载完毕就执行动画效果 22 $("div").animate({ 23 width:"400px" 24 },2000) 25 .animate({ 26 height:200 27 },1000) 28 .delay(1000) 29 .animate({ 30 height:50 31 }) 32 .animate({ 33 width:50 34 }) 35 //02 当点击按钮的时候停止动画 36 $("button").click(function () { 37 //01 没有传递参数:结束当前的动画,继续执行后面的动画 38 //$("div").stop(); 39 //02 传递1参数: 40 // true:所有的动画全部结束 41 // false:结束当前的动画,继续执行后面的动画 42 //$("div").stop(true); 43 //$("div").stop(false); 44 //03 传递2参数: 45 //true true : 立刻结束到达当前动画的终点,后面的不再执行 46 //true false:所有的动画全部结束 47 //false false:结束当前的动画,继续执行后面的动画 48 //false true : 立刻结束到达当前动画的终点,后面的继续执行 49 //$("div").stop(true,true); 50 //$("div").stop(true,false); 51 //$("div").stop(false,false); 52 $("div").stop(false,true); 53 }) 54 }) 55 </script> 56 </body> 57 </html> Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
第四章 jQuery框架的选择器 4.1 jQuery选择器说明 jQuery 最核心的组成部分就是选择器引擎。它完全继承了 CSS 的风格,可以对 DOM 元 素的标签名、属性名、状态等进行快速准确的选 择,而且不必担心浏览器的兼容性,写法更加简洁。 jQuery 选择器实现了 CSS1~CSS3 的大部分规则之外,还实现了一些自定义的选择器,用于各种特殊状态的选择。 优点:相对于直接使用 JavaScript 获取页面元素和处理业务逻辑相比,使用jQuery 选择器来进行操作代码更简单且拥有完善的代码检测机制。 jQuery 选择器根据获取页面中元素的不同,可以划分为四大类 :基本选择器、层级选择器、筛选选择器和表单选择器。 4.2 基本选择器 顾名思义,基本选择器是jQuery中用的最多, 使用最频繁的选择器,通过基本选择器我们可以实现大多数页面元素的选择。基本选择器主要有:ID选择器、类选择器、标签选择器、并集选择器和通配符选择器。 选择器 语法 功能 参考示例 ID选择器 #id 根据给定的ID匹配一个元素 $(“#divID”) 类选择器 .class 根据给定的类名匹配所有的元素 $(“.box”) 标签选择器 element 根据给定的元素名匹配所有的元素 $(“div”) 通配符选择器 * 匹配所有的元素 $(“*”) 并集选择器 #id,.class 将每个选择器匹配到的元素合并在一起后返回 $(“#divID,.box”) 基本选择器代码示例 1 <body> 2 <div id="demo">我是id为demo的div标签</div> 3 <div class="box1">我是class为box1的div标签</div> 4 <div class="box1">我是class为box1的div标签</div> 5 <div class="box2">我是class为box2的div标签</div> 6 <div class="box2">我是class为box2的div标签</div> 7 <p>我是p标签</p> 8 <script> 9 $(function () { 10 //基本选择器: 11 //(1) ID选择器 $("#ID"); 12 //(2) 类选择器 $(".类"); 13 //(3) 标签选择器 $("标签名"); 14 //(4) 并集选择器 $("选择器,选择器") 15 //(5) 通配符 $("*") 16 17 //ID选择器:获取页面中id为demo的标签,设置背景颜色为红色 18 $("#demo").css("background","red"); 19 //类选择器:获取页面中所有class为box1的标签,并设置背景颜色 20 $(".box1").css("background","green"); 21 //类选择器:获取页面中所有class为box2的标签,并设置背景颜色 22 $(".box2").css("background","yellow"); 23 //标签选择器:获取页面中所有的p标签 24 $("p").css("background","red"); 25 //并集选择器:获取页面中id为demo的标签以及class为box2的所有标签 26 $("#demo,.box2").css("background","green"); 27 //通配符选择器:获取页面中所有的标签(包括HTML),设置背景颜色 28 $("*").css("background","green"); 29 }) 30 </script> 4.3 层级选择器 层次选择器通过 DOM 元素间的层次关系获取元素,其主要的层次关系包括后代、直接后代、下一个相邻兄弟和后面所有兄弟元素的关系,通过其中某类关系可以方便快捷地定位元素。 选择器 语法 功能 参考示例 后代选择器 parent child 根据祖先元素匹配所有的后代元素 $(“div p”) 直接后代选择器 parent > child 根据父元素匹配所有的子元素 $(“div > .box”) 下一个相邻兄弟 prev + next 匹配所有紧接在prev元素后的相邻元素 $(“#demoID + div”) 后面所有兄弟 prev ~ siblings 匹配 prev 元素之后的所有兄弟元素 $(“#demoID ~ div”) 说明: 后代选择器获取的是所有的后代标签(层次关系是祖先与后代),而直接后代仅仅获取指定标签的子节点满足条件的标签(层次关系为父子关系)。 补充 next() == 下一个相邻兄弟 || nextAll() == 后面所有兄弟 代码示例 1 <script> 2 $(function () { 3 //.... 4 //(1) 后代标签 $(".box div") 5 //要获取class为box的标签的所有后代中的div标签 6 $(".box div").css("background","red"); 7 //(2) 直接后代 $(".box>div") 8 $(".box>div").css("background","green"); 9 //(3) 当前标签后面的第一个兄弟节点 $(".box1 + div") 10 $(".box1 +div").css("background","green"); 11 //(4) 当前标签后面的所有的兄弟节点 $(".box1 ~ div") 12 $(".box1 ~ div").css("background","green"); 13 }) 14 </script> 父子选择器相关方法 `parent() 获取当前标签的父节点``parents()获取当前标签的祖先节点``parentsUntil()获取当前标签的祖先节点直到…``children()获取当前标签的子节点``siblings()获取当前标签的兄弟节点` 代码示例 1 <body> 2 <div> 3 <div class="box"> 4 <div>demo</div> 5 <div class="active">demo</div> 6 <div>demo</div> 7 <div>demo</div> 8 <div>demo</div> 9 </div> 10 <span>我是span</span> 11 </div> 12 <button>点击我</button> 13 <script> 14 $(function () { 15 $("button").click(function () { 16 //(1) 获取当前标签的父节点 17 //console.log(this); 18 //console.log($(".active").parent()); 19 //$(".active").parent().css("background","red"); 20 //(2) 获取当前标签的祖先节点 21 //$(".active").parents().css("background","red"); 22 //(3) 获取当前标签的祖先节点直到... 23 //$(".active").parentsUntil("body").css("background","red"); 24 //(4) 获取当前标签的子节点 25 //$(".box").children().css("background","green"); 26 //(5) 获取除了当前标签之外的其他兄弟节点 27 //$(".active ~div").css("background","green"); 28 $(".active").siblings().css("background","green"); 29 }) 30 }) 31 </script> 4.4 筛选选择器 筛选选择器可以划分为 :基本筛选选择器、内容筛选选择器、可见性筛选选择器、属性筛选选择器、子元素筛选选择器、表单对象属性筛选选择器。 4.4.1 基本筛选选择器 选择器语法 功能 :first 获取第一个元素 :last 获取最后一个元素 :eq(index) 获取指定索引值的元素 :gt(index) 获取大于给定索引值的元素 :lt(index) 获取小于给定索引值的元素 :not(selector) 获取除给定选择器外的所有元素 :header 获取所有标题类型的元素,如h1 h2 :animated 获取正在执行动画效果的元素 :even 获取所有索引值为偶数的元素,索引号从0开始 :odd 获取所有索引值为奇数的元素,索引号从0开始 代码示例 1 <script> 2 $(function () { 3 //01 获取整个页面中第一个li标签,并设置背景颜色 4 $("li:first").css("background","green"); 5 //02 获取整个页面中最后一个li标签,并设置背景颜色 6 $("li:last").css("background","green"); 7 //03 获取整个页面中所有的li标签,除了最后一个 8 $("li:not(:last)").css("background","green"); 9 //04 获取整个页面中所有的li标签,除了索引为2的之外 10 $("li:not(:eq(2))").css("background","green"); 11 //05 获取索引值为偶数的li标签 12 $("li:even").css("background","green"); 13 //06 获取索引值为奇数的li标签 14 $("li:odd").css("background","green"); 15 //07 获取索引值为4的li标签 16 $("li:eq(4)").css("background","green"); 17 //08 获取所有索引值大于4的li标签 18 $("li:gt(4)").css("background","green"); 19 //09 获取所有索引值小于4的li标签 20 $("li:lt(4)").css("background","green"); 21 }); 22 </script> 4.4.2 内容筛选选择器 内容筛选选择器根据元素中的文字内容或所包含的子元素特征获取元素,其文字内容可以模糊或绝对匹配进行元素定位。 选择器语法 功能 :contains(text) 获取包含给定文本的元素 :parent 获取含有子元素或者文本的元素 :empty 获取所有不包含子元素或文本的空元素 :has(selector) 获取含有选择器所匹配的元素 代码示例 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="jquery-3.1.1.js"></script> 7 <style> 8 div{ 9 width: 100px; 10 height: 40px; 11 } 12 </style> 13 </head> 14 <body> 15 <div>天王盖地虎</div> 16 <div><span>我是span</span></div> 17 <div>宝塔镇河妖</div> 18 <div></div> 19 <script> 20 $(function () { 21 //(1) 获取包含给定文本的元素 22 $("div:contains('天')").css("background","red"); 23 $("div:contains('塔')").css("background","green"); 24 //(2) 获取不包含子元素或文本的空元素 25 $("div:empty").css("background","red"); 26 //(3) 获取含有子元素或者是文本的元素 27 $("div:parent").css("background","yellow"); 28 //(4) 获取含有span子标签的div 29 $("div:has('span')").css("background","red"); 30 }) 31 </script> 32 </body> 33 </html> 4.4.3 属性筛选选择器 属性过滤选择器根据元素的某个属性获取元素,在使用的时候我们可以匹配单个属性也可以进行多个属性的匹配。 选择器语法 功能 [属性名] 获取包含给定属性的元素 [属性名1][属性名2] 获取满足多个条件的复合属性的元素 [属性名=’value’] 获取包含给定属性且等于指定值的元素 [属性名!=’value’] 获取包含给定属性且不等于指定值的元素 [属性名^=’value’] 获取包含给定属性且以指定字符开头的元素 [属性名$=’value’] 获取包含给定属性且以指定字符结尾的元素 [属性名*=’value’] 获取包含给定属性且包含指定字符或者是子串的元素 示例代码 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <div> 9 <a href="www.baidu.com">www.baidu.com</a><br> 10 <a href="www.jd.com">www.jd.com</a><br> 11 <a href="www.taobao.com">www.taobao.com</a><br> 12 <a href="www.520it.cn">www.520it.cn</a><br> 13 <a href="www.520it.com" title="demo">www.520it.com</a><br> 14 <a href="www.520it.com" title="Test">www.520it.com</a><br> 15 <a>我什么也不是</a> 16 </div> 17 <button>设置所有拥有href属性的a标签</button><br> 18 <button>设置harf属性值为www.baidu.com的a标签</button><br> 19 <button>设置harf属性值不为www.baidu.com的a标签</button><br> 20 <button>设置harf属性值以www开头的a标签</button><br> 21 <button>设置harf属性值以com结尾的a标签</button><br> 22 <button>设置harf属性值包含520的a标签</button><br> 23 <button>设置harf属性值中以www开头且title中包含demo的a标签</button><br> 24 <script src="js/jquery-3.2.1.js"></script> 25 <script> 26 $(function () { 27 $("button").eq(0).click(function () { 28 $("a[href]").css("background","green"); 29 }); 30 $("button").eq(1).click(function () { 31 $("a[href='www.baidu.com']").css("background","green"); 32 }); 33 $("button").eq(2).click(function () { 34 $("a[href!='www.baidu.com']").css("background","green"); 35 }); 36 $("button").eq(3).click(function () { 37 $("a[href^='www']").css("background","green"); 38 }); 39 $("button").eq(4).click(function () { 40 $("a[href$='com']").css("background","green"); 41 }); 42 $("button").eq(5).click(function () { 43 $("a[href*='520']").css("background","green"); 44 }); 45 $("button").eq(6).click(function () { 46 $("a[href^='www'][title='demo']").css("background","green"); 47 }); 48 }); 49 </script> 50 </body> 51 </html> 4.4.4 子元素筛选选择器 通过子元素筛选选择器可以方便轻松的获取父元素中指定的某个元素。 选择器语法 功能 :first-child 获取每个父元素下的第一个子元素 :last-child 获取每个父元素下的最后一个子元素 :only-child 获取每个父元素下的仅有一个子元素 :nth-child(eq-index) 获取每个父元素下特定位置的元素索引从1开始 4.4.5 可见性筛选选择器 可见性过滤选择器根据元素是否可见的特征获取元素,分为可见和不可见两种。 选择器语法 功能 :visible 获取所有的可见元素 :hidden 获取所有不可见元素,或者type为hidden的元素 4.4.6 表单对象属性筛选选择器 表单对象属性筛选选择器通过表单中某对象的属性特征获取该类元素,主要有enabled、disabled、checked(选中)elected等属性。 选择器语法 功能 :enabled 获取表单中所有属性为可用的元素 :disabled 获取表单中所有属性为不可用的元素 :checked 获取表单中所有被选中的元素 :selected 获取表单中所有被选中option的元素 4.4.5 可见性筛选选择器 可见性过滤选择器根据元素是否可见的特征获取元素,分为可见和不可见两种。 选择器语法 功能 :visible 获取所有的可见元素 :hidden 获取所有不可见元素,或者type为hidden的元素 4.5 表单选择器 表单在前端开发中是非常重要的标签,在显示和提交数据的数据经常需要用到,在 jQuery 框架中引入了表单选择器,该选择器专为表单量身打造,通过表单选择器可以在页面中快速定位某表单对象。 表单选择器的语法 选择器语法 功能 :file 获取所有文件域 :image 获取所有的图像域 :text 获取所有的单行文本框 :reset 获取所有重置按钮 :radio 获取所有单选框按钮 :button 获取所有按钮 :submit 获取所有提交按钮 :checkbox 获取所有的复选框 :password 获取所有的密码框 :input 获取所有的input、textarea、select标签 附录 jQuery选择器知识结构 Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
第三章 jQuery框架操作CSS 3.1 jQuery框架的CSS方法 jQuery框架提供了css方法,我们通过调用该方法传递对应的参数,可以方便的来批量设置标签的CSS样式。 使用JavaScript设置标签的样式相对来说比较麻烦,而如果需要批量的设置多个标签的样式那需要写很多代码,使用jQuery可以为我们简化该过程。 使用原生的方式来设置标签的样式(代码示例) 1 <body> 2 <div>我是div标签</div> 3 <button id="btnID">按钮</button> 4 <script> 5 window.onload = function () { 6 var oBtn = document.getElementById("btnID"); 7 oBtn.onclick = function () { 8 var oDiv = document.getElementsByTagName("div")[0]; 9 oDiv.style.height = "50px"; 10 oDiv.style.width = "200px"; 11 oDiv.style.background = "red"; 12 } 13 } 14 </script> 15 </body> 使用jQuery中的css方法来设置标签的样式(代码示例) 1 <body> 2 <div>我是div标签</div> 3 <button id="btnID">按钮</button> 4 <script src="jquery-3.2.1.js"></script> 5 <script> 6 $(function () { 7 $("#btnID").click(function () { 8 $("div").css("height","50px").css("width","200px").css("background","red"); 9 }) 10 }) 11 </script> 12 </body> CSS方法的语法 ① $("selector").css(name,value);② $("selector").css(name1,value).css(name2,value)...;③ $("selector").css( { name1 : value , name2 : value}) 代码示例 1 <script> 2 $(function () { 3 $("#btnID").click(function () { 4 //01 CSS方法的第一种使用方式 5 //$("div").css("height","50px"); 6 //$("div").css("width","200px"); 7 //$("div").css("background","red"); 8 //02 CSS方法的第二种使用方式:链式编程 9 //$("div").css("height","50px").css("width","200px").css("background","red"); 10 //03 CSS方法的第三种使用方式:传递样式键值对的对象作为参数 11 $("div").css({ 12 "height":"100px", 13 "width":"200px", 14 "background":"red" 15 }) 16 }) 17 }) 18 </script> 3.2 jQuery框架操作Class jQuery框架中操作class的方法主要有: addClass : 为选中的所有标签批量的添加ClasshasClass:检查选定的标签中是否存在指定的ClassremoveClass:把选定标签中指定的Class删除toggleClass: 切换Class addClass:为选中的所有标签批量的添加Class。 ① $("selector")addClass("class1");② $("selector")addClass("class1 class2");③ $("selector")addClass("class1").addClass("class2"); hasClass:检查选定的标签中是否存在指定的Class。 只要选中的所有标签中有一个标签存在该Class,那么就把返回true,如果选中的所有标签中都没有找到该class ,那么就返回false。 $("selector")hasClass("class1"); removeClass:把所有选定标签中指定的Class删除。 遍历jQuery实例对象中所有的标签,如果当前标签中存在该指定的class,那么就删除,如果不存在,则不作处理。 ① $("selector")removeClass("class1");② $("selector")removeClass("class1 class2");③ $("selector")removeClass("class1").removeClass("class2"); toggleClass:切换所有选中标签的Class。 如果当前标签中存在指定的Class,那么就删除,如果不存在,那么就添加。 ① $("selector")toggleClass("class1");② $("selector")toggleClass("class1 class2");③ $("selector")toggleClass("class1").toggleClass("class2"); 代码示例 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="JS/jquery-3.2.1.js"></script> 7 <style> 8 .class1{ 9 width: 400px; 10 height: 50px; 11 background: red; 12 } 13 .class2{ 14 width: 600px; 15 background: green; 16 border: 1px solid #000000; 17 } 18 </style> 19 </head> 20 <body> 21 <script> 22 $(function () { 23 //.... 24 //jQuery对Class的操作: 25 //添加class addClass 26 //删除class removeClass 27 //检查class hasClass 28 //切换class toggleClass 29 //01 添加class 30 $("button").eq(0).click(function () { 31 //console.log("点击"); 32 //获取指定的标签,并且设置class 33 //添加class: 34 //(1) jQ对象.addClass("类") 35 //(2) jQ对象.addClass("类1")..addClass("类2") 36 //(3) jQ对象.addClass("类1 类2") 37 //$("div").addClass("class1") 38 //$("div").addClass("class1").addClass("class2") 39 $("div").addClass("class1 class2") 40 }) 41 //02 检查class 42 $("button").eq(1).click(function () { 43 //console.log("点击"); 44 //获取指定的标签,并且设置class 45 //检查class:检查div标签中是否存在class1,如果存在那么就返回true,否则返回false 46 console.log($("div").hasClass("class1")); 47 console.log($("p").hasClass("demo1")); 48 }) 49 //03 删除class 50 $("button").eq(2).click(function () { 51 //console.log("点击"); 52 //获取指定的标签,并且设置class 53 //删除class: 54 //(1) jQ对象.removeClass("类") 55 //(2) jQ对象.removeClass("类1")..removeClass("类2") 56 //(3) jQ对象.removeClass("类1 类2") 57 //$("div").removeClass("class2") 58 //$("div").removeClass("class1").removeClass("class2") 59 $("div").removeClass("class1 class2") 60 }) 61 //04 切换class 62 $("button").eq(3).click(function () { 63 //console.log("点击"); 64 //获取指定的标签,并且设置class 65 //切换class:如果指定的class存在那么就删除,否则就添加 66 //(1) jQ对象.toggleClass("类") 67 //(2) jQ对象.toggleClass("类1 类2") 68 //$("div").toggleClass("class2") 69 $("div").toggleClass("class1 class2") 70 }) 71 }) 72 </script> 73 <div>我是div</div> 74 <p class="demo1"></p> 75 <p class="demo2"></p> 76 <button>添加</button> 77 <button>检查</button> 78 <button>删除</button> 79 <button>切换</button> 80 </body> 81 </html> 3.3 jQuery框架操作位置的方法介绍 ① width和height方法 $("selector").width()和$("selector").height()方法的使用:如果不传递参数则表示获取指定标签的宽度|高度,如果传递参数则表示要设置标签的宽度|高度。 ② offset和position方法 offset表示获取当前标签距离浏览器窗口的位置,而position获取当前标签距离父标签的位置 代码示例 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <script src="JS/jquery-3.2.1.js"></script> 7 <style> 8 .class1{ 9 width: 200px; 10 height: 200px; 11 background: rebeccapurple; 12 position: relative; 13 } 14 .class2{ 15 width: 50px; 16 height: 50px; 17 background: #2aa198; 18 left: 10px; 19 top:20px; 20 position: absolute; 21 } 22 </style> 23 </head> 24 <body> 25 <script> 26 $(function () { 27 //.... 28 //01 width和height: 29 //console.log($(".class2").get(0)); 30 //获取宽度和高度:不传递参数 31 console.log($(".class2").width()); 32 console.log($(".class2").height()); 33 //设置宽度和高度:传递参数 34 $(".class2").width(100); 35 $(".class2").height(100); 36 console.log($(".class2").width()); 37 console.log($(".class2").height()); 38 //02 位置:获取当前标签距离窗口的位置 offset 39 console.log($(".class2").offset().left); 40 console.log($(".class2").offset().top); 41 //03 位置:获取当前标签距离父标签的位置 position 42 console.log($(".class2").position().left); 43 console.log($(".class2").position().top); 44 }) 45 </script> 46 <div class="class1"> 47 <div class="class2"></div> 48 </div> 49 </body> 50 </html> Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
第二章 jQuery框架使用准备 2.1 jQuery框架和JavaScript加载模式对比 jQuery框架的加载模式 1 <script> 2 window.onload = function () { 3 console.log("window.onload——1") 4 }; 5 window.onload = function () { 6 console.log("window.onload——2") 7 } 8 </script> 打印结果:window.onload——2 JavaScript的加载模式 1 <script src="jquery-3.2.1.js"></script> 2 <script> 3 $(document).ready(function () { 4 console.log("$().ready——1") 5 }); 6 $(document).ready(function () { 7 console.log("ready——2") 8 }) 9 </script> 打印结果:ready——1 ready——2 两种加载模式对比 ① 监听时机不相同 window.onload方法需要等所有的资源(CSS\JS\图片等)都加载完毕后执行包裹代码。 jQuery框架的ready方法等DOM结构加载完毕后执行包裹代码。 ② 执行次数不相同 window.onload方法,N次只会执行一次,后面的会把前面的覆盖。 jQuery框架的ready方法,N次会执行N次,不存在覆盖的问题。 ③ 简写方式不相同 jQuery框架中的加载方式可以简写为: $().ready(function () {}) $(function () {}) 2.2 jQuery框架解决冲突 在实际的开发中,我们的项目中可能需要用到并引入多个第三方框架,如果这些框架本身在设计的时候,没有命名空间的约束,那么库与库之间发生冲突将在所难免。 jQuery框架在设计的使用,使用闭包的形式在所有的代码都封装到一个立即调用函数中,对外仅仅提供了美元符号和jQuery作为框架的入口。 jQuery当中所有的操作都是使用美元符号或者是jQuery对象进行的。假如,我们在使用jQuery框架之前已经在页面的代码中用到了美元符号,那么这种情况下,我们再按照常规的方式使用jQuery就可能会发生错误。 为了避免这种情况的发生,jQuery框架使用noConflict方法,可以在使用之前把美元符号替换成其它的标识符,相当于是给jQuery对象换个其他的名字。 代码示例 1 <script src="jquery-3.1.1.js"></script> 2 <script> 3 var $ = "文顶顶"; 4 $(function () { 5 console.log("DOM加载完毕"); 6 }) 7 </script> 代码说明 如果直接执行上面的代码,那么会报错。报错信息:Uncaught TypeError: $ is not a function报错原因:美元符号被定义为字符串,jQuery框架中美元符号被覆盖。 解决冲突(给jQuery框架安排新的代言人) 1 <script> 2 //在$符号被定义之前使用noConflict方法来重新设置名称 3 var jq = $.noConflict(); 4 // $符号可能被定义为字符串或其他数据 5 var $ = "文顶顶"; 6 //解决冲突之后,使用新设置的名称来操作 7 jq(function () { 8 console.log("DOM加载完毕"); 9 }) 10 </script> 2.3 jQuery对象和DOM对象的相互转换 DOM对象:每个HTML页面都是一个 DOM对象(Document Object Model,文本对象模型),通过传统的JavaScript方法访问页面中的元素,就是访问 DOM 对象。 jQuery对象:在 jQuery框架中,通过本身自带的方法获取页面元素的对象,称为 jQuery 对象 ; 备注:其实jQuery本身只是个工厂函数,我们通常意义上所说的jQuery实例对象其实是jQuery的原型对象上面的init方法创建出来的实例对象。即 jQuery对象 = new jQuery.prototype.init() , 只是因为init方法和jQuery构造函数共用相同的原型对象,因此我们才会称init构造函数创建出来的对象为jQuery实例。 代码示例 1 <body> 2 <div class="box">我是div</div> 3 <script src="jquery-3.2.1.js"></script> 4 <script> 5 //通过DOM提供的api获取页面中所有class为box的标签 6 var oDiv = document.getElementsByClassName("box"); 7 console.log(oDiv); 8 //通过jQuery提供的方法获取页面中所有class为box的标签 9 var ojQueryObj = $("div"); 10 console.log(ojQueryObj); 11 </script> 12 </body> DOM对象和jQuery对象的转换 DOM对象可以理解为标签对象,我们在操作这些标签的时候,有很多标签自带的方法可以使用,如innerHTML、innerText属性,或者是appendChild方法等。 jQuery对象可以理解为jQuery初始化方法这个构造函数创建的实例化对象,因为它的原型对象为jQuery.prototype,因此所有的jQuery实例对象都可以访问jQuery原型对象上面的成员[属性或方法],如html、text方法等。 注意: DOM对象不能直接访问jQuery原型对象上面的成员,jQuery对象也不能直接访问标签对象上面的成员,如需访问则应该先进行转换。 DOM标签对象 -> jQuery实例对象 $(DOM标签对象)jQuery实例对象 -> DOM标签对象 jQuery对象.get(index) | jQuery对象[index] 代码示例 1 <body> 2 <div class="box">我是div</div> 3 <script src="jquery-3.1.1.js"></script> 4 <script> 5 //通过DOM提供的api获取页面中所有class为box的标签 6 var oDiv = document.getElementsByClassName("box")[0]; 7 //通过jQuery提供的方法获取页面中所有class为box的标签 8 var ojQueryObj = $("div"); 9 //DOM -> jQuery 10 console.log($(oDiv).html()); 11 //jQuery -> DOM 12 console.log(ojQueryObj.get(0)); 13 console.log(ojQueryObj[0]); 14 </script> 15 </body> Posted by 博客园·文顶顶 ~ 文顶顶的个人博客_花田半亩 联系作者 简书·文顶顶 新浪微博·文顶顶 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 文顶顶
终于回到了熟悉的地方,亲切莫名。 太长时间没有更新博客,期间有很多原因。 有一段日子,我找了个新的地方(花田半亩)也写一些文章,可还是没有这里的味道。 以后的博文尽量做到两个站点同时更新。 这些年,几经波折,文顶顶已经老了,还好,博客尚在。 这些天,读到同乡诗人孙成龙先生的诗,现抄录如下。 杀爸爸过年 文/孙成龙 腊月十八 二狗子的尸体 从工地运回 亲人们手忙脚乱 给他剃光头 修胡须 刮体毛 洗身子 他的双眼 一直没有闭上 它们 正透过门缝 看着他三岁的儿子 哇哇大哭 我不吃肉了 我不要杀爸爸过年 最后,祝喜乐平安。
01 Carthage简单介绍 主页:https://github.com/Carthage/Carthage.git 作者:Justin Spahr-Summers等 版本:0.18 目标:用最简单的方式来管理Cocoa第三方框架 性质:第三方框架管理工具(类似于cocoapods) Carthage为用户管理第三方框架和依赖,但不会自动修改项目文件和生成配置,把对项目结构和设置的控制权交给用户。 原理:自动将第三方框架编程为Dynamic framework(动态库) 限制:仅支持iOS8+。它只支持框架,所以不能用来针对iOS8以前的系统版本进行开发 02 Carthage和cocoapods 1)使用了CocoaPods的项目是高度集成的,而Carthage更灵活强调尽可能将任务委托给Xcode和Git。 "CocoaPods在使用中会自动创建和更新workspace、依赖和Pod项目并进行整合; "Carthage在使用中不需要创建和集成相应的workspace和project,只需要依赖打包好的framework文件即可。 "总结一下,CocoaPods的方法更容易使用,而Carthage更灵活且对项目没有侵入性。 2)CocoaPods相对来说功能要比Carthage多很多,因此也更复杂,而CocoaPods配置简单项目干净。 3)CocoaPods有一个中心仓库,而Carthage是去中心化的,没有中心服务器也就避免了可能因中心节点错误而带来的失败,即Carthage每次配置和更新环境,只会去更新具体的库,时间更快。 4)想让自己的第三方库支持Carthage比让其支持CocoaPods更加的简单。 5)Carthage的不足 ① 库依然不如 CocoaPods 丰富 ② 仅支持iOS8+ ③ 工具尚且不如cocoapods晚上(已经发布了app) ④ 在使用第三方库的过程中无法查看源码 03 Carthage的安装和使用 1)直接下载Carthage.pkg安装包,安装运行 2)如果使用的XCode为7.0+版本,那么也可以使用下面的方法来安装 〇 安装homebrew $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" ① 升级brew $ brew update ② 使用brew来安装 $ brew install Carthage ③ 查看版本 $ Carthage version 04 Carthage的使用 1)先进入到项目所在文件夹 $ cd 项目路径 2)创建一个空的Carthage文件 $ touch Cartfile 3)编辑cartfile文件,例如要安装AFN框架 git "https://github.com/AFNetworking/AFNetworking.git" 4)保存并关闭cartfile文件,使用cartfile安装框架 $ Carthage update --platform iOS 5)打开Carthage 查看生产的文件目录 $ open Carthage 文件目录说明: "|| Carthage/Checkouts目录:从github获取的源代码 "|| Carthage/Build目录:编译出来的Framework二进制代码库 6) 配置项目 打开项目,点击Target -> Build Phases -> Link Library with Libraries选择Carthage/Build目录中要导入的framework 7)添加编译的脚本(该脚本文件保证在提交归档时会对相关文件和dSYMs进行复制) (1)点击Build Phases,点击“+” -> New Run Script Phase (2)添加添加脚本 /usr/local/bin/Carthage copy-frameworks (3)添加"Input Files" $(SRCROOT)/Carthage/Build/iOS/AFNetworking.framework 8)在项目中使用第三方库 #import <AFNetworking/AFNetworking.h> 其它: 卸载Carthage:$ brew uninstall Carthage 更新第三方框架: 更新多个框架:修改Cartfile文件,并重新执行 $ Carthage update 更新某个框架:$ Carthage update 具体的框架名称 05 Carthage的工作过程说明 ① 创建一个Cartfile文件,在该文件中列出您想使用的框架 ② 运行Carthage,获取并编译Cartfile文件中列出的框架 ③ 把框架的二进制文件配置到项目中
Bruce Eckel:编程生涯(转载) 说明:Bruce Eckel 著有大名鼎鼎的《Thinking in C++》和《Thinking in Java》。本文是他对程序员(尤其是新手)的忠告 正文如下: —— 大家总是问一个错误的问题:“我应该学习C++还是Java?”在本文中,我将告诉大伙儿:对于选择编程生涯真正需要关注的是哪些问题。请注意,这篇文章的目标读者并不是那些已经做出自己选择的人。(对于这些人而言)你会继续自己的编程生涯,而不管别人会怎么说。因为它已经渗透到你的血液中,你已经无法摆脱。你已经知道答案:C++、Java、Shell脚本、Python、还有其它一大堆的语言和技术,你都理所当然地会去学习。甚至有可能你才仅仅14岁,就已经知道好几种不同的语言。问我这样的问题的人可能来自其他行业,或者来自诸如Web开发之类的领域。他们知道HTML是一种类编程语言,而且想尝试构建某些更大型的应用。但我特别希望,当你在问这个问题时,你已经意识到了想要在计算机领域取得成功,你需要掌握自学能力,而且永不停息。在这个领域做得越多,我越觉得软件开发比任何行业都更接近于写作。 我们从来不知道是什么造就了优秀的作者,我们只知道什么时候我们会喜欢某个人的文字。编程不是一种工程,仅需要把东西从入口倒进去,然后再转动手柄。把软件开发看成确定性的,是一个诱人的想法。因为这个想法,人们总想搞出一些工具来帮我们开发出想要的软件。但是我的经验告诉我,事实并非如此——人的重要性远高于流程。而软件是否运行在一部精确的机器上已经越来越不重要了——这犹如测不准原理对人类的影响。我的父亲是造房子的,小时候我偶尔会帮忙打下手,放放砖块之类。他和他的木工告诉我,他们是为我好才让我干这些活——这样我就不至于走入这个行业。事实确实是这样。我们不妨把软件开发比作盖房子。造房子的人当然不可能完全一样。这些人里面有:混凝土工、屋顶工、管道工、电工、砖瓦工、水泥工、瓦片工、搬运工、粗木工、细木工。当然,还有工头。每个工种都需要相应的技能,这些技能都需要花时间和精力去掌握。跟软件开发一样,造房子也是一个“建立/推翻”的过程。如果你想很快地获得回报,你可能从搬运工和砖瓦工开始做,这样的话,你无需太多的学习曲线就可以获得回报。当需求很多时,你的工作会很稳固,甚至收入也可能提升——如果没有足够的人手的话。但是,一旦行情不妙,木匠甚至工头就可能把砖瓦工一脚踢开。当互联网刚刚兴起时,仅仅是花一点时间学习HTML,你就可以得到一份薪水丰厚的工作。但是当形势惨淡时,对于技能的要求更高了——HTML程序员(就像搬运工和砖瓦工一样)第一个被抛弃了,而拥有更高技能的程序员则留了下来。我想说的是: 除非你准备活到老学到老,不然的话,不要进入这个行业!编程看起来似乎是一个高收入而又稳定的工作。但要做到这一点,唯一的途径是:始终让自己更有价值。当然,你总能找到例外。总有那么一些人,仅仅学了一门编程语言,就可以胜任留在一个岗位上,而不需要增长他的技能。但他们只是幸免于难而已,他们最终无疑是很脆弱的。为了不让自己变得脆弱,你需要持续的提高自己,通过阅读、加入用户组、参加研讨会...... 你学得越深入,你就越有价值,也就意味着你有更好的职业前景,可以配得上更高的薪水。另一个方法是:先大致了解这个领域,找到最适合你的地方。打个比方:我的兄弟对软件很感兴趣,也入了这行,只不过他的工作是安装、维修、升级电脑。他总是一丝不苟,所以当他把电脑搞好,一定会很完美——不光是软件,连电线都会被仔细地捆好。他总是生意兴隆,远超出他的精力所能及。他甚至都不用担心 .com 泡沫的崩溃。显然他的饭碗不容易被抢走。我在高校里待了很久,甚至还在UCLA(加州大学洛杉矶分校)进修博士学位,后来又幸运地终止了。我说“幸运”是因为我不再喜欢呆在学校,而我之前在高校待了那么久,只是因为我很享受它。但我所享受的,基本上是不务正业的东西——艺术和舞蹈课,在校报工作,还有一小撮计算机课程(之所以说计算机课程“不务正业”,是因为我本科是物理专业,研究生才是计算机专业)。虽然我在学术上远谈不上卓越(有意思的是很多当时也许不会接受我这个学生的学校现在却用我的书做教材)。我真的很享受作为学生的日子,当我完成博士课程,也许会以一个教授的身份终老一生。但就如现在看到的,我在学校里最大的收获恰恰来自我那些“不务正业”的课程,它们拓展了我的思维,使之超越了“我们已经知道的东西”。在计算机领域中,你总是为某种目标而编程。你对目标了解得越多,你就做得越好。我遇到过一些欧洲的研究生,他们需要结合其它专业领域来搞编程,他们的论文需要解决这个专业领域的特定的问题。了解编程之外的领域,将会极大得提高你解决问题的能力 (就如同多学几种编程语言将极大地提高你的编程技能)。很多时候,我发现仅仅学习计算机专业的学生,比那些(除了计算机之外)拥有其它背景的学生,在思维上有更多的局限性。因为后者有着更严谨的思维,也不那么容易想当然。有一次我组织了一次会议,其中一个议题是:理想的应聘者有哪些特征:◇把学习当成生活方式。比如:你应该知道不止一种语言,没有什么比学习一门新语言更能让你开阔眼界了。◇知道如何获取知识◇Study prior art◇善用工具◇学会把事情简化◇理解业务◇为自己的错误负责。“我就是这样的”是不能接受的托词。能找到自己的失误。◇成为一个领导者,善于沟通和激励。◇搞清楚你在为谁服务◇没有绝对正确的答案(更好的方法总是存在的)。展示并讨论你的代码,不要带着感情因素——你的代码并不等于你本人。◇明白完美是渐进的适当尝试一些冒险——尤其是能令人感到害怕的冒险。当你尝试之后,将体会到出乎意料的兴奋。(在冒险的过程中)最好不要刻意去计划某个特定的结果。当你过于注重结果,你往往会错过那些真正有价值的问题。我的冒险往往是这样开始的——“我们先做些试验,看看它会把我们带到什么地方”。或许某些人会对我的回答感到失望,并回复我说:“是的,这很有趣也很有用。但我到底应该学什么?C++还是Java?” 我再重复一次:并不是所有的问题都有一个唯一的简单的答案。问题的关键不在于选择某个编程语言,然后掌握之。问题的关键在于:持续学习,并且很多时候,有不止一个选择。 相信我所说的,你的生活会更精彩!原始出处:http://www.artima.com/weblogs/viewpost.jsp?thread=259358
iOS开发拓展篇——如何把项目托管到GitHub 说明:本文主要介绍如何把一个OC项目托管到Github,重操作轻理论。 第一步:先注册一个Github的账号,这是必须的 注册地址:Github官网注册入口 第二步:准备工作 gitHub网站使用Git版本管理工具来对仓库进行管理,注意它们并不等同。 gitHub是全球最大的第三方开源库集散地,Git是一款分布式的版本管理控制工具(除了git之外还有一些其他的版本管理控制工具如SVN等)。 关于Git的基本介绍以及基本使用这里不会做更多介绍,如有需要请参考:Git教程。 关于Git使用的相关书籍:Git版本控制管理(第2版)| Git权威指南(电子版下载地址) 关于Github的介绍和说明可以参考书籍GitHub入门与实践(部分内容已经过时暂无电子版提供下载)或者直接通过官网的帮助页面查看。 第三步:在github上面建立空的仓库(repositories) 我们假设你已经拥有了一个github的账号,并且登录成功,那么你将能够找到下面这样的一个界面。 简单说明下: 其中第1块区域会显示你的个人信息,比如昵称,头像,联系方式等等。 第2块区域展示的相关的一些第三方库。 第3块区域显示你在github网站上面的活跃度,分别以各色的方块来表示,其中一个小格子表示一天(人生说白了就是三万多个小格子,说到这里赶紧算算还剩几个没用的),颜色越深表示活跃度越高。 第4块区域可以做一些配置操作等等的。 看到这个界面之后,点击上图箭头所指的+,选择new repositories选项会跳转到如下界面,简单配置后即可创建一个空的仓库。 上图中使用红色线条框起来的地方是需要注意的,在创建仓库的时候需要对该仓库进行简单的配置,如仓库名称等等的。 其中repositories name处需要填写的是仓库的名称。 Description (optional)处填写对该仓库的简单描述,主要是介绍下仓库的功能等(当然如果比较懒的话可以不写)。 接下来是两个选项(Public|private)表示你创建的仓库是公开的还是私有的。Public和private有什么不同呢?如果仓库是公开的那么意味着任何人都可以无条件的获得你整个仓库的内容,免费。如果仓库是私有的,那么别人无法访问你的私有仓库,条件是需要花钱交保护费,保护费不贵每月7$而已。在这里我仅做演示选择public仓库即可。 Initialize this repository with a README 表示在初始化仓库的时候,是否生成一个readMe文件。我们在查看别人框架的时候,在框架主页上会有对该框架版本信息,作用使用方法等等的介绍,这个文件就是readMe文件,在这里选择勾上。 Add .gitignore按钮,点击之后会出现一个下拉框,问你是否要设置仓库的忽略文件。这个看你自己的需要,通常如果你的仓库和代码项目有关系,那么最好选择相应的忽略文件(如OC项目可以选择Object-C,swift项目可以选择Swift),至于为什么请参考GIT的基本使用。 Add a license按钮,点击之后会出现一个下拉框,需要你选择一种开源协议,开源协议有很多种用的比较多的有MIT的或者是Apache的,不同的开源协议对项目的使用方式等有不同的规定,详情可以参考Choose an open source license。 上面的信息都设置好之后,接下来只需要轻轻点击Create respository按钮即可创建一个空的仓库。 第四步:了解仓库主界面 至此,一个空的仓库就已经创建完成了,你将看到如下界面。 这里先简单介绍下这个仓库的页面信息。 其中第1块区域是该仓库的名称,你创建仓库后别人可以通过搜索仓库名的方式找到该仓库。 第2块区域是和仓库相关的一些信息,比如Code选项列出仓库的内容(文件),ISSues选项会列出其他人对该仓库的疑问,Pull Request选项提供其他开发者在对该仓库进行维护时请求合并的入口,Setting选项列出对该仓库可以进行的一些操作如删除等等。 第3块区域列出该仓库的提交次数、tag版本等等。 第4块区域列出该仓库的内容,当前仓库中只有三个文件,分别是.gitignore忽略文件,LICENSE开源协议以及初始化生成的ReadMe文件。在该区域右上角有一个clone or download按钮,点击Download Zip按钮即可把该仓库下载到本地。 第5块区域展示ReadMe.md文件的内容。 第6块区域列出有多少人关注该仓库,仓库的受欢迎程度,以及被fork的次数等。我们看一个框架是否受欢迎可以通过查看该仓库Star的个数以及被fork的次数。 上面是对github自动生成空白仓库进行的简单介绍,有了这个空白的仓库之后我们才可以进行后面的操作。 第五步:连接远程仓库 连接远程仓库的方式有很多种,可以使用第三方的GIT管理图形界面工具如sourceTree,也可以使用终端(命令行),或者是XCode。在这里就选择使用XCode来完成该操作。 把MAC 上面的XCode打开,打开之后,左上角菜单选择Xcode->Preference选项,如下图所示: 进入到XCode的配置信息窗口。 切换到Accounts菜单,点击左下角的+号,选择Add Respository添加仓库。出现如下界面,需要进行配置。 Address需要输入要连接的远程仓库地址,其实要连接远程仓库有两种方式可以选择一种是HTTPS请求的,一种是SSH密钥对。在这里,我们先讲解HTTPS请求应该如何处理,如何获得仓库地址呢?选择clone with https后拷贝输出框中地址,如下图所示: Type:表示使用的版本管理方式可以选择GIT和SVN,在这里只能选择Git(因为GIThub只支持git的方式来管理) Authentication:表示认证的方式即验证身份的,Github提供两种验证方式,HTTPS和SSH,我们当前选择的是HTTPS的方式,所以此处选择User Name and Password. User Name 和passWord两处填入github网站登录的用户名和密码即可。 配置完成之后,点击add按钮,出现下面的窗口,则说明连接仓库成功。 第六步:把远程仓库下载到本地 连接上远程仓库之后,接下来我们把远程仓库下载到本地,如图选择菜单栏的source control->check out。 之后会弹出如下界面,找到要下载的仓库(通常在最后),点击Next 点击Next按钮之后,需要你选择仓库下载后的存储位置,然后点击Finsh完成即可。把仓库下载完成之后,可以发现内部有一个.git的隐藏文件,即git的版本库。 补充:可以在终端输入如下命令来显示或者是不显示隐藏文件 # 显示隐藏文件 $ defaults write com.apple.finder AppleShowAllFiles Yes && killall Finder # 不显示隐藏文件$ defaults write com.apple.finder AppleShowAllFiles No && killall Finder 第七步:添加OC项目到仓库中并推送到远程服务器 1)首先先新建一个项目,假设为Test,项目创建后保存到仓库路径下(和.git文件同级)如下图 2)打开项目,会发现在项目的导航栏中各个文件会显示不同的状态如?M等等,通过这种方法来初始化项目,项目本身已经被纳入到git版本库的管理范畴。 3)把修改提交到本地并推送到远程服务器。 XCode本身已经对git进行了很好的集成,点击菜单栏上面的source control选项可以看到如下视图: 其中check out 等同于git 命令行中的clone指令,用于把远程仓库下载到本地。 commit 等同于git命令行中的commit指令(无差别),用于把更改提交到本地git版本库。 push等同于git命令行中的push指令(无差别),用于把更改提交到远程的git仓库中。 pull指令等同于git命令行中pull指令(无差别),用于获取得到最新的远程仓库信息。 此处,我们需要先选择commit选项,先把更改提交到本地的版本库。选择commit之后出现如下界面: 其中,第1个区域表示要提交的内容,第2个区域输入对此次提交的说明(注释),第3个区域可以选择勾上,如果勾上的话,那么当点击右下角按钮的时候,会先把所有的更改提交到本地的git版本库,然后再把更改推送到远程的git仓库。 点击提交之后,即可刷新github上面仓库的主页,此时显示有新的更新,并且项目提交已经完成。如下图所示,点击可以查看项目文件。 第八步:命令行操作 上面所有演示都没有涉及到命令行,那如果不使用XCode而是使用命令行,应该如何处理增加、删除、提交等常见操作呢? 1)新创建文件,需要把创建的文件提交到本地的git版本库。 把终端打开,cd进入到仓库路径,然后创建一个NewFile文件,如下图所示: 新创建了NewFile文件之后,该文件默认并不会被git版本库管理(可以使用git status命令查看),需要使用add 命令先把指定的文件添加到git的暂缓区,然后再提交到git的版本库,如下图所示。 命令说明: $ ls -l 以列表的方式显示当前目录下面的文件(NewFile文件为刚刚创建的) $ git status 查看git仓库文件状态 (NewFile文件状态为红色,表示未被git管理) $ git add NewFile 把NewFile文件添加到git的暂缓区中(如果需要一次性提交多个文件,那么可以使用git add .命令) $ git status 重新查看状态(此时NewFile文件状态为绿色,表示更改已经提交到了暂缓区) $ git commit -m "创建了NewFile文件" NewFile 表示把暂缓区中NewFile文件对应的更改提交到本地的git版本库(如果要一次性提交所有的更改,那么可以直接把具体的文件省略即可) 2)把文件提交到远程(github)的仓库 说明:使用git push指令把本地的更改推送到远程仓库。 此时重新刷新下github网站上该仓库的主页,可以发现NewFile文件已经提交。 3)修改文件,并把修改操作提交到远程仓库。 假设我们修改了NewFile文件,然后把修改提交到git暂缓区,提交到本地的git版本库之后,再推送到远程的仓库。 第九步:其他操作(tag标记) 如果你的项目已经完成了里程碑开发,那么可以确定为一个新的版本,比如说发布为正式的1.0版本,而后续可能接着发布1.1.0或者是2.0版本等等,那么使用github托管的项目如何确定为一个新的版本呢?或者是如何进行tag标记。 如果仓库没有发布特定的版本,那么release处显示为0. 假设当前的项目已经完成了阶段性的开发,需要正式确定为Test1.0版本,那么应该如何处理?如下图所示: 上面的命令行~ (1)先使用git tag指令查看当前被打上tag标签的版本,最开始的时候无 (2)然后把当前的仓库打上tag标签,为Test1.0版本,并添加注释 (3)再使用git tag指令查看,即可以发现有Test1.0版本 确定了新版本之后,还需要把标签推送到远程服务器,命令行为git push origin 标签名,具体操作如下图所示: 推送到远程仓库之后,刷新github上面该仓库主页,发现release处变为1,点击该按钮可以看到如下界面(提供标记版本的压缩下载)
iOS开发Swift篇(02) NSThread线程相关简单说明 一 说明 1)关于多线程部分的理论知识和OC实现,在之前的博文中已经写明,所以这里不再说明。 2)该文仅仅简单讲解NSThread在swift语境中的一些使用和注意点,别他。 3)本文涉及代码可以从https://github.com/HanGangAndHanMeimei/Code地址获得。 二 NSThread的基本使用和创建 1)基本用法(主线程|当前线程) 1 //1.获得执行该方法的当前线程 2 let currentThread = NSThread.currentThread() 3 print("当前线程为\(currentThread)") 4 5 //2.获得应用程序的主线程 6 let mainThread = NSThread.mainThread() 7 print("应用程序的主线程\(mainThread)") 8 9 //3.判断当前线程是否是主线程 10 let isMain = NSThread.isMainThread() 2)创建线程 说明:此处列出创建线程的四种方法:分别是 直接创建|分离出一条子线程|创建一条后台线程|自定义线程类继承自NSThread重写内部的main方法封装任务,然后init创建。 1 //NSThread创建线程的四种方式 2 func createNewThreadWithNSThreadMethodOne() 3 { 4 //1.创建线程 5 let thread = NSThread.init(target: self, selector:Selector("run"), object: nil) 6 7 //设置线程的名称 8 thread.name = "线程A" 9 10 //2.启动线程 11 thread.start() 12 } 13 14 func createNewThreadWithNSThreadMethodTwo() 15 { 16 //分离出一条子线程,自动启动线程,但无法获得线程对象 17 NSThread.detachNewThreadSelector(Selector("run"), toTarget: self, withObject: nil) 18 } 19 20 func createNewThreadWithNSThreadMethodThree() 21 { 22 //开启一条后台线程,自动启动线程,但无法获得线程对象 23 self.performSelectorInBackground(Selector("run"), withObject: nil); 24 } 25 26 func createNewThreadWithNSThreadMethodFour() 27 { 28 //let thread = CustomThread.init(target: self, selector:Selector("run"), object: nil) 29 let thread = CustomThread(); 30 thread.start() 31 } 32 33 func run() 34 { 35 //获得当前执行run方法的线程 36 let thread = NSThread.currentThread() 37 print("run--\(thread.name)-\(thread)"); 38 } 三 NSThread线程的状态和线程安全 1)线程的状态 线程的状态:新建-就绪-运行-阻塞-死亡 1 //线程的退出 2 NSThread.exit() 3 //线程的休眠1 4 NSThread.sleepForTimeInterval(2.0) 5 //线程的休眠2 6 NSThread.sleepUntilDate(NSDate.init(timeIntervalSinceNow: 3.0)) 2)线程安全 说明:多线程访问同一个资源的时候可能会出现数据错乱等安全问题,解决方法是对必要的代码段进行加锁。 注意:在OC中加互斥锁使用@synchronized(self) {},在swift可以使用objc_sync_enter(self)和objc_sync_exit(self)方法,注意这两个方法必须成对使用,把要加锁的代码放在中间 1 class ViewController: UIViewController { 2 3 //设置总票数为100张 4 var totalTickets = 100 5 6 override func viewDidLoad() { 7 super.viewDidLoad() 8 9 //多线程访问资源加锁 10 //创建三条线程分别代表售票员A、售票员B、售票员C 11 let thread01 = NSThread.init(target: self, selector:Selector("saleTickect"), object: nil) 12 let thread02 = NSThread.init(target: self, selector: Selector("saleTickect"), object: nil); 13 let thread03 = NSThread.init(target: self, selector: Selector("saleTickect"), object: nil); 14 15 //设置线程的名称 16 thread01.name = "售票员A" 17 thread02.name = "售票员B" 18 thread03.name = "售票员C" 19 20 //开启线程 21 thread01.start() 22 thread02.start() 23 thread03.start() 24 25 } 26 27 //模拟售票的函数 28 func saleTickect() 29 { 30 while(true) 31 { 32 //加互斥锁 33 /* 34 * 1)同OC中的@synchronized(self) {} 35 * 2)objc_sync_enter(self)和objc_sync_exit(self)必须成对使用,把要加锁的代码放在中间 36 */ 37 38 objc_sync_enter(self) 39 40 //检查是否有余票,如果有则卖出去一张 41 let temp = totalTickets 42 for var i=0;i<100000;i++ 43 { 44 //空的for循环,模拟延迟 45 } 46 47 if(temp>0) 48 { 49 totalTickets = temp - 1 50 print("\(NSThread.currentThread().name)卖出去了一张票,还剩\(totalTickets)") 51 }else 52 { 53 print("\(NSThread.currentThread().name)发现票已经卖完了") 54 break; 55 } 56 57 objc_sync_exit(self) 58 } 59 60 } 61 62 } 线程安全Code示例 三 NSThread线程间通信 1)说明 所谓线程间通信,即如何从一个线程进入到另一个线程继续执行任务或者是传递参数(如从子线程回到主线程) 下面的代码示例演示在主线程中先创建一个子线程下载图片,当图片下载完成后又切换到主线程设置图片的操作。 1 //!!!注意,该案例内部下载图片,发送了http请求需要修改info.plist文件 2 class ViewController: UIViewController { 3 4 @IBOutlet weak var imageView: UIImageView! 5 6 override func viewDidLoad() { 7 super.viewDidLoad() 8 9 //程序启动后开子线程下载图片,图片下载完成之后回到主线程设置图片 10 NSThread.detachNewThreadSelector(Selector("downloadImage"), toTarget: self, withObject: nil) 11 } 12 13 func downloadImage() 14 { 15 //1.获得要下载图片的url 16 let url = NSURL.init(string: "http://p9.qhimg.com/t014d1bd470cb60ac6e.jpg") 17 18 //2.把url地址指向资源的二进制下载到本地 19 let imageData = NSData.init(contentsOfURL: url!) 20 21 //3.把二进制数据转换为图片 22 let image = UIImage.init(data: imageData!); 23 24 //4.打印查看当前线程(应该是在子线程中下载图片) 25 print("当前线程为\(NSThread.currentThread())") 26 27 //5.线程间通信 28 //方法一 29 self.performSelectorOnMainThread(Selector("showImage:"), withObject: image, waitUntilDone:true) 30 //方法二 31 //imageView.performSelectorOnMainThread(Selector("setImage:"), withObject: image, waitUntilDone:true) 32 } 33 34 35 func showImage(image:UIImage) 36 { 37 //设置图片 38 imageView.image = image 39 40 //打印查看设置图片操作的线程 41 print("处理UI刷新操作的线程\(NSThread.currentThread())") 42 43 } 44 } 线程间通信示例Code
Perl语言——简单说明 一、简单说明 Perl语言全称:实用摘录与报表语言|病态折中式垃圾列表器。Perl名称并不是缩写词,而是个溯写字。 Perl语言历史:Larry Wall(拉里·沃尔)20世纪80年代中期 适合处理的任务:约有90%和文字处理有关,10%与其它事物有关的问题。 在MAC OSX系统上面默认已经自带提供了Perl编译器,可以把终端打开后,通过perl -v指令查看当前安装的Perl版本。 经测试发现是5.18 二、第一个Perl程序 要求:输出hello Word wendingding! 1)新建一个文本文件 如hello.pl 。说明文件的后缀随便写。 2)在该文本文件中编写程序文本如下: #!usr/bin/perl print "Hello word Wendingding!\n"; 图示: 3)打开终端,进入到hello.pl文件路径 4)执行终端命令授权:chmod u+x hello.pl 5)编译和运行程序:perl hello.pl 6)输入上面的指令后,命令行会输出hello Word wendingding!字样。 图示: 说明: 1)建议和鼓励适当使用缩排以增加程序的阅读性。 2)注释可以对代码的作用提供简单说明,注释为从#开始一直到行末尾的部分。
iOS开发Swift篇(01) 变量&常量&元组 说明: 1)终于要写一写swift了。其实早在14年就已经写了swift的部分博客,无奈时过境迁,此时早已不同往昔了。另外,对于14年部分iOS开发Swift篇专题的博文也不再做任何的校正和更新,特此说明。 2)该博文对应代码可以在https://github.com/HanGangAndHanMeimei/Code获得。 一、变量和常量 01 变量和常量的定义 在swift中变量使用var来修饰,常量使用let来修饰,变量可以修改而常量不能被修改。 变量:var 常量:let 格式:修饰符(var|let) 变量|常量名称:数据类型 = 值 注意:在使用swift开发过程中,一般情况下先使用let,只有该数据需要修改的时候再修改为var,可以保证数据安全性。 02 类型推导 在swift中如果在变量|常量定义的时候进行初始化,那么数据类型可以省略不写,系统会自动推导出该变量|常量的数据类型。 建议:在开发中应该尽量使用自动推导(除非是需要明确指定数据长度或者是需要先定义后初始化),可以最大化的降低代码的冗余。 03 类型转换 在OC中有显示转换和隐式的类型转换,但在swift中没有隐式类型转换,只有相同类型的数据才能进行赋值或运算。 二、元组 元组是一种复合的数据类型,只要将多个数据(可以是相同或不同数据类型的)使用一夜()括起来就称之为一个元组。 元组的优点在于,可以方便的实现函数返回多个值。 元组的定义格式:常量|变量修饰符 常量|变量的名称:(数据类型1,数据类型2,数据类型3)=(值1,值2,值3)
iOS开发实用技巧—Objective-C中的各种遍历(迭代)方式 说明: 1)该文简短介绍在iOS开发中遍历字典、数组和集合的几种常见方式。 2)该文对应的代码可以在下面的地址获得:https://github.com/HanGangAndHanMeimei/Code 一、使用for循环 要遍历字典、数组或者是集合,for循环是最简单也用的比较多的方法,示例如下: 1 //普通的for循环遍历 2 -(void)iteratorWithFor 3 { 4 //////////处理数组////////// 5 NSArray *arrayM = @[@"1",@"2",@"3",@"4"]; 6 NSInteger arrayMCount = [arrayM count]; 7 for (int i = 0; i<arrayMCount; i++) { 8 NSString *obj = arrayM[i]; 9 NSLog(@"%@",obj); 10 } 11 12 //////////处理字典////////// 13 NSDictionary *dictM = @{@"1":@"one",@"2":@"two",@"3":@"three"}; 14 NSArray *dictKeysArray = [dictM allKeys]; 15 for (int i = 0; i<dictKeysArray.count; i++) { 16 NSString *key = dictKeysArray[i]; 17 NSString *obj = [dictM objectForKey:key]; 18 NSLog(@"%@:%@",key,obj); 19 } 20 21 //////////处理集合////////// 22 NSSet * setM = [[NSSet alloc] initWithObjects:@"one",@"two",@"three",@"four", nil]; 23 NSArray *setObjArray = [setM allObjects]; 24 for (int i = 0; i<setObjArray.count; i++) { 25 NSString *obj = setObjArray[i]; 26 NSLog(@"%@",obj); 27 } 28 29 //////////反向遍历----降序遍历----以数组为例 30 NSArray *arrayM2 = @[@"1",@"2",@"3",@"4"]; 31 NSInteger arrayMCount2 = [arrayM2 count] - 1; 32 33 for (NSInteger i = arrayMCount2; i>0; i--) { 34 NSString *obj = arrayM2[i]; 35 NSLog(@"%@",obj); 36 } 37 } 优点:简单 缺点:由于字典和集合内部是无序的,导致我们在遍历字典和集合的时候需要借助一个新的『数组』作为中介来处理,多出了一部分开销。 二、使用NSEnumerator遍历 NSEnumerator的使用和基本的for循环类似,不过代码量要大一些。示例如下: 1 //使用NSEnumerator遍历 2 -(void)iteratorWithEnumerator 3 { 4 //////////处理数组////////// 5 NSArray *arrayM = @[@"1",@"2",@"3",@"4"]; 6 NSEnumerator *arrayEnumerator = [arrayM objectEnumerator]; 7 NSString *obj; 8 while ((obj = [arrayEnumerator nextObject]) != nil) { 9 NSLog(@"%@",obj); 10 } 11 12 //////////处理字典////////// 13 NSDictionary *dictM = @{@"1":@"one",@"2":@"two",@"3":@"three"}; 14 NSEnumerator *dictEnumerator = [dictM keyEnumerator]; 15 NSString *key; 16 while ((key = [dictEnumerator nextObject]) != nil) { 17 NSString *obj = dictM[key]; 18 NSLog(@"%@",obj); 19 } 20 21 22 //////////处理集合////////// 23 NSSet * setM = [[NSSet alloc] initWithObjects:@"one",@"two",@"three",@"four", nil]; 24 NSEnumerator *setEnumerator = [setM objectEnumerator]; 25 NSString *setObj; 26 while ((setObj = [setEnumerator nextObject]) != nil) { 27 NSLog(@"%@",setObj); 28 } 29 30 31 //////////反向遍历----降序遍历----以数组为例 32 NSArray *arrayM2 = @[@"1",@"2",@"3",@"4"]; 33 NSEnumerator *arrayEnumerator2 = [arrayM2 reverseObjectEnumerator]; 34 NSString *obj2; 35 while ((obj2 = [arrayEnumerator2 nextObject]) != nil) { 36 NSLog(@"%@",obj2); 37 } 38 39 } 优点:对于不同的数据类型,遍历的语法相似;内部可以简单的通过reverseObjectEnumerator设置进行反向遍历。 缺点:代码量稍大。 三、使用for...In遍历 在Objective-C 2.0 中增加了for ...In 形式的快速遍历。此种遍历方式语法简洁,速度飞快。示例如下: 1 //使用for...In进行快速遍历 2 -(void)iteratorWithForIn 3 { 4 //////////处理数组////////// 5 NSArray *arrayM = @[@"1",@"2",@"3",@"4"]; 6 for (id obj in arrayM) { 7 NSLog(@"%@",obj); 8 } 9 10 //////////处理字典////////// 11 NSDictionary *dictM = @{@"1":@"one",@"2":@"two",@"3":@"three"}; 12 for (id obj in dictM) { 13 NSLog(@"%@",dictM[obj]); 14 } 15 16 //////////处理集合////////// 17 NSSet * setM = [[NSSet alloc] initWithObjects:@"one",@"two",@"three",@"four", nil]; 18 for (id obj in setM) { 19 NSLog(@"%@",obj); 20 } 21 22 //////////反向遍历----降序遍历----以数组为例 23 NSArray *arrayM2 = @[@"1",@"2",@"3",@"4"]; 24 for (id obj in [arrayM2 reverseObjectEnumerator]) { 25 NSLog(@"%@",obj); 26 } 27 } 优点:1)语法简洁;2)效率最高; 缺点:无法获得当前遍历操作所针对的下标。 四、基于Block的遍历方式 基于Block的方式来进行遍历是最新引入的方法。它提供了遍历数组|字典等类型数据的最佳实践。示例如下: 1 //基于块(block)的遍历方式 2 -(void)iteratorWithBlock 3 { 4 //////////处理数组////////// 5 NSArray *arrayM = @[@"1",@"2",@"3",@"4"]; 6 [arrayM enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 7 NSLog(@"%zd--%@",idx,obj); 8 }]; 9 10 //////////处理字典////////// 11 NSDictionary *dictM = @{@"1":@"one",@"2":@"two",@"3":@"three"}; 12 [dictM enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { 13 NSLog(@"%@:%@",key,obj); 14 }]; 15 16 //////////处理集合////////// 17 NSSet * setM = [[NSSet alloc] initWithObjects:@"one",@"two",@"three",@"four", nil]; 18 [setM enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) { 19 NSLog(@"%@",obj); 20 }]; 21 22 //////////反向遍历----降序遍历----以数组为例 23 NSArray *arrayM2 = @[@"1",@"2",@"3",@"4"]; 24 [arrayM2 enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 25 NSLog(@"%zd--%@",idx,obj); 26 }]; 27 } 优点:1)遍历时可以直接从block中获得需要的所有信息,包括下标、值等。特别相对于字典而言,不需要做多余的编码即可同时获得key和value的值。 2)能够直接修改block中key或者obj的类型为真实类型,可以省去类型转换的工作。 3)可以通过NSEnumerationConcurrent枚举值开启并发迭代功能。 说明:基于Block的遍历方式在实现反向遍历的时候也非常简单,使用enumerateObjectsWithOptions方法,传递NSEnumerationReverse作为参数即可,在处理遍历操作的时候推荐基于Block的遍历方式。 五、使GCD中的dispatch_apply函数 使用GCD中的dispatch_apply函数也能实现字典、数组等的遍历,该函数比较适合处理耗时较长、迭代次数较多的情况。示例如下: 1 //使用GCD中的dispatch_apply函数 2 -(void)iteratorWithApply 3 { 4 //////////处理数组////////// 5 NSArray *arrayM = @[@"1",@"2",@"3",@"4"]; 6 7 //获得全局并发队列 8 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); 9 10 dispatch_apply(arrayM.count, queue, ^(size_t index) { 11 NSLog(@"%@--%@",arrayM[index],[NSThread currentThread]); 12 }); 13 } 优点:开启多条线程并发处理遍历任务,执行效率高。 缺点:1)对于字典和集合的处理需借助数组;2)无法实现反向遍历。
iOS开发网络篇—发送GET和POST请求(使用NSURLSession) 说明: 1)该文主要介绍如何使用NSURLSession来发送GET请求和POST请求 2)本文将不再讲解NSURLConnection的使用,如有需要了解NSURLConnection如何发送请求。 详细信息,请参考:http://www.cnblogs.com/wendingding/p/3813706.html 3)本文示例代码发送的请求均为http请求,已经对info.plist文件进行配置。 如何配置,请参考:https://github.com/HanGangAndHanMeimei/iOS9AdaptationTips 4)本文示例代码,可以在下面的地址获取: https://github.com/HanGangAndHanMeimei/Code 一、简单说明 在iOS9.0之后,以前使用的NSURLConnection过期,苹果推荐使用NSURLSession来替换NSURLConnection完成网路请求相关操作。 NSURLSession的使用非常简单,先根据会话对象创建一个请求Task,然后执行该Task即可。 NSURLSessionTask本身是一个抽象类,在使用的时候,通常是根据具体的需求使用它的几个子类。关系如下: 二、发送GET请求 使用NSURLSession发送GET请求的方法和NSURLConnection类似,整个过程如下: 1)确定请求路径(一般由公司的后台开发人员以接口文档的方式提供),GET请求参数直接跟在URL后面 2)创建请求对象(默认包含了请求头和请求方法【GET】),此步骤可以省略 3)创建会话对象(NSURLSession) 4)根据会话对象创建请求任务(NSURLSessionDataTask) 5)执行Task 6)当得到服务器返回的响应后,解析数据(XML|JSON|HTTP) 示例代码: 1 -(void)get1 2 { 3 //对请求路径的说明 4 //http://120.25.226.186:32812/login?username=520it&pwd=520&type=JSON 5 //协议头+主机地址+接口名称+?+参数1&参数2&参数3 6 //协议头(http://)+主机地址(120.25.226.186:32812)+接口名称(login)+?+参数1(username=520it)&参数2(pwd=520)&参数3(type=JSON) 7 //GET请求,直接把请求参数跟在URL的后面以?隔开,多个参数之间以&符号拼接 8 9 //1.确定请求路径 10 NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"]; 11 12 //2.创建请求对象 13 //请求对象内部默认已经包含了请求头和请求方法(GET) 14 NSURLRequest *request = [NSURLRequest requestWithURL:url]; 15 16 //3.获得会话对象 17 NSURLSession *session = [NSURLSession sharedSession]; 18 19 //4.根据会话对象创建一个Task(发送请求) 20 /* 21 第一个参数:请求对象 22 第二个参数:completionHandler回调(请求完成【成功|失败】的回调) 23 data:响应体信息(期望的数据) 24 response:响应头信息,主要是对服务器端的描述 25 error:错误信息,如果请求失败,则error有值 26 */ 27 NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 28 29 if (error == nil) { 30 //6.解析服务器返回的数据 31 //说明:(此处返回的数据是JSON格式的,因此使用NSJSONSerialization进行反序列化处理) 32 NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; 33 34 NSLog(@"%@",dict); 35 } 36 }]; 37 38 //5.执行任务 39 [dataTask resume]; 40 } 发送GET请求的第一种方法 1 -(void)get2 2 { 3 //1.确定请求路径 4 NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"]; 5 6 //2.获得会话对象 7 NSURLSession *session = [NSURLSession sharedSession]; 8 9 //3.根据会话对象创建一个Task(发送请求) 10 /* 11 第一个参数:请求路径 12 第二个参数:completionHandler回调(请求完成【成功|失败】的回调) 13 data:响应体信息(期望的数据) 14 response:响应头信息,主要是对服务器端的描述 15 error:错误信息,如果请求失败,则error有值 16 注意: 17 1)该方法内部会自动将请求路径包装成一个请求对象,该请求对象默认包含了请求头信息和请求方法(GET) 18 2)如果要发送的是POST请求,则不能使用该方法 19 */ 20 NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 21 22 //5.解析数据 23 NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; 24 NSLog(@"%@",dict); 25 26 }]; 27 28 //4.执行任务 29 [dataTask resume]; 30 } 发送GET请求的第二种方法 执行结果: 此处打印的值是一个字典,字典中success这个key对应的value打印出来为Unicode编码的,如果想输出中文,可以为NSDictionary提供一个分类,重写系统中的方法。 1 #import "NSDictionary+Log.h" 2 3 @implementation NSDictionary (Log) 4 5 -(NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level 6 { 7 //初始化可变字符串 8 NSMutableString *string = [NSMutableString string]; 9 //拼接开头[ 10 [string appendString:@"["]; 11 12 //拼接字典中所有的键值对 13 [self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { 14 [string appendFormat:@"%@:",key]; 15 [string appendFormat:@"%@",obj]; 16 }]; 17 18 //拼接结尾] 19 [string appendString:@"]"]; 20 21 return string; 22 } 23 24 @end 字典分类中重写系统方法 执行结果: 三、发送POST请求 使用NSURLSession发送POST请求的方法和NSURLConnection类似,整个过程如下: 1)确定请求路径(一般由公司的后台开发人员以接口文档的方式提供) 2)创建可变的请求对象(因为需要修改),此步骤不可以省略 3)修改请求方法为POST 4)设置请求体,把参数转换为二进制数据并设置请求体 5)创建会话对象(NSURLSession) 6)根据会话对象创建请求任务(NSURLSessionDataTask) 7)执行Task 8)当得到服务器返回的响应后,解析数据(XML|JSON|HTTP) 示例代码: 1 -(void)post 2 { 3 //对请求路径的说明 4 //http://120.25.226.186:32812/login 5 //协议头+主机地址+接口名称 6 //协议头(http://)+主机地址(120.25.226.186:32812)+接口名称(login) 7 //POST请求需要修改请求方法为POST,并把参数转换为二进制数据设置为请求体 8 9 //1.创建会话对象 10 NSURLSession *session = [NSURLSession sharedSession]; 11 12 //2.根据会话对象创建task 13 NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"]; 14 15 //3.创建可变的请求对象 16 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 17 18 //4.修改请求方法为POST 19 request.HTTPMethod = @"POST"; 20 21 //5.设置请求体 22 request.HTTPBody = [@"username=520it&pwd=520it&type=JSON" dataUsingEncoding:NSUTF8StringEncoding]; 23 24 //6.根据会话对象创建一个Task(发送请求) 25 /* 26 第一个参数:请求对象 27 第二个参数:completionHandler回调(请求完成【成功|失败】的回调) 28 data:响应体信息(期望的数据) 29 response:响应头信息,主要是对服务器端的描述 30 error:错误信息,如果请求失败,则error有值 31 */ 32 NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 33 34 //8.解析数据 35 NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]; 36 NSLog(@"%@",dict); 37 38 }]; 39 40 //7.执行任务 41 [dataTask resume]; 42 } 发送POST请求的方法 四、NSURLSession代理方法简单介绍 有的时候,我们可能需要监听网络请求的过程(如下载文件需监听文件下载进度),那么就需要用到代理方法。 接下来通过代码简单说明NSURLSession中普通网络请求会涉及代理方法的使用 1 #import "ViewController.h" 2 3 @interface ViewController ()<NSURLSessionDataDelegate> 4 @property (nonatomic, strong) NSMutableData *responseData; 5 @end 6 7 @implementation ViewController 8 9 -(NSMutableData *)responseData 10 { 11 if (_responseData == nil) { 12 _responseData = [NSMutableData data]; 13 } 14 return _responseData; 15 } 16 17 //当点击控制器View的时候会调用该方法 18 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 19 { 20 [self delegateTest]; 21 } 22 23 //发送请求,代理方法 24 -(void)delegateTest 25 { 26 //1.确定请求路径 27 NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"]; 28 29 //2.创建请求对象 30 //请求对象内部默认已经包含了请求头和请求方法(GET) 31 NSURLRequest *request = [NSURLRequest requestWithURL:url]; 32 33 //3.获得会话对象,并设置代理 34 /* 35 第一个参数:会话对象的配置信息defaultSessionConfiguration 表示默认配置 36 第二个参数:谁成为代理,此处为控制器本身即self 37 第三个参数:队列,该队列决定代理方法在哪个线程中调用,可以传主队列|非主队列 38 [NSOperationQueue mainQueue] 主队列: 代理方法在主线程中调用 39 [[NSOperationQueue alloc]init] 非主队列: 代理方法在子线程中调用 40 */ 41 NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]]; 42 43 //4.根据会话对象创建一个Task(发送请求) 44 NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request]; 45 46 //5.执行任务 47 [dataTask resume]; 48 } 49 50 //1.接收到服务器响应的时候调用该方法 51 -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler 52 { 53 //在该方法中可以得到响应头信息,即response 54 NSLog(@"didReceiveResponse--%@",[NSThread currentThread]); 55 56 //注意:需要使用completionHandler回调告诉系统应该如何处理服务器返回的数据 57 //默认是取消的 58 /* 59 NSURLSessionResponseCancel = 0, 默认的处理方式,取消 60 NSURLSessionResponseAllow = 1, 接收服务器返回的数据 61 NSURLSessionResponseBecomeDownload = 2,变成一个下载请求 62 NSURLSessionResponseBecomeStream 变成一个流 63 */ 64 65 completionHandler(NSURLSessionResponseAllow); 66 } 67 68 //2.接收到服务器返回数据的时候会调用该方法,如果数据较大那么该方法可能会调用多次 69 -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data 70 { 71 NSLog(@"didReceiveData--%@",[NSThread currentThread]); 72 73 //拼接服务器返回的数据 74 [self.responseData appendData:data]; 75 } 76 77 //3.当请求完成(成功|失败)的时候会调用该方法,如果请求失败,则error有值 78 -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error 79 { 80 NSLog(@"didCompleteWithError--%@",[NSThread currentThread]); 81 82 if(error == nil) 83 { 84 //解析数据,JSON解析请参考http://www.cnblogs.com/wendingding/p/3815303.html 85 NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:self.responseData options:kNilOptions error:nil]; 86 NSLog(@"%@",dict); 87 } 88 } 89 @end 代码执行结果:
SDWebImage ReadMe.md 文档 附:SDWebImage框架github下载地址:https://github.com/rs/SDWebImage注1:该文章简单翻译了SDWebImage最新版本(3.8.1)的readMe.md,时间紧促,如有不当请指正修改,十分感激。注2:对该框架的学习将持续进行,在个人的github地址可以得到最新进展。 Web Image 版本3.8.1|平台iOS|开源协议MIT| 该库为UIImageView提供了一个分类来处理远程图片资源的加载。 它: 1)为Cocoa Touch框架提供一个UIImageView的分类,加载图片并进行缓存处理。 2)异步图像下载 3)异步存储器+具备自动缓存过期处理的磁盘映像缓存 4)支持GIF播放 5)支持WebP格式 6)背景图像解压缩 7)保证同一个url图片资源不被多次下载 8)保证错误url不被反复尝试下载 9)保证不会阻塞主线程 10)高性能 11)使用GCD和ARC 12)支持Arm64架构 注:SDWebimage3.8版本要求iOS系统版本为iOS7.0或以上(因为内部使用了NSURLSession)。3.0~3.7版本要求iOS系统版本为iOS5.1.1以上。如果你需要在iOS5.0以前的版本使用,那么请您使用2.0版本。 How is SDWebImage better than X? ·AFNetworking已经提供UIImageView相似的功能,还有必要使用SDWebimage吗? Who Use It(谁用) 查看哪些应用使用了SDWebImage框架,添加你的应用到列表。 How To Use(如何用) 查看api文档CocoaDocs - SDWebImage TableView加载图片使用UIImageView+WebCache分类 只需要包含UIImageView+WebCache.h头文件,并在tableView的数据源方法tableView:cellForRowAtIndexPath: 中调用sd_setImageWithURL:placeholderImage:方法即可。异步下载和缓存处理这一切都将会自动为你处理。 #import <SDWebImage/UIImageView+WebCache.h> ... - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *MyIdentifier = @"MyIdentifier"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease]; } // Here we use the new provided sd_setImageWithURL: method to load the web image [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]]; cell.textLabel.text = @"My Text"; return cell; } 使用Blocks 使用block,你将能够得到图片的下载进度并获知图片是否下载成功或者失败: // Here we use the new provided sd_setImageWithURL: method to load the web image [cell.imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { ... completion code here ... }]; 注意:如果图像请求在完成前被取消了,那么成功和失败的block块将都不会被调用。 使用SDWebImageManager UIImageView+WebCache分类背后调用的是SDWebImageManager类的方法,负责图像的异步下载和缓存处理。你可以直接使用这个类来下载图片和进行缓存处理。 这有一个如何使用SDWebImageManager的简单示例: SDWebImageManager *manager = [SDWebImageManager sharedManager]; [manager downloadImageWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) { // progression tracking code } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (image) { // do something with image } }]; 单独异步下载图片 它也可以独立使用异步图片下载: SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader]; [downloader downloadImageWithURL:imageURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) { // progression tracking code } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) { if (image && finished) { // do something with image } }]; 单独异步缓存图片 也可以单独使用基于图像的高速异步缓存处理。SDImagecache提供内存高速缓存和可选的磁盘高速缓存。磁盘高速缓存写入操作是异步的,所以不需要在用户界面添加不必要的延迟。 为了方便,SDImageCache类提供了一个单一的实例,但如果你想自定义缓存空间,那么可以创建自己的实例。 您可以使用queryDiskCacheForKey:done:方法查找缓存。如果该方法返回nil,则说明当前image没有缓存。你需要负责下载和进行缓存处理。图像缓存的key通常为该图像的URL。 SDImageCache *imageCache = [[SDImageCache alloc] initWithNamespace:@"myNamespace"]; [imageCache queryDiskCacheForKey:myCacheKey done:^(UIImage *image) { // image is not nil if image was found }]; 默认情况下,如果对应图片的内存缓存不存在,那么SDImageCache将查找磁盘缓存。可以通过调用imageFromMemoryCacheForKey:方法来阻止。 你可以调用storeImage:forKey: method:方法保存图片到缓存。 [[SDImageCache sharedImageCache] storeImage:myImage forKey:myCacheKey]; 默认情况下,图像将被进行内存缓存和磁盘缓存(异步)。如果你只想要内存缓存,那么可以使用storeImage:forKey:toDisk:方法,第三个参数传NO即可。 使用缓存key筛选器 有时,你可能会因为URL的一部分是动态的而不希望使用URL作为图像缓存的key。 SDWebImageManager提供了一种方式来设置,输入URL输出对应的字符串。 下面的示例在应用程序的委托中设置一个过滤器,在使用它的缓存键之前将从URL中删除任何查询字符串 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { SDWebImageManager.sharedManager.cacheKeyFilter = ^(NSURL *url) { url = [[NSURL alloc] initWithScheme:url.scheme host:url.host path:url.path]; return [url absoluteString]; }; // Your app init code... return YES; } 常见问题 使用动态图像大小的UITableViewCell UITableViewCell通过第一个单元格设置的图像决定图像的尺寸。如果你加载的网络图片和占位符图像尺寸不一致,那么您可能会遇到奇怪的变形比例问题。下面的文章给出了解决此问题的方法: http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/ 处理图像刷新 SDWebimage默认情况下做了很好的缓存处理。它忽略通过HTTP服务器返回的所有类型的缓存控制头,并且没有时间限制地缓存返回的图像 它意味着你的url指向的图片是一成不变的。更好的做法是如果url指向的图片发生了改变,那么图片也应该变化。 在这种情况下,你可以使用SDWebImageRefreshCached标志。这将略微降低性能,但会尊重HTTP缓存控制头: [imageView sd_setImageWithURL:[NSURL URLWithString:@"https://graph.facebook.com/olivier.poitrey/picture"] placeholderImage:[UIImage imageNamed:@"avatar-placeholder.png"] options:SDWebImageRefreshCached]; 添加进度指示器 参考:https://github.com/JJSaccolo/UIActivityIndicator-for-SDWebImage 安装 有三种方法把SDWebImage安装到您的项目中: 1)使用Cocoapods 2)复制所有文件到您的项目 3)作为静态库导入项目 Posted by 博客园·文顶顶 联系作者 简书·文顶顶 新浪微博·文顶顶_iOS 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | 小码哥教育·文顶顶
iOS开发实用技巧—在手机浏览器头部弹出app应用下载提示 本文介绍其简单使用: 第一步:在本地建立一个访问的服务端。 打开本地终端,在本地新建一个文件夹,在该文件夹中存放测试的html页面。 在终端输入一行代码:python -m SimpleHTTPServer ---第二步:配置环境 打开safari,偏好设置->勾选 "在菜单栏中显示开发菜单",如下图所示。 第三步:编写html文件 在测试文件appdown.html中编写的测试代码如下: `说明`444934666为对应的APP的ID。这里以QQ为例。 <!DOCTYPE html> <html><head> <meta name="apple-itunes-app" content="app-id=980497809"></head><body> hello <br/> </body> 第四步:调试和效果 在safari菜单栏->开发->相应的设备即可查看显示效果并进行调试。(`打开Web检查器`) 显示效果如下:
一、简单说明 Alcatraz 是一款 Xcode的插件管理工具,可以用来管理XCode的 插件、模版以及颜色配置的工具。 二、如何安装 1.github地址:https://github.com/alcatraz/Alcatraz 2.安装方法 方法一:在命令行中输入如下指令:curl -fsSL https://raw.github.com/alcatraz/Alcatraz/master/Scripts/install.sh | sh 图示: 注意:在安装的过程中需要先把XCode退出,等安装完成之后重新打开XCode。打开之后,会弹窗如下图所示,选择load bunble。 此时打开XCode可以检查是否安装成功。 方法二:通过上面的github地址下载Alcatraz并command +r运行,完成后command+q退出XCode重启。注意需要检查XCode偏好设置如下: 三、如何使用 1.如何使用Alcatraz来安装插件 Alcatraz安装成功后,选择Window->package Manager,会打开Alcatraz的工作窗口。假设我们需要安装的VVDocumenter-Xcode插件,那么在搜索框中直接搜索VVDocumenter-Xcode即可。搜索完成之后界面显示如下。 点击INSTALL即可安装该插件,插件安装完成之后。显示如下,INSTALL按钮变成REMOVE按钮。 command+q把XCode退出,重新打开,选择下图中的load bundle按钮,到此VVDocumenter-Xcode就已经安装好了,可以直接使用。 2.如何使用Alcatraz来卸载插件 我们可以通过Alcatraz来查看当前XCode中集成了哪些插件。如果要卸载相关插件非常简单,点击插件左边的Remove按钮即可。 四、如何卸载 命令行输入:rm -rf ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins/Alcatraz.xcplugin 注:文章简书地址:http://www.jianshu.com/writer#/notebooks/1921941/notes/2395407
今天新开了微博,小伙伴们可以关注下哦。 新浪微博地址:http://weibo.com/u/3800117445
iOS开发实用技巧篇—项目新特性页面的处理 说明:本文主要说明在项目开发中会涉及到的最最简单的新特性界面(实用UIScrollView展示多张图片的轮播)的处理。 代码示例: 新建一个专门的处理新特性界面的控制器,可以实用代码也可以用xib,在这里实用纯代码方式,创建一个控制器NewfeatureViewController。 头文件代码: 1 // 2 // JMNewfeatureViewController.h 3 // 4 5 #import <UIKit/UIKit.h> 6 7 typedef enum:NSInteger 8 { 9 NewfeatureTypeFromeSetting, //从设置界面进入该页 10 NewfeatureTypeFromeWelcom, //第一次安装的时候进入 11 } NewfeatureType; 12 13 @interface JMNewfeatureViewController : UIViewController 14 15 @property(nonatomic,assign)NewfeatureType newfeatureType; 16 17 @end .m文件代码: 1 // 2 // JMNewfeatureViewController.m 3 // 4 5 #import "JMNewfeatureViewController.h" 6 #import "JMTabBarViewController.h" 7 #import "JMAboutTableViewController.h" 8 9 #define JMNewfeatureImageCount 4 10 11 @interface JMNewfeatureViewController () <UIScrollViewDelegate> 12 13 @property (nonatomic, weak) UIPageControl *pageControl; 14 15 16 - (void)setupScrollView; 17 - (void)setupPageControl; 18 - (void)setupLastImageView:(UIImageView *)imageView; 19 - (void)setupStartButton:(UIImageView *)imageView; 20 21 @end 22 23 @implementation JMNewfeatureViewController 24 25 #pragma mark --------------------- 26 #pragma mark - CycLife 27 28 - (void)viewDidLoad 29 { 30 [super viewDidLoad]; 31 32 [UIApplication sharedApplication].statusBarHidden = YES; 33 34 [self setupScrollView]; // 添加UISrollView 35 [self setupPageControl]; // 添加pageControl 36 } 37 38 #pragma mark --------------------- 39 #pragma mark - Methods 40 41 //添加UISrollView 42 - (void)setupScrollView 43 { 44 // 添加UISrollView 45 UIScrollView *scrollView = [[UIScrollView alloc] init]; 46 scrollView.frame = self.view.bounds; 47 scrollView.bounces = NO; 48 scrollView.delegate = self; 49 [self.view addSubview:scrollView]; 50 51 // 添加图片 52 CGFloat imageW = scrollView.width; 53 CGFloat imageH = scrollView.height; 54 for (int i = 0; i<JMNewfeatureImageCount; i++) { 55 // 创建UIImageView 56 UIImageView *imageView = [[UIImageView alloc] init]; 57 NSString *name = [NSString stringWithFormat:@"banner%d.jpg", i + 1]; 58 imageView.image = [UIImage imageNamed:name]; 59 [scrollView addSubview:imageView]; 60 61 // 设置frame 62 imageView.y = 0; 63 imageView.width = imageW; 64 imageView.height = imageH; 65 imageView.x = i * imageW; 66 67 // 给最后一个imageView添加按钮 68 if (i == JMNewfeatureImageCount - 1) { 69 [self setupLastImageView:imageView]; 70 } 71 } 72 73 // 3.设置其他属性 74 scrollView.contentSize = CGSizeMake(JMNewfeatureImageCount * imageW, 0); 75 scrollView.pagingEnabled = YES; 76 scrollView.showsHorizontalScrollIndicator = NO; 77 scrollView.backgroundColor = YYColor(246, 246, 246); 78 } 79 80 //添加pageControl 81 - (void)setupPageControl 82 { 83 // 添加PageControl 84 UIPageControl *pageControl = [[UIPageControl alloc] init]; 85 pageControl.numberOfPages = JMNewfeatureImageCount; 86 pageControl.centerX = self.view.width * 0.5; 87 pageControl.centerY = self.view.height - 20; 88 [self.view addSubview:pageControl]; 89 90 // 设置圆点的颜色 91 self.pageControl = pageControl; 92 [self changePageControlImage:self.pageControl]; 93 } 94 95 96 //设置最后一个UIImageView中的内容 97 - (void)setupLastImageView:(UIImageView *)imageView 98 { 99 imageView.userInteractionEnabled = YES; 100 101 // 添加开始按钮 102 [self setupStartButton:imageView]; 103 } 104 105 //添加开始按钮 106 - (void)setupStartButton:(UIImageView *)imageView 107 { 108 // 1.添加开始按钮 109 UIButton *startButton = [[UIButton alloc] init]; 110 imageView.userInteractionEnabled = YES; 111 [imageView addSubview:startButton]; 112 113 // 2.设置背景图片 114 [startButton setBackgroundImage:[UIImage imageNamed:@"banner_button_moren.jpg"] forState:UIControlStateNormal]; 115 [startButton setBackgroundImage:[UIImage imageNamed:@"banner_button_dianji.jpg"] forState:UIControlStateHighlighted]; 116 117 // 3.设置frame 118 startButton.size = startButton.currentBackgroundImage.size; 119 startButton.centerX = self.view.width * 0.5; 120 startButton.centerY = self.view.height * 0.8; 121 122 // 4.设置文字 123 [startButton setTitle:@"立即体验" forState:UIControlStateNormal]; 124 [startButton setTitle:@"" forState:UIControlStateHighlighted]; 125 [startButton addTarget:self action:@selector(start) forControlEvents:UIControlEventTouchUpInside]; 126 } 127 128 129 //改变pagecontrol中圆点样式 130 - (void)changePageControlImage:(UIPageControl *)pageControl 131 { 132 static UIImage *imgCurrent = nil; 133 static UIImage *imgOther = nil; 134 static dispatch_once_t onceToken; 135 136 dispatch_once(&onceToken, ^{ 137 imgCurrent = [UIImage imageNamed:@"yuan_01"]; 138 imgOther = [UIImage imageNamed:@"yuan1"]; 139 }); 140 141 142 if (kSystemVersionMoreThan7) { 143 [pageControl setValue:imgCurrent forKey:@"_currentPageImage"]; 144 [pageControl setValue:imgOther forKey:@"_pageImage"]; 145 } else { 146 for (int i = 0;i < pageControl.numberOfPages; i++) { 147 UIImageView *imgv = [pageControl.subviews objectAtIndex:i]; 148 imgv.frame = CGRectMake(imgv.frame.origin.x, imgv.frame.origin.y, 20, 20); 149 imgv.image = pageControl.currentPage == i ? imgCurrent : imgOther; 150 } 151 } 152 } 153 154 #pragma mark --------------------- 155 #pragma mark - Events 156 157 //立即体验 158 - (void)start 159 { 160 [UIApplication sharedApplication].statusBarHidden = NO; 161 162 //判断类型 163 if (self.newfeatureType == NewfeatureTypeFromeWelcom) { 164 JMTabBarViewController *tabVC = [[JMTabBarViewController alloc]init]; 165 // 切换控制器 166 UIWindow *window = [UIApplication sharedApplication].keyWindow; 167 window.rootViewController = tabVC; 168 }else 169 { 170 171 [self.navigationController popViewControllerAnimated:YES]; 172 [self.navigationController setNavigationBarHidden:NO animated:NO]; 173 } 174 175 } 176 177 #pragma mark - UIScrollViewDelegate 178 - (void)scrollViewDidScroll:(UIScrollView *)scrollView 179 { 180 // 获得页码 181 CGFloat doublePage = scrollView.contentOffset.x / scrollView.width; 182 int intPage = (int)(doublePage + 0.5); 183 184 // 设置页码 185 self.pageControl.currentPage = intPage; 186 [self changePageControlImage:self.pageControl]; 187 } 188 189 @end 注意点: 下面的方法可以为pageControl提供当前状态和默认状态下的图片设置。 1 //改变pagecontrol中圆点样式 2 - (void)changePageControlImage:(UIPageControl *)pageControl 3 { 4 static UIImage *imgCurrent = nil; 5 static UIImage *imgOther = nil; 6 static dispatch_once_t onceToken; 7 8 dispatch_once(&onceToken, ^{ 9 imgCurrent = [UIImage imageNamed:@"yuan_01"]; 10 imgOther = [UIImage imageNamed:@"yuan1"]; 11 }); 12 13 14 if (kSystemVersionMoreThan7) { 15 [pageControl setValue:imgCurrent forKey:@"_currentPageImage"]; 16 [pageControl setValue:imgOther forKey:@"_pageImage"]; 17 } else { 18 for (int i = 0;i < pageControl.numberOfPages; i++) { 19 UIImageView *imgv = [pageControl.subviews objectAtIndex:i]; 20 imgv.frame = CGRectMake(imgv.frame.origin.x, imgv.frame.origin.y, 20, 20); 21 imgv.image = pageControl.currentPage == i ? imgCurrent : imgOther; 22 } 23 } 24 } 本例中,新特性部分的业务逻辑非常简单,可以直接套用。 实用图片替换pageControl的效果如下:
iOS开发拓展篇-XMPP简单介绍 一、即时通讯简单介绍 1、简单说明 即时通讯技术(IM)支持用户在线实时交谈。如果要发送一条信息,用户需要打开一个小窗口,以便让用户及其朋友在其中输入信息并让交谈双方都看到交谈的内容 有许多的IM系统,如AOL IM、Yahoo IM、 MSN以及QQ,它们最大的区别在于各自通讯协议的实现,所以即时通讯技术的核心在于它的传输协议 协议用来说明信息在网络上如何传输,如果有了统一的传输协议,那么应当可以实现各个IM之间的直接通讯,为了创建即时通讯的统一标准,目前已经出现过的IM协议包括:IETF的对话初始协议(SIP)和即时通讯对话初始协议和表示扩展协议(SIMPLE)、应用交换协议(APEX)、显示和即时通讯协议(PRIM)及基于XML且开放的可扩展通讯和表示协议(XMPP)协议(常称为 Jabber 协议) 业界经过多次努力,试图统一各大主要IM供应商的标准(AOL、Yahoo 及 Microsoft),但无一成功,且每一种IM仍然继续使用自己所拥有的协议 2.遵守XMPP即时通讯协议 设计一款全世界都使用的即时通讯协议,无论使用什么即时通讯软件,都可以互联互通。 3.即时通讯在垂直社交中的意义 和第一代大而全的水平网站(又称综合性网站)不同,垂直网站注意力集中在某些特定的领域或某种特定的需求,提供有关这个领域或需求的全部深度信息和相关服务,作为互联网的新亮点,垂直网站正引起越来越多人的关注。在越来越多的网络吸引老百姓的注意力时,网民却逐渐走出时髦、好奇的初级阶段,不断在网上寻找着实际、实用和实惠。随着移动互联网应用的发展,现在越来越多的App正在向垂直应用迁移,垂直应用中同类用户群体之间的沟通,产生聚合就显得尤为重要了,因此目前国内市场的XMPP应用开发的人才需求非常大 4.XMPP的起源 XMPP是基于XML的协议,用于即时消息(IM)以及在线现场探测。最初,XMPP作为一个框架开发,目标是支持企业环境内的即时消息传递和联机状态应用程序。当时的即时消息传递网络是私有的,不适合企业使用。XMPP前身是Jabber(1998年),是一个开源组织定义的网络即时通信协议 XMPP是一个分散型通信网络,这意味着,只要网络基础设施允许,任何XMPP用户都可以向其他任何XMPP用户传递消息。多个XMPP服务器也可以通过一个专门的“服务器-服务器”协议相互通信,提供了创建分散型社交网络和协作框架的可能性 尽管XMPP的出现是为了满足“个人-个人”即时消息传递的要求,但它完全不必局限于此任务 二、XMPP相关 1.XMPP介绍 XMPP是一种基于XML的协议,它继承了在XML环境中灵活的发展性。这表明XMPP是可扩展的。可以通过发送扩展的信息来处理用户的需求,以及在 XMPP的顶端建立如内容发布系统和基于地址的服务等应用程序。而且,XMPP包含了针对服务器端的软件协议,使之能与另一个进行通话,这使得开发者更容易建立客户应用程序或给一个配置好的系统添加功能XMPP的核心XML流传输协议的定义使得XMPP能够在一个比以往网络通信协议更规范的平台上。借助于XML易于解析和阅读的特性,使得XMPP的协议能够非常漂亮XMPP的即时通讯扩展应用部分是根据IETF在这之前对即时通讯的一个抽象定义的,与其他业已得到广泛使用的即时通讯协议,诸如AIM,QQ等有功能完整,完善等先进性XMPP的扩展协议Jingle使得其支持语音和视频,目前iOS尚不支持XMPP的官方文档是RFC 3920XMPP协议曾经是Google力推的即时通信协议,其代表作品是GTalk。 注意:相对xml,json更高效一些。 2.Google Talk说明 Google Talk是Google的IM工具,除了具有IM功能外,另外还加上了Voip功能,“界面清新大方”,可直接链接Gmail,接受查看邮件。由于Google Talk是基于Jabber开源标准,这种标准允许用户和其它的即时讯息系统相连,比如苹果电脑的iChat,GAIM,Trillian Pro以及Psi。Google Talk只能够在Windows平台上运行。Google Talk的用户无法使用这种软件与AIM,MSN Messenger或者雅虎Messenger的用户进行互通。 2013年5月,在Google I/O大会上,Google推出统一跨平台聊天应用Hangouts(环聊)取代Google Talk。Google Hangouts不支持XMPP协议。Google Hangouts用户与非Google服务如XMPP协议的强有力支持者jabber.org之间的交流无法进行。 目前,Google Wave是基于XMPP协议的,并且Google还开发Jingle扩展进行基于XMPP协议的视频聊天。尽管Google尚未宣布Talk的关闭日期,不过从Google关闭Reader和iCal支持来看,Talk关闭之日不会太远 3.XMPP定义 XMPP:The Extensible Messaging and Presence Protocol(可扩展通讯和表示协议) XMPP可用于服务类实时通讯、表示和需求响应服务中的XML数据元流式传输。XMPP以Jabber协议为基础,而Jabber是即时通讯中常用的开放式协议 XMPP是基于XML的协议,用于即时消息(IM)以及在线现场探测。促进服务器之间的准即时操作。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同 4.基本结构 XMPP是一个典型的C/S架构,而不是像大多数即时通讯软件一样,使用P2P客户端到客户端的架构,也就是说在大多数情况下,当两个客户端进行通讯时, 他们的消息都是通过服务器传递的。采用这种架构,主要是为了简化客户端,将大多数工作放在服务器端进行 XMPP中定义了三个角色,客户端,服务器,网关。通信能够在这三者的任意两个之间双向发生 服务器同时承担了客户端信息记录,连接管理和信息的路由功能。 网关承担着与异构即时通信系统的互联互通,异构系统可以包括SMS(短信),MSN,ICQ等 基本的网络形式是单客户端通过TCP/IP连接到单服务器,然后在之上传输XML流
iOS开发拓展篇—应用之间的跳转和数据传 说明:本文介绍app如何打开另一个app,并且传递数据。 一、简单说明 新建两个应用,分别为应用A和应用B. 实现要求:在appA的页面中点击对应的按钮,能够打开appB这个应用。 1.新建两个应用,分别为A和B. 2.设置应用B的url。 3.在应用A中编写打开app的代码 点击之后,会跳转到新的控制器。 注意:打开应用B的过程中,B有两种状态。 第一种状态:B并没有启动,那么会启动B。并调用下面的方法。 第二种状态:此时B已经启动了,但是在后台运行,这个时候不会调用该方法。 二:说明 如果一个应用被另外一个应用打开,那么会调用下面的代理方法,且在该方法中可以实现两个应用之间数据的传递。 代码说明: 1 #import "YYAppDelegate.h" 2 3 @implementation YYAppDelegate 4 5 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 6 { 7 NSLog(@"didFinishLaunchingWithOptions---B"); 8 return YES; 9 } 10 11 //当一个应用程序被其他程序打开的时候会调用这个方法,在该方法中可以实现两个应用程序间的数据局传递 12 -(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation 13 { 14 NSLog(@"%@",url); 15 NSLog(@"%@",sourceApplication); 16 return YES; 17 }
iOS开发网络篇—使用ASI框架进行文件下载 说明:本文介绍iOS网络编程中经常用到的框架ASI,如何使用该框架进行文件的下载。 一、简单介绍 代码示例: 1 #import "YYViewController.h" 2 #import "ASIHTTPRequest.h" 3 4 @interface YYViewController () 5 6 7 @end 8 9 @implementation YYViewController 10 11 - (void)viewDidLoad 12 { 13 [super viewDidLoad]; 14 } 15 16 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 17 { 18 //下载服务器上的文件 19 [self download]; 20 } 21 22 #pragma mark-下载文件 23 -(void)download 24 { //1.创建请求对象 25 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/resources/video.zip"]; 26 ASIHTTPRequest *request=[ASIHTTPRequest requestWithURL:url]; 27 28 //2.添加请求参数(请求体中的参数) 29 [request setDataReceivedBlock:^(NSData *data) { 30 NSLog(@"%d",data.length); 31 }]; 32 33 //3.异步发送网络请求 34 [request startAsynchronous]; 35 } 36 37 @end 代码说明:上面的代码从服务器上异步下载文件,每当接收到数据的时候就打印接收到的数据的长度。 打印结果如下: 注意:在实际的开发中不能这样去下载文件,因为他不断的拼接文件数据的操作是在内存中进行的,如果所下载文件的数据较大,那么将会直接导致内存爆掉。 二、实际开发中的使用 代码示例(演示2): 1 #import "YYViewController.h" 2 #import "ASIHTTPRequest.h" 3 4 @interface YYViewController () 5 6 7 @end 8 9 @implementation YYViewController 10 11 - (void)viewDidLoad 12 { 13 [super viewDidLoad]; 14 } 15 16 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 17 { 18 //下载服务器上的文件 19 [self download1]; 20 } 21 22 #pragma mark-下载文件 23 //演示1 24 -(void)download 25 { //1.创建请求对象 26 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/resources/video.zip"]; 27 ASIHTTPRequest *request=[ASIHTTPRequest requestWithURL:url]; 28 29 //2.添加请求参数(请求体中的参数) 30 [request setDataReceivedBlock:^(NSData *data) { 31 NSLog(@"%d",data.length); 32 }]; 33 34 //3.异步发送网络请求 35 [request startAsynchronous]; 36 } 37 38 //演示2 39 -(void)download1 40 { 41 //1.创建请求对象 42 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/resources/video.zip"]; 43 ASIHTTPRequest *request=[ASIHTTPRequest requestWithURL:url]; 44 45 //2.设置下载文件保存的路径 46 NSString *cachepath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]; 47 NSString *filename=[cachepath stringByAppendingPathComponent:@"video.zip"]; 48 request.downloadDestinationPath=filename; 49 NSLog(@"%@",filename); 50 51 //3.发送网络请求(异步) 52 [request startAsynchronous]; 53 54 //4.当下载完成后通知 55 [request setCompletionBlock:^{ 56 NSLog(@"下载成功"); 57 }]; 58 } 59 60 @end 下载成功: 代码说明: 在实际的开发中如果要使用asi框架来下载服务器上的文件,只需要执行下面简单的几个步骤即可(参照上面的代码)。 (1)创建请求对象; (2)设置下载文件保存的路径; (3)发送下载文件的网络请求(异步)。 按照上面的几个步骤执行,程序会自动开启异步线程,一点一点的把数据写入到指定的文件路径,而且不论是下载多大的文件都不会占用大量的内存空间。 asi框架是基于底层的cfnoteworking的,性能很好。当然也可以设置block,或者是监听下载的进度。 下面介绍使用asi框架下载文件,如何监听下载的进度。 设置下载代理,注意不是控制器代理。 代码示例: 1 #import "YYViewController.h" 2 #import "ASIHTTPRequest.h" 3 4 @interface YYViewController ()<ASIProgressDelegate> 5 @property (weak, nonatomic) IBOutlet UIProgressView *progress; 6 @end 7 8 @implementation YYViewController 9 10 - (void)viewDidLoad 11 { 12 [super viewDidLoad]; 13 } 14 15 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 16 { 17 //下载服务器上的文件 18 [self download]; 19 } 20 21 #pragma mark-下载文件 22 -(void)download 23 { 24 //1.创建请求对象 25 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/resources/video.zip"]; 26 ASIHTTPRequest *request=[ASIHTTPRequest requestWithURL:url]; 27 28 //2.设置下载文件保存的路径 29 NSString *cachepath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]; 30 NSString *filename=[cachepath stringByAppendingPathComponent:@"video.zip"]; 31 request.downloadDestinationPath=filename; 32 NSLog(@"%@",filename); 33 34 //3.设置下载进度的代理 35 request.downloadProgressDelegate=self.progress; 36 37 //4.发送网络请求(异步) 38 [request startAsynchronous]; 39 40 //5.下载完毕后通知 41 [request setCompletionBlock:^{ 42 NSLog(@"文件已经下载完毕"); 43 }]; 44 } 45 46 #pragma mark-监听下载进度的代理方法 asi的文件下载还有一个属性可以设置是否支持断点下载。 设置支持断点下载的代码如下: request.allowResumeForFileDownloads=YES; 这样的话,比如一个文件已经下载了百分之30到程序的沙盒中,这个时候取消了下载。当下一次点击下载文件的时候,会接着下载剩余的百分之70并一点一点的写入到沙盒中。 提示:取消下载的代码为: [request clearDelegatesAndCancel]; 三,结合一些进度显示的第三方框架使用 去code4app上面随便下载一个显示下载进度的第三方框架,这里以DACircularProgressView为例子。 导入该框架必要的文件后,简单使用如下。 代码示例: 1 #import "YYViewController.h" 2 #import "ASIHTTPRequest.h" 3 #import "DACircularProgressView.h" 4 5 @interface YYViewController ()<ASIProgressDelegate> 6 7 @property (weak, nonatomic) IBOutlet DACircularProgressView *circularView; 8 @property (weak, nonatomic) IBOutlet UIProgressView *progress; 9 @end 10 11 @implementation YYViewController 12 13 - (void)viewDidLoad 14 { 15 [super viewDidLoad]; 16 17 //设置基本的一些属性 18 self.circularView.trackTintColor=[UIColor lightGrayColor]; 19 self.circularView.progressTintColor=[UIColor yellowColor]; 20 } 21 22 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 23 { 24 //下载服务器上的文件 25 [self download]; 26 } 27 28 #pragma mark-下载文件 29 -(void)download 30 { 31 //1.创建请求对象 32 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/resources/video.zip"]; 33 ASIHTTPRequest *request=[ASIHTTPRequest requestWithURL:url]; 34 35 //2.设置下载文件保存的路径 36 NSString *cachepath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]; 37 NSString *filename=[cachepath stringByAppendingPathComponent:@"video.zip"]; 38 request.downloadDestinationPath=filename; 39 NSLog(@"%@",filename); 40 41 //3.设置下载进度的代理 42 request.downloadProgressDelegate=self.circularView; 43 44 //4.发送网络请求(异步) 45 [request startAsynchronous]; 46 47 //5.设置支持断点下载 48 request.allowResumeForFileDownloads=YES; 49 50 //5.下载完毕后通知 51 [request setCompletionBlock:^{ 52 NSLog(@"文件已经下载完毕"); 53 }]; 54 } 55 56 #pragma mark-监听下载进度的代理方法 57 @end 显示效果: 特别提示:
iOS开发网络篇—数据缓存 一、关于同一个URL的多次请求 有时候,对同一个URL请求多次,返回的数据可能都是一样的,比如服务器上的某张图片,无论下载多少次,返回的数据都是一样的。 上面的情况会造成以下问题 (1)用户流量的浪费 (2)程序响应速度不够快 解决上面的问题,一般考虑对数据进行缓存。 二、缓存 为了提高程序的响应速度,可以考虑使用缓存(内存缓存\硬盘缓存) 第一次请求数据时,内存缓存中没有数据,硬盘缓存中没有数据。 缓存数据的过程 当服务器返回数据时,需要做以下步骤 (1)使用服务器的数据(比如解析、显示) (2)将服务器的数据缓存到硬盘(沙盒) 此时缓存的情况是:内存缓存中有数据,硬盘缓存中有数据。 再次请求数据分为两种情况: (1)如果程序并没有被关闭,一直在运行 那么此时内存缓存中有数据,硬盘缓存中有数据。如果此时再次请求数据,直接使用内存缓存中的数据即可 (2)如果程序重新启动 那么此时内存缓存已经消失,没有数据,硬盘缓存依旧存在,还有数据。如果此时再次请求数据,需要读取内存中缓存的数据。 提示:从硬盘缓存中读取数据后,内存缓存中又有数据了 三、缓存的实现 1.说明: 由于GET请求一般用来查询数据,POST请求一般是发大量数据给服务器处理(变动性比较大) 因此一般只对GET请求进行缓存,而不对POST请求进行缓存 在iOS中,可以使用NSURLCache类缓存数据 iOS 5之前:只支持内存缓存。从iOS 5开始:同时支持内存缓存和硬盘缓存 2.NSURLCache iOS中得缓存技术用到了NSURLCache类。 缓存原理:一个NSURLRequest对应一个NSCachedURLResponse 缓存技术:把缓存的数据都保存到数据库中。 3.NSURLCache的常见用法 (1)获得全局缓存对象(没必要手动创建)NSURLCache *cache = [NSURLCache sharedURLCache]; (2)设置内存缓存的最大容量(字节为单位,默认为512KB)- (void)setMemoryCapacity:(NSUInteger)memoryCapacity; (3)设置硬盘缓存的最大容量(字节为单位,默认为10M)- (void)setDiskCapacity:(NSUInteger)diskCapacity; (4)硬盘缓存的位置:沙盒/Library/Caches (5)取得某个请求的缓存- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request; (6)清除某个请求的缓存- (void)removeCachedResponseForRequest:(NSURLRequest *)request; (7)清除所有的缓存- (void)removeAllCachedResponses; 4.缓存GET请求 要想对某个GET请求进行数据缓存,非常简单 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; // 设置缓存策略 request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; 只要设置了缓存策略,系统会自动利用NSURLCache进行数据缓存 5.iOS对NSURLRequest提供了7种缓存策略:(实际上能用的只有4种) NSURLRequestUseProtocolCachePolicy // 默认的缓存策略(取决于协议) NSURLRequestReloadIgnoringLocalCacheData // 忽略缓存,重新请求 NSURLRequestReloadIgnoringLocalAndRemoteCacheData // 未实现 NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData // 忽略缓存,重新请求 NSURLRequestReturnCacheDataElseLoad// 有缓存就用缓存,没有缓存就重新请求 NSURLRequestReturnCacheDataDontLoad// 有缓存就用缓存,没有缓存就不发请求,当做请求出错处理(用于离线模式) NSURLRequestReloadRevalidatingCacheData // 未实现 6.缓存的注意事项 缓存的设置需要根据具体的情况考虑,如果请求某个URL的返回数据: (1)经常更新:不能用缓存!比如股票、彩票数据 (2)一成不变:果断用缓存 (3)偶尔更新:可以定期更改缓存策略 或者 清除缓存 提示:如果大量使用缓存,会越积越大,建议定期清除缓存 四、简单的代码示例 1 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 2 { 3 // 1.创建请求 4 NSURL *url = [NSURL URLWithString:@"http://127.0.0.1:8080/YYServer/video"]; 5 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 6 7 // 2.设置缓存策略(有缓存就用缓存,没有缓存就重新请求) 8 request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; 9 10 // 3.发送请求 11 [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { 12 if (data) { 13 NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil]; 14 15 NSLog(@"%@", dict); 16 } 17 }]; 18 } 19 20 /** 21 // 定期处理缓存 22 // if (缓存没有达到7天) { 23 // request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; 24 // } 25 // 获得全局的缓存对象 26 NSURLCache *cache = [NSURLCache sharedURLCache]; 27 // if (缓存达到7天) { 28 // [cache removeCachedResponseForRequest:request]; 29 // } 30 31 // lastCacheDate = 2014-06-30 11:04:30 32 33 NSCachedURLResponse *response = [cache cachedResponseForRequest:request]; 34 if (response) { 35 NSLog(@"---这个请求已经存在缓存"); 36 } else { 37 NSLog(@"---这个请求没有缓存"); 38 } 39 */
iOS开发网络篇—发送json数据给服务器以及多值参数 一、发送JSON数据给服务器 发送JSON数据给服务器的步骤: (1)一定要使用POST请求 (2)设置请求头 (3)设置JSON数据为请求体 代码示例: 1 #import "YYViewController.h" 2 3 @interface YYViewController () 4 5 @end 6 7 @implementation YYViewController 8 9 - (void)viewDidLoad 10 { 11 [super viewDidLoad]; 12 } 13 14 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 15 { 16 // 1.创建请求 17 NSURL *url = [NSURL URLWithString:@"http://192.168.1.200:8080/MJServer/order"]; 18 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 19 request.HTTPMethod = @"POST"; 20 21 // 2.设置请求头 22 [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; 23 24 // 3.设置请求体 25 NSDictionary *json = @{ 26 @"order_id" : @"123", 27 @"user_id" : @"789", 28 @"shop" : @"Toll" 29 }; 30 31 // NSData --> NSDictionary 32 // NSDictionary --> NSData 33 NSData *data = [NSJSONSerialization dataWithJSONObject:json options:NSJSONWritingPrettyPrinted error:nil]; 34 request.HTTPBody = data; 35 36 // 4.发送请求 37 [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { 38 NSLog(@"%d", data.length); 39 }]; 40 } 41 42 @end 二、多值参数 多值参数:一个参数对应多个值。 如下面的请求参数: http://192.168.1.103:8080/MJServer/weather?place=北京&place=河南&place=湖南 服务器的place属性是一个数组。因此用同一个参数不会把服务器的值覆盖。
iOS开发网络篇—监测网络状态 一、说明 在网络应用中,需要对用户设备的网络状态进行实时监控,有两个目的: (1)让用户了解自己的网络状态,防止一些误会(比如怪应用无能) (2)根据用户的网络状态进行智能处理,节省用户流量,提高用户体验 WIFI\3G网络:自动下载高清图片 低速网络:只下载缩略图 没有网络:只显示离线的缓存数据 苹果官方提供了一个叫Reachability的示例程序,便于开发者检测网络状态 https://developer.apple.com/library/ios/samplecode/Reachability/Reachability.zip 二、监测网络状态 Reachability的使用步骤 添加框架SystemConfiguration.framework 添加源代码 包含头文件 #import "Reachability.h" 代码示例: 1 #import "YYViewController.h" 2 #import "Reachability.h" 3 4 @interface YYViewController () 5 @property (nonatomic, strong) Reachability *conn; 6 @end 7 8 @implementation YYViewController 9 10 - (void)viewDidLoad 11 { 12 [super viewDidLoad]; 13 14 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkStateChange) name:kReachabilityChangedNotification object:nil]; 15 self.conn = [Reachability reachabilityForInternetConnection]; 16 [self.conn startNotifier]; 17 } 18 19 - (void)dealloc 20 { 21 [self.conn stopNotifier]; 22 [[NSNotificationCenter defaultCenter] removeObserver:self]; 23 } 24 25 - (void)networkStateChange 26 { 27 [self checkNetworkState]; 28 } 29 30 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 31 { 32 33 } 34 35 - (void)checkNetworkState 36 { 37 // 1.检测wifi状态 38 Reachability *wifi = [Reachability reachabilityForLocalWiFi]; 39 40 // 2.检测手机是否能上网络(WIFI\3G\2.5G) 41 Reachability *conn = [Reachability reachabilityForInternetConnection]; 42 43 // 3.判断网络状态 44 if ([wifi currentReachabilityStatus] != NotReachable) { // 有wifi 45 NSLog(@"有wifi"); 46 47 } else if ([conn currentReachabilityStatus] != NotReachable) { // 没有使用wifi, 使用手机自带网络进行上网 48 NSLog(@"使用手机自带网络进行上网"); 49 50 } else { // 没有网络 51 52 NSLog(@"没有网络"); 53 } 54 } 55 @end 56 57 // 用WIFI 58 // [wifi currentReachabilityStatus] != NotReachable 59 // [conn currentReachabilityStatus] != NotReachable 60 61 // 没有用WIFI, 只用了手机网络 62 // [wifi currentReachabilityStatus] == NotReachable 63 // [conn currentReachabilityStatus] != NotReachable 64 65 // 没有网络 66 // [wifi currentReachabilityStatus] == NotReachable 67 // [conn currentReachabilityStatus] == NotReachable
iOS开发网络篇—简单介绍ASI框架的使用 说明:本文主要介绍网络编程中常用框架ASI的简单使用。 一、ASI简单介绍 ASI:全称是ASIHTTPRequest,外号“HTTP终结者”,功能十分强大。 ASI的实现基于底层的CFNetwork框架,因此运行效率很高。可惜作者早已停止更新,有一些潜在的BUG无人去解决 ASI的github地址 https://github.com/pokeb/asi-http-request ASI的使用参考 http://www.cnblogs.com/dotey/archive/2011/05/10/2041966.html http://www.oschina.net/question/54100_36184 二、ASI的使用 1导入 下载并导入ASI框架,注意该框架依赖于Reachability. 导入框架后,如果编译的话会出现一大堆的错误。其中一个最主要的原因是因为该框架是非ARC的。 思考:如果一个框架,其中很多文件都是非ARC的,那么应该如何进行设置? 在这里,介绍最傻瓜的一种方法,通过进行如下的一些设置以解决问题。(注意:对于所有的asi框架中得文件都需要进行此番设置) 设置完成后,还存在一些错误。产生这些错误的原因是ASI框架它依赖于下面的几个框架,把需要的框架添加后再编译,就不会有问题了。 添加依赖的框架: 把所需的三个框架逐个添加到项目中 2发送网络请求(同步和异步*GET和POST请求) 示例程序代码: 1 #import "YYViewController.h" 2 #import "ASIHTTPRequest.h" 3 #import "ASIFormDataRequest.h" 4 5 @interface YYViewController ()<ASIHTTPRequestDelegate> 6 @property(nonatomic,strong)ASIHTTPRequest *request; 7 8 @end 9 10 @implementation YYViewController 11 12 - (void)viewDidLoad 13 { 14 [super viewDidLoad]; 15 } 16 17 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 18 { 19 //同步请求 20 // [self sync]; 21 //异步请求 22 // [self async1]; 23 24 [self post]; 25 } 26 27 #pragma mark-发送post请求 28 -(void)post 29 { //1.创建网络请求(POST) 30 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/login"]; 31 ASIFormDataRequest *request=[ASIFormDataRequest requestWithURL:url]; 32 33 //2.添加请求参数(请求体中得参数) 34 [request setPostValue:@"123" forKey:@"username"]; 35 [request setPostValue:@"123" forKey:@"pwd"]; 36 37 //3.发送请求 38 [request startAsynchronous]; 39 40 //4.监听 41 [request setStartedBlock:^{ 42 //开始 43 NSLog(@"该方法会覆盖代理方法中得对应方法"); 44 }]; 45 46 } 47 48 -(void)dealloc 49 { 50 #warning 当控制器销毁的时候,清除并取消代理 51 [self.request clearDelegatesAndCancel]; 52 53 //如果是非arc的环境,那么还需要清空请求 54 //self.request=Nil; 55 } 56 #pragma mark-异步请求 57 /** 58 * 异步请求的第一种方式:设置代理,用代理方法监听 59 */ 60 -(void)async1 61 { 62 //1.创建网络请求(GET) 63 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/login?username=123&pwd=123"]; 64 self.request=[ASIHTTPRequest requestWithURL:url]; 65 //设置网络请求的延时为10秒钟 66 self.request.timeOutSeconds=10; 67 68 //2.设置代理 69 self.request.delegate=self; 70 71 72 //3.发送请求(异步请求) 73 [self.request startAsynchronous]; 74 } 75 76 #pragma mark-异步请求的代理方法 77 //请求开始的时候调用 78 -(void)requestStarted:(ASIHTTPRequest *)request 79 { 80 81 } 82 //接收到服务器返回的数据时调用(数据量比较大的时候,这个方法会被调用多次,每次只能拿到部分数据) 83 -(void)request:(ASIHTTPRequest *)request didReceiveData:(NSData *)data 84 { 85 86 } 87 //请求结束的时候调用(在该方法中拿到最终的数据) 88 -(void)requestFinished:(ASIHTTPRequest *)request 89 { 90 //request.responseData:服务器返回的所有数据,这个data已经拼接了接收到的所有数据 91 } 92 //发送网络请求失败的时候调用 93 -(void)requestFailed:(ASIHTTPRequest *)request 94 { 95 } 96 97 /** 98 * 异步请求的第二种方式:selector,以设置代理为基本前提 99 */ 100 -(void)async2 101 { 102 //1.创建网络请求(GET) 103 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/login?username=123&pwd=123"]; 104 self.request=[ASIHTTPRequest requestWithURL:url]; 105 //设置网络请求的延时为10秒钟 106 self.request.timeOutSeconds=10; 107 108 //2.设置代理 109 self.request.delegate=self; 110 111 //通过selector的方法,当请求开始的时候,由请求的代理即控制器调用start方法进行监听 112 //说明:该方法会覆盖代理方法 113 [self.request setDidStartSelector:@selector(start)]; 114 115 //3.发送请求(异步请求) 116 [self.request startAsynchronous]; 117 } 118 119 -(void)start 120 { 121 NSLog(@"该方法会覆盖代理方法中得对应方法"); 122 } 123 124 /** 125 * 异步请求的第三种方式:使用block回调 126 */ 127 -(void)async3 128 { 129 //1.创建网络请求(GET) 130 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/login?username=123&pwd=123"]; 131 self.request=[ASIHTTPRequest requestWithURL:url]; 132 //设置网络请求的延时为10秒钟 133 self.request.timeOutSeconds=10; 134 135 //2.使用block回调监听 136 [self.request setStartedBlock:^{ 137 //请求开始的时候调用 138 }]; 139 [self.request setFailedBlock:^{ 140 //请求失败的时候调用 141 }]; 142 [self.request setDataReceivedBlock:^(NSData *data) { 143 //开始接收数据的时候调用 144 }]; 145 [self.request setCompletionBlock:^{ 146 //请求成功完成的时候调用 147 }]; 148 149 //3.发送请求(异步请求) 150 [self.request startAsynchronous]; 151 } 152 153 154 #pragma mark-同步请求 155 -(void)sync 156 { 157 //1.创建网络请求(GET) 158 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/login?username=123&pwd=123"]; 159 ASIHTTPRequest *request=[ASIHTTPRequest requestWithURL:url]; 160 //设置网络请求的延时为10秒钟 161 request.timeOutSeconds=10; 162 163 //2.发送请求(同步请求) 164 [request startSynchronous]; 165 166 167 //3.检测服务器返回的结果 168 if (request.error) {//请求出错,比如超时 169 NSLog(@"请求超时,错误信息为%@",request.error); 170 }else//请求成功 171 { 172 //打印状态码和状态信息 173 NSLog(@"状态码---%d,状态信息---%@",request.responseStatusCode, request.responseStatusMessage); 174 //打印返回的数据的长度 175 NSLog(@"返回数据的长度--%d",request.responseData.length); 176 //将返回的数据转换为字符串 177 NSLog(@"返回的数据---%@",request.responseString); 178 179 //将服务器返回的数据解析为字典 180 NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:request.responseData options:NSJSONReadingMutableLeaves error:Nil]; 181 NSLog(@"%@",dictionary); 182 } 183 } 184 @end 程序说明: (1)同步发送请求打印的消息 (2)异步发送请求 ASI以异步的方式发送网络请求有三种方式,第一种是通过代理进行监听;第二种方法是通过block进行监听。还有一种方法时使用selector,这是建立在设置代理的基础之上的,调用的方法会覆盖代理方法。 第一种方法: 1 /** 2 * 异步请求的第一种方式:设置代理,用代理方法监听 3 */ 4 -(void)async1 5 { 6 //1.创建网络请求(GET) 7 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/login?username=123&pwd=123"]; 8 self.request=[ASIHTTPRequest requestWithURL:url]; 9 //设置网络请求的延时为10秒钟 10 self.request.timeOutSeconds=10; 11 12 //2.设置代理 13 self.request.delegate=self; 14 15 16 //3.发送请求(异步请求) 17 [self.request startAsynchronous]; 18 } 19 20 #pragma mark-异步请求的代理方法 21 //请求开始的时候调用 22 -(void)requestStarted:(ASIHTTPRequest *)request 23 { 24 25 } 26 //接收到服务器返回的数据时调用(数据量比较大的时候,这个方法会被调用多次,每次只能拿到部分数据) 27 -(void)request:(ASIHTTPRequest *)request didReceiveData:(NSData *)data 28 { 29 30 } 31 //请求结束的时候调用(在该方法中拿到最终的数据) 32 -(void)requestFinished:(ASIHTTPRequest *)request 33 { 34 //request.responseData:服务器返回的所有数据,这个data已经拼接了接收到的所有数据 35 } 36 //发送网络请求失败的时候调用 37 -(void)requestFailed:(ASIHTTPRequest *)request 38 { 39 } 第二种方法: 1 /** 2 * 异步请求的第二种方式:selector,以设置代理为基本前提 3 */ 4 -(void)async2 5 { 6 //1.创建网络请求(GET) 7 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/login?username=123&pwd=123"]; 8 self.request=[ASIHTTPRequest requestWithURL:url]; 9 //设置网络请求的延时为10秒钟 10 self.request.timeOutSeconds=10; 11 12 //2.设置代理 13 self.request.delegate=self; 14 15 //通过selector的方法,当请求开始的时候,由请求的代理即控制器调用start方法进行监听 16 //说明:该方法会覆盖代理方法 17 [self.request setDidStartSelector:@selector(start)]; 18 19 //3.发送请求(异步请求) 20 [self.request startAsynchronous]; 21 } 22 23 -(void)start 24 { 25 NSLog(@"该方法会覆盖代理方法中得对应方法"); 26 } 第三种方法: 1 /** 2 * 异步请求的第三种方式:使用block回调 3 */ 4 -(void)async3 5 { 6 //1.创建网络请求(GET) 7 NSURL *url=[NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/login?username=123&pwd=123"]; 8 self.request=[ASIHTTPRequest requestWithURL:url]; 9 //设置网络请求的延时为10秒钟 10 self.request.timeOutSeconds=10; 11 12 //2.使用block回调监听 13 [self.request setStartedBlock:^{ 14 //请求开始的时候调用 15 }]; 16 [self.request setFailedBlock:^{ 17 //请求失败的时候调用 18 }]; 19 [self.request setDataReceivedBlock:^(NSData *data) { 20 //开始接收数据的时候调用 21 }]; 22 [self.request setCompletionBlock:^{ 23 //请求成功完成的时候调用 24 }]; 25 26 //3.发送请求(异步请求) 27 [self.request startAsynchronous]; 28 } 提示:block是ios4之后才引入的技术。 这两种方法各有优缺点 说明:如果要同时发送多个请求,他们都设置控制器为自己的代理,这样需要进行一些必要的判断。而如果各自都是有block这样的方式的话,那么将不会存在这些问题,多个请求之间相互不存在干扰。 新的问题:如果又有block又设置了代理,那么情况是什么样子的呢? 答案是block和相关的代理方法都会被调用。 (3)补充 提示:ASI中已经考虑到了线程安全的问题。 3.发送POST请求。 代码: 注意需要包含一个头文件 内部默认就是POST的。 注意add和set的区别,一个是添加(适用于多值参数),一个是覆盖(内部先remove,再add)。 服务器接收到的请求信息:
iOS开发网络篇—文件的上传 说明:文件上传使用的时POST请求,通常把要上传的数据保存在请求体中。本文介绍如何不借助第三方框架实现iOS开发中得文件上传。 由于过程较为复杂,因此本文只贴出部分关键代码。 主控制器的关键代码: YYViewController.m 1 #import "YYViewController.h" 2 3 #define YYEncode(str) [str dataUsingEncoding:NSUTF8StringEncoding] 4 5 @interface YYViewController () 6 7 @end 8 9 @implementation YYViewController 10 11 - (void)viewDidLoad 12 { 13 [super viewDidLoad]; 14 // Do any additional setup after loading the view, typically from a nib. 15 } 16 17 - (void)upload:(NSString *)name filename:(NSString *)filename mimeType:(NSString *)mimeType data:(NSData *)data parmas:(NSDictionary *)params 18 { 19 // 文件上传 20 NSURL *url = [NSURL URLWithString:@"http://192.168.1.200:8080/YYServer/upload"]; 21 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 22 request.HTTPMethod = @"POST"; 23 24 // 设置请求体 25 NSMutableData *body = [NSMutableData data]; 26 27 /***************文件参数***************/ 28 // 参数开始的标志 29 [body appendData:YYEncode(@"--YY\r\n")]; 30 // name : 指定参数名(必须跟服务器端保持一致) 31 // filename : 文件名 32 NSString *disposition = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", name, filename]; 33 [body appendData:YYEncode(disposition)]; 34 NSString *type = [NSString stringWithFormat:@"Content-Type: %@\r\n", mimeType]; 35 [body appendData:YYEncode(type)]; 36 37 [body appendData:YYEncode(@"\r\n")]; 38 [body appendData:data]; 39 [body appendData:YYEncode(@"\r\n")]; 40 41 /***************普通参数***************/ 42 [params enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 43 // 参数开始的标志 44 [body appendData:YYEncode(@"--YY\r\n")]; 45 NSString *disposition = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n", key]; 46 [body appendData:YYEncode(disposition)]; 47 48 [body appendData:YYEncode(@"\r\n")]; 49 [body appendData:YYEncode(obj)]; 50 [body appendData:YYEncode(@"\r\n")]; 51 }]; 52 53 /***************参数结束***************/ 54 // YY--\r\n 55 [body appendData:YYEncode(@"--YY--\r\n")]; 56 request.HTTPBody = body; 57 58 // 设置请求头 59 // 请求体的长度 60 [request setValue:[NSString stringWithFormat:@"%zd", body.length] forHTTPHeaderField:@"Content-Length"]; 61 // 声明这个POST请求是个文件上传 62 [request setValue:@"multipart/form-data; boundary=YY" forHTTPHeaderField:@"Content-Type"]; 63 64 // 发送请求 65 [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { 66 if (data) { 67 NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil]; 68 NSLog(@"%@", dict); 69 } else { 70 NSLog(@"上传失败"); 71 } 72 }]; 73 } 74 75 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 76 { 77 // Socket 实现断点上传 78 79 //apache-tomcat-6.0.41/conf/web.xml 查找 文件的 mimeType 80 // UIImage *image = [UIImage imageNamed:@"test"]; 81 // NSData *filedata = UIImagePNGRepresentation(image); 82 // [self upload:@"file" filename:@"test.png" mimeType:@"image/png" data:filedata parmas:@{@"username" : @"123"}]; 83 84 // 给本地文件发送一个请求 85 NSURL *fileurl = [[NSBundle mainBundle] URLForResource:@"itcast.txt" withExtension:nil]; 86 NSURLRequest *request = [NSURLRequest requestWithURL:fileurl]; 87 NSURLResponse *repsonse = nil; 88 NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&repsonse error:nil]; 89 90 // 得到mimeType 91 NSLog(@"%@", repsonse.MIMEType); 92 [self upload:@"file" filename:@"itcast.txt" mimeType:repsonse.MIMEType data:data parmas:@{ 93 @"username" : @"999", 94 @"type" : @"XML"}]; 95 } 96 97 @end 补充说明: 文件上传请求数据格式 部分文件的MIMEType
iOS开发网络篇—多线程断点下载 说明:本文介绍多线程断点下载。项目中使用了苹果自带的类,实现了同时开启多条线程下载一个较大的文件。因为实现过程较为复杂,所以下面贴出完整的代码。 实现思路:下载开始,创建一个和要下载的文件大小相同的文件(如果要下载的文件为100M,那么就在沙盒中创建一个100M的文件,然后计算每一段的下载量,开启多条线程下载各段的数据,分别写入对应的文件部分)。 项目中用到的主要类如下: 完成的实现代码如下: 主控制器中的代码: 1 #import "YYViewController.h" 2 #import "YYFileMultiDownloader.h" 3 4 @interface YYViewController () 5 @property (nonatomic, strong) YYFileMultiDownloader *fileMultiDownloader; 6 @end 7 8 @implementation YYViewController 9 - (YYFileMultiDownloader *)fileMultiDownloader 10 { 11 if (!_fileMultiDownloader) { 12 _fileMultiDownloader = [[YYFileMultiDownloader alloc] init]; 13 // 需要下载的文件远程URL 14 _fileMultiDownloader.url = @"http://192.168.1.200:8080/MJServer/resources/jre.zip"; 15 // 文件保存到什么地方 16 NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 17 NSString *filepath = [caches stringByAppendingPathComponent:@"jre.zip"]; 18 _fileMultiDownloader.destPath = filepath; 19 } 20 return _fileMultiDownloader; 21 } 22 23 - (void)viewDidLoad 24 { 25 [super viewDidLoad]; 26 27 } 28 29 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 30 { 31 [self.fileMultiDownloader start]; 32 } 33 34 @end 自定义一个基类 YYFileDownloader.h文件 1 #import <Foundation/Foundation.h> 2 3 @interface YYFileDownloader : NSObject 4 { 5 BOOL _downloading; 6 } 7 /** 8 * 所需要下载文件的远程URL(连接服务器的路径) 9 */ 10 @property (nonatomic, copy) NSString *url; 11 /** 12 * 文件的存储路径(文件下载到什么地方) 13 */ 14 @property (nonatomic, copy) NSString *destPath; 15 16 /** 17 * 是否正在下载(有没有在下载, 只有下载器内部才知道) 18 */ 19 @property (nonatomic, readonly, getter = isDownloading) BOOL downloading; 20 21 /** 22 * 用来监听下载进度 23 */ 24 @property (nonatomic, copy) void (^progressHandler)(double progress); 25 26 /** 27 * 开始(恢复)下载 28 */ 29 - (void)start; 30 31 /** 32 * 暂停下载 33 */ 34 - (void)pause; 35 @end YYFileDownloader.m文件 1 #import "YYFileDownloader.h" 2 3 @implementation YYFileDownloader 4 @end 下载器类继承自YYFileDownloader这个类 YYFileSingDownloader.h文件 1 #import "YYFileDownloader.h" 2 3 @interface YYFileSingleDownloader : YYFileDownloader 4 /** 5 * 开始的位置 6 */ 7 @property (nonatomic, assign) long long begin; 8 /** 9 * 结束的位置 10 */ 11 @property (nonatomic, assign) long long end; 12 @end YYFileSingDownloader.m文件 1 #import "YYFileSingleDownloader.h" 2 @interface YYFileSingleDownloader() <NSURLConnectionDataDelegate> 3 /** 4 * 连接对象 5 */ 6 @property (nonatomic, strong) NSURLConnection *conn; 7 8 /** 9 * 写数据的文件句柄 10 */ 11 @property (nonatomic, strong) NSFileHandle *writeHandle; 12 /** 13 * 当前已下载数据的长度 14 */ 15 @property (nonatomic, assign) long long currentLength; 16 @end 17 18 @implementation YYFileSingleDownloader 19 20 - (NSFileHandle *)writeHandle 21 { 22 if (!_writeHandle) { 23 _writeHandle = [NSFileHandle fileHandleForWritingAtPath:self.destPath]; 24 } 25 return _writeHandle; 26 } 27 28 /** 29 * 开始(恢复)下载 30 */ 31 - (void)start 32 { 33 NSURL *url = [NSURL URLWithString:self.url]; 34 // 默认就是GET请求 35 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 36 // 设置请求头信息 37 NSString *value = [NSString stringWithFormat:@"bytes=%lld-%lld", self.begin + self.currentLength, self.end]; 38 [request setValue:value forHTTPHeaderField:@"Range"]; 39 self.conn = [NSURLConnection connectionWithRequest:request delegate:self]; 40 41 _downloading = YES; 42 } 43 44 /** 45 * 暂停下载 46 */ 47 - (void)pause 48 { 49 [self.conn cancel]; 50 self.conn = nil; 51 52 _downloading = NO; 53 } 54 55 56 #pragma mark - NSURLConnectionDataDelegate 代理方法 57 /** 58 * 1. 当接受到服务器的响应(连通了服务器)就会调用 59 */ 60 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 61 { 62 63 } 64 65 /** 66 * 2. 当接受到服务器的数据就会调用(可能会被调用多次, 每次调用只会传递部分数据) 67 */ 68 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 69 { 70 // 移动到文件的尾部 71 [self.writeHandle seekToFileOffset:self.begin + self.currentLength]; 72 // 从当前移动的位置(文件尾部)开始写入数据 73 [self.writeHandle writeData:data]; 74 75 // 累加长度 76 self.currentLength += data.length; 77 78 // 打印下载进度 79 double progress = (double)self.currentLength / (self.end - self.begin); 80 if (self.progressHandler) { 81 self.progressHandler(progress); 82 } 83 } 84 85 /** 86 * 3. 当服务器的数据接受完毕后就会调用 87 */ 88 - (void)connectionDidFinishLoading:(NSURLConnection *)connection 89 { 90 // 清空属性值 91 self.currentLength = 0; 92 93 // 关闭连接(不再输入数据到文件中) 94 [self.writeHandle closeFile]; 95 self.writeHandle = nil; 96 } 97 98 /** 99 * 请求错误(失败)的时候调用(请求超时\断网\没有网, 一般指客户端错误) 100 */ 101 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 102 { 103 104 } 105 106 @end 设计多线程下载器(利用HMFileMultiDownloader能开启多个线程同时下载一个文件) 一个多线程下载器只下载一个文件 YYFileMultiDownloader.h文件 1 #import "YYFileDownloader.h" 2 3 @interface YYFileMultiDownloader : YYFileDownloader 4 5 @end YYFileMultiDownloader.m文件 1 #import "YYFileMultiDownloader.h" 2 #import "YYFileSingleDownloader.h" 3 4 #define YYMaxDownloadCount 4 5 6 @interface YYFileMultiDownloader() 7 @property (nonatomic, strong) NSMutableArray *singleDownloaders; 8 @property (nonatomic, assign) long long totalLength; 9 @end 10 11 @implementation YYFileMultiDownloader 12 13 - (void)getFilesize 14 { 15 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]]; 16 request.HTTPMethod = @"HEAD"; 17 18 NSURLResponse *response = nil; 19 #warning 这里要用异步请求 20 [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil]; 21 self.totalLength = response.expectedContentLength; 22 } 23 24 - (NSMutableArray *)singleDownloaders 25 { 26 if (!_singleDownloaders) { 27 _singleDownloaders = [NSMutableArray array]; 28 29 // 获得文件大小 30 [self getFilesize]; 31 32 // 每条路径的下载量 33 long long size = 0; 34 if (self.totalLength % YYMaxDownloadCount == 0) { 35 size = self.totalLength / YYMaxDownloadCount; 36 } else { 37 size = self.totalLength / YYMaxDownloadCount + 1; 38 } 39 40 // 创建N个下载器 41 for (int i = 0; i<YYMaxDownloadCount; i++) { 42 YYFileSingleDownloader *singleDownloader = [[YYFileSingleDownloader alloc] init]; 43 singleDownloader.url = self.url; 44 singleDownloader.destPath = self.destPath; 45 singleDownloader.begin = i * size; 46 singleDownloader.end = singleDownloader.begin + size - 1; 47 singleDownloader.progressHandler = ^(double progress){ 48 NSLog(@"%d --- %f", i, progress); 49 }; 50 [_singleDownloaders addObject:singleDownloader]; 51 } 52 53 // 创建一个跟服务器文件等大小的临时文件 54 [[NSFileManager defaultManager] createFileAtPath:self.destPath contents:nil attributes:nil]; 55 56 // 让self.destPath文件的长度是self.totalLengt 57 NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:self.destPath]; 58 [handle truncateFileAtOffset:self.totalLength]; 59 } 60 return _singleDownloaders; 61 } 62 63 /** 64 * 开始(恢复)下载 65 */ 66 - (void)start 67 { 68 [self.singleDownloaders makeObjectsPerformSelector:@selector(start)]; 69 70 _downloading = YES; 71 } 72 73 /** 74 * 暂停下载 75 */ 76 - (void)pause 77 { 78 [self.singleDownloaders makeObjectsPerformSelector:@selector(pause)]; 79 _downloading = NO; 80 } 81 82 @end 补充说明:如何获得将要下载的文件的大小?
iOS开发UI篇—模仿ipad版QQ空间登录界面 一、实现和步骤 1.一般ipad项目在命名的时候可以加一个HD,标明为高清版 2.设置项目的文件结构,分为home和login两个部分 3.登陆界面的设置 (1)设置第一个控制器和自定义的控制器类(登陆)关联 (2)设置控制器的view的颜色,RGB三个值都为42 (3)导入相关的素材图片 关于图片:一般给竖屏用的图片,以portrait名称标识,给横屏用的图片,以Landscape名称标识 修改plist文件,调整图片 提示:在项目中(ipad的使用中)有很多的地方都会用到图标 补充:关于apple开发运用中图标的尺寸,可以查看官方文档(apple icon)。 (4)初步的界面设置 添加一个UIimageView到storyboard中,设置其对应的图片。 使用outLayOut对其进行布局。设置其距离view的顶部有50的距离并固定,设置其水平居中。 添加一个view到storyboard中,用来装载密码,登陆等控件。 添加一个imageView到storyboard中,用来设置密码和登陆。(注意:这里提供的图片需要拉伸,设置拉伸最中间的1个像素)。 设置账号,调整约束。添加一个对应的textfield控件,设置约束,设置内部的文字颜色为白色,设置取出白色的背景,设置当编辑时提供一个清除按钮,设置键盘为数字键盘。 设置其边框样式 设置账号输入框的弹出键盘为数字键盘 设置弹出的数字键盘的return键位Next. 设置密码,步骤类似于账号,设置账号内部文字为密文。 设置清除按钮 设置密码输入框的弹出键盘的return键位Done. (5)关于键盘的处理。 默认键盘。next,和Done。 让控制器称为文本框的代理。控制器需要遵守协议。 关于自动对文本框当前是否有数值进行判断,勾选选项,当textField中没有值的时候,(return)为灰色按钮,有值的时候可点。 点击Next按键,光标移动到密码输入框上,点击Done按键,执行登录相关操作。 实现代码如下(注意已经对两个textField进行了拖线处理) 说明:这里最简单的办法是在storyboard中给两个textfield设置两个tag值,在代码处理中根据其tag值取出相对应的textField,但是不推荐这么做。 二、登陆的设置 提示:在storyboard或者xib中对一块图片进行拉伸只对imageView有效,对按钮是没有效果的。 如何设置按钮填充: 第一种方式:使用代码拉伸最中间的一个像素。 第二种方式:直接对图片进行设置。
iOS开发UI篇—iPad开发中得modal介绍 一、简单介绍 说明1: 在iPhone开发中,Modal是一种常见的切换控制器的方式 默认是从屏幕底部往上弹出,直到完全盖住后面的内容为止 说明2: 在iPad开发中,Modal的使用频率也是非常高的 对比iPhone开发,Modal在iPad开发中多了一些用法 二、呈现样式 (一)什么叫呈现样式 Modal出来的控制器,最终显示出来的样子 (二)Modal常见有4种呈现样式 (1)UIModalPresentationFullScreen :全屏显示(默认) (2)UIModalPresentationPageSheet 宽度:竖屏时的宽度(768) 高度:当前屏幕的高度(填充整个高度) (3)UIModalPresentationFormSheet :占据屏幕中间的一小块(比较常用) (4)UIModalPresentationCurrentContext :跟随父控制器的呈现样式 (三)代码示例 (四)注意点 说明:给tableView包装一个导航控制器(注意modal谁就设置谁的现实样式) 现实效果: 三、过渡样式 (一)什么叫过渡样式 Modal出来的控制器,是以怎样的动画呈现出来 (二)Modal一共4种过渡样式 UIModalTransitionStyleCoverVertical :从底部往上钻(默认) UIModalTransitionStyleFlipHorizontal :三维翻转 UIModalTransitionStyleCrossDissolve :淡入淡出 UIModalTransitionStylePartialCurl :翻页(只显示部分,使用前提:呈现样式必须是UIModalPresentationFullScreen) (三)代码示例 实现效果(注意页面效果的显示)
iOS开发UI篇—popoverController使用注意 一、设置尺寸 提示:不建议,像下面这样吧popover的宽度和高度写死。 1 //1.新建一个内容控制器 2 YYMenuViewController *menuVc=[[YYMenuViewController alloc]init]; 3 4 //2.新建一个popoverController,并设置其内容控制器 5 self.popover=[[UIPopoverController alloc]initWithContentViewController:menuVc]; 6 7 //3.设置尺寸 8 self.popover.popoverContentSize=CGSizeMake(300, 200); 9 10 //4.显示 11 [self.popover presentPopoverFromBarButtonItem:self.navigationItem.leftBarButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 更好的设计是:popover的尺寸应该由内部控制器的内容所决定。 内容控制器可以自行设置自己在popover中显示的尺寸,其中有两种方法: (1)在iOS 7之前 @property (nonatomic,readwrite) CGSize contentSizeForViewInPopover; (2)从iOS 7开始 @property (nonatomic) CGSize preferredContentSize; 以上属性都是UIViewController的 1 -(NSArray *)menus 2 { 3 if (_menus==nil) { 4 _menus=@[@"列表1",@"列表2",@"列表3",@"列表4",@"列表4",@"列表4",@"列表4",@"列表4",@"列表4",@"列表4",@"列表1",@"列表2",@"列表1",@"列表2"]; 5 } 6 return _menus; 7 } 8 - (void)viewDidLoad 9 { 10 [super viewDidLoad]; 11 12 //设置控制器将来在popover中的尺寸 13 CGFloat maxH=MIN(480,self.menus.count*44); 14 //ios7以前的设置 15 // self.contentSizeForViewInPopover=CGSizeMake(150, maxH); 16 //ios7以后 17 self.preferredContentSize=CGSizeMake(150, maxH); 18 19 } 效果: 关于MIN(A,B)的说明,最终的大小取决于B,但是最大不能超过A,如果超过A那么值就等于A。 二、设置显示的位置 1.设置显示的位置有2种方法 (1)围绕着一个UIBarButtonItem显示(箭头指定那个UIBarButtonItem) - (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)item permittedArrowDirections:(UIPopoverArrowDirection)arrowDirections animated:(BOOL)animated; item :围绕着哪个UIBarButtonItem显示 arrowDirections :箭头的方向 animated :是否通过动画显示出来 (2)围绕着某一块特定区域显示(箭头指定那块特定区域) - (void)presentPopoverFromRect:(CGRect)rect inView:(UIView *)view permittedArrowDirections:(UIPopoverArrowDirection)arrowDirections animated:(BOOL)animated; rect :指定箭头所指区域的矩形框范围(位置和尺寸),以view的左上角为坐标原点 view :rect参数是以view的左上角为坐标原点(0,0) arrowDirections :箭头的方向 animated :是否通过动画显示出来 rect和view参数如下: 相关代码: 1 // 2 // YYViewController.m 3 // 01-PopoverController简单介绍 4 // 5 // Created by apple on 14-8-17. 6 // Copyright (c) 2014年 yangyong. All rights reserved. 7 // 8 9 #import "YYViewController.h" 10 #import "YYMenuViewController.h" 11 12 @interface YYViewController ()<UIPopoverControllerDelegate> 13 @property(nonatomic,strong)UIPopoverController *popover; 14 - (IBAction)buttonClick:(UIButton *)sender; 15 @end 16 17 @implementation YYViewController 18 19 - (void)viewDidLoad 20 { 21 [super viewDidLoad]; 22 } 23 24 -(void)showPopoverFromItem 25 { 26 //1.新建一个内容控制器 27 YYMenuViewController *menuVc=[[YYMenuViewController alloc]init]; 28 29 //2.新建一个popoverController,并设置其内容控制器 30 self.popover=[[UIPopoverController alloc]initWithContentViewController:menuVc]; 31 32 //3.设置尺寸 33 // self.popover.popoverContentSize=CGSizeMake(300, 200); 34 35 //4.显示 36 [self.popover presentPopoverFromBarButtonItem:self.navigationItem.leftBarButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 37 38 //5.设置代理 39 self.popover.delegate=self; 40 } 41 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 42 { 43 44 } 45 46 #pragma mark-代理方法 47 //popoverController消失的时候调用 48 -(void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController 49 { 50 } 51 //popoverController的位置改变的时候调用(如竖屏变横屏) 52 -(void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view 53 { 54 55 } 56 //用来决定用户点击了蒙版后,popoverController是否可以dismiss,返回YES代表可以,返回NO代表不可以 57 -(BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController 58 { 59 return NO; 60 } 61 - (IBAction)buttonClick:(UIButton *)sender { 62 63 //1.新建一个popoverController并设置其内容控制器 64 YYMenuViewController *menuVc=[[YYMenuViewController alloc]init]; 65 self.popover=[[UIPopoverController alloc]initWithContentViewController:menuVc]; 66 67 //2.显示 68 //2.1第一种方式 69 // [self.popover presentPopoverFromBarButtonItem:<#(UIBarButtonItem *)#> permittedArrowDirections:<#(UIPopoverArrowDirection)#> animated:<#(BOOL)#>]; 70 //2.2第二种方式 71 [self.popover presentPopoverFromRect:sender.bounds inView:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 72 //说明:popover会指向sender.bounds这一块矩形框,这块矩形框以sender的左上角为坐标原点 73 //注意:注意sender.frame和sender.bounds的区别 74 75 } 76 @end 界面效果:(部分) 关于frame坐标计算的图示: 下面两者是等价的: 即如果想让箭头指向某一个UIView的做法有2种做法,比如指向一个button 方法1 [popover presentPopoverFromRect:button.bounds inView:button permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES]; 方法2 [popover presentPopoverFromRect:button.frame inView:button.superview permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES]; 三、设置代理 代理对象 @property (nonatomic, assign) id <UIPopoverControllerDelegate> delegate; 是否可见 @property (nonatomic, readonly, getter=isPopoverVisible) BOOL popoverVisible; 箭头方向 @property (nonatomic, readonly) UIPopoverArrowDirection popoverArrowDirection; 关闭popover(让popover消失) - (void)dismissPopoverAnimated:(BOOL)animated; 代码说明: 1 ....... 2 //5.设置代理 3 self.popover.delegate=self; 4 } 5 6 #pragma mark-代理方法 7 //popoverController消失的时候调用 8 -(void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController 9 { 10 } 11 //popoverController的位置改变的时候调用(如竖屏变横屏) 12 -(void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing *)view 13 { 14 15 } 16 //用来决定用户点击了蒙版后,popoverController是否可以dismiss,返回YES代表可以,返回NO代表不可以 17 -(BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController 18 { 19 return NO; 20 } 四、防止点击UIPopoverController区域外消失 默认情况下 只要UIPopoverController显示在屏幕上,UIPopoverController背后的所有控件默认是不能跟用户进行正常交互的 点击UIPopoverController区域外的控件,UIPopoverController默认会消失 要想点击UIPopoverController区域外的控件时不让UIPopoverController消失,解决办法是设置passthroughViews属性 @property (nonatomic, copy) NSArray *passthroughViews; 这个属性是设置当UIPopoverController显示出来时,哪些控件可以继续跟用户进行正常交互。这样的话,点击区域外的控件就不会让UIPopoverController消失了 代码示例: 1 - (IBAction)buttonClick:(UIButton *)sender { 2 3 //1.新建一个popoverController并设置其内容控制器 4 YYMenuViewController *menuVc=[[YYMenuViewController alloc]init]; 5 self.popover=[[UIPopoverController alloc]initWithContentViewController:menuVc]; 6 7 //设置过滤掉一些控件 8 self.popover.passthroughViews=@[self.switchview]; 9 10 //2.显示 11 //2.1第一种方式 12 // [self.popover presentPopoverFromBarButtonItem:<#(UIBarButtonItem *)#> permittedArrowDirections:<#(UIPopoverArrowDirection)#> animated:<#(BOOL)#>]; 13 //2.2第二种方式 14 // [self.popover presentPopoverFromRect:sender.bounds inView:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 15 [self.popover presentPopoverFromRect:sender.frame inView:sender.superview permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 16 //说明:popover会指向sender.bounds这一块矩形框,这块矩形框以sender的左上角为坐标原点 17 //注意:注意sender.frame和sender.bounds的区别 18 19 } 补充: UIPopoverController这个类是只能用在iPad中的 要想在iPhone中实现popover效果,必须得自定义view,可以参考 http://code4app.com/ios/Popover-View-in-iPhone/4fa931bd06f6e78d0f000000 http://code4app.com/ios/Popup-Menu/512231ac6803fa9e08000000
iOS开发UI篇—popoverController简单介绍 一、简单介绍 1.什么是UIPopoverController 是iPad开发中常见的一种控制器(在iPhone上不允许使用) 跟其他控制器不一样的是,它直接继承自NSObject,并非继承自UIViewController 它只占用部分屏幕空间来呈现信息,而且显示在屏幕的最前面 2.使用步骤 要想显示一个UIPopoverController,需要经过下列步骤 (1)设置内容控制器 由于UIPopoverController直接继承自NSObject,不具备可视化的能力。因此UIPopoverController上面的内容必须由另外一个继承自UIViewController的控制器来提供,这个控制器称为“内容控制器” (2)设置内容的尺寸 显示出来占据多少屏幕空间 (3)显示,即从哪个地方冒出来 二、具体的步骤 代码示例: 新建一个ipad项目,编写如下代码: 新建一个继承自UITableView的控制器,让其作为popoverController的内容控制器。 YYMenuViewController.m文件 1 // 2 // YYMenuViewController.m 3 // 01-PopoverController简单介绍 4 // 5 // Created by apple on 14-8-17. 6 // Copyright (c) 2014年 yangyong. All rights reserved. 7 // 8 9 #import "YYMenuViewController.h" 10 11 @interface YYMenuViewController () 12 @property(nonatomic,strong)NSArray *menus; 13 @end 14 15 @implementation YYMenuViewController 16 17 -(NSArray *)menus 18 { 19 if (_menus==nil) { 20 _menus=@[@"列表1",@"列表2",@"列表3",@"列表4"]; 21 } 22 return _menus; 23 } 24 - (void)viewDidLoad 25 { 26 [super viewDidLoad]; 27 } 28 29 -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 30 { 31 return 1; 32 } 33 -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 34 { 35 return self.menus.count; 36 } 37 -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 38 { 39 static NSString *ID=@"ID"; 40 UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:ID]; 41 if (cell==nil) { 42 cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID]; 43 } 44 45 cell.textLabel.text=self.menus[indexPath.row]; 46 return cell; 47 } 48 49 @end YYViewController.m文件 1 // 2 // YYViewController.m 3 // 01-PopoverController简单介绍 4 // 5 // Created by apple on 14-8-17. 6 // Copyright (c) 2014年 yangyong. All rights reserved. 7 // 8 9 #import "YYViewController.h" 10 #import "YYMenuViewController.h" 11 12 @interface YYViewController () 13 @property(nonatomic,strong)UIPopoverController *popover; 14 @end 15 16 @implementation YYViewController 17 18 - (void)viewDidLoad 19 { 20 [super viewDidLoad]; 21 } 22 23 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 24 { 25 //1.新建一个内容控制器 26 YYMenuViewController *menuVc=[[YYMenuViewController alloc]init]; 27 28 //2.新建一个popoverController,并设置其内容控制器 29 self.popover=[[UIPopoverController alloc]initWithContentViewController:menuVc]; 30 31 //3.设置尺寸 32 self.popover.popoverContentSize=CGSizeMake(300, 200); 33 34 //4.显示 35 [self.popover presentPopoverFromBarButtonItem:self.navigationItem.leftBarButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; 36 } 37 @end 实现效果如下图: 说明:在storyboard中添加了导航控制器,并添加了两个按钮。 三、常见报错 在popover的使用过程中,经常会遇到这个错误 -[UIPopoverController dealloc] reached while popover is still visible. 错误的大体意思是:popover在仍旧可见的时候被销毁了(调用了dealloc) 从错误可以得出的结论 当popover仍旧可见的时候,不准销毁popover对象 在销毁popover对象之前,一定先让popover消失(不可见) 如:在上述代码中,如果不适用全局变量popover,那么将会出现上面的错误。
iOS开发UI篇—iPad和iPhone开发的比较 一、iPad简介 1.什么是iPad 一款苹果公司于2010年发布的平板电脑 定位介于苹果的智能手机iPhone和笔记本电脑产品之间 跟iPhone一样,搭载的是iOS操作系统 2.iPad的市场情况 截止至2013年10月23日,iPad已经累计销售1.7亿台 在平板市场的占有率高达81% 二、关于iphone和iPad 说明:iPhone是手机,iPad、iPad Mini是平板电脑 iPhone和iPad开发的区别 屏幕的尺寸 \分辨率 UI元素的排布 \设计 键盘 API 屏幕方向的支持 详细 : (1)屏幕的尺寸 \分辨率 在iOS开发中,只需要关注以下几种情况 iPhone 3.5 inch:320 x 480 4.0 inch:320 x 568 iPad、iPad Mini 9.7 inch、7.9 inch:768 x 1024 (2)UI元素的排布 \设计 因为iPad屏幕比iPhone大,可以容纳更多的UI元素,因此排列方式是不一样的 比如新浪微博:(左图是iPhone,右图的iPad) (3)键盘 iPad的虚拟键盘多了个退出键盘的按钮 左图为iPhone键盘,右图为iPad键盘 (4)iPad特有的API iPad多了一些特有的类,比如: UIPopoverController(左图) UISplitViewController(右图) (5)共有API的差异 有些API在iPhone和iPad都能用,但是显示效果是有差异的,比如UIActionSheet(左图iPhone,右图iPad) (6)屏幕方向的支持 (7)横竖屏支持 一般情况下,iPhone应用就一种屏幕方向,要么竖屏,要么横屏(游戏) 其次,苹果官方建议:iPad应用最好同时支持横屏、竖屏两种方向 三、开发细节 1.新建一个iPad应用程序 2.设备支持的应用程序 iPhone上只能运行iPhone程序 iPad上能够运行iPhone \ iPad程序 3.开发过程 iPhone和iPad开发的流程是一致的 在iPhone开发中学到的所有知识基本都能用在iPad上
iOS开发拓展篇—音频处理(音乐播放器6) 一、图片处理 说明: Aspect表示按照原来的宽高比进行缩放。 Aspectfit表示按照原来的宽高比缩放,要求看到全部图片,后果是不能完全覆盖窗口,会留有空白。 Aspectfill表示按照原来的宽高比缩放,但只能看到部分图片。引发的问题:可能会有一部分超出屏幕。 所以,如果选择了Aspectfill模式,那么需要剪切超出的图片,在storyboard中也可以进行设置。 下面的两种设置是等效的。 (1)在storyboard中进行设置 (2)使用代码裁剪 二、播放处理 1.当前歌曲播放结束之后,继续播放后面的歌曲 解决方案:成为播放器的代理。监听播放器的播放。 2.播放中断处理 1 #pragma mark-音乐播放器的代理 2 //播放器播放完毕后就会调用该方法 3 -(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag 4 { 5 [self next]; 6 } 7 //当播放器遇到中断的时候(如来电),调用该方法 8 -(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player 9 { 10 if (self.player.isPlaying) { 11 //如果当前正在播放,那么就暂停 12 [self playOrPause]; 13 } 14 } 15 //中断事件结束后调用下面的方法 16 -(void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flags 17 { 18 //可以什么都不做,让用户决定是继续播放还是暂停 19 } 3.开启后台任务 说明:新的问题——当播放任务进入到后台运行的时候,音乐的播放会停止。 解决方案: YYAppDelegate.m文件中的代码处理如下: 争取更多的机会: 告诉其是一个音乐播放任务 4.最好是在音频播放工具类中也进行处理。 1 #import "YYAudioTool.h" 2 3 @implementation YYAudioTool 4 +(void)initialize 5 { 6 //音频会话 7 AVAudioSession *session =[AVAudioSession sharedInstance]; 8 //设置绘画类型(播放类型,播放模式,会自动停止其他音乐的播放) 9 [session setCategory:AVAudioSessionCategorySoloAmbient error:nil]; 10 //激活会话 11 [session setActive:YES error:nil]; 12 } 13 //....... 三、创建歌词控件 创建歌词控件 歌词的格式说明: 播放效果: 毛玻璃效果的实现 (1)让美工提供一张半透明的图片 (2)利用一大堆的图形算法生成毛玻璃样式的UIImage对象 这里使用一个第三方框架来生成毛玻璃效果 毛玻璃:英文blur 第三方框架:DRNRealTimeBlur 具体实现: 新建一个类,让其继承自,就能够实现毛玻璃效果。 在xib中添加一个歌词控件。 注意歌词控件的层级关系,退出和词图两个按钮应该在歌词控件的上面,这样才能够点击切换。 把该控件和新建的类进行关联。 添加约束,并清空其背景颜色。默认不显示,(设置隐藏) 毛玻璃效果如下: 简单的代码处理如下: YYLrcView.m文件 1 // 2 // YYLrcView.m 3 // 24-音频处理(音乐播放器5) 4 // 5 // Created by apple on 14-8-15. 6 // Copyright (c) 2014年 yangyong. All rights reserved. 7 // 8 9 #import "YYLrcView.h" 10 11 @interface YYLrcView ()<UITableViewDataSource,UITableViewDelegate> 12 @property(nonatomic,strong)UITableView *tableView; 13 @end 14 @implementation YYLrcView 15 16 - (id)initWithFrame:(CGRect)frame 17 { 18 self = [super initWithFrame:frame]; 19 if (self) { 20 [self setup]; 21 } 22 return self; 23 } 24 25 -(id)initWithCoder:(NSCoder *)aDecoder 26 { 27 self=[super initWithCoder:aDecoder]; 28 if (self) { 29 [self setup]; 30 } 31 return self; 32 } 33 34 -(void)setup 35 { 36 //添加表格控件 37 UITableView *tableView=[[UITableView alloc]init]; 38 tableView.delegate=self; 39 tableView.dataSource=self; 40 [self addSubview:tableView]; 41 self.tableView=tableView; 42 } 43 44 #pragma mark-公共方法 45 -(void)setLrcname:(NSString *)lrcname 46 { 47 _lrcname=[lrcname copy]; 48 } 49 50 #pragma mark-数据源方法 51 #warning TODO 52 53 @end 代码说明: 注意:不要认为只有控制器才能作为tableView的数据源和代理。这也就是为什么代理和数据源属性的类型为id的原因,遵守其协议即可做其代理和数据源。 -(id)initWithCoder:。从文件中读取一个对象的时候调用,为了程序的严谨性,建议在两个方法中调用初始化的代码。 调用这个方法,说明对象是从文件中解析出来的。 如果是通过代码alloc\init创建的对象,那么调用-(id)initWithFrame:方法。 说明:xib文件的本质是xml文件。 四、主控制器的代码补充 YYPlayingViewController.m文件 1 // 2 // YYPlayingViewController.m 3 // 4 5 #import "YYPlayingViewController.h" 6 #import "YYMusicTool.h" 7 #import "YYMusicModel.h" 8 #import "YYAudioTool.h" 9 #import "YYLrcView.h" 10 11 @interface YYPlayingViewController ()<AVAudioPlayerDelegate> 12 - (IBAction)lyricOrPic:(UIButton *)sender; 13 @property (weak, nonatomic) IBOutlet YYLrcView *lrcView; 14 //显示拖拽进度 15 @property (weak, nonatomic) IBOutlet UIButton *currentTimeView; 16 //进度条 17 @property (weak, nonatomic) IBOutlet UIView *progressView; 18 //滑块 19 @property (weak, nonatomic) IBOutlet UIButton *slider; 20 @property (weak, nonatomic) IBOutlet UIImageView *iconView; 21 @property (weak, nonatomic) IBOutlet UILabel *songLabel; 22 @property (weak, nonatomic) IBOutlet UILabel *singerLabel; 23 //当前播放的音乐的时长 24 @property (weak, nonatomic) IBOutlet UILabel *durationLabel; 25 //正在播放的音乐 26 @property(nonatomic,strong)YYMusicModel *playingMusic; 27 //音乐播放器对象 28 @property(nonatomic,strong)AVAudioPlayer *player; 29 //定时器 30 @property(nonatomic,strong)NSTimer *CurrentTimeTimer; 31 - (IBAction)exit; 32 - (IBAction)tapProgressBg:(UITapGestureRecognizer *)sender; 33 - (IBAction)panSlider:(UIPanGestureRecognizer *)sender; 34 - (IBAction)previous; 35 - (IBAction)playOrPause; 36 - (IBAction)next; 37 @property (weak, nonatomic) IBOutlet UIButton *playOrPauseButton; 38 39 @end 40 41 @implementation YYPlayingViewController 42 43 -(void)viewDidLoad 44 { 45 [super viewDidLoad]; 46 47 //裁剪圆角 48 self.currentTimeView.layer.cornerRadius=8; 49 50 } 51 #pragma mark-公共方法 52 -(void)show 53 { 54 //1.禁用整个app的点击事件 55 UIWindow *window=[UIApplication sharedApplication].keyWindow; 56 window.userInteractionEnabled=NO; 57 58 //2.添加播放界面 59 //设置View的大小为覆盖整个窗口 60 self.view.frame=window.bounds; 61 //设置view显示 62 self.view.hidden=NO; 63 //把View添加到窗口上 64 [window addSubview:self.view]; 65 66 //3.检测是否换了歌曲 67 if (self.playingMusic!=[YYMusicTool playingMusic]) { 68 [self resetPlayingMusic]; 69 } 70 71 //4.使用动画让View显示 72 self.view.y=self.view.height; 73 [UIView animateWithDuration:0.25 animations:^{ 74 self.view.y=0; 75 } completion:^(BOOL finished) { 76 77 //设置音乐数据 78 [self starPlayingMusic]; 79 window.userInteractionEnabled=YES; 80 }]; 81 } 82 83 84 #pragma mark-私有方法 85 //重置正在播放的音乐 86 -(void)resetPlayingMusic 87 { 88 //1.重置界面数据 89 self.iconView.image=[UIImage imageNamed:@"play_cover_pic_bg"]; 90 self.songLabel.text=nil; 91 self.singerLabel.text=nil; 92 93 //2.停止播放 94 [YYAudioTool stopMusic:self.playingMusic.filename]; 95 //把播放器进行清空 96 self.player=nil; 97 98 //3.停止定时器 99 [self removeCurrentTime]; 100 101 //4.设置音乐播放按钮的状态 102 self.playOrPauseButton.selected=NO; 103 } 104 //开始播放音乐数据 105 -(void)starPlayingMusic 106 { 107 //1.设置界面数据 108 109 //如果当前播放的音乐就是传入的音乐,那么就直接返回 110 if (self.playingMusic==[YYMusicTool playingMusic]) 111 { 112 //把定时器加进去 113 [self addCurrentTimeTimer]; 114 return; 115 } 116 //存取音乐 117 self.playingMusic=[YYMusicTool playingMusic]; 118 self.iconView.image=[UIImage imageNamed:self.playingMusic.icon]; 119 self.songLabel.text=self.playingMusic.name; 120 self.singerLabel.text=self.playingMusic.singer; 121 122 //2.开始播放 123 self.player = [YYAudioTool playMusic:self.playingMusic.filename]; 124 self.player.delegate=self; 125 126 //3.设置时长 127 //self.player.duration; 播放器正在播放的音乐文件的时间长度 128 self.durationLabel.text=[self strWithTime:self.player.duration]; 129 130 //4.添加定时器 131 [self addCurrentTimeTimer]; 132 133 //5.设置音乐播放按钮的状态 134 self.playOrPauseButton.selected=YES; 135 136 //6.设置歌词 137 self.lrcView.lrcname=self.playingMusic.lrcname; 138 } 139 140 /** 141 *把时间长度-->时间字符串 142 */ 143 -(NSString *)strWithTime:(NSTimeInterval)time 144 { 145 int minute=time / 60; 146 int second=(int)time % 60; 147 return [NSString stringWithFormat:@"%d:%d",minute,second]; 148 } 149 150 #pragma mark-定时器控制 151 /** 152 * 添加一个定时器 153 */ 154 -(void)addCurrentTimeTimer 155 { 156 //如果当前没有在播放,那么就直接返回 157 if (self.player.isPlaying==NO) return; 158 159 //在添加一个定时器之前,先把以前的定时器移除 160 [self removeCurrentTime]; 161 162 //提前先调用一次进度更新,以保证定时器的工作时及时的 163 [self updateCurrentTime]; 164 165 //创建一个定时器,每一秒钟调用一次 166 self.CurrentTimeTimer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateCurrentTime) userInfo:nil repeats:YES]; 167 //把定时器加入到运行时中 168 [[NSRunLoop mainRunLoop]addTimer:self.CurrentTimeTimer forMode:NSRunLoopCommonModes]; 169 } 170 /** 171 *移除一个定时器 172 */ 173 -(void)removeCurrentTime 174 { 175 [self.CurrentTimeTimer invalidate]; 176 177 //把定时器清空 178 self.CurrentTimeTimer=nil; 179 } 180 181 /** 182 * 更新播放进度 183 */ 184 -(void)updateCurrentTime 185 { 186 //1.计算进度值 187 double progress=self.player.currentTime/self.player.duration; 188 189 //2.计算滑块的x值 190 // 滑块的最大的x值 191 CGFloat sliderMaxX=self.view.width-self.slider.width; 192 self.slider.x=sliderMaxX*progress; 193 //设置滑块上的当前播放时间 194 [self.slider setTitle:[self strWithTime:self.player.currentTime] forState:UIControlStateNormal]; 195 196 //3.设置进度条的宽度 197 self.progressView.width=self.slider.center.x; 198 199 } 200 201 #pragma mark-内部的按钮监听方法 202 //返回按钮 203 - (IBAction)exit { 204 205 //0.移除定时器 206 [self removeCurrentTime]; 207 //1.禁用整个app的点击事件 208 UIWindow *window=[UIApplication sharedApplication].keyWindow; 209 window.userInteractionEnabled=NO; 210 211 //2.动画隐藏View 212 [UIView animateWithDuration:0.25 animations:^{ 213 self.view.y=window.height; 214 } completion:^(BOOL finished) { 215 window.userInteractionEnabled=YES; 216 //设置view隐藏能够节省一些性能 217 self.view.hidden=YES; 218 }]; 219 220 } 221 222 /** 223 *点击了进度条 224 */ 225 - (IBAction)tapProgressBg:(UITapGestureRecognizer *)sender { 226 //获取当前单击的点 227 CGPoint point=[sender locationInView:sender.view]; 228 //切换歌曲的当前播放时间 229 self.player.currentTime=(point.x/sender.view.width)*self.player.duration; 230 //更新播放进度 231 [self updateCurrentTime]; 232 } 233 /** 234 *拖动滑块 235 */ 236 - (IBAction)panSlider:(UIPanGestureRecognizer *)sender { 237 238 //1.获得挪动的距离 239 CGPoint t=[sender translationInView:sender.view]; 240 //把挪动清零 241 [sender setTranslation:CGPointZero inView:sender.view]; 242 243 //2.控制滑块和进度条的frame 244 CGFloat sliderMaxX=self.view.width-self.slider.width; 245 self.slider.x+=t.x; 246 //控制滑块的frame,不让其越界 247 if(self.slider.x<0) 248 { 249 self.slider.x=0; 250 }else if (self.slider.x>sliderMaxX) 251 { 252 self.slider.x=sliderMaxX; 253 } 254 //设置进度条的宽度 255 self.progressView.width=self.slider.center.x; 256 257 //3.设置时间值 258 double progress=self.slider.x/sliderMaxX; 259 //当前的时间值=音乐的时长*当前的进度值 260 NSTimeInterval time=self.player.duration*progress; 261 [self.slider setTitle:[self strWithTime:time] forState:UIControlStateNormal]; 262 263 //设置拖拽进度的X的值 264 self.currentTimeView.x=self.slider.x; 265 [self.currentTimeView setTitle:self.slider.currentTitle forState:UIControlStateNormal]; 266 267 //4.如果开始拖动,那么就停止定时器 268 if (sender.state==UIGestureRecognizerStateBegan) { 269 //停止定时器 270 [self removeCurrentTime]; 271 272 //设置拖拽进度 273 //显示 274 self.currentTimeView.hidden=NO; 275 self.currentTimeView.y=self.currentTimeView.superview.height-5-self.currentTimeView.height; 276 277 }else if(sender.state==UIGestureRecognizerStateEnded) 278 { 279 //隐藏 280 self.currentTimeView.hidden=YES; 281 //设置播放器播放的时间 282 self.player.currentTime=time; 283 #warning 如果正在播放,才需要添加定时器 284 // if (self.player.isPlaying) { 285 //开启定时器 286 [self addCurrentTimeTimer]; 287 // } 288 } 289 } 290 291 //上一首 292 - (IBAction)previous { 293 //1.在开始播放之前,禁用一切的app点击事件 294 UIWindow *window=[[UIApplication sharedApplication].windows lastObject]; 295 window.userInteractionEnabled=NO; 296 297 //2.重置当前歌曲 298 [self resetPlayingMusic]; 299 300 //3.获得上一首歌曲 301 [YYMusicTool setPlayingMusic:[YYMusicTool previousMusic]]; 302 303 //4.播放上一首歌曲 304 [self starPlayingMusic]; 305 306 //5.回复window的点击为可用 307 window.userInteractionEnabled=YES; 308 } 309 //下一首 310 - (IBAction)next { 311 //1.在开始播放之前,禁用一切的app点击事件 312 UIWindow *window=[[UIApplication sharedApplication].windows lastObject]; 313 window.userInteractionEnabled=NO; 314 315 //2.重置当前歌曲 316 [self resetPlayingMusic]; 317 318 //3.获得下一首歌曲 319 [YYMusicTool setPlayingMusic:[YYMusicTool nextMusic]]; 320 321 //4.播放下一首歌曲 322 [self starPlayingMusic]; 323 324 //5.回复window的点击为可用 325 window.userInteractionEnabled=YES; 326 } 327 328 //继续或暂停播放 329 - (IBAction)playOrPause { 330 if (self.playOrPauseButton.isSelected) {//暂停 331 self.playOrPauseButton.selected=NO; 332 //暂停播放 333 [YYAudioTool pauseMusic:self.playingMusic.filename]; 334 //停掉定时器 335 [self removeCurrentTime]; 336 }else 337 { 338 self.playOrPauseButton.selected=YES; 339 //继续播放 340 [YYAudioTool playMusic:self.playingMusic.filename]; 341 //开启定时器 342 [self addCurrentTimeTimer]; 343 } 344 } 345 346 #pragma mark-音乐播放器的代理 347 //播放器播放完毕后就会调用该方法 348 -(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag 349 { 350 [self next]; 351 } 352 //当播放器遇到中断的时候(如来电),调用该方法 353 -(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player 354 { 355 if (self.player.isPlaying) { 356 //如果当前正在播放,那么就暂停 357 [self playOrPause]; 358 } 359 } 360 //中断事件结束后调用下面的方法 361 -(void)audioPlayerEndInterruption:(AVAudioPlayer *)player withOptions:(NSUInteger)flags 362 { 363 //可以什么都不做,让用户决定是继续播放还是暂停 364 } 365 - (IBAction)lyricOrPic:(UIButton *)sender { 366 if (self.lrcView.hidden) { 367 //显示歌词 368 self.lrcView.hidden=NO; 369 sender.selected=YES; 370 }else 371 { 372 //隐藏歌词,显示歌手图片 373 self.lrcView.hidden=YES; 374 sender.selected=NO; 375 } 376 } 377 @end
iOS开发拓展篇—xib中关于拖拽手势的潜在错误 一、错误说明 自定义一个用来封装工具条的类 搭建xib,并添加一个拖拽的手势。 主控制器的代码:加载工具条 封装工具条以及手势拖拽的监听事件 此时运行程序,程序直接崩溃,报错如下: 说明:手势不会有superView方法,superView是UIView的方法,说明我们错误的把手势对象当成是UIView来用了。 调试查看出现问题的原因: 出现问题的原因: 说明:通过lastObject取出来的对象是手势,而不是xib,因此出现上面的错误。 把lastObject换成firstObject即可,必要时可以把数组中的所有对象都打印出来查看。
iOS开发拓展篇—音频处理(音乐播放器5) 实现效果: 一、半透明滑块的设置 1 /** 2 *拖动滑块 3 */ 4 - (IBAction)panSlider:(UIPanGestureRecognizer *)sender { 5 6 //1.获得挪动的距离 7 CGPoint t=[sender translationInView:sender.view]; 8 //把挪动清零 9 [sender setTranslation:CGPointZero inView:sender.view]; 10 11 //2.控制滑块和进度条的frame 12 CGFloat sliderMaxX=self.view.width-self.slider.width; 13 self.slider.x+=t.x; 14 //控制滑块的frame,不让其越界 15 if(self.slider.x<0) 16 { 17 self.slider.x=0; 18 }else if (self.slider.x>sliderMaxX) 19 { 20 self.slider.x=sliderMaxX; 21 } 22 //设置进度条的宽度 23 self.progressView.width=self.slider.center.x; 24 25 //3.设置时间值 26 double progress=self.slider.x/sliderMaxX; 27 //当前的时间值=音乐的时长*当前的进度值 28 NSTimeInterval time=self.player.duration*progress; 29 [self.slider setTitle:[self strWithTime:time] forState:UIControlStateNormal]; 30 31 //设置拖拽进度的X的值 32 self.currentTimeView.x=self.slider.x; 33 [self.currentTimeView setTitle:self.slider.currentTitle forState:UIControlStateNormal]; 34 35 //4.如果开始拖动,那么就停止定时器 36 if (sender.state==UIGestureRecognizerStateBegan) { 37 //停止定时器 38 [self removeCurrentTime]; 39 40 //设置拖拽进度 41 //显示 42 self.currentTimeView.hidden=NO; 43 self.currentTimeView.y=self.currentTimeView.superview.height-5-self.currentTimeView.height; 44 45 }else if(sender.state==UIGestureRecognizerStateEnded) 46 { 47 //隐藏 48 self.currentTimeView.hidden=YES; 49 //设置播放器播放的时间 50 self.player.currentTime=time; 51 #warning 如果正在播放,才需要添加定时器 52 // if (self.player.isPlaying) { 53 //开启定时器 54 [self addCurrentTimeTimer]; 55 // } 56 } 57 } 裁剪圆角的细节处理: 二、播放或暂停、上一首、下一首的实现 1 //上一首 2 - (IBAction)previous { 3 //1.在开始播放之前,禁用一切的app点击事件 4 UIWindow *window=[[UIApplication sharedApplication].windows lastObject]; 5 window.userInteractionEnabled=NO; 6 7 //2.重置当前歌曲 8 [self resetPlayingMusic]; 9 10 //3.获得上一首歌曲 11 [YYMusicTool setPlayingMusic:[YYMusicTool previousMusic]]; 12 13 //4.播放上一首歌曲 14 [self starPlayingMusic]; 15 16 //5.回复window的点击为可用 17 window.userInteractionEnabled=YES; 18 } 19 //下一首 20 - (IBAction)next { 21 //1.在开始播放之前,禁用一切的app点击事件 22 UIWindow *window=[[UIApplication sharedApplication].windows lastObject]; 23 window.userInteractionEnabled=NO; 24 25 //2.重置当前歌曲 26 [self resetPlayingMusic]; 27 28 //3.获得下一首歌曲 29 [YYMusicTool setPlayingMusic:[YYMusicTool nextMusic]]; 30 31 //4.播放下一首歌曲 32 [self starPlayingMusic]; 33 34 //5.回复window的点击为可用 35 window.userInteractionEnabled=YES; 36 } 37 38 //继续或暂停播放 39 - (IBAction)playOrPause { 40 if (self.playOrPauseButton.isSelected) {//暂停 41 self.playOrPauseButton.selected=NO; 42 //暂停播放 43 [YYAudioTool pauseMusic:self.playingMusic.filename]; 44 //停掉定时器 45 [self removeCurrentTime]; 46 }else 47 { 48 self.playOrPauseButton.selected=YES; 49 //继续播放 50 [YYAudioTool playMusic:self.playingMusic.filename]; 51 //开启定时器 52 [self addCurrentTimeTimer]; 53 } 54 } 说明:播放和暂停按钮的图片设置在两种状态下并不一样,设置播放按钮的状态 三、对存在的bug进行改进 拖拽还存在问题(定时器的问题) 更好的方法时在添加定时器的地方进行更细的控制: 1 /** 2 * 添加一个定时器 3 */ 4 -(void)addCurrentTimeTimer 5 { 6 //如果当前没有在播放,那么就直接返回 7 if (self.player.isPlaying==NO) return; 8 9 //在添加一个定时器之前,先把以前的定时器移除 10 [self removeCurrentTime]; 11 12 //提前先调用一次进度更新,以保证定时器的工作时及时的 13 [self updateCurrentTime]; 14 15 //创建一个定时器,每一秒钟调用一次 16 self.CurrentTimeTimer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateCurrentTime) userInfo:nil repeats:YES]; 17 //把定时器加入到运行时中 18 [[NSRunLoop mainRunLoop]addTimer:self.CurrentTimeTimer forMode:NSRunLoopCommonModes]; 19 } 四、补充 完整的代码如下: 1 // 2 // YYPlayingViewController.m 3 // 20-音频处理(音乐播放器1) 4 // 5 // Created by apple on 14-8-13. 6 // Copyright (c) 2014年 yangyong. All rights reserved. 7 // 8 9 #import "YYPlayingViewController.h" 10 #import "YYMusicTool.h" 11 #import "YYMusicModel.h" 12 #import "YYAudioTool.h" 13 14 @interface YYPlayingViewController () 15 //显示拖拽进度 16 @property (weak, nonatomic) IBOutlet UIButton *currentTimeView; 17 //进度条 18 @property (weak, nonatomic) IBOutlet UIView *progressView; 19 //滑块 20 @property (weak, nonatomic) IBOutlet UIButton *slider; 21 @property (weak, nonatomic) IBOutlet UIImageView *iconView; 22 @property (weak, nonatomic) IBOutlet UILabel *songLabel; 23 @property (weak, nonatomic) IBOutlet UILabel *singerLabel; 24 //当前播放的音乐的时长 25 @property (weak, nonatomic) IBOutlet UILabel *durationLabel; 26 //正在播放的音乐 27 @property(nonatomic,strong)YYMusicModel *playingMusic; 28 //音乐播放器对象 29 @property(nonatomic,strong)AVAudioPlayer *player; 30 //定时器 31 @property(nonatomic,strong)NSTimer *CurrentTimeTimer; 32 - (IBAction)exit; 33 - (IBAction)tapProgressBg:(UITapGestureRecognizer *)sender; 34 - (IBAction)panSlider:(UIPanGestureRecognizer *)sender; 35 - (IBAction)previous; 36 - (IBAction)playOrPause; 37 - (IBAction)next; 38 @property (weak, nonatomic) IBOutlet UIButton *playOrPauseButton; 39 40 @end 41 42 @implementation YYPlayingViewController 43 44 -(void)viewDidLoad 45 { 46 [super viewDidLoad]; 47 48 //裁剪圆角 49 self.currentTimeView.layer.cornerRadius=8; 50 51 } 52 #pragma mark-公共方法 53 -(void)show 54 { 55 //1.禁用整个app的点击事件 56 UIWindow *window=[UIApplication sharedApplication].keyWindow; 57 window.userInteractionEnabled=NO; 58 59 //2.添加播放界面 60 //设置View的大小为覆盖整个窗口 61 self.view.frame=window.bounds; 62 //设置view显示 63 self.view.hidden=NO; 64 //把View添加到窗口上 65 [window addSubview:self.view]; 66 67 //3.检测是否换了歌曲 68 if (self.playingMusic!=[YYMusicTool playingMusic]) { 69 [self resetPlayingMusic]; 70 } 71 72 //4.使用动画让View显示 73 self.view.y=self.view.height; 74 [UIView animateWithDuration:0.25 animations:^{ 75 self.view.y=0; 76 } completion:^(BOOL finished) { 77 78 //设置音乐数据 79 [self starPlayingMusic]; 80 window.userInteractionEnabled=YES; 81 }]; 82 } 83 84 85 #pragma mark-私有方法 86 //重置正在播放的音乐 87 -(void)resetPlayingMusic 88 { 89 //1.重置界面数据 90 self.iconView.image=[UIImage imageNamed:@"play_cover_pic_bg"]; 91 self.songLabel.text=nil; 92 self.singerLabel.text=nil; 93 94 //2.停止播放 95 [YYAudioTool stopMusic:self.playingMusic.filename]; 96 //把播放器进行清空 97 self.player=nil; 98 99 //3.停止定时器 100 [self removeCurrentTime]; 101 102 //4.设置音乐播放按钮的状态 103 self.playOrPauseButton.selected=NO; 104 } 105 //开始播放音乐数据 106 -(void)starPlayingMusic 107 { 108 //1.设置界面数据 109 110 //如果当前播放的音乐就是传入的音乐,那么就直接返回 111 if (self.playingMusic==[YYMusicTool playingMusic]) 112 { 113 //把定时器加进去 114 [self addCurrentTimeTimer]; 115 return; 116 } 117 //存取音乐 118 self.playingMusic=[YYMusicTool playingMusic]; 119 self.iconView.image=[UIImage imageNamed:self.playingMusic.icon]; 120 self.songLabel.text=self.playingMusic.name; 121 self.singerLabel.text=self.playingMusic.singer; 122 123 //2.开始播放 124 self.player = [YYAudioTool playMusic:self.playingMusic.filename]; 125 126 //3.设置时长 127 //self.player.duration; 播放器正在播放的音乐文件的时间长度 128 self.durationLabel.text=[self strWithTime:self.player.duration]; 129 130 //4.添加定时器 131 [self addCurrentTimeTimer]; 132 133 //5.设置音乐播放按钮的状态 134 self.playOrPauseButton.selected=YES; 135 } 136 137 /** 138 *把时间长度-->时间字符串 139 */ 140 -(NSString *)strWithTime:(NSTimeInterval)time 141 { 142 int minute=time / 60; 143 int second=(int)time % 60; 144 return [NSString stringWithFormat:@"%d:%d",minute,second]; 145 } 146 147 #pragma mark-定时器控制 148 /** 149 * 添加一个定时器 150 */ 151 -(void)addCurrentTimeTimer 152 { 153 //如果当前没有在播放,那么就直接返回 154 if (self.player.isPlaying==NO) return; 155 156 //在添加一个定时器之前,先把以前的定时器移除 157 [self removeCurrentTime]; 158 159 //提前先调用一次进度更新,以保证定时器的工作时及时的 160 [self updateCurrentTime]; 161 162 //创建一个定时器,每一秒钟调用一次 163 self.CurrentTimeTimer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateCurrentTime) userInfo:nil repeats:YES]; 164 //把定时器加入到运行时中 165 [[NSRunLoop mainRunLoop]addTimer:self.CurrentTimeTimer forMode:NSRunLoopCommonModes]; 166 } 167 /** 168 *移除一个定时器 169 */ 170 -(void)removeCurrentTime 171 { 172 [self.CurrentTimeTimer invalidate]; 173 174 //把定时器清空 175 self.CurrentTimeTimer=nil; 176 } 177 178 /** 179 * 更新播放进度 180 */ 181 -(void)updateCurrentTime 182 { 183 //1.计算进度值 184 double progress=self.player.currentTime/self.player.duration; 185 186 //2.计算滑块的x值 187 // 滑块的最大的x值 188 CGFloat sliderMaxX=self.view.width-self.slider.width; 189 self.slider.x=sliderMaxX*progress; 190 //设置滑块上的当前播放时间 191 [self.slider setTitle:[self strWithTime:self.player.currentTime] forState:UIControlStateNormal]; 192 193 //3.设置进度条的宽度 194 self.progressView.width=self.slider.center.x; 195 196 } 197 198 #pragma mark-内部的按钮监听方法 199 //返回按钮 200 - (IBAction)exit { 201 202 //0.移除定时器 203 [self removeCurrentTime]; 204 //1.禁用整个app的点击事件 205 UIWindow *window=[UIApplication sharedApplication].keyWindow; 206 window.userInteractionEnabled=NO; 207 208 //2.动画隐藏View 209 [UIView animateWithDuration:0.25 animations:^{ 210 self.view.y=window.height; 211 } completion:^(BOOL finished) { 212 window.userInteractionEnabled=YES; 213 //设置view隐藏能够节省一些性能 214 self.view.hidden=YES; 215 }]; 216 } 217 218 /** 219 *点击了进度条 220 */ 221 - (IBAction)tapProgressBg:(UITapGestureRecognizer *)sender { 222 //获取当前单击的点 223 CGPoint point=[sender locationInView:sender.view]; 224 //切换歌曲的当前播放时间 225 self.player.currentTime=(point.x/sender.view.width)*self.player.duration; 226 //更新播放进度 227 [self updateCurrentTime]; 228 } 229 /** 230 *拖动滑块 231 */ 232 - (IBAction)panSlider:(UIPanGestureRecognizer *)sender { 233 234 //1.获得挪动的距离 235 CGPoint t=[sender translationInView:sender.view]; 236 //把挪动清零 237 [sender setTranslation:CGPointZero inView:sender.view]; 238 239 //2.控制滑块和进度条的frame 240 CGFloat sliderMaxX=self.view.width-self.slider.width; 241 self.slider.x+=t.x; 242 //控制滑块的frame,不让其越界 243 if(self.slider.x<0) 244 { 245 self.slider.x=0; 246 }else if (self.slider.x>sliderMaxX) 247 { 248 self.slider.x=sliderMaxX; 249 } 250 //设置进度条的宽度 251 self.progressView.width=self.slider.center.x; 252 253 //3.设置时间值 254 double progress=self.slider.x/sliderMaxX; 255 //当前的时间值=音乐的时长*当前的进度值 256 NSTimeInterval time=self.player.duration*progress; 257 [self.slider setTitle:[self strWithTime:time] forState:UIControlStateNormal]; 258 259 //设置拖拽进度的X的值 260 self.currentTimeView.x=self.slider.x; 261 [self.currentTimeView setTitle:self.slider.currentTitle forState:UIControlStateNormal]; 262 263 //4.如果开始拖动,那么就停止定时器 264 if (sender.state==UIGestureRecognizerStateBegan) { 265 //停止定时器 266 [self removeCurrentTime]; 267 268 //设置拖拽进度 269 //显示 270 self.currentTimeView.hidden=NO; 271 self.currentTimeView.y=self.currentTimeView.superview.height-5-self.currentTimeView.height; 272 273 }else if(sender.state==UIGestureRecognizerStateEnded) 274 { 275 //隐藏 276 self.currentTimeView.hidden=YES; 277 //设置播放器播放的时间 278 self.player.currentTime=time; 279 #warning 如果正在播放,才需要添加定时器 280 // if (self.player.isPlaying) { 281 //开启定时器 282 [self addCurrentTimeTimer]; 283 // } 284 } 285 } 286 287 //上一首 288 - (IBAction)previous { 289 //1.在开始播放之前,禁用一切的app点击事件 290 UIWindow *window=[[UIApplication sharedApplication].windows lastObject]; 291 window.userInteractionEnabled=NO; 292 293 //2.重置当前歌曲 294 [self resetPlayingMusic]; 295 296 //3.获得上一首歌曲 297 [YYMusicTool setPlayingMusic:[YYMusicTool previousMusic]]; 298 299 //4.播放上一首歌曲 300 [self starPlayingMusic]; 301 302 //5.回复window的点击为可用 303 window.userInteractionEnabled=YES; 304 } 305 //下一首 306 - (IBAction)next { 307 //1.在开始播放之前,禁用一切的app点击事件 308 UIWindow *window=[[UIApplication sharedApplication].windows lastObject]; 309 window.userInteractionEnabled=NO; 310 311 //2.重置当前歌曲 312 [self resetPlayingMusic]; 313 314 //3.获得下一首歌曲 315 [YYMusicTool setPlayingMusic:[YYMusicTool nextMusic]]; 316 317 //4.播放下一首歌曲 318 [self starPlayingMusic]; 319 320 //5.回复window的点击为可用 321 window.userInteractionEnabled=YES; 322 } 323 //继续或暂停播放 324 - (IBAction)playOrPause { 325 if (self.playOrPauseButton.isSelected) {//暂停 326 self.playOrPauseButton.selected=NO; 327 //暂停播放 328 [YYAudioTool pauseMusic:self.playingMusic.filename]; 329 //停掉定时器 330 [self removeCurrentTime]; 331 }else 332 { 333 self.playOrPauseButton.selected=YES; 334 //继续播放 335 [YYAudioTool playMusic:self.playingMusic.filename]; 336 //开启定时器 337 [self addCurrentTimeTimer]; 338 } 339 } 340 341 342 @end
iOS开发拓展篇—音频处理(音乐播放器4) 说明:该文主要介绍音乐播放器实现过程中的一些细节控制。 实现的效果: 一、完整的代码 YYPlayingViewController.m文件 1 // 2 // YYPlayingViewController.m 3 // 20-音频处理(音乐播放器1) 4 // 5 // Created by apple on 14-8-13. 6 // Copyright (c) 2014年 yangyong. All rights reserved. 7 // 8 9 #import "YYPlayingViewController.h" 10 #import "YYMusicTool.h" 11 #import "YYMusicModel.h" 12 #import "YYAudioTool.h" 13 14 @interface YYPlayingViewController () 15 //进度条 16 @property (weak, nonatomic) IBOutlet UIView *progressView; 17 //滑块 18 @property (weak, nonatomic) IBOutlet UIButton *slider; 19 @property (weak, nonatomic) IBOutlet UIImageView *iconView; 20 @property (weak, nonatomic) IBOutlet UILabel *songLabel; 21 @property (weak, nonatomic) IBOutlet UILabel *singerLabel; 22 //当前播放的音乐的时长 23 @property (weak, nonatomic) IBOutlet UILabel *durationLabel; 24 //正在播放的音乐 25 @property(nonatomic,strong)YYMusicModel *playingMusic; 26 //音乐播放器对象 27 @property(nonatomic,strong)AVAudioPlayer *player; 28 //定时器 29 @property(nonatomic,strong)NSTimer *CurrentTimeTimer; 30 - (IBAction)exit; 31 - (IBAction)tapProgressBg:(UITapGestureRecognizer *)sender; 32 - (IBAction)panSlider:(UIPanGestureRecognizer *)sender; 33 34 @end 35 36 @implementation YYPlayingViewController 37 #pragma mark-公共方法 38 -(void)show 39 { 40 //1.禁用整个app的点击事件 41 UIWindow *window=[UIApplication sharedApplication].keyWindow; 42 window.userInteractionEnabled=NO; 43 44 //2.添加播放界面 45 //设置View的大小为覆盖整个窗口 46 self.view.frame=window.bounds; 47 //设置view显示 48 self.view.hidden=NO; 49 //把View添加到窗口上 50 [window addSubview:self.view]; 51 52 //3.检测是否换了歌曲 53 if (self.playingMusic!=[YYMusicTool playingMusic]) { 54 [self RresetPlayingMusic]; 55 } 56 57 //4.使用动画让View显示 58 self.view.y=self.view.height; 59 [UIView animateWithDuration:0.25 animations:^{ 60 self.view.y=0; 61 } completion:^(BOOL finished) { 62 63 //设置音乐数据 64 [self starPlayingMusic]; 65 window.userInteractionEnabled=YES; 66 }]; 67 } 68 69 70 #pragma mark-私有方法 71 //重置正在播放的音乐 72 -(void)RresetPlayingMusic 73 { 74 //1.重置界面数据 75 self.iconView.image=[UIImage imageNamed:@"play_cover_pic_bg"]; 76 self.songLabel.text=nil; 77 self.singerLabel.text=nil; 78 79 //2.停止播放 80 [YYAudioTool stopMusic:self.playingMusic.filename]; 81 //把播放器进行清空 82 self.player=nil; 83 84 //3.停止定时器 85 [self removeCurrentTime]; 86 } 87 //开始播放音乐数据 88 -(void)starPlayingMusic 89 { 90 //1.设置界面数据 91 92 //如果当前播放的音乐就是传入的音乐,那么就直接返回 93 if (self.playingMusic==[YYMusicTool playingMusic]) 94 { 95 //把定时器加进去 96 [self addCurrentTimeTimer]; 97 return; 98 } 99 //存取音乐 100 self.playingMusic=[YYMusicTool playingMusic]; 101 self.iconView.image=[UIImage imageNamed:self.playingMusic.icon]; 102 self.songLabel.text=self.playingMusic.name; 103 self.singerLabel.text=self.playingMusic.singer; 104 105 //2.开始播放 106 self.player = [YYAudioTool playMusic:self.playingMusic.filename]; 107 108 //3.设置时长 109 //self.player.duration; 播放器正在播放的音乐文件的时间长度 110 self.durationLabel.text=[self strWithTime:self.player.duration]; 111 112 //4.添加定时器 113 [self addCurrentTimeTimer]; 114 115 } 116 117 /** 118 *把时间长度-->时间字符串 119 */ 120 -(NSString *)strWithTime:(NSTimeInterval)time 121 { 122 int minute=time / 60; 123 int second=(int)time % 60; 124 return [NSString stringWithFormat:@"%d:%d",minute,second]; 125 } 126 127 #pragma mark-定时器控制 128 /** 129 * 添加一个定时器 130 */ 131 -(void)addCurrentTimeTimer 132 { 133 //提前先调用一次进度更新,以保证定时器的工作时及时的 134 [self updateCurrentTime]; 135 136 //创建一个定时器,每一秒钟调用一次 137 self.CurrentTimeTimer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateCurrentTime) userInfo:nil repeats:YES]; 138 //把定时器加入到运行时中 139 [[NSRunLoop mainRunLoop]addTimer:self.CurrentTimeTimer forMode:NSRunLoopCommonModes]; 140 } 141 /** 142 *移除一个定时器 143 */ 144 -(void)removeCurrentTime 145 { 146 [self.CurrentTimeTimer invalidate]; 147 148 //把定时器清空 149 self.CurrentTimeTimer=nil; 150 } 151 152 /** 153 * 更新播放进度 154 */ 155 -(void)updateCurrentTime 156 { 157 //1.计算进度值 158 double progress=self.player.currentTime/self.player.duration; 159 160 //2.计算滑块的x值 161 // 滑块的最大的x值 162 CGFloat sliderMaxX=self.view.width-self.slider.width; 163 self.slider.x=sliderMaxX*progress; 164 //设置滑块上的当前播放时间 165 [self.slider setTitle:[self strWithTime:self.player.currentTime] forState:UIControlStateNormal]; 166 167 //3.设置进度条的宽度 168 self.progressView.width=self.slider.center.x; 169 170 } 171 172 #pragma mark-内部的按钮监听方法 173 //返回按钮 174 - (IBAction)exit { 175 176 //0.移除定时器 177 [self removeCurrentTime]; 178 //1.禁用整个app的点击事件 179 UIWindow *window=[UIApplication sharedApplication].keyWindow; 180 window.userInteractionEnabled=NO; 181 182 //2.动画隐藏View 183 [UIView animateWithDuration:0.25 animations:^{ 184 self.view.y=window.height; 185 } completion:^(BOOL finished) { 186 window.userInteractionEnabled=YES; 187 //设置view隐藏能够节省一些性能 188 self.view.hidden=YES; 189 }]; 190 } 191 192 /** 193 *点击了进度条 194 */ 195 - (IBAction)tapProgressBg:(UITapGestureRecognizer *)sender { 196 //获取当前单击的点 197 CGPoint point=[sender locationInView:sender.view]; 198 //切换歌曲的当前播放时间 199 self.player.currentTime=(point.x/sender.view.width)*self.player.duration; 200 //更新播放进度 201 [self updateCurrentTime]; 202 } 203 204 - (IBAction)panSlider:(UIPanGestureRecognizer *)sender { 205 206 //1.获得挪动的距离 207 CGPoint t=[sender translationInView:sender.view]; 208 //把挪动清零 209 [sender setTranslation:CGPointZero inView:sender.view]; 210 211 //2.控制滑块和进度条的frame 212 self.slider.x+=t.x; 213 //设置进度条的宽度 214 self.progressView.width=self.slider.center.x; 215 216 //3.设置时间值 217 CGFloat sliderMaxX=self.view.width-self.slider.width; 218 double progress=self.slider.x/sliderMaxX; 219 //当前的时间值=音乐的时长*当前的进度值 220 NSTimeInterval time=self.player.duration*progress; 221 [self .slider setTitle:[self strWithTime:time] forState:UIControlStateNormal]; 222 223 //4.如果开始拖动,那么就停止定时器 224 if (sender.state==UIGestureRecognizerStateBegan) { 225 //停止定时器 226 [self removeCurrentTime]; 227 }else if(sender.state==UIGestureRecognizerStateEnded) 228 { 229 //设置播放器播放的时间 230 self.player.currentTime=time; 231 //开启定时器 232 [self addCurrentTimeTimer]; 233 } 234 } 235 @end 二、代码说明(一) 调整开始播放音乐按钮,让其返回一个音乐播放器,而非BOOL型的。 1 /** 2 *播放音乐 3 */ 4 +(AVAudioPlayer *)playMusic:(NSString *)filename 5 { 6 if (!filename) return nil;//如果没有传入文件名,那么直接返回 7 //1.取出对应的播放器 8 AVAudioPlayer *player=[self musicPlayers][filename]; 9 10 //2.如果播放器没有创建,那么就进行初始化 11 if (!player) { 12 //2.1音频文件的URL 13 NSURL *url=[[NSBundle mainBundle]URLForResource:filename withExtension:nil]; 14 if (!url) return nil;//如果url为空,那么直接返回 15 16 //2.2创建播放器 17 player=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil]; 18 19 //2.3缓冲 20 if (![player prepareToPlay]) return nil;//如果缓冲失败,那么就直接返回 21 22 //2.4存入字典 23 [self musicPlayers][filename]=player; 24 } 25 26 //3.播放 27 if (![player isPlaying]) { 28 //如果当前没处于播放状态,那么就播放 29 [player play]; 30 } 31 32 return player;//正在播放,那么就返回YES 33 } 三、代码说明(二) 把时间转换为时间字符串的方法: 1 /** 2 *把时间长度-->时间字符串 3 */ 4 -(NSString *)strWithTime:(NSTimeInterval)time 5 { 6 int minute=time / 60; 7 int second=(int)time % 60; 8 return [NSString stringWithFormat:@"%d:%d",minute,second]; 9 } 四、代码说明(三) 说明:进度控制 监听当前的播放,使用一个定时器,不断的监听当前是第几秒。 关于定时器的处理:这里使用了三个方法,分别是添加定时器,移除定时器,和更新播放进度。 注意细节: (1)移除定时器后,对定时器进行清空处理。 1 /** 2 *移除一个定时器 3 */ 4 -(void)removeCurrentTime 5 { 6 [self.CurrentTimeTimer invalidate]; 7 8 //把定时器清空 9 self.CurrentTimeTimer=nil; 10 } (2)当看不到界面的时候,停止定时器。 (3)在开始播放音乐的方法中进行判断,如果当前播放的音乐和传入的音乐一致,那么添加定时器后直接返回。 (4)重置播放的音乐方法中,停止定时器。 五、代码说明(四) 说明:点击和拖动进度条的处理 1.点击进度条 先添加单击的手势识别器。 往控制器拖线: 涉及的代码: 1 /** 2 *点击了进度条 3 */ 4 - (IBAction)tapProgressBg:(UITapGestureRecognizer *)sender { 5 //获取当前单击的点 6 CGPoint point=[sender locationInView:sender.view]; 7 //切换歌曲的当前播放时间 8 self.player.currentTime=(point.x/sender.view.width)*self.player.duration; 9 //更新播放进度 10 [self updateCurrentTime]; 11 } 2.拖拽进度条 先添加拖拽手势识别器 往控制器拖线 涉及的代码: 1 /** 2 *拖动滑块 3 */ 4 - (IBAction)panSlider:(UIPanGestureRecognizer *)sender { 5 6 //1.获得挪动的距离 7 CGPoint t=[sender translationInView:sender.view]; 8 //把挪动清零 9 [sender setTranslation:CGPointZero inView:sender.view]; 10 11 //2.控制滑块和进度条的frame 12 self.slider.x+=t.x; 13 //设置进度条的宽度 14 self.progressView.width=self.slider.center.x; 15 16 //3.设置时间值 17 CGFloat sliderMaxX=self.view.width-self.slider.width; 18 double progress=self.slider.x/sliderMaxX; 19 //当前的时间值=音乐的时长*当前的进度值 20 NSTimeInterval time=self.player.duration*progress; 21 [self .slider setTitle:[self strWithTime:time] forState:UIControlStateNormal]; 22 23 //4.如果开始拖动,那么就停止定时器 24 if (sender.state==UIGestureRecognizerStateBegan) { 25 //停止定时器 26 [self removeCurrentTime]; 27 }else if(sender.state==UIGestureRecognizerStateEnded) 28 { 29 //设置播放器播放的时间 30 self.player.currentTime=time; 31 //开启定时器 32 [self addCurrentTimeTimer]; 33 } 34 }