gRpc-web在项目中的落地使用

gRPC概念


gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.

业务背景

后端项目繁多,算法部门要将旧有项目进行gRPC化,使用gRPC能够高效的在服务端间进行通信,所以,为了配合后端的gRPC更改,前端也使用gRPC-web,进行开发,与后端进行请求。 第一次接到这个需求,便开始了大量的gRPC的文档探索,以及将github的两个gRPC-web项目进行clone,然后深读代码,并进行尝试。项目使用react+antd+typescript全新搭建,按着文档进行依赖下载,也安装了go,本着自己完全跑一遍的心态试了一次,发现,这个项目过于久老,有个go的依赖一直出错。所以放弃了对这个本地调试,跑一遍demo的心态。 后端大哥本身是开发Go的,所以,他电脑装的比较齐全,提前踩好坑进行开发了,定义了一堆PB文件,然后开始发给我生成好的对应的ts以及js文件,这就让我不用再去纠结使用grpc github的仓库进行启动,并去自己操作一遍。   然后,开始开心的前端踩坑时刻,文档估计很久没更新了,使用的grpc-web库是0.4.0的版本,以下是我从官网拉过来的依赖文件。

1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "grpc-web-commonjs-example",
"dependencies": {
"google-protobuf": "^3.6.1",
"grpc-web": "^0.4.0"
},
"devDependencies": {
"browserify": "^16.2.2",
"webpack": "^4.16.5",
"webpack-cli": "^3.1.0"
}
}

前面也说了,这个文档大概很久没更新了,所以我安装后调用grpc的时候一直报错,百思不得其姐,只能在issue中查找对应的信息,这方面,国内项目看起来几乎没人使用,都是外国大哥们在使用,所以找的也是比较费力,总算是在茫茫issue中一眼看中了一个解答,那就是使用新的grpc-web库进行处理,这让茫然的我看到了一丝丝希望,果断将项目中的grpc-web库更新到了1.0.0

1
2
3
4
5
6
{
"dependencies": {
"google-protobuf": "^3.6.1",
"grpc-web": "^1.0.0"
}
}

终于不负我所寄托,这次终于是接通了,让人欣喜。

grpc-web的使用

使用方法主要是针对PB文件展开,后端生成的PB文件包含:

  1. 定义方法,属性的PB文件
  2. 定义请求client请求的PB文件

所以封装请求,主要是依靠将定义请求的PB文件进行导入,并按需处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 这是生成的client请求PB文件
import * as grpcWeb from 'grpc-web';
import * as common_pb from './common_pb';
import { UserLogin } from './bisys_pb';
// 请求实体
export class UserBehaviorClient {
constructor (hostname: string,
credentials?: null { [index: string]: string; },
options?: null { [index: string]: string; });

login(
request: UserLogin, // 请求参为UserLogin
metadata: grpcWeb.Metadata undefined,
callback: (err: grpcWeb.Error,
response: RespLoginedMsg) =>; void
): grpcWeb.ClientReadableStream;
}


//?这是定义请求参的PB文件,请求参是 UserLogin 对象
import * as jspb from "google-protobuf"
import * as common_pb from './common_pb';

export class UserLogin extends jspb.Message {
getAccount(): string;
setAccount(value: string): UserLogin;

getPassword(): string;
setPassword(value: string): UserLogin;

getToken(): string;
setToken(value: string): UserLogin;

serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): UserLogin.AsObject;
static toObject(includeInstance: boolean, msg: UserLogin): UserLogin.AsObject;
static serializeBinaryToWriter(message: UserLogin, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): UserLogin;
static deserializeBinaryFromReader(message: UserLogin, reader: jspb.BinaryReader): UserLogin;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 这是自己封装的service
import { baseUrl } from '@/config/base';
import { UserLogin } from '@/_proto/login/bisys_pb';
import { UserBehaviorPromiseClient } from '@/_proto/login/bisys_grpc_web_pb';

// 自己定义的interface
export interface LoginParamsType {
userName: string;
password: string;
token: string;
}
export async function fakeAccountLogin(params: LoginParamsType) {
// 首先引入对应的client实例化
const userbehavior: UserBehaviorPromiseClient = new UserBehaviorPromiseClient(baseUrl);
// 对请求参进行实例化
const uselogin: UserLogin = new UserLogin();
// 请求参的值赋值方式,写过java的 肯定很熟悉,这是对象的set操作
// 看到这里,是不是想说取值也是get操作?是的!
uselogin.setAccount(params.userName);
uselogin.setPassword(params.password);
uselogin.setToken(params.token);
return userbehavior.login(uselogin);
}


//?使用该请求的地方?上面service已经对登录方法进行封装?所以在使用接口的地方直接引入
import?{ fakeAccountLogin }?from?'@/service/login';
import { RespLoginedMsg } from '@/_proto/login/bisys_pb';

const response: RespLoginedMsg = fakeAccountLogin();
// 到这里,基本上已经能够调通了,取值上面也说了,是get操作,但是grpc的数据流是二进制的,
// 所以虽然使用get操作能够取到对应的值,但是并不是我们常见的json格式,
// 这里就需要用到PB文件中定义的toObject()方法 这是你看到的就是平时请求返回的json格式
// 到这里,基本grpc你已经可以使用了 并且个人感觉很舒服,毕竟和TS搭配,
// 所以,这样写也避免了前端很多时候会传值出问题的情况,个人比较喜欢
console.log('response ===>', response.toObject())

gRPC的部署

部署这个grpc时的Nginx配置 envoy、grpcproxy代理

阅读更多

玩转小程序云开发

前言

好久没写小程序的文章了,最近小程序变动也比较大。最主要的是我之前也曾写过个人如果开发小程序,可以使用Bmob免费的服务去开发自己的一套小程序,Bmob提供了免费的云函数以及数据库(20G空间)。But!!!微信小程序推出了自己的云开发服务,虽然数据库只有5G的空间,但是对于个人开发基本上足够使用,20支云函数接口以及云存储(相当于OSS服务器一样存放图片json文件等),以及用户管理。


1. 先来玩玩云函数

云函数是什么呢?估计很多玩过阿里云以及腾讯云的人已经对这个名词早已很熟悉。一种无需服务器的执行环境,平台会给出它支持的语言以及核心的代码,即可以在服务商提供的设施上弹性、安全的运行。这个很大程度上省却了很多开发部署工作,用过的都说好(假的)。 小程序内部提供了专门用于云函数调用的API,而我们只需要在云函数内将每次获取到的appId以及openId不再需要后台经过一系列鉴定加密就可以获取到可信任的用户登录状态!!!这个是最主要的,再之前利用Bmob也好,自己开发也好,这个是必备的,具体前往微信登录流程查看。现在利用小程序本身的云函数,省去了大量工作,真的是个人开发者的福音。 废话这么多,赶紧开始第一个云函数的添加以及调用吧 —->


  配置云开发: 第一步:?project.config.json?中进行添加字段?”cloudfunctionRoot”: “cloudfunctions/“??这个字段主要是为了指定存放云函数的文件夹?”cloudfunctions/“? 第二步:?app.json?中添加?”cloud”: true?指定是云开发模式 第三步: 添加云函数 第四步:添加对应的函数后,会下载依赖,这一步玩过node及npm的都知道,就是下载依赖,现在下载的是微信小程序官方的sdk。。。 http://https://img2018.cnblogs.com/blog/1333487/201809/1333487-20180928231832055-537545509.png 等下载完成后,点开云开发控制台的云函数一栏就会发现函数名称已经有了test这个云函数了。。。具体看下图所示: 这个test云函数就是我们刚刚添加的云函数,此时在开发者工具中打开test目录下的index.js文件,就可以进行返回我们需要返回的内容。。。如下:测试 这里如果是利用了获取授权的按钮之后就会在return中返回用户登录后的openId和appId,所以我们就return默认的event对象以及context(上下文)对象。主要是为了看看小程序默认提供的对象包含了什么,我们可以用什么??? 保存完了,还不够因为这个云函数我们都说了是小程序提供的服务平台来运行我们做的类似于接口的函数,所以我们必须得上传,具体看下图: 等待上传完毕,在pages下index.wxml中需要做的是

由于获取授权需要配合点击后js的操作,所以在index.js中添加对应的getUserInfo的方法,并且注意这里的wx.cloud.callFunction方法,因为这是调用云函数的方法。不可忽视~~~

复制代码

getUserInfo: function(e){
wx.cloud.callFunction({
name: “test”,//这里填写云函数的名字
data: {
userInfo: e // 这里是把参数e直接传给test函数处理
},
success: res => {
console.log(res) // 返回的文本如下图所示:
},
fail: err => {
console.log(err)
}
})
}

复制代码

可以看到输出的信息中会返回用户的openId以及appId还有对应云函数认证的信息。。。具体的实际可以自行测试查看,这里不做进一步探测… 这里云函数已经相信大家会玩了。。。就不再做其他的一一测试以及讲解。


2. 数据库

对于数据库我相信大家都早已用的不能再熟悉,mySql, SQLServer, Oracle, MongoDb, indexDb等等。。。。接下来带着大家玩玩这个微信云开发提供的数据库。。。 云开发提供的应该是类似于NoSql类型的数据库,存储的是JSON对象。一个数据库也是多个集合(相思雨MongoDb) 接下来让我们开始快乐的使用这个数据库吧~~~

复制代码

//首先 要实例一个数据库对象

let db = wx.cloud.database();
db.collection(‘counters’).add({
data: {
count: 1
},
success: res => {
// 在返回结果中会包含新创建的记录的 _id
this.setData({
counterId: res._id,
count: 1
})
wx.showToast({
title: ‘新增记录成功’,
})
console.log(‘[数据库] [新增记录] 成功,记录 _id: ‘, res._id)
},
fail: err => {
wx.showToast({
icon: ‘none’,
title: ‘新增记录失败’
})
console.error(‘[数据库] [新增记录] 失败:’, err)
}
})

复制代码

这是利用添加数据的方法进行添加的数据,此时可以在云开发控制台看到对应得数据:下图—》 此时可以看到插入进去的数据。。。接下来可以看看增删改查的其余操作:

复制代码

//查询数据:
const db = wx.cloud.database()
// 查询当前用户所有的 counters
db.collection(‘counters’).where({
_openid: this.data.openid
}).get({
success: res => {
this.setData({
queryResult: JSON.stringify(res.data, null, 2)
})
console.log(‘[数据库] [查询记录] 成功: ‘, res)
},
fail: err => {
wx.showToast({
icon: ‘none’,
title: ‘查询记录失败’
})
console.error(‘[数据库] [查询记录] 失败:’, err)
}
})

// 修改数据
const db = wx.cloud.database()
const newCount = this.data.count + 1
db.collection(‘counters’).doc(this.data.counterId).update({
data: {
count: newCount
},
success: res => {
this.setData({
count: newCount
})
},
fail: err => {
icon: ‘none’,
console.error(‘[数据库] [更新记录] 失败:’, err)
}
})

// 删除数据
const db = wx.cloud.database()
db.collection(‘counters’).doc(this.data.counterId).remove({
success: res => {
wx.showToast({
title: ‘删除成功’,
})
this.setData({
counterId: ‘’,
count: null,
})
},
fail: err => {
wx.showToast({
icon: ‘none’,
title: ‘删除失败’,
})
console.error(‘[数据库] [删除记录] 失败:’, err)
}
})

复制代码

当然,在数据库操作之前需要利用云函数login获取对应的openId, 获取方法请细看上方云函数test部分。。。


 

3. 存储部分

小程序只有2M的空间,个人开发要么将图片放在这小小的2M内,要么自己配置https证书的域名,再要么利用github将自己的域名免费转为https,不会玩的可以私信我,我可以再单独写一篇将自己的域名利用github穿https证书的方法,哈哈(略过)。将图片放github不用自己的域名也行,但是利用github就要忍受它的网速慢以及随时被墙的可能性。。。 现在我们来用云开发提供的存储:

复制代码

wx.chooseImage({
count: 1,
sizeType: [‘compressed’],
sourceType: [‘album’, ‘camera’],
success: function (res) {

    wx.showLoading({
      title: '上传中',
    })

    const filePath = res.tempFilePaths\[0\]
    
    // 上传图片
    const cloudPath = 'my-image' + filePath.match(/\\.\[^.\]+?$/)\[0\]
    wx.cloud.uploadFile({
      cloudPath,
      filePath,
      success: res => {
        console.log('\[上传文件\] 成功:', res)

        let fileId = res.fileID
        let cloudPath = cloudPath
        let imagePath = filePath
        
        console.log( fileId )
        console.log( cloudPath )
       console.log( imagePath )
      },
      fail: e => {
        console.error('\[上传文件\] 失败:', e)
        wx.showToast({
          icon: 'none',
          title: '上传失败',
        })
      },
      complete: () => {
        wx.hideLoading()
      }
    })

  },
  fail: e => {
    console.error(e)
  }
})

复制代码

代码已经奉上,上传成功后再跑去云开发控制台查看一番~~~ 好了,云开发已经基本上完成,当然云存储也可以存储json文件,然后请求静态数据~~~看自己的需要了。 云开发是将前端开发者摆到了更重要的地位,不再很依赖于服务端。这真的是一个福利~~~~ OVER~~SLEEP!!!

H5中的桌面通知Notification

H5中的桌面通知Notification

前言:?对于一个前段开发者,逛网页总会留意一些新奇的功能,对于上班总会用到Teambition的我,总是能收到Notification…所以今天就来研究下这个H5功能…


  1. 实例一个Notification

复制代码

let n = new Notification(
  “这是一个通知消息”, //这是必选Title 一定会显示的通知标题
  {
   icon: “xxx.png”, // 这个icon是用来显示通知中的左边图片
   body: “你好啊,我是xxx”,   // 通知中的内容字符
    dir: auto,             // 文字的方向 值包含: auto(自动), ltr(从左到右), rtl(从右向左)
    tag: “gxlself”          // 给与这个通知消息一个ID, 用来对这个通知消息进行 刷新.移除.替换 等操作
    // long: “en-US”               //lang字段 需要参考https://tools.ietf.org/html/bcp47 并不是必须 没看出啥作用…
    // …            // 其余可选属性见下实例属性
  }
)
访问对应的实例属性:
n.actions // 一个只读的NotificationAction对象数组。每一个对象描述用户可以在一个通知中选择的单个操作。
n.image // 通知的一部分显示的图像的URL
n.badge // 当没有足够的空间显示通知本身时,用于表示通知的图像的URL。
n.permission // 有三个值 granted, denied, 或default 当状态值为granted时可以发送通知消息 default默认用户没处理 denied 用户拒绝 n.renotify // 布尔值。新通知出现的时候是否替换之前的。如果设为true,则表示替换,表示当前标记的通知只会出现一个。注意这里的“当前标记”没?没错,true参数要想起作用,tag必须需要设置属性值。 n.requireInteraction // 布尔值, 指的是通知是否保持活动直到用户点击或取消通知 而不是自动关闭.. n.silent // 布尔值。通知出现的时候,是否要有声音。默认false, 表示无声。 n.timestamp //通知创建或者可以使用的时间。 n.data // 任意类型和通知相关联的数据。 n.vibrate // 通知显示时候,设备震动硬件需要的震动模式。所谓振动模式,指的是一个描述交替时间的数组,分别表示振动和不振动的毫秒数,一直交替下去。例如[200, 100, 200]表示设备振动200毫秒,然后停止100毫秒,再振动200毫秒。 (移动端) n.sound // 字符串。音频地址。表示通知出现要播放的声音资源。 n.sticky // 通知吸附不容易被清除… (移动端) n.noscreen // 布尔值。是否不再屏幕上显示通知信息。默认false, 表示要在屏幕上显示通知内容。(移动端) n. 此时实例 n 有四个事件处理: 1> onclick 用户对通知信息的点击事件 2> onshow 通知消息展示之后触发的事件 3> onerror 遇到错误会触发的事件 4> onclose close事件的处理

复制代码

2. Notification对象会有什么属性/方法呢?利用控制台中的window对象输出点开查看就可以看到: 值得注意的是: requestPermission()方法仅在Notification对象有效,实例对象无效!!!这个方法是用来向用户申请显示通知的权限,只能被用户主动去调用(在页面onload中可以调用,可以向用户申请,之后再去发送…)   实例对象拥有的方法有: (1). close()? 用于关闭通知消息 –> 也可以在onshow方法加延迟调用,提高用户体验感… (2). addEventListener() 监听事件(这个通用方法) (3). removeEventListener 卸载监听事件(通用,同上) (4). dispatchEvent 分派事件(同上)   接下来,写一个js测试, 如果使用的是谷歌浏览器,建议在设置中显示通知将本地服务地址加入允许通知 但是,http的域名在谷歌浏览器被默认关闭,还不允许更改,查看谷歌浏览器控制台有警告信息—> index.js:78 [Deprecation] The Notification API may no longer be used from insecure origins. You should consider switching your application to a secure origin, such as HTTPS. See https://goo.gl/rStTGz for more details. 嗯,好吧,让加https证书,真的是有毒…虽然在自己的主页中添加该功能也只能在火狐浏览器爽一爽…. (腾讯云有免费一年的ssl证书,可自己进行安装…)

复制代码

// index.js
window.onload = function(){
  let gxlself = new Gxlself()
  gxlself.requestPermission()
  setTimeout(()=>{
    gxlself.showNotification()
  },3000)
}

class Gxlself{
   constructor(){
    this.isNotificationSupported = “Notification” in window;
   }
isPermissionGranted(){
return Notification.permission === ‘granted’;
}
requestPermission(){
if(!this.isNotificationSupported){
return;
}
Notification.requestPermission(status=>{
let permission = Notification.permission;
})
}
showNotification(){
if (!this.isNotificationSupported) {
return;
}
if (!this.isPermissionGranted()) {
return;
}
var n = new Notification(“gxlself对您发来问候”, {
icon : ‘gxlself.png’,
body : ‘欢迎来访,鄙人万分感激! 点击即可跳转至我的博客页面~’
});
n.onshow = function () {
console.log(‘gxlself已经发送通知信息’);
setTimeout(function() {
n.close();
}, 5000);
}
n.onclick = function () {
location.href = ‘http://gxlself.com/blog
n.close()
}
n.onerror = function (err) {
console.log(err)
}
n.onclose = function () {
console.log(‘gxlself消息窗口关闭’)
}
}
}

复制代码

这是火狐浏览器执行后的效果展示:   再来看看谷歌本地跑的效果:(域名已经被默认拦截关闭,上面已经说明,不再重复) 谷歌的效果其实挺好看的,嗯…只是一个https把我给屏蔽了? ?哎… ——-? 桌面版应用 ———– 当你要在开放 web 应用中使用通知时,请确保将?desktop-notification?权限添加到你的 manifest 文件中。通知可以被用于任何权限级别,hosted 或更高。

1
2
3
  "permissions": {
   "desktop-notification":{}
  }

  这个Notification比较好玩,也是未来发送消息的一个重要的一块,留此记录…

利用Bomb打造自己的小程序

首先,小程序的开发已是热门,一个前段技术人员必备的技术就是开发小程序.在这里推荐一个入门小程序文章(连胜出品)

对于小程序的入门开发就不再做详细介绍,这里针对Bmob辅助开发小程序进行介绍。

  1. Bmob是什么?

  Bmob 为小程序开发提供一体化的后端云服务,免去了服务器维护、证书配置、数据存储互通等繁琐的工作,大幅度提高开发效率,减少研发时间和成本。接入 Bmob,还能同时打通小程序、App、Web 之间的数据,提高运营效率。

  利用Bomb可以做到WEB,APP,小程序共享一套数据,并且是免费的提供20G的存储空间,对于个人开发足以。

  2. Bmob开发

  ->注册Bmob

  ->创建一个应用

  

  接着将自己注册的小程序对应的AppId以及AppSecert填入Bmob,如下图:

  

  之后,在应用秘钥中获取Bomb提供的Application ID和REST API Key,这是进入Bmob的凭证,注意保护…

  

  此时,下载Bmob SDK(Bomb.min.js),将此SDK放入utils文件下,这样就可以在app.js中进行引入

var Bmob = require(‘../../utils/bmob.js’);
Bmob.initialize(“你的Application ID”, “你的REST API Key”);

  此时,可以进行对数据的查询:

var Diary = Bmob.Object.extend(“diary”);

//利用每一条对应的ObjectId查询 var query = new Bmob.Query(Diary);
  query.get(“4edc3f6ee9”, {
success: function(result) { // The object was retrieved successfully.
console.log(“该日记标题为”+result.get(“title”));
},
error: function(result, error) {
console.log(“查询失败”);
}
});

//利用其它字段进行查询
query.equalTo(“title”, “bmob”);//利用title字段(示例)
query.find({
  success: function(results) {
    console.log(“共查询到 “ + results.length + “ 条记录”); // 循环处理查询到的数据
    for (var i = 0; i < results.length; i++) {
      var object = results[i]; console.log(object.id + ‘ - ‘ + object.get(‘title’)); }
  },
  error: function(error) {
    console.log(“查询失败: “ + error.code + “ “ + error.message);
  }
});

  除了查询,还可以进行对应的增删改,eg:

//增加数据 创建类和实例
var Diary = Bmob.Object.extend(“diary”); var diary = new Diary();
diary.set(“title”,”hello”);
diary.set(“content”,”hello world”); //添加数据,第一个入口参数是null
diary.save(null, {
success: function(result) { // 添加成功,返回成功之后的objectId(注意:返回的属性名字是id,不是objectId),你还可以在Bmob的Web管理后台看到对应的数据
console.log(“日记创建成功, objectId:”+result.id);
},
error: function(result, error) { // 添加失败
console.log(‘创建日记失败’);

    }
});

//删除数据 //单个请求每次最多删除50条。
var query = new Bmob.Query(‘diary’);
query.equalTo(“title”, “后端云”);
query.find().then(function (todos) { return Bmob.Object.destroyAll(todos);
}).then(function (todos) {
console.log(todos); // 删除成功
}, function (error) { // 异常处理
}); // 更改数据
var Diary = Bmob.Object.extend(“diary”); var Diary = new Diary();
Diary.set(“title”, “111”);
Diary.set(“content”, “222”); //添加数据
Diary.save(null, {
success: function(result) { // 修改数据
result.set(“title”, “111”);
result.set(“content”, “222”);
result.save();
}
})

  当然,Bmob服务不至于数据服务,只是更多的我们操作数据多一点.

  例如:短信验证码服务:

Bmob.Sms.requestSmsCode({“mobilePhoneNumber”: “131xxxxxxxx”} ).then(function(obj) {
console.log(“smsId:”+obj.smsId); // }, function(err){
console.log(“发送失败:”+err);
});

返回数据:
{ “smsId”: 1232222 }

  还有更多好玩的,不再做一一展示,Bmob官方文档比较浅显易懂..

原型笔记

  • 1. Object.getOwnPropertyNames()

在学习使用该方法的时候,查阅了《JavaScript高级程序设计》与 MDN 来综合学习。

先来看看MDN对其的表述:

参数

obj:

  一个对象,其自身的可枚举和不可枚举属性的名称被返回。

返回值

      在给定对象上找到的属性对应的字符串数组。

描述

Object.getOwnPropertyNames() 返回一个数组,该数组对元素是 obj自身拥有的枚举或不可枚举属性名称字符串。 数组中枚举属性的顺序与通过 for...in 循环(或 Object.keys)迭代该对象属性时一致。数组中不可枚举属性的顺序未定义。

同时,举出了几个例子来表显示,例如:

var arr = [“a”, “b”, “c”];
console.log(Object.getOwnPropertyNames(arr).sort()); // [“0”, “1”, “2”, “length”] // 类数组对象
var obj = { 0: “a”, 1: “b”, 2: “c”};
console.log(Object.getOwnPropertyNames(obj).sort()); // [“0”, “1”, “2”] // 使用Array.forEach输出属性名和属性值
Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) {
console.log(val + “ -> “ + obj[val]);
}); // 输出 // 0 -> a // 1 -> b // 2 -> c //不可枚举属性
var my_obj = Object.create({}, {
getFoo: {
value: function() { return this.foo; },
enumerable: false }
});
my_obj.foo = 1;

console.log(Object.getOwnPropertyNames(my_obj).sort()); // [“foo”, “getFoo”]

  正如:MDN上所述,该方法返回的是对象自身的可枚举与不可枚举属性,即返回的是一个数组。并且在查阅《JavaScript高级程序设计》这本书籍的时候,却发现了一个问题。

function Person(){

}

Person.prototype.name = ‘asfd’
Person.prototype.age = 15
Person.prototype.sayName = function () {
console.log(this.name)
}

var keys = Object.getOwnPropertyNames(Person.prototype) console.log(keys)
// 书籍中表述keys返回的是 [‘constructor’, ‘name’, ‘age’, ‘sayName’]

// 但是当将Person.property设置一个以对象字面量形式创建的对象时,就不再返回constructor. 书籍中已经解释此时的constructor不再指向Person // 于是,我真的操作了一遍才发现,真的如此
// 实例如下:
function Person(){ }
Person.prototype = {
name: ‘zwj’,
age: 21,
say: function () {
console.log(‘say’)
}
}
var keys = Object.getOwnPropertyNames(Person.property)
console.log(keys)
// 此时返回的 [‘name’,’age’,’sayName’]

  我根据书中理解如下:设置一个以对象字面量形式创建的对象,虽然结果相同,但是constructor不再指向Person,这就涉及到原型链了

  首先,每个构造函数被创建的时候,同时就创建了一个构造函数对应的原型对象,即: 构造函数.prototype 

  这个对象就会自动获得一个constructor属性,这个属性就是该对象指向构造函数的指针,你也可以试试输出: Person.prototype    Person.prototype.constructor     Person.prototype.constructor.prototype 等等,就会发现形成了一个闭环…

  这里我们使用的语法去接收属性值,本质上默写了prototype对象,这时候,改写的对象默认的constructor指向为Object构造函数,毕竟函数也是对象,在JS中一切皆为对象…

  所以如果我们通常为了在原型上进行大量的属性写入,就必须对这部分操作进行注意,需要在使用设置对象字面量方式之后手动为其添加constructor属性的指向,这样做的目的就是为了确保通过该属性能够访问到适当的值…

  当然你会觉得这样设置并不是完美,毕竟对于constructor这个属性,他应该是不可枚举的,此时,却是可枚举的,这是就需要我们使用Object.defineProperty()进行相应的设置.相应的代码如下:

function Person(){
} // 此时重写了prototype对象 constructor指向Object
Person.prototype = {
name: ‘Nick’,
age: 15,
sayName: function(){
consolelog(this.name)
}
} // 对constructor进行设置修改
Object.defineProperty(Person.prototype, ‘constructor’, {
enumerable: false, // 设置是否可枚举 false为不可枚举
value: Person // 设置值为Person
}) //可以通过definePrototype进行更改数据属性的共有6个,其余4个分别为: // configurable ———-> 表示是否可以通过delete进行删除 默认为true // writable ———-> 表示是否可以修改属性的值 默认为true
// get ———-> 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。
// set ——–> 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。

  总结: 在进行创建构造函数的时候,以前习惯只是将constructor直接写在重写的prototype对象中,但是却忽略了其中最重要的一条信息,那就是constructor属性本身是不可枚举的属性,还需要更深一层的操作去设置constructor.看问题不能只看表面,更应该注重于内在…