kfqweb

kfqweb 查看完整档案

北京编辑通辽职业学院  |  风力发电专业 编辑某公司  |  前端工程师 编辑 github.com/kfqweb 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

kfqweb 赞了文章 · 2019-09-18

不要再问我跨域的问题了

写下这篇文章后我想,要不以后就把这种基础的常见知识都归到这个“不要再问我XX的问题”,形成一系列内容,希望大家看完之后再有人问你这些问题,你心里会窃喜:“嘿嘿,是时候展现真正的技术了!”
一、不要再问我this的指向问题了

跨域这两个字就像一块狗皮膏药一样黏在每一个前端开发者身上,无论你在工作上或者面试中无可避免会遇到这个问题。为了应付面试,我每次都随便背几个方案,也不知道为什么要这样干,反正面完就可以扔了,我想工作上也不会用到那么多乱七八糟的方案。到了真正工作,开发环境有webpack-dev-server搞定,上线了服务端的大佬们也会配好,配了什么我不管,反正不会跨域就是了。日子也就这么混过去了,终于有一天,我觉得不能再继续这样混下去了,我一定要彻底搞懂这个东西!于是就有了这篇文章。

要掌握跨域,首先要知道为什么会有跨域这个问题出现

确实,我们这种搬砖工人就是为了混口饭吃嘛,好好的调个接口告诉我跨域了,这种阻碍我们轻松搬砖的事情真恶心!为什么会跨域?是谁在搞事情?为了找到这个问题的始作俑者,请点击浏览器的同源策略
这么官方的东西真难懂,没关系,至少你知道了,因为浏览器的同源策略导致了跨域,就是浏览器在搞事情。
所以,浏览器为什么要搞事情?就是不想给好日子我们过?对于这样的质问,浏览器甩锅道:“同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。”
这么官方的话术真难懂,没关系,至少你知道了,似乎这是个安全机制。
所以,究竟为什么需要这样的安全机制?这样的安全机制解决了什么问题?别急,让我们继续研究下去。

没有同源策略限制的两大危险场景

据我了解,浏览器是从两个方面去做这个同源策略的,一是针对接口的请求,二是针对Dom的查询。试想一下没有这样的限制上述两种动作有什么危险。

没有同源策略限制的接口请求

有一个小小的东西叫cookie大家应该知道,一般用来处理登录等场景,目的是让服务端知道谁发出的这次请求。如果你请求了接口进行登录,服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,浏览器会自动将cookie附加在HTTP请求的头字段Cookie中,服务端就能知道这个用户已经登录过了。知道这个之后,我们来看场景:
1.你准备去清空你的购物车,于是打开了买买买网站www.maimaimai.com,然后登录成功,一看,购物车东西这么少,不行,还得买多点。
2.你在看有什么东西买的过程中,你的好基友发给你一个链接www.nidongde.com,一脸yin笑地跟你说:“你懂的”,你毫不犹豫打开了。
3.你饶有兴致地浏览着www.nidongde.com,谁知这个网站暗地里做了些不可描述的事情!由于没有同源策略的限制,它向www.maimaimai.com发起了请求!聪明的你一定想到上面的话“服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,浏览器会自动将cookie附加在HTTP请求的头字段Cookie中”,这样一来,这个不法网站就相当于登录了你的账号,可以为所欲为了!如果这不是一个买买买账号,而是你的银行账号,那……
这就是传说中的CSRF攻击浅谈CSRF攻击方式
看了这波CSRF攻击我在想,即使有了同源策略限制,但cookie是明文的,还不是一样能拿下来。于是我看了一些cookie相关的文章聊一聊 cookieCookie/Session的机制与安全,知道了服务端可以设置httpOnly,使得前端无法操作cookie,如果没有这样的设置,像XSS攻击就可以去获取到cookieWeb安全测试之XSS;设置secure,则保证在https的加密通信中传输以防截获。

没有同源策略限制的Dom查询

1.有一天你刚睡醒,收到一封邮件,说是你的银行账号有风险,赶紧点进www.yinghang.com改密码。你吓尿了,赶紧点进去,还是熟悉的银行登录界面,你果断输入你的账号密码,登录进去看看钱有没有少了。
2.睡眼朦胧的你没看清楚,平时访问的银行网站是www.yinhang.com,而现在访问的是www.yinghang.com,这个钓鱼网站做了什么呢?

// HTML
<iframe name="yinhang" data-original="www.yinhang.com"></iframe>
// JS
// 由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom
const iframe = window.frames['yinhang']
const node = iframe.document.getElementById('你输入账号密码的Input')
console.log(`拿到了这个${node},我还拿不到你刚刚输入的账号密码吗`)

由此我们知道,同源策略确实能规避一些危险,不是说有了同源策略就安全,只是说同源策略是一种浏览器最基本的安全机制,毕竟能提高一点攻击的成本。其实没有刺不穿的盾,只是攻击的成本和攻击成功后获得的利益成不成正比。

跨域正确的打开方式

经过对同源策略的了解,我们应该要消除对浏览器的误解,同源策略是浏览器做的一件好事,是用来防御来自邪门歪道的攻击,但总不能为了不让坏人进门而把全部人都拒之门外吧。没错,我们这种正人君子只要打开方式正确,就应该可以跨域。
下面将一个个演示正确打开方式,但在此之前,有些准备工作要做。为了本地演示跨域,我们需要:
1.随便跑起一份前端代码(以下前端是随便跑起来的vue),地址是http://localhost:9099。
2.随便跑起一份后端代码(以下后端是随便跑起来的node koa2),地址是http://localhost:9971。

同源策略限制下接口请求的正确打开方式

1.JSONP
在HTML标签里,一些标签比如script、img这样的获取资源的标签是没有跨域限制的,利用这一点,我们可以这样干:

后端写个小接口

// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async jsonp (ctx) {
    // 前端传过来的参数
    const query = ctx.request.query
    // 设置一个cookies
    ctx.cookies.set('tokenId', '1')
    // query.cb是前后端约定的方法名字,其实就是后端返回一个直接执行的方法给前端,由于前端是用script标签发起的请求,所以返回了这个方法后相当于立马执行,并且把要返回的数据放在方法的参数里。
    ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}, 'success'))})`
  }
}
module.exports = CrossDomain

简单版前端

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <script type='text/javascript'>
      // 后端返回直接执行的方法,相当于执行这个方法,由于后端把返回的数据放在方法的参数里,所以这里能拿到res。
      window.jsonpCb = function (res) {
        console.log(res)
      }
    </script>
    <script data-original='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb' type='text/javascript'></script>
  </body>
</html>

简单封装一下前端这个套路

/**
 * JSONP请求工具
 * @param url 请求的地址
 * @param data 请求的参数
 * @returns {Promise<any>}
 */
const request = ({url, data}) => {
  return new Promise((resolve, reject) => {
    // 处理传参成xx=yy&aa=bb的形式
    const handleData = (data) => {
      const keys = Object.keys(data)
      const keysLen = keys.length
      return keys.reduce((pre, cur, index) => {
        const value = data[cur]
        const flag = index !== keysLen - 1 ? '&' : ''
        return `${pre}${cur}=${value}${flag}`
      }, '')
    }
    // 动态创建script标签
    const script = document.createElement('script')
    // 接口返回的数据获取
    window.jsonpCb = (res) => {
      document.body.removeChild(script)
      delete window.jsonpCb
      resolve(res)
    }
    script.src = `${url}?${handleData(data)}&cb=jsonpCb`
    document.body.appendChild(script)
  })
}
// 使用方式
request({
  url: 'http://localhost:9871/api/jsonp',
  data: {
    // 传参
    msg: 'helloJsonp'
  }
}).then(res => {
  console.log(res)
})

2.空iframe加form
细心的朋友可能发现,JSONP只能发GET请求,因为本质上script加载资源就是GET,那么如果要发POST请求怎么办呢?

后端写个小接口

// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async iframePost (ctx) {
    let postData = ctx.request.body
    console.log(postData)
    ctx.body = successBody({postData: postData}, 'success')
  }
}
module.exports = CrossDomain

前端

const requestPost = ({url, data}) => {
  // 首先创建一个用来发送数据的iframe.
  const iframe = document.createElement('iframe')
  iframe.name = 'iframePost'
  iframe.style.display = 'none'
  document.body.appendChild(iframe)
  const form = document.createElement('form')
  const node = document.createElement('input')
  // 注册iframe的load事件处理程序,如果你需要在响应返回时执行一些操作的话.
  iframe.addEventListener('load', function () {
    console.log('post success')
  })

  form.action = url
  // 在指定的iframe中执行form
  form.target = iframe.name
  form.method = 'post'
  for (let name in data) {
    node.name = name
    node.value = data[name].toString()
    form.appendChild(node.cloneNode())
  }
  // 表单元素需要添加到主文档中.
  form.style.display = 'none'
  document.body.appendChild(form)
  form.submit()

  // 表单提交后,就可以删除这个表单,不影响下次的数据发送.
  document.body.removeChild(form)
}
// 使用方式
requestPost({
  url: 'http://localhost:9871/api/iframePost',
  data: {
    msg: 'helloIframePost'
  }
})

3.CORS

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)跨域资源共享 CORS 详解。看名字就知道这是处理跨域问题的标准做法。CORS有两种请求,简单请求和非简单请求。

这里引用上面链接阮一峰老师的文章说明一下简单请求和非简单请求。
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求。
(1) 请求方法是以下三种方法之一:

  • HEAD
  • GET
  • POST

(2)HTTP的头信息不超出以下几种字段:

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

1.简单请求
后端

// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async cors (ctx) {
    const query = ctx.request.query
    // *时cookie不会在http请求中带上
    ctx.set('Access-Control-Allow-Origin', '*')
    ctx.cookies.set('tokenId', '2')
    ctx.body = successBody({msg: query.msg}, 'success')
  }
}
module.exports = CrossDomain

前端什么也不用干,就是正常发请求就可以,如果需要带cookie的话,前后端都要设置一下,下面那个非简单请求例子会看到。

fetch(`http://localhost:9871/api/cors?msg=helloCors`).then(res => {
  console.log(res)
})

2.非简单请求
非简单请求会发出一次预检测请求,返回码是204,预检测通过才会真正发出请求,这才返回200。这里通过前端发请求的时候增加一个额外的headers来触发非简单请求。
clipboard.png

后端

// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async cors (ctx) {
    const query = ctx.request.query
    // 如果需要http请求中带上cookie,需要前后端都设置credentials,且后端设置指定的origin
    ctx.set('Access-Control-Allow-Origin', 'http://localhost:9099')
    ctx.set('Access-Control-Allow-Credentials', true)
    // 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)
    // 这种情况下除了设置origin,还需要设置Access-Control-Request-Method以及Access-Control-Request-Headers
    ctx.set('Access-Control-Request-Method', 'PUT,POST,GET,DELETE,OPTIONS')
    ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, t')
    ctx.cookies.set('tokenId', '2')

    ctx.body = successBody({msg: query.msg}, 'success')
  }
}
module.exports = CrossDomain

一个接口就要写这么多代码,如果想所有接口都统一处理,有什么更优雅的方式呢?见下面的koa2-cors。

const path = require('path')
const Koa = require('koa')
const koaStatic = require('koa-static')
const bodyParser = require('koa-bodyparser')
const router = require('./router')
const cors = require('koa2-cors')
const app = new Koa()
const port = 9871
app.use(bodyParser())
// 处理静态资源 这里是前端build好之后的目录
app.use(koaStatic(
  path.resolve(__dirname, '../dist')
))
// 处理cors
app.use(cors({
  origin: function (ctx) {
    return 'http://localhost:9099'
  },
  credentials: true,
  allowMethods: ['GET', 'POST', 'DELETE'],
  allowHeaders: ['t', 'Content-Type']
}))
// 路由
app.use(router.routes()).use(router.allowedMethods())
// 监听端口
app.listen(9871)
console.log(`[demo] start-quick is starting at port ${port}`)

前端

fetch(`http://localhost:9871/api/cors?msg=helloCors`, {
  // 需要带上cookie
  credentials: 'include',
  // 这里添加额外的headers来触发非简单请求
  headers: {
    't': 'extra headers'
  }
}).then(res => {
  console.log(res)
})

4.代理
想一下,如果我们请求的时候还是用前端的域名,然后有个东西帮我们把这个请求转发到真正的后端域名上,不就避免跨域了吗?这时候,Nginx出场了。
Nginx配置

server{
    # 监听9099端口
    listen 9099;
    # 域名是localhost
    server_name localhost;
    #凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871 
    location ^~ /api {
        proxy_pass http://localhost:9871;
    }    
}

前端就不用干什么事情了,除了写接口,也没后端什么事情了

// 请求的时候直接用回前端这边的域名http://localhost:9099,这就不会跨域,然后Nginx监听到凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871 
fetch('http://localhost:9099/api/iframePost', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    msg: 'helloIframePost'
  })
})

Nginx转发的方式似乎很方便!但这种使用也是看场景的,如果后端接口是一个公共的API,比如一些公共服务获取天气什么的,前端调用的时候总不能让运维去配置一下Nginx,如果兼容性没问题(IE 10或者以上),CROS才是更通用的做法吧。

同源策略限制下Dom查询的正确打开方式

1.postMessage
window.postMessage() 是HTML5的一个接口,专注实现不同窗口不同页面的跨域通讯。
为了演示方便,我们将hosts改一下:127.0.0.1 crossDomain.com,现在访问域名crossDomain.com就等于访问127.0.0.1。

这里是http://localhost:9099/#/crossDomain,发消息方

<template>
  <div>
    <button @click="postMessage">给http://crossDomain.com:9099发消息</button>
    <iframe name="crossDomainIframe" data-original="http://crossdomain.com:9099"></iframe>
  </div>
</template>

<script>
export default {
  mounted () {
    window.addEventListener('message', (e) => {
      // 这里一定要对来源做校验
      if (e.origin === 'http://crossdomain.com:9099') {
        // 来自http://crossdomain.com:9099的结果回复
        console.log(e.data)
      }
    })
  },
  methods: {
    // 向http://crossdomain.com:9099发消息
    postMessage () {
      const iframe = window.frames['crossDomainIframe']
      iframe.postMessage('我是[http://localhost:9099], 麻烦你查一下你那边有没有id为app的Dom', 'http://crossdomain.com:9099')
    }
  }
}
</script>

这里是http://crossdomain.com:9099,接收消息方

<template>
  <div>
    我是http://crossdomain.com:9099
  </div>
</template>

<script>
export default {
  mounted () {
    window.addEventListener('message', (e) => {
      // 这里一定要对来源做校验
      if (e.origin === 'http://localhost:9099') {
        // http://localhost:9099发来的信息
        console.log(e.data)
        // e.source可以是回信的对象,其实就是http://localhost:9099窗口对象(window)的引用
        // e.origin可以作为targetOrigin
        e.source.postMessage(`我是[http://crossdomain.com:9099],我知道了兄弟,这就是你想知道的结果:${document.getElementById('app') ? '有id为app的Dom' : '没有id为app的Dom'}`, e.origin);
      }
    })
  }
}
</script>

结果可以看到:

clipboard.png

2.document.domain
这种方式只适合主域名相同,但子域名不同的iframe跨域。
比如主域名是http://crossdomain.com:9099,子域名是http://child.crossdomain.com:9099,这种情况下给两个页面指定一下document.domain即document.domain = crossdomain.com就可以访问各自的window对象了。

3.canvas操作图片的跨域问题
这个应该是一个比较冷门的跨域问题,张大神已经写过了我就不再班门弄斧了解决canvas图片getImageData,toDataURL跨域问题

最后

希望看完这篇文章之后,再有人问跨域的问题,你可以嘴角微微上扬,冷笑一声:“不要再问我跨域的问题了。”
扬长而去。

查看原文

赞 1214 收藏 933 评论 95

kfqweb 赞了文章 · 2019-03-01

完整的url以及同源跨域处理

前言:随着工作时间的增长,前面学过的东西开始慢慢遗忘,抽空的时候就将一些资料整理整理,顺一顺,也当作一种温习。
我只是前端工匠,防止自己成为【一断网就无法工作的程序员】

url的完整结构

协议类型(protocol)

    通过URL可以指定的主要有以下几种:http、ftp、gopher、telnet、file等
    
    URL的组成协议 1、protocol(协议):指定使用的传输协议,下表列出 protocol 属性的有效方案名称。 
    
    最常用的是HTTP协议,它也是目前WWW中应用最广的协议。
    
    http —— 超文本传输协议访问该资源。 格式 http://
    https —— 用安全套接字层传送的超文本传输协议访问该资源。 格式 https://
    ftp —— 通过 FTP访问资源。格式 FTP://
    mailto —— 电子邮件地址 通过 SMTP 访问。 格式 mailto: 
    ldap —— 轻型目录访问协议搜索
    file —— 资源是本地计算机上的文件。格式file://
    news —— Usenet新闻组
    gopher —— Gopher协议
    telnet —— Telnet协议


主机名(hostname)

    是指存放资源的服务器的域名系统 (DNS) 主机名或 IP 地址。
    有时,在主机名前也可以包含连接到服务器所需的用户名和密码(格式:username:password)。
端口号(port)
    整数,可选,省略时使用方案的默认端口,各种传输协议都有默认的端口号,
    如http的默认端口为80,https的默认端口为443
路径及文件名(path)
    由零或多个“/”符号隔开的字符串,一般用来表示主机上的一个目录或文件地址
参数(parameters)
    传递参数,可有多个参数,用“&”符号隔开,每个参数的名和值用“=”符号隔开
hash值
    #是用来指导浏览器动作的,对服务器端完全无用。所以,HTTP请求中不包括#。
    这些字符都不会被发送到服务器端。
    改变#不触发网页重载
    改变#会改变浏览器的访问历史
    
    默认情况下,Google的网络蜘蛛忽视URL的#部分。
    但是,Google还规定,如果你希望Ajax生成的内容被浏览引擎读取,
    那么URL中可以使用"#!",Google会自动将其后面的内容转成查询字符串_escaped_fragment_的值
    

同源策略

  • 协议相同
  • 域名相同
  • 端口相同

如果非同源,共有三种行为收到限制

(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。

Cookie

Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。

cookie的组成部分
    Set-Cookie: NAME=VALUE;Expires=DATE;Path=PATH;Domain=DOMAIN_NAME;SECURE

    NAME=VALUE
    NAME是该Cookie的名称,VALUE是该Cookie的值。
    在字符串“NAME=VALUE”中,不含分号、逗号和空格等字符。
    
    Expires=DATE:Expires变量是一个只写变量,它确定了Cookie有效终止日期。
    该属性值DATE必须以特定的格式来书写:星期几,DD-MM-YY HH:MM:SS GMT,
    GMT表示这是格林尼治时间。
    反之,不以这样的格式来书写,系统将无法识别。
    该变量可省,如果缺省时,则Cookie的属性值不会保存在用户的硬盘中,
    而仅仅保存在内存当中,Cookie文件将随着浏览器的关闭而自动消失。
    
    Domain=DOMAIN-NAME:Domain该变量是一个只写变量,
    它确定了哪些Internet域中的Web服务器可读取浏览器所存取的Cookie,
    即只有来自这个域的页面才可以使用Cookie中的信息。
    这项设置是可选的,如果缺省时,设置Cookie的属性值为该Web服务器的域名。
    
    Path=PATH:Path属性定义了Web服务器上哪些路径下的页面可获取服务器设置的Cookie。
    一般如果用户输入的URL中的路径部分从第一个字符开始包含Path属性所定义的字符串,
    浏览器就认为通过检查。如果Path属性的值为“/”,
    则Web服务器上所有的WWW资源均可读取该Cookie。
    
    Secure:在Cookie中标记该变量,
    表明只有当浏览器和Web Server之间的通信协议为加密认证协议时,
    浏览器才向服务器提交相应的Cookie。当前这种协议只有一种,即为HTTPS。
    
cookie 在 Request Headers 中的传输格式
    Cookie: KEY=VALUE; KEY=VALUE; KEY=VALUE
    是没有 域 和 过期时间 的

跨域处理

  1. 两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。

    
    document.domain = 'example.com';
    
    
  2. 如果两个网页不同源,就无法拿到对方的DOM。

    
    典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。
  3. AJAX

    除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),

    vue项目中 开发环境的跨域处理

    proxyTable

    dev: {
         
        // Paths
        assetsSubDirectory: 'static',
        assetsPublicPath: './',
        proxyTable: {
            '/api': {
                target: 'http://temp.com',// 请换成你需要跨域请求的地址
                changeOrigin: true,
                pathRewrite: {
                  '^/api': ''
                }
            }
        }
    }
    

    proxyTable中的pathRewrite的/api理解成用‘/api’代替target里面的地址,
    后面组件中我们掉接口时直接用api代替

    有三种方法规避这个限制

    JSONP
    WebSocket
    CORS
    
    JSONP
    是服务器与客户端跨源通信的常用方法。
    最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
    它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,
    这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
    
    WebSocket
    WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。
    该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

    CORS
    CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。
    它是W3C标准,是跨源AJAX请求的根本解决方法。
    相比JSONP只能发GET请求,CORS允许任何类型的请求。
    
    
    
    

CORS详解

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

两种请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求

(1)请求方法是以下三种方法之一:
    HEAD
    GET
    POST
    
(2)HTTP的头信息不超出以下几种字段:
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
    
凡是不同时满足上面两个条件,就属于非简单请求。
简单请求

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。

    
    GET /cors HTTP/1.1
    Origin: http://api.bob.com
    Host: api.alice.com
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0...

上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

    
    
    Access-Control-Allow-Origin: http://api.bob.com
    Access-Control-Allow-Credentials: true
    Access-Control-Expose-Headers: FooBar
    Content-Type: text/html; charset=utf-8
    

上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。

  1. Access-Control-Allow-Origin

    该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求

  2. Access-Control-Allow-Credentials

    该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

  3. Access-Control-Expose-Headers

    该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

除了Origin字段,"预检"请求的头信息包括两个特殊字段。

  1. Access-Control-Request-Method

    该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。

  2. Access-Control-Request-Headers

    该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。

    OPTIONS /cors HTTP/1.1
    Origin: http://api.bob.com
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: X-Custom-Header
    Host: api.alice.com
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0...
    
    

预检请求的回应

服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

    HTTP/1.1 200 OK
    Date: Mon, 01 Dec 2008 01:15:39 GMT
    Server: Apache/2.0.61 (Unix)
    Access-Control-Allow-Origin: http://api.bob.com
    Access-Control-Allow-Methods: GET, POST, PUT
    Access-Control-Allow-Headers: X-Custom-Header
    Content-Type: text/html; charset=utf-8
    Content-Encoding: gzip
    Content-Length: 0
    Keep-Alive: timeout=2, max=100
    Connection: Keep-Alive
    Content-Type: text/plain
    
    
上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,
表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。

关于更多的cors详情请查看阮一峰 跨域资源共享 CORS 详解

查看原文

赞 76 收藏 52 评论 0

kfqweb 赞了回答 · 2019-01-09

解决javascript 连等赋值问题

赋值是从右到左的,但不要被绕晕了, 其实很简单,从运算符优先级来考虑

a.x = a = {n:2};

.运算优先于=赋值运算,因此此处赋值可理解为

  1. 声明a对象中的x属性,用于赋值,此时b指向a,同时拥有未赋值的x属性
  2. 对a对象赋值,此时变量名a改变指向到对象{n:2}
  3. 对步骤1中x属性,也即a原指向对象的x属性,也即b指向对象的x属性赋值

赋值结果:

a => {n: 2}
b => {n: 1, x: {n: 2 } }    

关注 76 回答 17

kfqweb 赞了文章 · 2018-12-21

JS的{} + {}与{} + []的结果是什么?

在JS中的运算符共同的情况中,(+)符号是很常见的一种,它有以下的使用情况:

  • 数字的加法运算,二元运算

  • 字符串的连接运算,二元运算,最高优先

  • 正号,一元运算,可延伸为强制转换其他类型的运算元为数字类型

当然,如果考虑多个符号一起使用时,(+=)与(++)又是另外的用途。

另一个常见的是花括号({}),它有两个用途也很常见:

  • 对象的字面文字定义

  • 区块语句

所以,要能回答这个问题,要先搞清楚重点是什么?

第一个重点是:

加号(+)运算在JS中在使用上的规定是什么。

第二个重点则是:

对象在JS中是怎么转换为原始数据类型的值的。

加号运算符(+)

除了上面说明的常见情况外,在标准中转换的规则还有以下几个,要注意它的顺序:

operand + operand = result

  1. 使用ToPrimitive运算转换左与右运算元为原始数据类型值(primitive)

  2. 在第1步转换后,如果有运算元出现原始数据类型是"字符串"类型值时,则另一运算元作强制转换为字符串,然后作字符串的连接运算(concatenation)

  3. 在其他情况时,所有运算元都会转换为原始数据类型的"数字"类型值,然后作数学的相加运算(addition)

ToPrimitive内部运算

因此,加号运算符只能使用于原始数据类型,那么对于对象类型的值,要如何转换为原始数据类型?下面说明是如何转换为原始数据类型的。

ECMAScript 6th Edition #7.1.1,有一个抽象的ToPrimitive运算,它会用于对象转换为原始数据类型,这个运算不只会用在加号运算符,也会用在关系比较或值相等比较的运算中。下面有关于ToPrimitive的说明语法:

ToPrimitive(input, PreferredType?)

input代表代入的值,而PreferredType可以是数字(Number)或字符串(String)其中一种,这会代表"优先的"、"首选的"的要进行转换到哪一种原始类型,转换的步骤会依这里的值而有所不同。但如果没有提供这个值也就是预设情况,则会设置转换的hint值为"default"。这个首选的转换原始类型的指示(hint值),是在作内部转换时由JS视情况自动加上的,一般情况就是预设值。

而在JS的Object原型的设计中,都一定会有两个valueOftoString方法,所以这两个方法在所有对象里面都会有,不过它们在转换有可能会交换被调用的顺序。

当PreferredType为数字(Number)时

PreferredType为数字(Number)时,input为要被转换的值,以下是转换这个input值的步骤:

  1. 如果input是原始数据类型,则直接返回input

  2. 否则,如果input是个对象时,则调用对象的valueOf()方法,如果能得到原始数据类型的值,则返回这个值。

  3. 否则,如果input是个对象时,调用对象的toString()方法,如果能得到原始数据类型的值,则返回这个值。

  4. 否则,抛出TypeError错误。

当PreferredType为字符串(String)时

上面的步骤2与3对调,如同下面所说:

  1. 如果input是原始数据类型,则直接返回input

  2. 否则,如果input是个对象时,调用对象的toString()方法,如果能得到原始数据类型的值,则返回这个值。

  3. 否则,如果input是个对象时,则调用对象的valueOf()方法,如果能得到原始数据类型的值,则返回这个值。

  4. 否则,抛出TypeError错误。

PreferredType没提供时,也就是hint为"default"时

PreferredType为数字(Number)时的步骤相同。

数字其实是预设的首选类型,也就是说在一般情况下,加号运算中的对象要作转型时,都是先调用valueOf再调用toString

但这有两个异常,一个是Date对象,另一是Symbol对象,它们覆盖了原来的PreferredType行为,Date对象的预设首选类型是字符串(String)。

因此你会看到在一些教程文件上会区分为两大类对象,一类是 Date 对象,另一类叫 非Date(non-date) 对象。因为这两大类的对象在进行转换为原始数据类型时,首选类型恰好相反。

模拟代码说明

以简单的模拟代码来说明,加号运算符(+)的运行过程就是像下面这个模拟码一样,我想这会很容易理解:

a + b:
    pa = ToPrimitive(a)
    pb = ToPrimitive(b)

    if(pa is string || pb is string)
       return concat(ToString(pa), ToString(pb))
    else
       return add(ToNumber(pa), ToNumber(pb))

步骤简单来说就是,运算元都用ToPrimitive先转换为原始数据类型,然后其一是字符串时,使用ToString强制转换另一个运算元,然后作字符串连接运算。要不然,就是都使用ToNumber强制转换为数字作加法运算。

ToPrimitive在遇到对象类型时,预设调用方式是先调用valueOf再调用toString,一般情况数字类型是首选类型。

上面说的ToStringToNumber这两个也是JS内部的抽象运算。

valueOf与toString方法

valueOfToString是在Object中的两个必有的方法,位于Object.prototype上,它是对象要转为原始数据类型的两个姐妹方法。从上面的内容已经可以看到,ToPrimitive这个抽象的内部运算,会依照设置的首选的类型,决定要先后调用valueOftoString方法的顺序,当数字为首选类型时,优先使用valueOf,然后再调用toString。当字符串为首选类型时,则是相反的顺序。预设调用方式则是如数字首选类型一样,是先调用valueOf再调用toString

JS对于Object与Array的设计

在JS中所设计的Object纯对象类型的valueOftoString方法,它们的返回如下:

  • valueOf方法返回值: 对象本身。

  • toString方法返回值: "[object Object]"字符串值,不同的内建对象的返回值是"[object type]"字符串,"type"指的是对象本身的类型识别,例如Math对象是返回"[object Math]"字符串。但有些内建对象因为覆盖了这个方法,所以直接调用时不是这种值。(注意: 这个返回字符串的前面的"object"开头英文是小写,后面开头英文是大写)

你有可能会看过,利用Object中的toString来进行各种不同对象的判断语法,这在以前JS能用的函数库或方法不多的年代经常看到,不过它需要配合使用函数中的call方法,才能输出正确的对象类型值,例如:

> Object.prototype.toString.call([])
"[object Array]"

> Object.prototype.toString.call(new Date)
"[object Date]"

所以,从上面的内容就可以知道,下面的这段代码的结果会是调用到toString方法(因为valueOf方法的返回并不是原始的数据类型):

> 1 + {}
"1[object Object]"

一元正号(+),具有让首选类型(也就是hint)设置为数字(Number)的功能,所以可以强制让对象转为数字类型,一般的对象会转为:

> +{} //相当于 +"[object Object]"
NaN

当然,对象的这两个方法都可以被覆盖,你可以用下面的代码来观察这两个方法的运行顺序,下面这个都是先调用valueOf的情况:

let obj = {
  valueOf: function () {
      console.log('valueOf');
      return {}; // object
  },
  toString: function () {
      console.log('toString');
      return 'obj'; // string
  }
}
console.log(1 + obj);  //valueOf -> toString -> '1obj'
console.log(+obj); //valueOf -> toString -> NaN
console.log('' + obj); //valueOf -> toString -> 'obj'

先调用toString的情况比较少见,大概只有Date对象或强制要转换为字符串时才会看到:

let obj = {
  valueOf: function () {
      console.log('valueOf');
      return 1; // number
  },
  toString: function () {
      console.log('toString');
      return {}; // object
  }
}
alert(obj); //toString -> valueOf -> alert("1");
String(obj); //toString -> valueOf -> "1";

而下面这个例子会造成错误,因为不论顺序是如何都得不到原始数据类型的值,错误消息是"TypeError: Cannot convert object to primitive value",从这个消息中很明白的告诉你,它这里面会需要转换对象到原始数据类型:

let obj = {
  valueOf: function () {
      console.log('valueOf');
      return {}; // object
  },
  toString: function () {
      console.log('toString');
      return {}; // object
  }
}

console.log(obj + obj);  //valueOf -> toString -> error!

Array(数组)很常用到,虽然它是个对象类型,但它与Object的设计不同,它的toString有覆盖,说明一下数组的valueOftoString的两个方法的返回值:

  • valueOf方法返回值: 对象本身。(与Object一样)

  • toString方法返回值: 相当于用数组值调用join(',')所返回的字符串。也就是[1,2,3].toString()会是"1,2,3",这点要特别注意。

Function对象很少会用到,它的toString也有被覆盖,所以并不是Object中的那个toString,Function对象的valueOftoString的两个方法的返回值:

  • valueOf方法返回值: 对象本身。(与Object一样)

  • toString方法返回值: 函数中包含的代码转为字符串值

Number、String、Boolean三个包装对象

包装对象是JS为原始数据类型数字、字符串、布尔专门设计的对象,所有的这三种原始数据类型所使用到的属性与方法,都是在这上面所提供。

包装对象的valueOftoString的两个方法在原型上有经过覆盖,所以它们的返回值与一般的Object的设计不同:

  • valueOf方法返回值: 对应的原始数据类型值

  • toString方法返回值: 对应的原始数据类型值,转换为字符串类型时的字符串值

toString方法会比较特别,这三个包装对象里的toString的细部说明如下:

  • Number包装对象的toString方法: 可以有一个传参,可以决定转换为字符串时的进位(2、8、16)

  • String包装对象的toString方法: 与String包装对象中的valueOf相同返回结果

  • Boolean包装对象的toString方法: 返回"true"或"false"字符串

另外,常被搞混的是直接使用Number()String()Boolean()三个强制转换函数的用法,这与包装对象的用法不同,包装对象是必须使用new关键字进行对象实例化的,例如new Number(123),而Number('123')则是强制转换其他类型为数字类型的函数。

Number()String()Boolean()三个强制转换函数,所对应的就是在ECMAScript标准中的ToNumberToStringToBoolean三个内部运算转换的对照表。而当它们要转换对象类型前,会先用上面说的ToPrimitive先转换对象为原始数据类型,再进行转换到所要的类型值。

不管如何,包装对象很少会被使用到,一般我们只会直接使用原始数据类型的值。而强制转换函数因为也有替换的语法,它们会被用到的机会也不多。

实例

字符串 + 其他原始类型

字符串在加号运算有最高的优先运算,与字符串相加必定是字符串连接运算(concatenation)。所有的其他原始数据类型转为字符串,可以参考ECMAScript标准中的ToString对照表,以下为一些简单的例子:

> '1' + 123
"1123"

> '1' + false
"1false"

> '1' + null
"1null"

> '1' + undefined
"1undefined"

数字 + 其他的非字符串的原始数据类型

数字与其他类型作相加时,除了字符串会优先使用字符串连接运算(concatenation)的,其他都要依照数字为优先,所以除了字符串之外的其他原始数据类型,都要转换为数字来进行数学的相加运算。如果明白这项规则,就会很容易的得出加法运算的结果。

所有转为数字类型可以参考ECMAScript标准中的ToNumber对照表,以下为一些简单的例子:

> 1 + true //true转为1, false转为0
2

> 1 + null //null转为0
1

> 1 + undefined //undefined转为NaN
NaN

数字/字符串以外的原始数据类型作加法运算

当数字与字符串以外的,其他原始数据类型直接使用加号运算时,就是转为数字再运算,这与字符串完全无关。

> true + true
2

> true + null
1

> undefined + null
NaN

空数组 + 空数组

> [] + []
""

两个数组相加,依然按照valueOf -> toString的顺序,但因为valueOf是数组本身,所以会以toString的返回值才是原始数据类型,也就是空字符串,所以这个运算相当于两个空字符串在相加,依照加法运算规则第2步骤,是字符串连接运算(concatenation),两个空字符串连接最后得出一个空字符串。

空对象 + 空对象

> {} + {}
"[object Object][object Object]"

两个空对象相加,依然按照valueOf -> toString的顺序,但因为valueOf是对象本身,所以会以toString的返回值才是原始数据类型,也就是"[object Object]"字符串,所以这个运算相当于两个"[object Object]"字符串在相加,依照加法运算规则第2步骤,是字符串连接运算(concatenation),最后得出一个"object Object"字符串。

但是这个结果有异常,上面的结果只是在Chrome浏览器上的结果(v55),怎么说呢?

有些浏览器例如Firefox、Edge浏览器会把{} + {}直译为相当于+{}语句,因为它们会认为以花括号开头({)的,是一个区块语句的开头,而不是一个对象字面量,所以会认为略过第一个{},把整个语句认为是个+{}的语句,也就是相当于强制求出数字值的Number({})函数调用运算,相当于Number("[object Object]")运算,最后得出的是NaN

特别注意: {} + {}在不同的浏览器有不同结果

如果在第一个(前面)的空对象加上圆括号(()),这样JS就会认为前面是个对象,就可以得出同样的结果:

> ({}) + {}
"[object Object][object Object]"

或是分开来先声明对象的变量值,也可以得出同样的结果,像下面这样:

> let foo = {}, bar = {};
> foo + bar;

注: 上面说的行为这与加号运算的第一个(前面)的对象字面值是不是个空对象无关,就算是里面有值的对象字面,例如{a:1, b:2},也是同样的结果。

注: 上面说的Chrome浏览器是在v55版本中的主控台直接运行的结果。其它旧版本有可能并非此结果。

空对象 + 空数组

上面同样的把{}当作区块语句的情况又会发生,不过这次所有的浏览器都会有一致结果,如果{}(空对象)在前面,而[](空数组)在后面时,前面(左边)那个运算元会被认为是区块语句而不是对象字面量。

所以{} + []相当于+[]语句,也就是相当于强制求出数字值的Number([])运算,相当于Number("")运算,最后得出的是0数字。

> {} + []
0

> [] + {}
"[object Object]"

特别注意: 所以如果第一个(前面)是{}时,后面加上其他的像数组、数字或字符串,这时候加号运算会直接变为一元正号运算,也就是强制转为数字的运算。这是个陷阱要小心。

Date对象

Date对象的valueOftoString的两个方法的返回值:

  • valueOf方法返回值: 给定的时间转为UNIX时间(自1 January 1970 00:00:00 UTC起算),但是以微秒计算的数字值

  • toString方法返回值: 本地化的时间字符串

Date对象上面有提及是首选类型为"字符串"的一种异常的对象,这与其他的对象的行为不同(一般对象会先调用valueOf再调用toString),在进行加号运算时时,它会优先使用toString来进行转换,最后必定是字符串连接运算(concatenation),例如以下的结果:

> 1 + (new Date())
> "1Sun Nov 27 2016 01:09:03 GMT+0800 (CST)"

要得出Date对象中的valueOf返回值,需要使用一元加号(+),来强制转换它为数字类型,例如以下的代码:

> +new Date()
1480180751492

Symbols类型

ES6中新加入的Symbols数据类型,它不算是一般的值也不是对象,它并没有内部自动转型的设计,所以完全不能直接用于加法运算,使用时会报错。

总结

{} + {}的结果是会因浏览器而有不同结果,Chrome(v55)中是[object Object][object Object]字符串连接,但其它的浏览器则是认为相当于+{}运算,得出NaN数字类型。

{} + []的结果是相当于+[],结果是0数字类型。

参考文章

查看原文

赞 54 收藏 61 评论 7

kfqweb 关注了用户 · 2018-11-30

Adrain @ryanism37

当你凝视深渊的时候 深渊也在凝视你。

关注 14

kfqweb 赞了文章 · 2018-11-30

JS所有内置对象属性和方法汇总

对象什么的,程序员可是有很多呢...

JS三大对象

对象,是任何一个开发者都无法绕开和逃避的话题,她似乎有些深不可测,但如此伟大和巧妙的存在,一定值得你去摸索、发现、征服。

我们都知道,JavaScript有3大对象,分别是本地对象内置对象宿主对象

在此引用ECMA-262(ECMAScript的制定标准)对于他们的定义:

  • 本地对象

    • 与宿主无关,独立于宿主环境的ECMAScript实现提供的对象。
    • 简单来说,本地对象就是 ECMA-262 定义的类(引用类型)。
    • 这些引用类型在运行过程中需要通过new来创建所需的实例对象。
    • 包含:ObjectArrayDateRegExpFunctionBooleanNumberString等。
  • 内置对象

    • 与宿主无关,独立于宿主环境的ECMAScript实现提供的对象。
    • 在 ECMAScript 程序开始执行前就存在,本身就是实例化内置对象,开发者无需再去实例化。
    • 内置对象是本地对象的子集。
    • 包含:GlobalMath
    • ECMAScript5中增添了JSON这个存在于全局的内置对象。
  • 宿主对象

    • 由 ECMAScript 实现的宿主环境提供的对象,包含两大类,一个是宿主提供,一个是自定义类对象。
    • 所有非本地对象都属于宿主对象。
    • 对于嵌入到网页中的JS来说,其宿主对象就是浏览器提供的对象,浏览器对象有很多,如WindowDocument等。
    • 所有的DOMBOM对象都属于宿主对象。

关于专业名词:本地对象也经常被叫做原生对象或内部对象,包含Global和Math在内的内置对象在《JavaScript高级程序设计》里也被叫做单体内置对象,很多时候,干脆也会直接把本地对象和内置对象统称为“内置对象”,也就是说除了宿主对象,剩下的都是ECMAScript的内部的“内置”对象。

声明:本文也将采取这种统称为“内置对象”的方式,比如文章标题。

Object类型

属性

constructor
prototype

实例方法

1、toString()

功能:返回当前对象的字符串形式,返回值为String类型。

示例:

[1,'2',true].toString(); //"1,2,true"
(new Date()).toString(); //"Sun Sep 24 2017 14:52:20 GMT+0800 (CST)"
({name:'ryan'}).toString(); //"[object Object]"

该方法属于Object对象,由于所有的对象都"继承"了Object的对象实例,因此几乎所有的实例对象都可以使用该方法。

JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。

2、toLocaleString

功能:返回当前对象的"本地化"字符串形式,以便于当前环境的用户辨识和使用,返回值为String类型。

示例:

(1234567).toLocaleString(); //"1,234,567"
(6.37588).toLocaleString(); //"6.376"
(new Date()).toLocaleString(); //"2017/9/24 下午2:58:21"

3、valueOf()

功能:返回指定对象的原始值。

JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的valueOf()方法的返回值和返回值类型均可能不同。

静态方法

1、Object.assign(target, ...sources)

功能:把一个或多个源对象的可枚举、自有属性值复制到目标对象中,返回值为目标对象。
参数:

  • 目标对象(必须)
  • 至少一个源对象(可选)

示例:

var target = {
    a:1
};
var source1 = {
    b:2
};
var source2 = {
    c:function(){
      console.log('c');
    }
};
Object.assign(target,source1,source2);
console.log(target); //{a: 1, b: 2, c: ƒ}

拓展:自定义实现一个assign方法

//自定义一个assign方法
  function copy(target){
    if(target == null){
      throwError('出错:Cannot convert undefined or null to object');
    }
    var target = new Object(target);
    for(var i = 1;i < arguments.length;i ++){
      var source = arguments[i];
      for(var key in source){
        if(source.hasOwnProperty(key)){
          //若当前属性为源对象自有属性,则拷贝至目标对象
          target[key] = source[key];
        }
      }
    }
    return target;
  }

2、Object.create(proto [,propertiesObject])

功能:创建一个对象,其原型为prototype,同时可添加多个属性。
参数:

  • proto(必须):原型对象,可以为null表示没有原型。
  • descriptors(可选):包含一个或多个属性描述符的对象。

propertiesObject参数详解:

  • 数据属性

    • value:值
    • writable:是否可修改属性的值
    • configurable:是否可通过delete删除属性,重新定义
    • enumerable:是否可for-in枚举
  • 访问属性

    • get():访问
    • set():设置

示例:

function Person(name){
    this.name = name;
  }
  Person.prototype.say = function(){console.log('my name is ' + this.name +',my age is ' + this.age);}

  var person = new Person('ryan');
  var p = Object.create(person,{
    age:{
      value: 23,
      writable: true,
      configurable: true
    },
    sex:{
      configurable: true,
      get:function(){return sex + '士';},
      set:function(value){sex = value;}
    }
  });
  
  p.sex = '男';
  p.say(); //'my name is ryan,my age is 23'
  console.log(p.sex); //'男士'
  p.sex = '女';
  console.log(p.sex); //'女士'

总结:Object.create(proto [,propertiesObject]) 是E5中提出的一种新的对象创建方式,第一个参数是要继承的原型,如果不是一个子函数,可以传一个null,第二个可选参数是对象的属性描述符。

3、Object.defineProperty(obj, prop, descriptor)

功能:在一个对象上定义一个新属性或修改一个现有属性,并返回该对象。

参数:

  • obj(必须):被操作的目标对象
  • prop(必须):被定义或修改的目标属性
  • descriptor(必须):属性的描述符

示例:

var obj = {};
Object.defineProperty(obj,'name',{
    writable: true,
    configurable: true,
    enumerable: false,
    value: '张三'
});

console.log(obj.name); //'张三'
for(var key in obj){
    console.log(obj[key]); //无结果
}

总结:在参数 descriptor中,如果不指定configurable, writable, enumerable ,则这些属性默认值为false,如果不指定value, get, set,则这些属性默认值为undefined。

4、Object.defineProperties(obj, props)

功能:在一个对象上定义一个或多个新属性或修改现有属性,并返回该对象。

参数:

  • obj(必须):被操作的目标对象
  • props(必须):该对象的一个或多个键值对定义了将要为对象添加或修改的属性的具体配置

示例:

var obj = {};
Object.defineProperties(obj,{
    name:{
      writable: true,
      configurable: true,
      enumerable: false,
      value: '张三'
    },
    age:{
      writable: true,
      configurable: true,
      enumerable: true,
      value: 23
    }
});

console.log(obj.name); //'张三'
console.log(obj.age); //23
for(var key in obj){
    console.log(obj[key]); //23
}

5、Object.seal(obj) / Object.isSealed(obj)
功能:密封对象,阻止其修改现有属性的配置特性,即将对象的所有属性的configurable特性设置为false(也就是全部属性都无法重新配置,唯独可以把writable的值由true改为false,即冻结属性),并阻止添加新属性,返回该对象。

参数:

  • obj(必须):被密封的对象

示例:

var obj = {name:'张三'};

Object.seal(obj);
console.log(Object.isSealed(obj)); //true

obj.name = '李四'; //修改值成功
console.log(obj.name); //'李四'
obj.age = 23; //无法添加新属性
console.log(obj.age); //undefined

Object.defineProperty(obj,'name',{ 
    writable: true,
    configurable: true,
    enumerable: true
}); //报错:Cannot redefine property: name

补充:Object.isSealed(obj)用于判断目标对象是否被密封,返回布尔值。

将一个对象密封后仅能保证该对象不被扩展且全部属性不可重配置,但是原属性值却是可以被修改的。

6、Object.freeze(obj) / Object.isFrozen(obj)

功能:完全冻结对象,在seal的基础上,属性值也不可以修改,即每个属性的wirtable也被设为false。

参数:

  • obj(必须):被冻结的对象

示例:

var obj = {name:'张三'};

Object.freeze(obj);
console.log(Object.isFrozen(obj)); //true

obj.name = '李四'; //修改值失败
console.log(obj.name); //'张三'
obj.age = 23; //无法添加新属性
console.log(obj.age); //undefined

Object.defineProperty(obj,'name',{ 
    writable: true,
    configurable: true,
    enumerable: true
}); //报错:Cannot redefine property: name

补充:Object.isFrozen(obj)用于判断目标对象是否被冻结,返回布尔值。

7、getOwnPropertyDescriptor(obj, prop)

功能:获取目标对象上某自有属性的配置特性(属性描述符),返回值为配置对象。

参数:

  • obj(必须):目标对象
  • prop(必须):目标自有属性

示例:

var obj = {};

Object.defineProperty(obj,'name',{
    writable: true,
    configurable: false,
    enumerable: true,
    value: '张三'
});

var prop = Object.getOwnPropertyDescriptor(obj,'name');
console.log(prop); //{value: "张三", writable: true, enumerable: true, configurable: false}

8、Object.getOwnPropertyNames(obj)

功能:获取目标对象上的全部自有属性名(包括不可枚举属性)组成的数组。

参数:

  • obj(必须):目标对象

示例:

var obj = {};
obj.say = function(){};

Object.defineProperties(obj,{
    name:{
      writable: true,
      configurable: true,
      enumerable: true,
      value: '张三'
    },
    age:{
      writable: true,
      configurable: true,
      enumerable: false,
      value: 23
    }
});

var arr = Object.getOwnPropertyNames(obj);
console.log(arr); //["say", "name", "age"]

9、Object.getPrototypeOf(obj)

功能:获取指定对象的原型,即目标对象的prototype属性的值。

参数:

  • obj(必须):目标对象

示例:

function Person(name){
    this.name = name;
}

var person = new Person('张三');
var p = Object.create(person); //对象p的原型为person
console.log(p); //Person {}

var __ptoto__ = Object.getPrototypeOf(p);
console.log(__ptoto__); //Person {name: "张三"}

10、Object.setPrototypeOf(obj, proto)

功能:设置目标对象的原型为另一个对象或null,返回该目标对象。

参数:

  • obj(必须):目标对象
  • proto(必须):原型对象

示例:

var obj = {a:1};
var proto = {};
Object.setPrototypeOf(obj,proto); //设置obj对象的原型

proto.b = 2; //为该原型对象添加属性
proto.c = 3;

console.log(obj.a); //1
console.log(obj.b); //2
console.log(obj.c); //3

解析:上述代码将proto对象设为obj对象的原型,所以从obj对象上可以顺利读取到proto 对象的属性,也就是原型链上的属性。

Object.setPrototypeOf()方法的作用与__proto__相同,用来设置当前对象的原型指向的对象(prototype)。它是 ES6 正式推荐的设置原型对象的方法。

11、Object.keys(obj)

功能:获取目标对象上所有可枚举属性组成的数组。

参数:

  • obj(必须):目标对象

示例:

var person = {
    type:'person',
    say:function(){}
  };
  //以person对象为原型,创建obj对象
  var obj = Object.create(person,{
    sex:{
      writable: true,
      configurable: true,
      enumerable: false, //设置sex属性为不可枚举
      value: 'male'
    },
    age:{
      writable: true,
      configurable: true,
      enumerable: true, //设置age属性为可枚举
      value: 23
    }
  });

  obj.name = '张三'; //自定义属性name默认为可枚举
  console.log(obj.propertyIsEnumerable('name')); //true,成功验证name属性为可枚举

  //用for-in可获取obj上全部可枚举的属性(包括自有和原型链上的)
  var arr = [];
  for(var key in obj){
    arr.push(key);
  }
  console.log(arr); //["age", "name", "type", "say"]

  //用Object.keys()可获取obj上全部可枚举的自有属性
  console.log(Object.keys(obj)); // ["age", "name"]

总结:Object.keys(obj)方法获取的集合和for-in遍历获取的不同在于,Object.keys()只获取目标对象上可枚举的自有属性,而for-in遍历会包含原型链上可枚举属性一并获取。

Object.keys()和Object.getOwnPropertyNames()的相同之处都是获取目标对象的自有属性,区别在于,后者会连同不可枚举的自有属性也一并获取组成数组并返回。

12、Object.preventExtensions(obj) / Object.isExtensible(obj)

功能:使某一对象不可扩展,也就是不能为其添加新属性。

参数:

  • obj(必须):目标对象

补充:Object.isExtensible(obj)方法用于判断一个对象是否可扩展,即是否可以添加新属性。

示例:

var obj = {
  name: '张三'
};

Object.preventExtensions(obj); //阻止obj的可扩展性
console.log(Object.isExtensible(obj)); //false,表明obj对象为不可扩展,即阻止成功

obj.age = 23; //默认添加失败
console.log(obj.age); //undefined

Array类型

Array 对象属性

1、length

设置或返回数组中元素的数目。

设置 length 属性可改变数组的大小。如果设置的值比其当前值小,数组将被截断,其尾部的元素将丢失。如果设置的值比它的当前值大,数组将增大,新的元素被添加到数组的尾部,它们的值为 undefined。

2、constructor

返回对创建此对象的数组函数的引用。

3、prototype

使您有能力向对象添加属性和方法。

Array 对象方法

1、concat()

  • 用于连接两个或多个数组,该方法不会改变现有的数组,而是返回被连接数组的一个副本。
  • 如果要进行 concat() 操作的参数是数组,那么添加的是数组中的元素,而不是数组。

2、join()

  • 把数组中的所有元素放入一个字符串,元素是通过指定的分隔符进行分隔的。
  • 若省略了分隔符参数,则使用逗号作为分隔符。

3、push()

  • 向数组的末尾添加一个或多个元素,并返回新的数组长度。

4、pop()

  • 用于删除数组的最后一个元素,把数组长度减1,并返回被删除元素。
  • 如果数组已经为空,则 pop() 不改变数组,并返回 undefined。

5、shift()

  • 用于把数组的第一个元素从其中删除,并返回被移除的这个元素。
  • 如果数组是空的,那么 shift() 方法将不进行任何操作,返回 undefined。
  • 该方法是直接修改原数组。

6、unshift()

  • 向数组的开头添加一个或更多元素,并返回新的数组长度。
  • 该方法是直接修改原数组。

7、reverse()

  • 用于颠倒数组中元素的顺序。
  • 该方法会直接修改原数组,而不会创建新数组。

8、sort()

  • 用于对数组的元素进行排序。
  • 该排序直接修改原数组,不生成副本。
  • 该方法接受一个可选参数,若未使用参数,将按字母顺序对数组元素进行排序,说得更精确点,是按照字符编码的顺序进行排序。要实现这一点,首先应把数组的元素都转换成字符串(如有必要),以便进行比较。
  • 如果想按照其他标准进行排序,就需要提供比较函数,该函数要比较两个值,然后返回一个用于说明这两个值的相对顺序的数字。比较函数应该具有两个参数 a 和 b,其返回值如下:

    • 若 a 小于 b,排序后 a 应该在 b 之前,则返回一个小于 0 的值。
    • 若 a 等于 b,则返回 0。
    • 若 a 大于 b,则返回一个大于 0 的值。

9、slice(start [,end])

  • 截取原数组从start到end位置(不包含它)元素组成的子数组。
  • 该方法返回一个新数组,不会修改原数组。
  • 若未指定end参数,那么截取尾巴直到原数组最后一个元素(包含它)。

10、splice(index,howmany [,item1,item2...])

  • 删除从 index 处开始的hownamy个元素,并且用可选参数列表中声明的一个或多个值来替换那些被删除的元素。
  • 该方法返回的是含有被删除的元素组成的数组,若无被删元素,则返回空数组。
  • 若参数只有index,那么原数组将从index开始删除直至结尾。
  • 该方法直接修改原数组。

map():返回一个新的Array,每个元素为调用func的结果

filter():返回一个符合func条件的元素数组

some():返回一个boolean,判断是否有元素是否符合func条件

every():返回一个boolean,判断每个元素是否符合func条件

forEach():没有返回值,只是针对每个元素调用func

reduce():reduce方法有两个参数,第一个参数是一个callback,用于针对数组项的操作;第二个参数则是传入的初始值,这个初始值用于单个数组项的操作。需要注意的是,reduce方法返回值并不是数组,而是形如初始值的经过叠加处理后的操作。

Date类型

Date对象:封装一个时间点,提供操作时间的API。Date对象中封装的是从1970年1月1日0点至今的毫秒数。

创建Date对象4种方式

var now = new Date(); //获取客户端的当前系统时间

var date - new Date("1994/02/04 03:23:55"); //创建自定义时间

var date = new Date(yyyy, MM, dd, hh, mm, ss); //创建自定义时间

var oldDate = new Date("1994/02/04");
var newDate = new Date(oldDate); //复制一个时间对象

日期API

日期分量:FullYear、Month、Date、Day、Hours、Minutes、Seconds、Milliseconds。
每一个日期分量都有一个getset方法(除了Day没有set方法),分别用于获取和设置时间对象。

日期的单位及范围:

年FullYear (无范围)
月Month (0~11, 0开始,没有12)
日Date (1~31, 和现实生活一样)
星期Day (0~6, 0是星期日,没有7)
时Hours (0~23. 0开始,没有24)
分Minutes (0~59)
秒Seconds (0~59)
毫秒MilliSeconds

RegExp类型

RegExp对象属性

1、global

  • 描述:RegExp 对象是否具有标志 g,即全局匹配。
  • 值:true或false。

2、ignoreCase

  • 描述:RegExp 对象是否具有标志 i,即忽略大小写。
  • 值:一个整数,它声明的是上一次匹配文本之后的第一个字符的位置。

3、lastIndex

  • 描述:lastIndex用于规定下次匹配的起始位置。
  • 值:true或false。

不具有标志 g 和不表示全局模式的 RegExp 对象不能使用 lastIndex 属性。

RegExp对象方法

1、compile()

  • compile() 方法用于在脚本执行过程中编译正则表达式。
  • compile() 方法也可用于改变和重新编译正则表达式。

2、exec()

  • 功能:用于检索字符串中的正则表达式的匹配。
  • 参数:string,必须,要检索的字符串。
  • 返回值:返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。

3、test()

  • 功能:用于检测一个字符串是否匹配某个模式。
  • 参数:string,必须,要检索的字符串。
  • 返回值:true或者false。

注意:支持正则表达式的 String 对象的方法有:search()、match()、replace()和split()。

Function类型

Function对象属性

1、arguments

  • arguments.length:获取函数实参的个数
  • arguments.callee:获取函数对象本身的引用
  • arguments.callee.length:获取函数形参的个数

Javascrip中每个函数都会有一个Arguments对象实例arguments,它引用着函数的实参,可以用数组下标的方式"[]"引用每个实际传入的参数。

示例:

function say(a,b,c){
  console.log(arguments.length); //2
  console.log(arguments[0],arguments[1]); //hello world
}
say('hello','world');

Function对象方法

1、toString()

  • 功能:将函数体转换成对应的字符串。

Boolean类型

常用方法:

1、toString()

  • 功能:根据布尔值返回字符串 "true" 或 "false"。

注释:在 Boolean 对象被用于字符串环境中时,此方法会被自动调用。

2、valueOf()

  • 功能:返回 Boolean 对象的原始值。

Number类型

常用方法:

1、toString()

功能:将Number数值转换为字符串,该方法接受一个可选参数基数,若省略该参数,则默认基数为10,即十进制。

var num = 10;
console.log(num.toString(2)); //1010

2、toLocaleString()
功能:把一个 Number 对象转换为本地格式的字符串。

3、valueOf()
功能:返回一个 Number 对象的基本数字值。

valueOf() 方法通常由 JavaScript 在后台自动进行调用,而不是显式地处于代码中。

String类型

String对象属性

1、length

功能:String 对象的 length 属性声明了该字符串中的字符数。

String对象方法

1、charAt()

  • 功能:返回指定位置的字符。
  • 参数:必须,为目标字符的下标位置。

若参数 index 不在 0 与 string.length 之间,该方法将返回一个空字符串。

2、charCodeAt()

  • 功能:返回在指定的位置的字符的 Unicode 编码。
  • 参数:必须,为目标字符的下标位置。

若参数 index 不在 0 与 string.length 之间,该方法将返回NaN。

3、indexOf()

  • 功能:检索字符串,返回指定子字符串在字符串中首次出现的位置。
  • 参数1:检索目标子字符串,必须。
  • 参数2:在字符串中开始检索的位置,可选。其合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。

注意:indexOf() 方法对大小写敏感!
注意:如果要检索的字符串值没有出现,则该方法返回 -1。

4、lastIndexOf()

  • 功能:从后向前搜索字符串,返回指定子字符串在字符串中首次出现的位置。
  • 参数1:检索目标子字符串,必须。
  • 参数2:在字符串中开始检索的位置,可选。其合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的最后一个字符开始检索。

5、match()

  • 功能:返回指定位置的字符。
  • 参数:必须,规定要检索的字符串值或待匹配的 RegExp 对象。
  • 返回值:存放匹配结果的数组。该数组的内容依赖于 regexp 是否具有全局标志 g。

如果 regexp 没有标志 g,那么 match() 方法就只能在 stringObject 中执行一次匹配。如果没有找到任何匹配的文本, match() 将返回 null。否则,它将返回一个数组,其中存放了与它找到的匹配文本有关的信息。该数组的第 0 个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本。除了这些常规的数组元素之外,返回的数组还含有两个对象属性。index 属性声明的是匹配文本的起始字符在 stringObject 中的位置,input 属性声明的是对 stringObject 的引用。

如果 regexp 具有标志 g,则 match() 方法将执行全局检索,找到 stringObject 中的所有匹配子字符串。若没有找到任何匹配的子串,则返回 null。如果找到了一个或多个匹配子串,则返回一个数组。不过全局匹配返回的数组的内容与前者大不相同,它的数组元素中存放的是 stringObject 中所有的匹配子串,而且也没有 index 属性或 input 属性。

示例:

var s = 'hello21 world21';
console.log(s.match(/\d{2}/)); //[ '21', index: 5, input: 'hello21 world21' ]

var s = 'hello21 world21';
console.log(s.match(/\d{2}/g)); //[ '21', '21' ]

6、replace()

  • 功能:在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
  • 参数1:regexp/substr,必须,规定子字符串或要匹配的 RegExp 对象。
  • 参数2:replacement,必须,用于替换的字符串值。
  • 返回值:替换后的一个新字符串。

示例:

var s = 'hello world hello';
console.log(s.replace('hello','hi')); //hi world hello
console.log(s.replace(/hello/,'hi')); //hi world hello
console.log(s.replace(/hello/g,'hi')); //hi world hi

replace方法返回一个新字符串,并不会修改原字符串。

7、search()

  • 功能:用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。
  • 参数:regexp/substr,必须,规定子字符串或要匹配的 RegExp 对象。
  • 返回值:原字符串中第一次匹配到目标字符串的起始位置。

示例:

var s = 'hello world hello';
console.log(s.search('hello')); //0
console.log(s.search(/hello/g)); //0
console.log(s.search(/hello2/)); //-1

search()方法不执行全局匹配,它将忽略标志 g。也就是说,它只匹配一次。若没匹配到结果,则返回-1。

8、toLowerCase() & toUpperCase()

  • 功能:把字符串转换为小写/大写。
  • 返回值:一个新的字符串。

示例:

var s = 'Hello World';
console.log(s.toLowerCase()); //hello world
console.log(s.toUpperCase()); //HELLO WORLD

9、concat()

  • 功能:用于连接两个或多个字符串。
  • 语法:stringObject.concat(stringX,stringX,...,stringX)
  • 参数:
  • 返回值:衔接后的一个新字符串。

concat方法不会修改原字符串。
stringObject.concat() 与 Array.concat() 很相似。
通常使用 " + " 运算符来进行字符串的连接运算通常会更简便一些。

示例:

var s1 = 'hello ';
var s2 = 'world ';
var s3 = '233';
console.log(s1.concat(s2,s3)); //hello world 233

10、split()

  • 功能:用于把一个字符串分割成字符串数组,是 Array.join( ) 的逆操作。
  • 参数1:separator,必须,字符串或正则表达式,从该参数指定的地方分割原字符串。
  • 参数2:howmany,可选,指定返回数组的最大长度。
  • 返回值:一个字符串数组。

示例:

var s = 'hi baby';
console.log(s.split('')); //[ 'h', 'i', ' ', 'b', 'a', 'b', 'y' ]
console.log(s.split(' '));  //[ 'hi', 'baby' ]
console.log(s.split('b')); //[ 'hi ', 'a', 'y' ]

11、slice()

  • 功能:截取字符串的某个部分,并以新的字符串返回被提取的部分。
  • 参数1:截取的起始位置,必须。
  • 参数2:截取的结束位置,可选。
  • 返回值:截取部分,一个新的字符串。

注意:String.slice() 与 Array.slice() 相似。
slice方法的两个参数接受负值,若为负数,则该参数规定的是从字符串的尾部开始算起的位置。也就是说,-1 指字符串的最后一个字符,-2 指倒数第二个字符,以此类推。
若未指定第二个参数,则默认截取至字符串的末尾。
slice方法不修改原字符串。

示例:

var s = 'hi baby';
console.log(s.slice(3)); //baby
console.log(s.slice(1,5)); //i ba
console.log(s.slice(-4)); //baby
console.log(s.slice(-4,-2)); //ba

12、substr()

  • 功能:截取从指定下标开始的指定数目的字符。
  • 参数1:start,必须,截取的起始位置,接受负值。
  • 参数2:length,可选,截取字符串的长度,若未指定,则默认截取到原字符串的末尾。
  • 返回值:截取部分,一个新的字符串。

注意:ECMAscript 没有对该方法进行标准化,因此不建议使用它。

示例:

var s = 'hi baby';
console.log(s.substr(3)); //baby
console.log(s.substr(3,2)); //ba
console.log(s.substr(-3,2)); //ab

13、substring()

  • 功能:截取字符串中介于两个指定下标之间的字符。
  • 参数1:start,必须,截取的起始位置。
  • 参数2:end,可选,截取的结束位置,若未指定,则默认截取到原字符串的末尾。
  • 返回值:截取部分,一个新的字符串。

示例:

var s = 'hi baby';
console.log(s.substring(3)); //baby
console.log(s.substring(3,5)); //ba
console.log(s.substring(5,3)); //ba
console.log(s.substring(3,3)); //''

注意:与 slice() 和 substr() 方法不同的是,substring() 不接受负的参数。
如果参数 start 与 stop 相等,那么该方法返回的一个空串。
如果 start 比 stop 大,那么该方法在提取子串之前会先交换这两个参数。

Global对象(全局对象)

关于全局对象:全局对象只是一个对象,而不是类。既没有构造函数,也无法实例化一个新的全局对象。

属性

Infinity
代表正的无穷大的数值。

示例:

console.log(6/0); //Infinity
console.log(-6/0); //-Infinity
console.log(0/0); //NaN
console.log(1.7976931348623157E+10308); //Infinity
console.log(-1.7976931348623157E+10308); //-Infinity

Infinity代表了超出JavaScript处理范围的数值。也就是说JS无法处理的数值都是Infinity。实践证明,JS所能处理的最大值是1.7976931348623157e+308,而最小值是5e-324。

NaN
代表非数字的值。

示例:

var a = Number('100');
var b = Number('hello world');

console.log(a); //100
console.log(b); //NaN
console.log(isNaN(a)); //false
console.log(isNaN(b)); //true

提示:请使用 isNaN() 方法来判断一个值是否是数字,原因是 NaN 与所有值都不相等,包括它自己。

Undefined
代表未定义的值。

示例:

var a;
var b = '';
var c = null;

console.log(a === undefined); //true
console.log(b === undefined); //false
console.log(c == undefined); //true

提示:判断一个变量是否未定义,只能用 === undefined 运算来测试,因为 == 运算符会认为 undefined 值等价于 null,即undefined == null会返回true。

注释:null 表示无值,而 undefined 表示一个未声明的变量,或已声明但没有赋值的变量,或一个并不存在的对象属性。

方法

1、encodeURI(URIString)

功能:将字符串作为URI进行编码,返回值为URIstring 的副本。

参数:

  • URIString(必须):一个待编码的字符串。

示例:

console.log(encodeURI('http://www.baidu.com')); //http://www.baidu.com
console.log(encodeURI('http://www.baidu.com/my mind')); //http://www.baidu.com/my%20mind
console.log(encodeURI(',/?:@&=+$#')); //,/?:@&=+$#

该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。

该方法的目的是对 URI 进行完整的编码,因此对以下在 URI 中具有特殊含义的 ASCII 标点符号,encodeURI() 函数是不会进行转义的:;/?:@&=+$,#

提示:如果 URI 组件中含有分隔符,比如 ? 和 #,则应当使用 encodeURIComponent() 方法分别对各组件进行编码。

2、encodeURIComponent(URIString)

功能:将字符串作为URI组件进行编码,返回值为URIstring的副本。

该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。

其他字符(比如 :;/?:@&=+$,# 这些用于分隔 URI 组件的标点符号),都是由一个或多个十六进制的转义序列替换的。

参数:

  • URIString(必须):一个待编码的字符串。

示例:

encodeURI和encodeURIComponent的区别:

它们都是编码URL,唯一区别就是编码的字符范围,其中encodeURI方法不会对下列字符编码 ASCII字母、数字、~!@#$&*()=:/,;?+'
encodeURIComponent方法不会对下列字符编码 ASCII字母、数字、~!*()'
所以encodeURIComponent比encodeURI编码的范围更大。
实际例子来说,encodeURIComponent会把 http:// 编码成 http%3A%2F%2F 而encodeURI却不会。

使用场景:

  • 当你需要编码整个URL,然后使用这个URL,则使用encodeURI。
console.log(encodeURI('http://www.baidu.com/home/some other thing'));
//编码后为:http://www.baidu.com/home/some%20other%20thing; 其中,空格被编码成了%20

//但是如果你用了encodeURIComponent
console.log(encodeURIComponent('http://www.baidu.com/home/some other thing'));
//http%3A%2F%2Fwww.baidu.com%2Fhome%2Fsome%20other%20thing 连 "/" 都被编码了,整个URL已经没法用了
  • 当你需要编码URL中的参数时,那么使用encodeURIComponent。
var param = "http://www.baidu.com/home/"; //param为参数
param = encodeURIComponent(param);
var url = "http://www.baidu.com?next=" + param;
console.log(url) //'http://www.baidu.com?next=http%3A%2F%2Fwww.baidu.com%2Fhome%2F'
//显然,参数中的 "/" 被编码了,而如果用encodeURI肯定要出问题,因为后面的/是需要编码的。

补充:相应的,存在decodeURI()和decodeURIComponent是用来解码的,逆向操作。

3、parseInt(string,radix)

功能:解析一个字符串,并返回一个整数。

参数:

  • string(必须):待解析的字符串
  • radix(可选):表示要解析的数字的基数。该值介于 2 ~ 36 之间。
    如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。

示例:

console.log(parseInt('10')); //10
console.log(parseInt('11',9)); //10 (9+1)
console.log(parseInt('11',2)); //3 (2+1)
console.log(parseInt('17',8)); //15 (8+7)
console.log(parseInt('1f',16)); //31 (16+15)
console.log(parseInt('010')); //10
console.log(parseInt('0x0011')); //17

4、parseFloat()

功能:解析一个字符串,并返回一个浮点数。
该函数指定字符串中的首个字符是否是数字。如果是,则对字符串进行解析,直到到达数字的末端为止。

参数:

  • string(必须):待解析的字符串

示例:

console.log(parseFloat('10')); //10
console.log(parseFloat('10.00')); //10 
console.log(parseFloat('10.33')); //10.33
console.log(parseFloat(' 60 ')); //60 首尾的空格会忽略
console.log(parseFloat('23 34 45')); //23 中间的空格不会忽略,会中断
console.log(parseFloat('23 years')); //23
console.log(parseFloat('i am 23')); //NaN

提示:开头和结尾的空格是允许的。如果字符串的第一个字符不能被转换为数字,那么 parseFloat() 会返回 NaN。如果只想解析数字的整数部分,请使用 parseInt() 方法。

5、isFinite(number)

功能:用于检查其参数是否是无穷大。

参数:

  • number(必须):待检测数字。
    如果 number 是有限数字(或可转换为有限数字),那么返回 true。否则,如果 number 是 NaN(非数字),或者是正、负无穷大的数,则返回 false。

示例:

console.log(isFinite(123)); //true
console.log(isFinite(-1.23)); //true
console.log(isFinite(5-2)); //true
console.log(isFinite(0)); //true
console.log(isFinite(0/0)); //false
console.log(isFinite('Hello')); //false

6、isNaN(number)

功能:用于检查其参数是否为非数字值。

参数:

  • number(必须):待检测数字。
    如果 number 是非数字值 NaN(或者能被转换成NaN),返回 true,否则返回 false。

示例:

console.log(isNaN(123)); //false
console.log(isNaN(-1.23)); //false
console.log(isNaN(5-2)); //false
console.log(isNaN(0)); //false
console.log(isNaN(0/0)); //true
console.log(isNaN('Hello')); //true

提示:isNaN() 函数通常用于检测 parseFloat() 和 parseInt() 的结果,以判断它们表示的是否是合法的数字。当然也可以用 isNaN() 函数来检测算数错误,比如用 0 作除数的情况。

7、Number(object)

功能:把对象的值转换为数字。

参数:

  • object(必须):待转换的对象。
    如果参数是 Date 对象,Number() 返回从1970年1月1日至今的毫秒数,即时间戳。如果对象的值无法转换为数字,那么 Number() 函数返回 NaN。

示例:

console.log(Number(new Boolean(true))); //1
console.log(Number(new Boolean(false))); //0
console.log(Number(new Date())); //1506266494726
console.log(Number(new String('999'))); //999
console.log(Number(new String('999 888'))); //NaN

8、String(object)

功能:把对象的值转换为字符串。

参数:

  • object(必须):待转换的对象。

示例:

console.log(String(new Boolean(true))); //'true'
console.log(String(new Boolean(false))); //'false'
console.log(String(new Date())); //'Sun Sep 24 2017 23:25:43 GMT+0800 (CST)'
console.log(String(new String('999'))); //'999'
console.log(String(new String('999 888'))); //'999 888'
console.log(String(12345)); //'12345'

Math对象

常用方法:

Math.abs(); //取绝对值
Math.ceil(); //向上取整
Math.floor(); //向下取整
Math.round(); //四舍五入取整
Math.random(); //生成0~1间的随机数(>0)
Math.max(x,y); //取x、y中较大的那个
Math.min(x,y); //取x、y中较小的那个

JSON对象

我们常说的对象字面量其实不是JSON对象,但是有真正的JSON对象。

两者完全不一样概念,在新版的浏览器里JSON对象已经被原生的内置对象了,目前有2个静态方法:JSON.parse用来将JSON字符串反序列化成对象,JSON.stringify用来将对象序列化成JSON字符串。

老版本的浏览器不支持这个对象,但你可以通过json2.js来实现同样的功能。

JSON对象方法

1、JSON.parse()

  • 功能:将字符串反序列化成对象
  • 参数:JSON字符串
  • 返回值:对象

示例:

var jsonString = '{"name":"ryan"}'; //JSON字符串(比如从AJAX获取字符串信息)
var obj = JSON.parse(jsonString); //将字符串反序列化成对象
console.log(obj); //{ name: 'ryan' }
console.log(obj.name == 'ryan'); //true

2、JSON.stringify()

  • 功能:将一个对象解析为JSON字符串
  • 参数:对象
  • 返回值:JSON字符串

示例:

var obj = {name:'ryan',age:23};
var jsonString = JSON.stringify(obj);
console.log(jsonString); //'{"name":"ryan","age":23}'
查看原文

赞 54 收藏 44 评论 5

kfqweb 评论了文章 · 2018-06-04

Vue组件 - 智能联想输入框

已经有很多成熟的智能输入框组件,如Form.js。但是现在MVVM框架,如vue、react的为了实现双向数据绑定会重绘所有的元素,这样就会难以兼容使用。所以笔者开发了Vue组件-智能输入框。

包含的功能大同小异:

  1. 获得焦点时显示所有备选项
  2. 失去焦点时隐藏备选项面板
  3. 输入字符后,检索可能的备选项
  4. 支持上下键和回车键进行选中
  5. 支持点击选中
  6. 支持多选
  7. 以逗号进行多选的分割

更新日志

2019-06-10

  1. 取消依赖jQuery和bootstrap
  2. 上传到github进行代码管理
  3. 增加示例文件和使用说明

代码托管

github地址:https://github.com/LeonSage/s...

示例:

图1:组件化的调用

图片描述

图2:实际应用的场景

图片描述

依赖

依赖vue,可以使用CDNhttps://cdnjs.cloudflare.com/...

使用方式

  1. 在页面中引入vue.js
  2. 在页面中引入smartInput.jssmartInput.css
  3. 在你的页面中建立vue对象:new Vue({el: '#root'})
  4. 在root根组件里直接添加<smart-input>标签即可调用该组件
# 调用组件
<smart-input :props="provinceList" @collect="collectProvince"></smart-input>

接口文档

我们只需要在初始化的vue对象里设置好该组件需要的相关属性即可生效:

provinceList: {
    list: ['北京市','天津市','上海市','重庆市','河北省','山西省','辽宁省','吉林省'],
    multiple: true,
    value: '我是初始值'
},

同时需要提供一个函数用于支持数据收集和回传:

methods: {
    // 跟智能输入框同步选中的业务
    collectProvince(data) {
        console.log(data);
    }
}

暂时只支持这3个参数。

后续需要完善的功能:

  1. 支持自定义分割符,添加参数delimiter: '-'
  2. 支持数据校验(不合法的不允许输入),添加参数stric: true
  3. 完善接口文档和补充在线测试用例
查看原文

kfqweb 关注了用户 · 2018-03-21

wuwhs @wuwhs

Code for work, write for progress!

关注 1432

kfqweb 赞了回答 · 2018-03-21

解决flex 布局

把“去下单”再包一层flex容器,align-items:center"控制垂直居中

...

<view style="display:flex; align-items:center">
  <view style='background-color:#ff0000;' bindtap='placeAnOrder'>去下单</view>
</view>

flex布局参考:http://www.ruanyifeng.com/blo...

关注 4 回答 3

kfqweb 回答了问题 · 2018-03-21

解决flex 布局

line-height虽然能解决你的部分问题,但是他会局限你的适配方案,目前不知道你的适配方案,所以只能给出次回答。

关注 4 回答 3

认证与成就

  • 获得 2 次点赞
  • 获得 7 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 7 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-12-14
个人主页被 589 人浏览