cocos使用clippingNode实现擦除效果

先直接上模拟器上运行效果图
img

代码也不复杂,只要知道怎么用clippingNode即可,先放上代码

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
bool HelloWorld::init() {
if ( !Layer::init()) {
return false;
}

auto winsize = Director::getInstance()->getWinSize();
auto width = winsize.width;
auto height = winsize.height;

auto stencil = mDrawNode = DrawNode::create();

auto cnode = ClippingNode::create(stencil);
cnode->setContentSize(winsize);
cnode->setInverted(true);
auto s = Sprite::create("HelloWorld.png");
s->setPosition(width * 0.5,height * 0.5);
cnode->addChild(s);

this->addChild(cnode);

auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = std::bind(&HelloWorld::onTouchBegan, this, std::placeholders::_1, std::placeholders::_2);
listener->onTouchMoved = std::bind(&HelloWorld::onTouchMoved, this, std::placeholders::_1, std::placeholders::_2);

EventDispatcher* ed = Director::getInstance()->getEventDispatcher();
ed->addEventListenerWithSceneGraphPriority(listener, this);

return true;
}

bool HelloWorld::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *event) {
mTouchBeganPos = touch->getLocation();
return true;
}

void HelloWorld::onTouchMoved(cocos2d::Touch * touch, cocos2d::Event * event) {
Vec2 pos = touch->getLocation();
std::vector<cocos2d::Vec2> vec(2);
vec[0] = mTouchBeganPos;
vec[1] = pos;
mPoints.push_back(vec);
mTouchBeganPos = pos;
refresh();
}

void HelloWorld::refresh() {
mDrawNode->clear();
mDrawNode->setLineWidth(10);
for(std::vector<std::vector<cocos2d::Vec2>>::iterator iter = mPoints.begin(); iter != mPoints.end(); iter++) {
std::vector<cocos2d::Vec2> vec = *iter;
mDrawNode->drawLine(vec[0], vec[1], Color4F::RED);
}
}

简单介绍一下clippingNode。它有一个stencil(可以是任何Node)用来决定切割区域,clippingNode的children都会被stencil切割,代码里我们的stencil是一个drawNode,需要擦除的图片HelloWorld.png添加为clippingNode的子节点。本来除了drawNode渲染的区域外都会被切割,我们通过setInverted接口设置后变成了相反,drawNode渲染的区域将被切割,被切割后我们就看到了图片下面的黑色背景,于是就呈现出擦除的效果。

使用setAlphaThreshold接口可以设置alpha阈值,当stencil中像素的alpha值大于这个阈值时,才会被绘制

svn之使用命令行做分支管理

使用svn不论是做分支(branch)还是标签(tag)都是使用svn cp命令,他们本质上没有区别,但用途上可能不同,这完全取决于开发者,例如一般trunk是作为开发目录,branch是为了作并行开发,而tag是作为milestone管理。

新建一个分支使用命令svn cp,他会增加一次提交

1
svn cp trunk-url tag-url -m "testtag"

然后就可以把这个分支co下来

1
svn checkout tag-url

svn使用的是全局版本号,分支之间是共享版本号的。例如我们在trunk下做一次修改并提交后版本是1063,此时我们到tag目录下执行svn up,会显示版本号也到了1063,但并不会把更新拉取下来。要将trunk下的更新拉取过来,需要使用svn merge命令

进入tag目录执行

1
svn merge trunk-url

就把trunk上的改动合并过来了,也可以使用-r参数指定将某两次提交之间的diff合并过来

从trunk上提交,然后到tag下去merge也是一样。

删除分支,使用

1
svn rm tag-url -m "remove tag"

最后稍微解释下svn cp和svn merge两个命令

svn cp

它的基本格式是:svn copy SRC DST,其中SRC和DST都可以是WC(working copy)或者URL

  • WC->WC 这个只是对本地文件的拷贝后执行了svn add
  • WC->URL 这个是拷贝后立即提交到了URL上,所以需要提供commit message
  • URL->WC 这个将URL上拷贝到本地后执行了svn add,可以用这个命令带上-r版本号来找回被删除的文件
  • URL->URL 这就是上面提到了创建分支了

svn merge

作用是应用两组源文件的差别到工作副本路径,基本格式为

1
2
3
4
5
svn merge sourceURL1[@N] sourceURL2[@M] [WCPATH]

svn merge sourceWCPATH1@N sourceWCPATH2@M [WCPATH]

svn merge [[-c M]... | [-r N:M]...] [SOURCE[@REV] [WCPATH]]

例如

1
2
svn merge http://xxxx/a.json -r 1063:1064
svn merge ../config/ -r 1063:1064

如果不指定初始和结束版本号,则默认为仓库起始和当前(HEAD)

最近刚刚发现一个问题,初始仓库trunk,使用svn cp生成release仓库,然后在trunk上增加一行代码,svn merge到release上去,在release上删除,然后svn merge到trunk上去。此时这行代码处不会提示有冲突,而是默认添加上了这行。我不太确定是否我的用法不对,所以暂时先记录下这个情况。在分支间来回merge时,要小心合并,尽量避免频繁merge吧,毕竟每次merge会有很多冲突

cocos之使用jpg+mask合成png实现方式(二)

第二种方式就是使用shader,这种方式的优点是不需要修改源代码,但性能上不如第一种方式。

编写shader涉及到顶点着色器和片断着色器,前者使用sprite默认的即可。sprite使用哪个着色器,只要看一下CCSprite.cpp里这句就找到了

1
setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP, texture));

然后我们稍微改动一下片断着色器,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef GL_ES
precision lowp float;
#endif

varying vec4 v_fragmentColor;
varying vec2 v_texCoord;

uniform sampler2D mask;

void main()
{
gl_FragColor = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
gl_FragColor.a = texture2D(mask, v_texCoord).a;
}

我们通过代码将mask作为一个Texture传进来,然后取它的每个像素的alpha值拿来用。

使用代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GLProgram *gp = GLProgram::createWithFilenames("res/test.vert", "res/test.frag");
GLProgramState* gs = GLProgramState::create(gp);

Image* img = new Image();
img->initWithImageFile("res/zjz_pop_word_title_zhanji.png");
Texture2D* t1 = new Texture2D();
t1->initWithImage(img);

gs->setUniformTexture("mask", t1);

Sprite* s = Sprite::create("res/splash_tuyoo_m.jpg");
s->setGLProgramState(gs);
s->setPosition(480,320);
this->addChild(s);

这里只需要小心一点不要把s->setGLProgramState(gs);写成s->setGLProgram(gp);就行,node上同时提供了这两个接口,但使用后者会创建一个新的GLProgramState,我们已经创建的gs也就失去了作用了

cocos之使用jpg+mask合成png实现方式(一)

第一种方式,在CCImage内读取图片数据后,合并起来使用。改写Image类,增加initWithJpgAndPng方法

代码如下

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
bool Image::initWithJpgAndPng(const std::string& jpgpath, const std::string& pngpath) {
bool ret = false;
unsigned char* jd = nullptr;
do{
std::string jp = FileUtils::getInstance()->fullPathForFilename(jpgpath);
Data jpgdata = FileUtils::getInstance()->getDataFromFile(jp);

unsigned char* jdata = jpgdata.getBytes();
ssize_t jsize = jpgdata.getSize();

ret = initWithJpgData(jdata, jsize);
if(!ret) {
break;
}

int jwidth = _width;
int jheight = _height;

ssize_t jlen = _dataLen;
jd = static_cast<unsigned char*>(malloc(jlen * sizeof(unsigned char)));
memcpy(jd, _data, jlen * sizeof(unsigned char)); //将jpg数据暂存起来

std::string pp = FileUtils::getInstance()->fullPathForFilename(pngpath);
Data pngdata = FileUtils::getInstance()->getDataFromFile(pp);
unsigned char* pdata = pngdata.getBytes();
ssize_t psize = pngdata.getSize();
ret = initWithPngData(pdata, psize);
if(!ret) {
break;
}

int pwidth = _width;
int pheight = _height;
if(pwidth != jwidth || pheight != jheight) {
break; //要求长宽必须严格相同
}

int pindex = 0;
int jindex = 0;

for(int index = 0; index < pwidth * pheight; index++) {
unsigned char alpha =*(_data+(pindex+3));

*(_data+pindex) = *(jd + jindex) * alpha / 255;
*(_data+(pindex+1)) = *(jd + jindex+1) * alpha / 255;
*(_data+(pindex+2)) = *(jd + jindex+2) * alpha / 255;

pindex += 4;
jindex += 3;
}
}while(0);

if(jd) {
CC_SAFE_FREE(jd);
}

return ret;
}

tips:

  1. 这里为了验证思路,找了个RBGA8888的png做mask图,如果使用的mask图不是该格式,则需要修改_renderFormat, _fileType等属性。
  2. 因为cocos默认png图片是pre_multi_alpha的,所以我们在加入alpha数据时,需要同时将alpha乘到rgb上

使用时代码如下:

1
2
3
4
5
6
7
8
Image* image = new Image();
image->initWithJpgAndPng("res/test1.jpg", "res/test2.png");

Texture2D* t = new Texture2D();
t->initWithImage(image);
Sprite* s = Sprite::createWithTexture(t);
s->setPosition(480,320);
this->addChild(s);

cocos3.x 渲染机制简述

cocos2dx-3.x对绘制部分进行了重构,将绘制从UI树的遍历中分离了出来。首先进行UI树的遍历给每个元素生成一个绘制命令。等遍历完之后,render开始执行栈中所有renderCommand。

遍历UI树

遍历UI树很简单,就是调用每个Node的visit函数,这是个虚函数,除了部分类做了重写(目前源代码里只有三个类重写了,分别是CCAttachNode, CCBillBoard, CCSprite3D),其余就沿用了Node的实现。通过

1
void Node::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)

我们可以看到

  1. 如果一个Node是不可见(_visible为false),则不会生成渲染命令,因此隐藏的节点不会增加渲染负担,只会占用内存消耗。
  2. 在遍历时,会对所有子节点以localZorder从小到大进行排序,如果localZorder相同,则以_orderOfArrival从小到大排序,这个子节点被添加的先后顺序。排序后先执行所有localZorder小于0的子节点的visit,再执行自己的draw,再执行所有localZorder大于0的子节点的visit。

draw函数为虚函数,且Node内的实现为空,具体实现在各个子类中,它的作用就是生成RenderCommand

RenderCommand

RenderCommand有以下类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum class Type
{
/** Reserved type.*/
UNKNOWN_COMMAND,
/** Quad command, used for draw quad.*/
QUAD_COMMAND,
/**Custom command, used for calling callback for rendering.*/
CUSTOM_COMMAND,
/**Batch command, used for draw batches in texture atlas.*/
BATCH_COMMAND,
/**Group command, which can group command in a tree hierarchy.*/
GROUP_COMMAND,
/**Mesh command, used to draw 3D meshes.*/
MESH_COMMAND,
/**Primitive command, used to draw primitives such as lines, points and triangles.*/
PRIMITIVE_COMMAND,
/**Triangles command, used to draw triangles.*/
TRIANGLES_COMMAND
};

在CCRender.cpp的processRenderCommand函数里,有各个类型command的具体处理。例如 CUSTOM_COMMAND就是执行设定的回调函数

通过这里可以了解cocos2dx-3.x的autoBatch机制,Sprite使用的是TrianglesCommand,在处理时,会先统一放在队列里,在遍历队列时,比较每个Command的materialID,如果相同则统一处理。决定materialID的有glProgram, _textureID,_blendType。此外不同globalZorder的精灵因为不在一批处理,所以不会自动autoBatch,不同父节点,不同localZOrder则不会影响,会自动autoBatch

cocos-addImageAsync解析

当我们在cocos内需要做一些比较耗性能的事情时,我们可以一些很巧妙地方案,例如开启多线程,以及将任务分解到每一帧完成一部分。在TextureCache的addImageAsync方法里,就同时用到了这两种办法,把这部分源码看明白,对自己实现能有很大的帮助

类TextureCache维护一个队列_asyncStructQueue,它存放需要执行的任务,这些任务将在异步线程执行,加载生成Image类

1
std::queue<AsyncStruct*>* _asyncStructQueue;

一个双向队列_imageInfoQueue,他存放加载好的Image,这些加载好的图片资源就会每帧从队列中获取一个,用来生成纹理Texture2D,生成好的纹理就可以在游戏内直接使用了

1
std::deque<ImageInfo*>* _imageInfoQueue;

这两个队列都是先进先出的操作,我也不是很明白为什么要用deque,有知道的可以告诉我一下。然后这两个队列因为都需要在异步线程操作,所以操作时需要用锁锁住。

使用一个_asyncRefCount来记录当前任务的数量,当往_asyncStructQueue添加一个任务asyncStruct时,计数+1,当一个纹理生成完成,销毁该任务asyncStruct,计数-1

1
int _asyncRefCount;

入口

需要异步加载一个图片时,首先调用

1
void TextureCache::addImageAsync(const std::string &path, const std::function<void(Texture2D*)>& callback)

首先判断一下这个path对应的texture是否已经在缓存里,如果在,则直接返回。否则如果_asyncStructQueue还未初始化,则执行初始化,然后生成一个AsyncStruct加入队列_asyncStructQueue中。

异步线程

类TextureCache维护一个异步线程,这个线程在_asyncStructQueue初始化时开启

1
std::thread* _loadingThread;

在这个线程执行的函数是

1
void TextureCache::loadImage()

这个线程通过std::condition_variable _sleepCondition来唤醒和休眠,当有任务加入任务队列_asyncStructQueue时,唤醒线程执行loadImage函数,在这个函数里发现_asyncStructQueue被清空时,休眠这个线程。

在执行loadImage时,会先看一下需要加载的图片是否已经在处理队列_imageInfoQueue中了。如果不在,则生成一个新的Image,执行

1
image->initWithImageFileThreadSafe(filename)

如果initWithImageFileThreadSafe返回true,则表示这张图片资源加载成功了,用这个image生成一个imageInfo放到_imageInfoQueue里。

定时回调

这个定时回调每帧都会被调用一次,它将上一步生成的image变成Texture2D。当_asyncRefCount从0变为1时,表示开始有任务需要完成,开启这个定时器,当_asyncRefCount变为0时表示任务全部完成,这个定时器会被关闭。

1
Director::getInstance()->getScheduler()->schedule(CC_SCHEDULE_SELECTOR(TextureCache::addImageAsyncCallBack), this, 0, false);

定时执行的函数就是addImageAsyncCallBack了,在这个函数里,通过

1
texture->initWithImage(image);

生成Texture2d,并存在缓存里,每次通过TextureCache加载纹理的时候,都会先看看是否已经存在缓存里了。
到这里,这个任务就完成了,执行下回调函数,然后把_asyncRefCount减一,搞定了

cocos图片资源加密

无意中看到hnliu’sblog上的方案,看完思路后,自己也照着写了一个,虽然不复杂,但是也花了将近两个小时。

python代码如下

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
41
42
43
import os
import random

ENCRYPTBYTE = random.randint(1,255) #不能随机到0,否则等于没加密
FIRSTBYTE = 0x12
SECONDBYTE = 0X34
THIRDBYTE = 0x56

print("encrypt key is " + str(ENCRYPTBYTE))

def getNewFileName(path):
arr = os.path.split(path)
dirname = arr[0]
filename = arr[1]
nameArr = filename.split(".")
return os.path.join(dirname, nameArr[0]+"-en." + nameArr[1])

def encrypt(path):
rf = open(path, "r")
newpath = getNewFileName(path)

wf = open(newpath, "w")

bytes = bytearray(rf.read())
if bytes[0] == FIRSTBYTE and bytes[1] == SECONDBYTE and bytes[2] == THIRDBYTE:
print "encrypted already, return"
else:
index = 4
newarr = bytearray(len(bytes) + 4)
newarr[0]= FIRSTBYTE
newarr[1]= SECONDBYTE
newarr[2]= THIRDBYTE
newarr[3]= ENCRYPTBYTE
for byte in bytes:
newb = byte ^ ENCRYPTBYTE
newarr[index] = newb
index += 1
wf.write(newarr)
wf.close()
rf.close()

if __name__ == '__main__':
encrypt("/Users/yangguang/project/test/test_cocosx/Resources/HelloWorld.png")

cocos端解析代码为把原来的initWithImageData改名为initWithImageDataInternal,然后重写initWithImageData方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool Image::initWithImageData(const unsigned char *data, ssize_t dataLen) {
char first = *data;
char second = *(data+1);
char third = *(data+2);
if(first == 0x12 && second == 0x34 && third == 0x56) {
char key = *(data+3);
unsigned char newarr[dataLen - 4];
ssize_t pos = 4;
while(pos < dataLen) {
char old = *(data+pos);
newarr[pos - 4] = old ^ key;
pos += 1;
}
return initWithImageDataInternal((const unsigned char*)(&newarr) , dataLen - 4);
}
return initWithImageDataInternal(data, dataLen);
}

中间出了一个比较土的问题,就是python代码中newarr[index] = newb这句,用了newarr.append(),因为newarr初始化了大小,导致newarr前面全是0,数据都被添加到后面去了

javaScript中的proto

__proto__

js的原型链继承很简单,对任意一个object,都有__proto__属性,当获取这个对象的属性或者方法时,现在对象本身去查找,如果不存在,则往它的__proto__上查找,因为__proto__本身也是一个object,所以如果没找到,则往__proto__的__proto__上查找,跟沿着一条链条走一样,直到找到返回,或者__proto__为null或undefined找到了尽头。

原型链不可能无止尽的延伸,所以js中有一个默认的object,它的__proto__就是undefined,除非我们强制打断原型链,否则最终都会查找到这个object上,它是长这个样子的,可以看到它是没有__proto__属性的

image

我们可以对一个object的__proto__属性随意赋值,但如果赋成null或者undefined,则它的__proto__属性变成了undefined,如果赋成基础数据类型,并不会改变其__proto__属性

__proto__来源

如果我们没对object的__proto__赋值,则它的__proto__来源于使用Object.create方法调用时传给他的参数,虽然构造一个object有好几种写法,例如

var a = {}
var b = new f();
var c = Object.create(proto)

但它们实际上都是执行了Object.create,例如var a = {}和var a = Object.create(Object.prototype)是一模一样的,而var b = new f()则基本可以认为等同于var b = Object.create(f.prototype)。所以上面例子里,a的__proto__是Object.prototype, b的__proto__是f.prototype, c的__proto__是proto

function的prototype

我们经常通过function来构造一个object,这种情况使用了该function的prototype来作为Object.create的参数,如果我们没有给function指定一个prototype,则它默认的prototype是这样的

img

可以看到,function默认的prototype有一个属性constructor指向函数自己,它的__proto__就是我们前面提到的原型链尽头的那个object(真该给他取个什么名字才好),当然我们可以给function指定一个prototype,而不使用它默认的,那就是我们在js中实现继承的办法。

function本身也是一个对象,所以上图里f也有__proto__属性,它的__proto__就是Function.prototype。Function是javascript解释器提供的实现,就不用再去追究它的prototype是什么了。

使用原型链实现继承

知道原型链是什么东西之后,就可以开始实现继承了。ES6开始有了class和extends关键字,就可以不用手动实现继承,但在ES6之前,我们需要自己来实现,例如

var base = {
    prop1:1,
    prop2:{
        a:1
    },
    func:function(){
        console.log("in base...");
    }
}

function Derived(){

}

Derived.prototype = base;
Derived.prototype.constructor = Derived;

var d1 = new Derived();
var d2 = new Derived();

d1.func();
d2.func();

console.log(d1.prop2.a);
d1.prop2.a = 2;
console.log(d2.prop2.a);

上面代码里,d1,d2是子类Derived的实例,它们继承了base里的属性和方法,所以可以直接调用d1.func(),也可以直接获取和设置d1.prop2.a的值,但也很容易发现问题,就是我们修改了d1.prop2.a的值,结果d2.prop2.a的值也改变了。prototype上的属性和方法,对于子类的实例是公共的,这是需要注意,容易出问题的地方。

这里顺便牵扯到一个问题,我们都知道object的constructor属性指向它的构造函数,所以在设定prototype时,必须再将prototype.constructor指向构造函数。就是上面代码里的

Derived.prototype.constructor = Derived;

如果没有注释掉的那句,则d1.constructor就不是Derived,而是变成了function Object(){[native code]}了

因为ES6之前没有原生的继承机制,很多地方都使用了John Resig的一种简单实现方式,使用25行代码实现了extends功能。

缩短原型链

如果原型链过长,可能会带来性能问题。所以需要注意的是尽量不要去扩展内置类型的原型。直接获取属性和for in函数都会查询原型链,如果想避免查询原型链,可以使用hasOwnProperty方法来判断

使用var a = Object.create(null) 代替 var a = {}的区别,前者明确将__proto__设置为null,查找属性时就不会再往原型链上查找了,后者__proto__其实是Object.prototype

js对象与继承(二)

类继承与原型继承

先分别举个例子吧,首先是原型继承

1
2
3
4
5
6
7
var proto = {"a":1,"b":{"c":2}}
var f1 = function(){}
var f2 = function(){}
f1.prototype = proto
f2.prototype = proto
var t1 = new f1()
var t2 = new f2()

其次是类继承

1
2
3
4
5
6
7
8
9
10
var proto = function(){
this.a = 1;
this.b = {"c":2};
}
var f1 = function(){}
var f2 = function(){}
f1.prototype = new proto()
f2.prototype = new proto()
var t1 = new f1()
var t2 = new f2()

它们的区别就在于,类继承先定义了一个基类proto,在继承时,子类构造函数的prototype是根据这个基类new出来的一个对象。

这两种方式各有优劣:

正如上一篇说到的,如果执行代码

1
2
t1.b.c = 12
console.log(t2.b.c)

对于原型继承,因为proto被改变了,所以t2.b.c也变成了12,而对于类继承则不会有问题,这对于不够熟悉js的人来说绝对是个坑。

不过原型继承实现起来比类继承要更灵活,因为不需要做抽象基类的工作,但如果随意地使用原型继承,例如用作prototype的对象是一个非常庞大复杂的对象,那显然会产生问题。

js对象与继承(一)

大家都知道在js中创建一个对象的一种很基本的方式

1
2
3
4
function f(){
this.a = 1;
}
var test = new f();

当执行new的时候,系统实际做的事情分了几步

  1. var obj = Object.create(f.prototype)
  2. var result = f.call(obj)
  3. result && typeof result === ‘object’ ? return result : return obj

用语言描述就是,首先用f.prototype构造一个Object,这里命名为obj,然后将obj作为参数this去调用构造函数f,返回值为result,如果result是一个对象,则返回它,也就意味着test = result,否则 test = obj。

然后再看prototype是什么东西吧,举个例子

1
2
3
4
5
6
7
var p = {"a":1, "b":{"c":2}}
var f = function(){this.d = 3}
f.prototype = p
var t = new f()
console.log(t.d)
console.log(t.a)

这里p是一个对象,f是一个构造函数,将f的prototype设为p,当使用f构造出一个对象t出来时,t会有一个属性__proto__,它就指向了p

当我们获取t.d时,t是有d属性的,这个没问题
当我们获取t.a时,t没有a属性,此时会沿着它的原型链往上查找,也就是查找它的__proto__,也就是p,p是有a属性的,所以将a的值返回。我们可以使用Object类上的一个方法hasOwnProperty来判断一个属性或方法是对象本身的,还是原型链上的。例如

1
2
console.log(t.hasOwnProperty("a"))	//false
console.log(t.hasOwnProperty("d")) //true

上面说的是取值,赋值的时候就稍微复杂些,例如

1
2
t.a = 11;
t.b.c = 12

执行第一句话,也就是对于基础数据类型,在chrome控制台上可以看到t的属性列表里出现了a并且值为11,而p的a仍然是1不变。也就是对原型上的基础数据类型赋值,不会影响原型,而是自身多了一个这样的属性。但使用hasOwnProperty看,仍然为false,这应该是js的一些不完善的地方吧。

执行第二句话,可以看到p的b变成了{“c”:12},也就是修改原型链上的非基础数据类型,会改变原型链本身。这也是javascript中使用类继承比使用原型继承更好的原因之一。下篇再分别讲这两种继承方式