用JS实现简单的省市联动

thbcm阅读(182)

用JS实现简单的省市联动

技术分析:

使用 javascript 实现城市二级联动(onchange() 当下拉框变化时触发事件 createTextNode() 方法和 createElement() 方法以及 appendChild() 方法)。

代码步骤:

1、首先确定事件为 onchange() 事件,并为该事件绑定一个函数。书写该函数。

2、创建一个二维数组。

3、在判断用户选择的是哪一个省份。

4、获取该省份下的城市。

5、创建文本节点和元素节点,将文本节点添加到元素节点中。

6、获取第二个下拉框元素并把元素节点添加到第二个下拉框中。7、清除第二个下拉框(如果不清除,就会有上一次的遗留)。效果图如下:

源代码如下:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>省市联动</title>
	</head>
	<body>
		<!--省份的下拉框-->
		<select name="province" id="province" onchange="changeCity()">   <!--使用改变下拉框onchange实现-->
		    <option value="0">请选择</option>
		    <option value="1">湖北省</option>
		    <option value="2">湖南省</option>
		    <option value="3">广东省</option>
		</select>
		<!--城市的下拉框-->
		<select name="city" id="city">
		    <option value="0">请选择</option>
		</select>
		
		<script>
		    // 创建一个二维的数组,用来存储各个省份对应的城市
		    var cities = new Array();
		    cities[0] = new Array("武汉市","黄冈市","荆州市","仙桃市","黄石市","宜昌市");
		    cities[1] = new Array("长沙市","湘潭市","株洲市","衡阳市","岳阳市","郴州市");
		    cities[2] = new Array("广州市","珠海市","深圳市","东莞市","佛山市","茂名市");
		
		    function changeCity() {
		        // 获取省份的下拉框
		        var pro = document.getElementById("province");
		        // 获取城市下拉框
		        var city = document.getElementById("city");
		        // 获取你点击的省的value
		        var val = pro.value;
		        val = val - 1;
		        // 清空城市下拉框里面的城市
		        city.options.length = 1;      //因为每次选新的省份,上次选的省份的城市也会出现,所以新添之前清空即可
		        // 遍历所有城市
		        for(var i=0;i<cities.length;i++){
		            // 匹配省份对应的城市
		            if(val == i){
		                // 遍历用户选择的省份的城市        <option value="3">广东省</option>这个里面的文本内容就称为文本节点。
		                for(var j=0;j<cities[i].length;j++){
		                    // 创建城市的文本节点
		                    var text = document.createTextNode(cities[i][j]);
		                    // 创建元素节点
		                    var opt = document.createElement("option");
		                    // 将城市的文本节点添加到option元素节点里面
		                    opt.appendChild(text) ;      //在option标签里添加一个子节点,子节点里放文本
		                    // 将添加了文本内容的option标签放在上面城市下拉框里面
		                    city.appendChild(opt);       //在城市下拉框里添加子节点,里面放option标签
		                }
		            }
		        }
		    }
		</script>
	</body>
</html>

推荐好课:JavaScript微课小白学前端:JavaScript零基础入门到进阶

JS数据结构栈内操作:处理十进制转二进制代码

thbcm阅读(188)

计算十进制转换为二进制的过程可以当成把每个计算后取余的数字压入栈内的操作

具体实现过程如下

//           十进制转二进制代码
function dec2bin(decNumber){

    //定义栈
var stack=new Stack()
    //将数字压入栈内
while(decNumber>0){
    // 1- 获取余数 将其压入栈内
    stack.push(decNumber%2)
    // 2- 获取整除后的结果 作为下一次取余的数字
    decNumber=Math.floor(decNumber/2)

}
//               从栈内取出

var result=''
while(!stack.isEmpty()){
    //将栈顶数字依次压入数组中 
result+=stack.pop()

}
//返回结果
return result

}
console.log(dec2bin(1000))
console.log(dec2bin(100))
console.log(dec2bin(10))

手写思路:

1、首先定义一个函数 并定义传入所需转换数字

2、使用栈结构(此处需提前封装好栈 功能 如 pop push)

3、循环判断(此处为将数字压入栈内操作) 数字是否大于0

  • 循环内部 :首先对传入的数字 取余(此处为十进制转二进制 需除2)然后 将数字取整后的结果更新 循环操作 直到数字小于等于0

4、首先定义一个空数组

5、循环判断(此处为将结果从栈内取出操作)栈内是否有元素

  • 循环内部 :空数组 加等 栈内每次取出的元素

下面是封装栈的代码 可省略

function Stack() {

    //栈 中的一些属性
    this.items = []
    //栈内操作

    //1.将元素压入栈
    Stack.prototype.push = function (element) {
        this.items.push(element)
    }

    //2.从栈中取出元素
    Stack.prototype.pop = function () {
        return this.items.pop()
    }

    //3.查看一下栈顶元素(不改变栈结构)
    Stack.prototype.peek = function () {
        return this.items[this.items.length - 1]
    }

    //4.判断栈是否为空
    Stack.prototype.isEmpty = function () {
        return this.items.length == 0
    }

    //5.获取栈中元素个数

    Stack.prototype.size = function () {
        return this.items.length
    }
 //6.toString方法
    Stack.prototype.toString = function () {
        var result = ''
        for (var i = 0; i < this.items.length; i++) {
            result += this.items[i]
        }
        return result
    }
}

cookie的介绍和使用

thbcm阅读(150)

一、概述

Cookie 是服务器保存在浏览器的一小段文本信息,一般大小不能超过4KB。浏览器每次向服务器发出请求,就会自动附上这段信息。

Cookie 主要保存状态信息,以下是一些主要用途。

  • 对话(session)管理:保存登录、购物车等需要记录的信息。
  • 个性化信息:保存用户的偏好,比如网页的字体大小、背景色等等。
  • 追踪用户:记录和分析用户行为。

Cookie 不是一种理想的客户端储存机制。它的容量很小(4KB),缺乏数据操作接口,而且会影响性能。客户端储存应该使用 Web storage API 和 IndexedDB。只有那些每次请求都需要让服务器知道的信息,才应该放在 Cookie 里面。

有关对Web storage的介绍,可以看这篇博客:sessionStorage 和 localStorage 的使用

每个 Cookie 都有以下几方面的元数据。

  • Cookie 的名字
  • Cookie 的值(真正的数据写在这里面)
  • 到期时间(超过这个时间会失效)
  • 所属域名(默认为当前域名)
  • 生效的路径(默认为当前网址)

举例来说,用户访问网址 ​www.example.com​,服务器在浏览器写入一个 Cookie。这个 Cookie 的所属域名为 ​www.example.com​,生效路径为根路径 ​/​。

如果 Cookie 的生效路径设为​ /user​,那么这个 Cookie 只有在访问​ www.example.com/use​r 及其子路径时才有效。以后,浏览器访问某个路径之前,就会找出对该域名和路径有效,并且还没有到期的 Cookie,一起发送给服务器。

用户可以设置浏览器不接受 Cookie,也可以设置不向服务器发送 Cookie。 window.navigator.cookieEnabled 属性返回一个布尔值,表示浏览器是否打开 Cookie 功能。

window.navigator.cookieEnabled // true

document.cookie​ 属性返回当前网页的 Cookie。

不同浏览器对 Cookie 数量和大小的限制,是不一样的。一般来说,单个域名设置的 Cookie 不应超过30个,每个 Cookie 的大小不能超过4KB。超过限制以后,Cookie 将被忽略,不会被设置。

两个网址只要域名相同,就可以共享 Cookie。 注意,这里不要求协议相同。也就是说,​http://example.com ​设置的 Cookie,可以被 ​https://example.com​ 读取。

二、Cookie 与 HTTP 协议

Cookie 由 HTTP 协议生成,也主要是供 HTTP 协议使用。

1、HTTP 回应:Cookie 的生成

服务器如果希望在浏览器保存 Cookie,就要在 HTTP 回应的头信息里面,放置一个 ​Set-Cookie​ 字段。

Set-Cookie:foo=bar

上面代码会在浏览器保存一个名为 foo 的 Cookie,它的值为 bar。

HTTP 回应可以包含多个 ​Set-Cookie​ 字段,即在浏览器生成多个 Cookie。

HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

除了 Cookie 的值,Set-Cookie字段还可以附加 Cookie 的属性。一个Set-Cookie字段里面,可以同时包括多个属性,没有次序的要求。

Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly

2、HTTP 请求:Cookie 的发送

浏览器向服务器发送 HTTP 请求时,每个请求都会带上相应的 Cookie。也就是说,把服务器早前保存在浏览器的这段信息,再发回服务器。这时要使用 HTTP 头信息的 ​Cookie​ 字段。

// 会向服务器发送名为 foo的 Cookie,值为 bar。
Cookie: foo=bar

// Cookie字段可以包含多个 Cookie,使用分号(;)分隔。
Cookie: name=value; name2=value2; name3=value3

下面是一个例子。

GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry

三、Cookie 的属性

1、Expires,Max-Age

Expires​ 属性指定一个具体的到期时间,到了指定时间以后,浏览器就不再保留这个 Cookie。它的值是 UTC 格式,可以使用​Date.prototype.toUTCString()​ 进行格式转换。

如果不设置该属性,或者设为null,Cookie 只在当前会话(session)有效,浏览器窗口一旦关闭,当前会话结束,该 Cookie 就会被删除。另外,浏览器根据本地时间,决定 Cookie 是否过期,由于本地时间是不精确的,所以没有办法保证 Cookie 一定会在服务器指定的时间过期。

Max-Age​ 属性指定从现在开始 Cookie 存在的秒数,比如 60 * 60 * 24 * 365(即一年)。过了这个时间以后,浏览器就不再保留这个 Cookie。

如果同时指定了 ​Expires​ 和 ​Max-Age​,那么​ Max-Age​ 的值将优先生效。

如果 ​Set-Cookie​ 字段没有指定​Expires​或​Max-Age​属性,那么这个 Cookie 就是 Session Cookie,即它只在本次对话存在,一旦用户关闭浏览器,浏览器就不会再保留这个 Cookie。

使用 Node 创建一个服务器来模拟演示:

const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
  console.log('request come', request.url)
  
  const html = fs.readFileSync('test.html', 'utf8')
  response.writeHead(200, {
    'Content-Type': 'text/html',
    'Set-Cookie': ['id=123;max-age=2', 'abc=456;HttpOnly']
  })
  response.end(html)

}).listen(8888)

console.log('http://127.0.0.1:8888')

2、Domain,Path

Domain​属性指定浏览器发出 HTTP 请求时,哪些域名要附带这个 Cookie。

  • 如果没有指定该属性,浏览器会默认将其设为当前域名,这时子域名将不会附带这个 Cookie。比如,​example.com​ 不设置 Cookie 的domain属性,那么 sub.example.com 将不会附带这个 Cookie。
  • 如果指定了domain属性,那么子域名也会附带这个 Cookie。如果服务器指定的域名不属于当前域名,浏览器会拒绝这个 Cookie。
  • 一句话总结:Domain标识指定了哪些主机可以接受Cookie。如果不指定,默认为当前主机(不包含子域名)。如果指定了Domain ,则一般包含子域名。

Path​属性指定浏览器发出 HTTP 请求时,哪些路径要附带这个 Cookie。只要浏览器发现,Path属性是 HTTP 请求路径的开头一部分,就会在头信息里面带上这个 Cookie。比如,PATH属性是 ​/​,那么请求 ​/docs ​路径也会包含该 Cookie。当然,前提是域名必须一致。

3、Secure,HttpOnly

Secure ​属性指定浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器。另一方面,如果当前协议是 HTTP,浏览器会自动忽略服务器发来的Secure属性。该属性只是一个开关,不需要指定值。如果通信是 HTTPS 协议,该开关自动打开。

HttpOnly​ 属性指定该 Cookie 无法通过 JavaScript 脚本拿到,主要是 ​document.cookie​ 属性、​XMLHttpRequest​ 对象和 ​Request API​ 都拿不到该属性。这样就防止了该 Cookie 被脚本读到,只有浏览器发出 HTTP 请求时,才会带上该 Cookie。出于安全考虑。

四、document.cookie

document.cookie​ 属性用于读写当前网页的 Cookie。读取的时候,它会返回当前网页的所有 Cookie,前提是该 Cookie 不能有HTTPOnly属性。

document.cookie // "foo=bar;baz=bar"

上面代码从​ document.cookie ​一次性读出两个 Cookie,它们之间使用分号分隔。必须手动还原,才能取出每一个 Cookie 的值。这就是cookie存取数据不方便的地方,它没有完善的存取数据的api让我们用,我们必须手动的从中提取自己需要的数据。

document.cookie​ 属性是可写的,可以通过它为当前网站添加 Cookie。写入的时候,Cookie 的值必须写成​key=value​的形式。注意,等号两边不能有空格。​ document.cookie​一次只能写入一个 Cookie,而且写入并不是覆盖,而是添加。

document.cookie = 'fontSize=14';

// 最终只会有 test1=456 被写进去
document.cookie = 'test1=456;hahah=123'

document.cookie ​读写行为的差异(一次可以读出全部 Cookie,但是只能写入一个 Cookie),与 HTTP 协议的 Cookie 通信格式有关。

  • 浏览器向服务器发送 Cookie 的时候,Cookie字段是使用一行将所有 Cookie 全部发送;
  • 服务器向浏览器设置 Cookie 的时候,​Set-Cookie​字段是一行设置一个 Cookie。

删除一个现存 Cookie 的唯一方法,是设置它的​ expires​ 属性为一个过去的日期。

document.cookie = 'fontSize=;expires=Thu, 01-Jan-1970 00:00:01 GMT';

五、参考资料

HTTP cookies 详解

在html中如何修改无序列表ul和li的格式呢?

thbcm阅读(171)

前端开发时常会用到无序列表(​ul​ ​li​) 来进行数据的展示等,这里记录一些常见的样式修改

1、去掉默认情况下前面的圆点

ul{list-style:none;}


2、横向展示排列

li{float: left;}


3、修改为圆点之外的样式

这里指定的是空心圆

li{
    /*float: left;*/
    list-style-type: circle;
}

下面给出其他类型:

描述
circle 空心圆
none 不使用
disc 默认,实心圆
square 实心方块
decimal 阿拉伯数字
lower-roman 小写罗马数字
upper-alpha 大写英文字母

4、更改圆点样式为图片

li{
    list-style-type:none;
    list-style-image: url(url);
}

CSS写法:

HTML写法:

点击 CSS无序列表在线实例 在线实操一下吧~

HTTP的两种常用请求方式GET和POST

thbcm阅读(154)

HTTP的两种常用请求方式GET和POST

HTTP:HTTP协议(HyperText Transfer),用于从万维网(WWWW:World Wide Web)服务器传输超文本到本地浏览器的传输协议。HTTP是一个基于 TCP/IP 通信协议来传递数据(HTML文件,图片文件,查询结果等)。

一、GET方法

使用 GET 方法时,查询字符串(名称或键值对)是在 GET 请求的 URL 中发送的:

/test/demo_form.php?name1=value1&name2=value2

说明

  • POST 请求不会被缓存
  • POST 请求不会保留在浏览器历史记录中
  • POST请求的URL不能被收藏为书签
  • POST 请求没有长度要求

HTTP的方法中POST向后台传输数据比较可靠,POST 方法打包信息的方式与 GET 方法基本相同,但是 POST 方法不是把信息作为 URL 中 ? 字符后的文本字符串进行发送,而是把这些信息作为一个单独的消息进行发送。Servlet 使用 doPost() 方法处理这种类型的请求。

三、GET方法与POST方法的区别

  • 在浏览器进行回退操作时,GET请求是无害的,而POST请求则会重新请求一次
  • GET请求参数是连接在URL后面的,而POST请求参数是存放在消息主体(Requestbody)内
  • GET请求因为浏览器对url长度有限制(不同浏览器长度限制不一样)对传参数量有限制,而post请求因为参数存放Requestbody内所以参数数量没有限制
  • 因为GET请求参数暴露在URL上,所以安全方面POST比GET更加安全
  • GET请求浏览器会主动缓存(Cache),POST并不会,除非主动设置
  • GET请求参数会保存在浏览器历史记录内,POST请求并不会
  • GET请求只能进行URL编码,而POST请求可以支持多种编码方式
  • GET请求产生1个Tcp数据包,POST请求产生2个Tcp数据包
  • 浏览器在发送GET请求时会将请求头(Header)和数据(Data)一起发送给服务器,服务器返回200状态码,而在发送POST请求时,会先将Header发送给服务器,服务器返回100,之后再将Data发送给服务器,服务器返回200

说明:

  • GET 请求可被缓存
  • GET 请求保留在浏览器历史记录中
  • GET 请求的URL可被收藏为书签
  • GET 请求不应在处理敏感数据时使用
  • GET 请求有长度限制
  • GET 请求只应当用于获取数据

GET 方法是默认的从浏览器向 Web 服务器传递信息的请求方法,它会产生一个很长的字符串,出现在浏览器的地址栏中。GET 方法有大小限制:请求字符串中最多只能有 1024 个字符。Servlet 

二、POST方法

使用POST方法时,查询字符串(名称或键值对)是在POST请求的HTTP消息主体重发送的:使用 doGet()方法处理这种类型的请求。

POST /test/demo_form.php HTTP/1.1
Host: runoob.com
name1=value1&name2=value2

说明

  • POST 请求不会被缓存
  • POST 请求不会保留在浏览器历史记录中
  • POST请求的URL不能被收藏为书签
  • POST 请求没有长度要求

HTTP的方法中POST向后台传输数据比较可靠,POST 方法打包信息的方式与 GET 方法基本相同,但是 POST 方法不是把信息作为 URL 中 ? 字符后的文本字符串进行发送,而是把这些信息作为一个单独的消息进行发送。Servlet 使用 doPost() 方法处理这种类型的请求。

三、GET方法与POST方法的区别

  • 在浏览器进行回退操作时,GET 请求是无害的,而 POST 请求则会重新请求一次
  • GET 请求参数是连接在 URL 后面的,而POST请求参数是存放在消息主体(Requestbody)内
  • GET 请求因为浏览器对 url 长度有限制(不同浏览器长度限制不一样)对传参数量有限制,而 post 请求因为参数存放 Requestbody 内所以参数数量没有限制 
  • 因为 GET 请求参数暴露在URL上,所以安全方面 POST 比 GET 更加安全
  • GET 请求浏览器会主动缓存(Cache),POST 并不会,除非主动设置
  • GET 请求参数会保存在浏览器历史记录内,POST 请求并不会
  • GET 请求只能进行 URL 编码,而 POST 请求可以支持多种编码方式
  • GET 请求产生1个 Tcp 数据包,POST 请求产生2个 Tcp 数据包
  • 浏览器在发送 GET 请求时会将请求头(Header)和数据(Data)一起发送给服务器,服务器返回200状态码,而在发送 POST 请求时,会先将 Header 发送给服务器,服务器返回100,之后再将 Data 发送给服务器,服务器返回200

推荐好课:HTML微课HTML+CSS基础实战HTML5新特性实战

Vue 中子组件访问父组件数据

thbcm阅读(156)

props

官方解释:所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

我们可以这样理解,当父级组件的数据发生改变的时候,子级组件接受的数据也会自动发生改变,但子级组件改变的数据不能对父级数据进行影响这也就是单向下行绑定。

但子组件不能直接引用父组件中的数据。我们需要进行引用。

子组件对父组件的数据引用

我们以两个 vue 界面为例

父组件为 HomeComponent,子组件为 TopArticles。

HomeComponent.vue

<script>
export default {
  name: "HomeComponents",
  components: {TopCoders, TopArticles, ComingCompetitions, TopNews},
  data() {
    return {
      topArticle:[
        {
          title:'title1',
          url:'url1',
          author:'author1'
        },
          {
          title:'title2',
          url:'url2',
          author:'author2'
        }
      ],
    }
  }
}
</script>

HomeComponent 在引用子组件的时候需要向子组件传递绑定数据。即 :top-articles=“topArticle”

HomeComponent.vue

<template>
  <div style="width: 100%;min-width: 1200px;">
        <top-articles class="articles" :top-articles="topArticle"></top-articles>
  </div>
</template>

data 中的 topArticle 为 topArticle 界面中需要引用的父级组件的数据。

指定数据的类型

topArticles.vue

<script>
export default {
  name: "topArticle",
  props: {
    topArticles: {
      // 指定类型
      Type: Array,
      required: true
    },
  },
}
</script>

数据展示

topArticles.vue

<template>
  <div>
    <sui-list>
      <sui-list-item v-for="(item, key) in topArticles">
        <span style="font-size: 18px">{{item.title}}</span>
        <span style="font-size: 18px">{{item.author}}</span>
      </sui-list-item>
    </sui-list>
  </div>
</template>

效果展示

推荐课程:Vue2.0微课vue.js1.0教程

文字阴影(凹凸)css实现

thbcm阅读(142)

文字阴影(凹凸)效果:

代码内容

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>w3cschool</title>

<style type="text/css">

body{

background-color: #ccc;

}

div{

font: 700 80px "微软雅黑";

color: #ccc;

}

div:first-child{

/* text-shadow: 水平位置 垂直位置 模糊距离 阴影颜色 */

text-shadow: 1px 1px 1px #000,-1px -1px 1px #fff;

}

div:last-child{

/* text-shadow: 水平位置 垂直位置 模糊距离 阴影颜色 */

text-shadow: -1px -1px 1px #000,1px 1px 1px #fff;

}

</style>

</head>

<body>

<div>我是凸起的文字</div>

<div>我是凹下的文字</div>

</body>

</html>

推荐好课:CSS微课HTML+CSS进阶实战

彻底解决小程序无法触发SESSION问题

thbcm阅读(254)

一、首先找到第一次发起网络请求的地址,将服务器返回​set-cookie​当全局变量存储起来

wx.request({
  ......
  success: function(res) {
    console.log(res.header);
    //set-cookie:PHPSESSID=ic4vj84aaavqgb800k82etisu0; path=/; domain=.fengkui.net

    // 登录成功,获取第一次的sessionid,存储起来
    // 注意:Set-Cookie(开发者工具中调试全部小写)(远程调试和线上首字母大写)
    wx.setStorageSync("sessionid", res.header["Set-Cookie"]);
  }
})

二、请求时带上将​ sessionid​ 放入 ​request​ 的 ​header ​头中传到服务器,服务器端可直接在 ​cookie ​中获取

wx.request({
  ......
  header: {
    'content-type': 'application/json', // 默认值
    'cookie': wx.getStorageSync("sessionid")
    //读取sessionid,当作cookie传入后台将PHPSESSID做session_id使用
  },
  success: function(res) {
    console.log(res)
  }
})

三、后台获取 ​cookie​ 中的 ​PHPSESSID​,将 ​PHPSESSID ​当作​ session_id ​使用

<?php
// 判断$_COOKIE['PHPSESSID']是否存在,存在则作session_id使用
if ($_COOKIE['PHPSESSID']) {
    session_id($_COOKIE['PHPSESSID']);
}

session_start();
echo session_id();

推荐好课:微信小程序微课微信小程序商城-3天学会界面设计

JavaScript原型与继承的秘密

thbcm阅读(216)

JavaScript 的原型与继承是每一个学习 JavaScript 的同学都会面对的一个问题,也是很多面试的必考题目; 但是经常会有一些同学对此一知半解,或者是浅尝辄止;这是因为很多讲解原型与继承的文章写的不是那么通俗易懂, 而本文的目的就是一次性的帮助大家把这一系列的知识点梳理清楚;希望我这次能够做一个好的投球手。

首先我们需要知道的是,JavaScript 是一种动态语言,本质上说它是没有​Class​(类)的;但是它也需要一种继承的方式, 那就是原型继承;JavaScript 对象的一些属性和方法都是继承自别的对象。

很多同学对 JavaScript 的原型和继承不是很理解,一个重要的原因就是大家没有理解​__proto__​和​prototype​这两个属性的意思。 接下来我们先来好好梳理一下这两个属性,看看它们存在哪里,代表了什么意义,又有什么作用。

首先来说一下​__proto__​这个属性吧,我们需要知道的是,除了​null​和​undefined​,​JavaScript​中的所有数据类型都有这个属性; 它表示的意义是:当我们访问一个对象的某个属性的时候,如果这个对象自身不存在这个属性, 那么就从这个对象的​__proto__​(为了方便下面描述,这里暂且把这个属性称作​p0​)属性上面 继续查找这个属性,如果p0上面还存在​__proto__​(p1)属性的话,那么就会继续在p1上面查找响应的属性, 直到查找到这个属性,或者没有​__proto__​属性为止。 我们可以用下面这两幅图来表示:

上面这幅图表示在obj原型链上面找到了属性名字是a的值

上面这幅图表示在obj的原型链上面没有找到属性名字是 a 的值

我们把一个对象的​__proto__​属性所指向的对象,叫做这个对象的原型;我们可以修改一个对象的原型来让这个对象拥有某种属性,或者某个方法。

// 修改一个Number类型的值的原型
const num = 1;
num.__proto__.name = "My name is 1";
console.log(num.name); // My name is 1

// 修改一个对象的原型
const obj = {};
obj.__proto__.name = "dreamapple";
console.log(obj.name); // dreamapple

这里需要特别注意的是,​__proto__​这个属性虽然被大多数的浏览器支持,但是其实它仅在​ECMAScript 2015 规范​中被准确的定义, 目的是为了给这个传统的功能定制一个标准,以确保浏览器之间的兼容性。通过使用​__proto__​属性来修改一个对象的原型是非常慢且影响性能的一种操作。 所以,现在如果我们想要获取一个对象的原型,推荐使用​Object.getPrototypeOf ​或者​Reflect.getPrototypeOf​,设置一个对象的原型推荐使用​Object.setPrototypeOf​或者是​Reflect.setPrototypeOf​。

到这里为止,我们来对​__proto__​属性做一个总结:

  • 存在哪里? 除了​null​和​undefined​所有其他的JavaScript对象或者原始类型都有这个属性
  • 代表了什么? 表示了一个对象的原型
  • 有什么作用? 可以获取和修改一个对象的原型

说完​__proto__​属性,接下来我们就要好好的来理解一下​prototype​属性了;首先我们需要记住的是,这个属性一般只存在于函数对象上面; 只要是能够作为构造器的函数,他们都包含这个属性。也就是说,只要这个函数能够通过使用​new​操作符来生成一个新的对象, 那么这个函数肯定具有​prototype​属性。因为我们自定义的函数都可以通过​new​操作符生成一个对象,所以我们自定义的函数都有​prototype​ 这个属性。

// 函数字面量
console.log((function(){}).prototype); // {constructor: ƒ}

// Date构造器
console.log(Date.prototype); // {constructor: ƒ, toString: ƒ, toDateString: ƒ, toTimeString: ƒ, toISOString: ƒ, …}

// Math.abs 不是构造器,不能通过new操作符生成一个新的对象,所以不含有prototype属性
console.log(Math.abs.prototype); // undefined

那这个prototype属性有什么作用呢?这个prototype属性的作用就是:函数通过使用new操作符生成的一个对象, 这个对象的原型(也就是__proto__)指向该函数的prototype属性。 那么一个比较简洁的表示__proto__prototype 属性之间关系的等式也就出来了,如下所示:

// 其中F表示一个自定义的函数或者是含有prototype属性的内置函数
new F().__proto__ === F.prototype // true

我们可以使用下面这张图来更加形象的表示上面这种关系:

看到上面等式,我想大家对于​__proto__​和​prototype​之间关系的理解应该会更深一层了。

好,接下来我们对​prototype​属性也做一个总结:

  • 存在哪里? 自定义的函数,或者能够通过​new​操作符生成一个对象的内置函数
  • 代表了什么? 它表示了某个函数通过​new​操作符生成的对象的原型
  • 有什么作用? 可以让一个函数通过​new​操作符生成的许多对象共享一些方法和属性

其实到这里为止,关于JavaScript的原型和继承已经讲得差不多了;下面的内容是一些基于上面的一些拓展, 可以让你更好地理解我们上面所说的。

当我们理解了上面的知识点之后,我们就可以对下面的表达式做一个判断了:

// 因为Object是一个函数,函数的构造器都是Function
Object.__proto__ === Function.prototype // true

// 通过函数字面量定义的函数的__proto__属性都指向Function.prototype
(function(){}).__proto__ === Function.prototype // true

// 通过对象字面量定义的对象的__proto__属性都是指向Object.prototype
({}).__proto__ === Object.prototype // true

// Object函数的原型的__proto__属性指向null
Object.prototype.__proto__ === null // true

// 因为Function本身也是一个函数,所以Function函数的__proto__属性指向它自身的prototype
Function.__proto__ === Function.prototype // true

// 因为Function的prototype是一个对象,所以Function.prototype的__proto__属性指向Object.prototype
Function.prototype.__proto__ === Object.prototype // true

如果你能够把上面的表达式都梳理清楚的话,那么说明你对这部分知识掌握的还是不错的。

谈及JavaScript的原型和继承,那么我们还需要知道另一个概念;那就是​constructor​,那什么是​constructor​呢? ​constructor​表示一个对象的构造函数,除了​null​和​undefined​以外,JavaScript中的所有数据类型都有这个属性; 我们可以通过下面的代码来验证一下:

null.constructor // Uncaught TypeError: Cannot read property 'constructor' of null ...
undefined.constructor // Uncaught TypeError: Cannot read property 'constructor' of undefined ...

(true).constructor // ƒ Boolean() { [native code] }
(1).constructor // ƒ Number() { [native code] }
"hello".constructor // ƒ String() { [native code] }

我们还可以使用下面的图来更加具体的表现:

但是其实上面这张图的表示并不算准确,因为一个对象的constructor属性确切地说并不是存在这个对象上面的; 而是存在这个对象的原型上面的(如果是多级继承需要手动修改原型的constructor属性,见文章末尾的代码),我们可以使用下面的代码来解释一下:

const F = function() {};
// 当我们定义一个函数的时候,这个函数的prototype属性上面的constructor属性指向自己本身
F.prototype.constructor === F; // true

下面的图片形象的展示了上面的代码所表示的内容:

关于constructor还有一些需要注意的问题,对与JavaScript的原始类型来说,它们的constructor属性是只读的,不可以修改。 我们可以通过下面的代码来验证一下:

(1).constructor = "something";
console.log((1).constructor); // 输出 ƒ Number() { [native code] }

当然,如果你真的想更改这些原始类型的constructor属性的话,也不是不可以,你可以通过下面的方式来进行修改:

Number.prototype.constructor = "number constructor";
(1).constructor = 1;
console.log((1).constructor); // 输出 number constructor

当然上面的方式我们是不推荐你在真实的开发中去使用的,接下来,我会使用一些代码来把今天讲解的知识再大致的回顾一下:

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

Animal.prototype.setName = function(name) {
  this.name = name;
};
Animal.prototype.getName = function(name) {
  return this.name;
};

function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);

// 因为上面的语句将我们原来的prototype的指向修改了,所以我们要重新定义Dog的prototype属性的constructor属性
Reflect.defineProperty(Dog.prototype, "constructor", {
  value: Dog,
  enumerable: false, // 不可枚举
  writable: true
});

const animal = new Animal("potato");
console.log(animal.__proto__ === Animal.prototype); // true
console.log(animal.constructor === Animal); // true
console.log(animal.name); // potato

const dog = new Dog("potato", "labrador");
console.log(dog.name); // potato
console.log(dog.breed); // labrador
console.log(dog.__proto__ === Dog.prototype); // true
console.log(dog.constructor === Dog); // true

推荐好课:JavaScript微课ES6微课

event loop的事件循环

thbcm阅读(182)

了解 event loop,我们先了解以下3点

①js 是单线程

②异步基于回调实现

③event loop 是异步回调的实现原理

我们来看个图

当检测到是同步任务时,该任务会直接移到调用栈里面再传给浏览器

调用栈-浏览器

当检测到是异步任务时,该任务会被放置到 webAPI 中即等待队列,当同步任务执行完,调用栈为空的时候,且该异步任务执行时机到了的时候,异步任务会被移到到回调队列

这时候 event loop 就会去回调队列找,如果回调队列有事件,event loop 会获取他并把他转移到调用栈再转移到浏览器

等待队列-同步任务执行完毕,调用栈为空,异步任务执行时机到了-回调队列-event loop 获取-调用栈-浏览器

 

这样说可能有点抽象,我们直接上例子

这个我们都知道输出答案为 a,c,b 具体我们来看看

从上往下执行先是 console.log(‘a’),该任务为同步任务,所以直接放置调用栈,再输出到浏览器,调用栈再清空

然后到定时器,这是个异步任务

先放在等待队列 WebAPI

当满足以下3个条件,异步任务转移到回调队列

①同步任务执行完毕

②调用栈清空

③异步任务到达时机可执行

当异步任务定时器3s到的时候即到时机执行,异步任务转移到回调队列

同步任务执行完后,event loop 开始运作,去回调队列找事件任务,找到后将其转移到调用栈

最后再将其传到浏览器,这就是 event loop 实现机制,也印证了上面的那句话,异步回调的原理是 event loop

联系我们