JS篇你不知道的-JS-知识点总结一

typeof null 为 ”object” 解释不同的对象在底层都表示为二进制,在JavaScript中二进制前三位都为0的话会被判断为object类型,null 的二进制表示都是0,自然前三位都是0,所以执行 typeof null 时,会返回 ”object”。传统编程语言编译的三个步骤分词/词法分析解析/语法分析代码生成变量的赋值操作编译阶段 -- 首次编译器会在当前作用域中声明一个变量(变量提升),如果之前没用声明过。运行阶段 -- 运行时引擎会在作用中查找该变量,如果能够找到就会对它赋值(LHS查询。作用域作用域是一套规则,用于确定在何处以及如何查找变量。如果查找的目的是对变量赋值,那么就会使用 LHS 查询。如果查找的目的是获取变量的值,那么就会使用 RHS 查询。无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。闭包当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。thisthis 在任何情况下都不指向函数的词法作用域。在 JavaScript 中作用域确实和对象类似,可见的标识符都是它的属性。但是作用域“对象”无法通过 JavaScript 代码访问,它存在 JavaScript 引擎的内部。this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调 用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(有时也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈),函数的调用方法,传入的参数等信息。this 就是记录其中的一个属性,会在函数执行的过程中用到。this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。调用栈 — 就是为了到达当前执行位置所调用到的所用函数。bind()会返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数。new 操作符使用 new 来调用函数时,会自动执行下面的操作: 创建一个新的空对象;这个对象会被执行[[原型]]连接;这个新对象会绑定到函数调用的 this;如果函数没用返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。判断 this可以根据下面的顺序来进行判断: 函数是否在 new 中调用(new绑定)如果是的话,this 是绑定的是新创建的对象。 var bar = new foo();函数是否是通过 call apply (显示绑定)或者硬绑定调用,如果是的话,this绑定的是指定的对象。 var bar = foo.call(obj2);函数是否在某个上下文对象中调用(隐式调用),如果是的话,this绑定的是那个上下文对象。 var bar = obj.foo();若果都不是的话,使用默认绑定,如果在严格模式下,就绑定到 undefined,否则绑定到全局对象上。 var bar = foo();ES6 新增可计算属性名var prefix = "foo";var myObject = { [prefix + "bar"]: "hello", [prefix + "baz"]: "world" }myObject["foobar"]; // hellomyObject["foobaz"]; // worldin / hasOwnProperty() -- 判断某个对象是否存在某个属性in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中。hasOwnProperty() 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 链。forEach() / every() / sone() -- 历数组的值forEach() 会遍历数组中的所有值并忽略回调函数的返回值(忽略返回值)。every() 方法测试数组的所有元素是否都通过了指定函数的测试(返回值是false终止)。some() 方法测试是否至少有一个元素通过由提供的函数实现的测试(返回值是true终止)。for...in -- 遍历数组下标/对象可枚举属性不保证 key 顺序。for...of -- 遍历可迭代对象的值在可迭代对象上(包括 Array,Map,Set,String,TypedArray,arguments 对象等)上创建一个迭代循环,调用自定义迭代钩子自定义的 @@iterator 对象 ,并为每个不同属性的值执行语句。var randoms = {
[Symbol.iterator]: function() { return {
next: function() { return { value: Math.random() }; } }; } }; var randoms_pool = []; for (var n of randoms) { randoms_pool.push( n ); // 防止无限运行! if (randoms_pool.length === 100) break; } 类的继承和多态多态并不表示子类和父类有关联,子类得到的只是父类的一份副本,类的继承其实就是复制。属性的设置和屏蔽var myObject = {}; myObject.foo = "bar";如果 myObject 对象中包含名为 foo 的普通数据访问属性,这条赋值语句只会修改已有的属性值。如果 foo 不是直接存在于 myObject 中,[[Prototype]] 链就会被遍历,类似 [[Get]] 操作。如果原型链上找不到 foo,foo 就会被直接添加到 myObject 上。然而,如果 foo 存在于原型链上层,赋值语句 myObject.foo = "bar" 的行为就会有些不同 (而且可能很出人意料)。如果属性名 foo 既出现在 myObject 中也出现在 myObject 的 [[Prototype]] 链上层,那 么就会发生屏蔽。myObject 中包含的 foo 属性会屏蔽原型链上层的所有 foo 属性,因为 myObject.foo 总是会选择原型链中最底层的 foo 属性。转载请注明出处,如果想要了解更多,请搜索微信公众号:webinfoq。 ...

April 26, 2019 · 1 min · jiezi

golang-new和make的区别

Go语言中new和make都是用来内存分配的原语(allocation primitives)。简单的说,new只分配内存,make用于slice,map,和channel的初始化。 newnew(T)函数是一个分配内存的内建函数。 我们都知道,对于一个已经存在变量,可对其指针进行赋值。 示例 var p intvar v *intv = &p*v = 11fmt.Println(*v)那么,如果不是已经存在的变量会如何呢?能对其直接赋值吗? 示例 var v *int*v = 8fmt.Println(*v)结果会报如下错误 panic: runtime error: invalid memory address or nil pointer dereference[signal 0xc0000005 code=0x1 addr=0x0 pc=0x48df66] 如何解决?通过Go提供了new来初始化一地址就可以解决。 var v *intv = new(int)*v = 8fmt.Println(*v)那么我们来分析一下 var v *int fmt.Println(*v)fmt.Println(v) //<nil>v = new(int) fmt.Println(*v)//fmt.Println(v)//0xc00004c088我们可以看到初始化一个指针变量,其值为nil,nil的值是不能直接赋值的。通过new其返回一个指向新分配的类型为int的指针,指针值为0xc00004c088,这个指针指向的内容的值为零(zero value)。 同时,需要注意的是不同的指针类型零值是不同的。 示例 type Name struct { P string}var av *[5]intvar iv *intvar sv *stringvar tv *Nameav = new([5]int)fmt.Println(*av) //[0 0 0 0 0 0]iv = new(int)fmt.Println(*iv) // 0sv = new(string) fmt.Println(*sv) //tv = new(Name)fmt.Println(*tv) //{}上面讲了对普通类型new()处理过后是如何赋值的,这里再讲一下对复合类型(数组,slice,map,channel等),new()处理过后,如何赋值。 ...

April 24, 2019 · 2 min · jiezi

JavaScript 中的 new 到底干了什么,跟原型链又有一些什么联系?

原文:https://legacy.ofcrab.com/press/javascript-new.html如果按面向对象的思路去讲 JavaScript 的 new,还是很难去理解,我们可以从另一个方向去理解一下它。你这些人类我是一名程序员,也是一个人,我可能:有一个响亮亮的名称在某一天出生是个男人我能行走我还能跑步还能跳跃能说话我还能写代码那么,在 JavaScript 中,我们可能像下面这样表达我:const me = { name: ‘大胡子农同工潘半仙’, birth: ‘1988-08-08’, sex: ‘male’, walk: function (speed, direction, duration) { // 以 speed 的速度向 direction 方向行走 duration 长的时间 }, run: function (speed, direction, duration) { // 像跑步一样,速度 }, jump: function (high, direction, angle) { // 以 angle 角度向 direction 方向跳 high 高 }, speak: function (letters) { // 说出 letters 这些词 }, coding: function (language, line) { // 写程序呢 }}你们这些人类当然,这个世界上不可能只有我一个程序员,更不可能只有我一个人,就像我们这个小公司,就有七八百人,似乎所有这些人的数据都保存在数据库里面:namesexbirth潘韬male1988-08-08高超male1985-08-09春雨male1999-08-08我们从数据库中查询出上面这几条记录,在 JavaScript 可能表示为一个二维数据,然后要创建出这三个人来,可能是下面这样的:const people = DB.query()// people = [[‘潘韬’, ‘male’, ‘1988-08-08’], […], […]]for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { name, sex, birth, walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {} }}重复的资源占用上面大家已经发现,像上面这样去创建三个对象, walk、run、jump、speak、coding 这五件能做的事情(方法),其实做法都一样,但是我们却重复的去描述该怎么做了,其实就占用了很多资源,所以,我们可能会像下面这样改进一下:const walk = function walk () {}const run = function run () {}const jump = function jump () {}const speak = function speak () {}const coding = function coding () {}for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { name, sex, birth, walk, run, jump, speak, coding }}不同的人共用相同的资源(方法)但是这个世界不止有人类对,人类相比于这个世界上的其它生物来讲,数量根本就值得一提,如果像上面这样,可能各种不同物种能做的事情都会要定义出不同的函数,蠕动肯定不是人类会去做的事情,但很多别的生物会做,那么为了代码管理方便,我们把人能做的所有事情都放在一个对象里面,这样就相当于有了一个命名空间了,不会再跟别的物种相冲突:const whatPeopleCanDo = { walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {}}for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { name, sex, birth, …whatPeopleCanDo }}原型但是,有的人可能我们并不知道他的 sex 信息是多少,有的也有可能不知道 birth 是多少,但是我们希望在创建这个人的时候,能给不知道的数据一些初始数据,所以, whatPeopleCanDo 并不能完全的表达出一个人,我们再改进:const peopleLike = { name: ‘’, sex: ‘unknown’, birth: ‘’, walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {}}for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { …peopleLike, name: name || peopleLike.name, sex: sex || peopleLike.sex, birth: birth || peopleLike.birth }}这样一来,我们就可以为不知道的属性加一些默认值,我们称 peopleLike 这个东东就为原型,它表示了像人类这样的物种有哪些属性,能干什么事情。原型链虽然上面已经比最开始的版本好得多了,但是还是能有很大的改进空间,我们现在像下面这样改一下:const peoplePrototype = { name: ‘’, sex: ‘unknown’, birth: ‘’, walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {}}for (let i = 0; i < people.length; i++) { let [name, sex, birth] = people[i] people[i] = { name: name || peoplePrototype.name, sex: sex || peoplePrototype.sex, birth: birth || peoplePrototype.birth, proto: peoplePrototype }}我们不再把人类原型里面的所有方法都绑定到某个人身上,而是像上面这样,用一个特殊的字段 proto 来指定:我的原型是 peoplePrototype 这个对象,同时,我们还制定了一个规则:如果你想请求我的某个方法,在我自己身上没有,那就去我的原型上面找吧,如果我的原型上面没有,那就去我的原型的原型上面去找,直到某个位置,没有更上层的原型为止像上面这样创建的 people 对象,有自己的属性,但是当我们去访问 people.speak() 方法的时候,其实访问的是 people.proto.speak(),这是我们的规则。更优雅的创建新新人类我们总不能在需要创建新人的时候,都像上面这样,自己去写一个对象,然后再手工指定它的原型是什么,所以,我们可以创建一个函数,专门用来生成人类的:const peoplePrototype = { name: ‘’, sex: ‘unknown’, birth: ‘’, walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {}}const makePeople = function makePeople(name, sex, birth) { let people = {} people.name = name || peoplePrototype.name people.sex = sex || peoplePrototype.sex people.birth = birth || peoplePrototype.birth people.proto = peoplePrototype return people}people = people.map(makePeople)现在这样我们只需要引入 makePeople 这个函数就可以随时随地创建新人了。更优雅一点的改进显然,上面这样并不是最好的办法,定义了一个原型,又定义了一个原型对象,我们可以把这两个合并到一起,所以,就可以有下面这样的实现了:const People = function People (name, sex, birth) { let people = {} people.name = name || People.prototype.name people.sex = sex || People.prototype.sex people.birth = birth || People.prototype.birth people.proto = People.prototype return people}People.prototype = { name: ‘’, sex: ‘unknown’, birth: ‘’, walk: function () {}, run: function () {}, jump: function () {}, speak: function () {}, coding: function () {}}我们直接把创建人类的那个函数叫作 People,这个函数有一个属性叫 prototype,它表示用我这个函数创建的对象的原型是什么,这个函数做的事情还是以前那些事儿,创建临时对象,设置对象的属性,绑定一下原型,然后返回。神奇的 this我们除了人,还有别的动物,比如 Tiger、Fish等,按上面的方式,在 Tiger() 或者 Fish() 函数里面都会建立不同的 tiger 或者 fish 名称的临时对象,这样太麻烦,我们把这种函数创建出来的对象,都可以统一叫作“这个对象” ,也就是 this object,不在关心是人是鬼,统一把所有的临时对象都叫 thisObject 或者更简单的就叫作:这个,即 this。const People = function People (name, sex, birth) { let this = {} this.name = name || People.prototype.name this.sex = sex || People.prototype.sex this.birth = birth || People.prototype.birth this.proto = People.prototype return this}当然,上面的这一段代码是有问题的,只是假想一样,这样是不是可行。new到现在为止,我们发现了整个代码的演变,是时候引出这个 new 了,它来干什么呢?它后面接一个类似上面这种 People 的函数,表示我需要创建一个 People 的实例,它的发明就是为了解决上面这些所有重复的事情,有了 new 之后,我们不需要再每一次定义一个临时对象,在 new 的上下文关系中,会在 People 函数体内自动为创建一个临时变量 this,这个就表示即将被创建出来的对象。同时,对于使用 new 创建的实例,会自动的绑定到创建函数的 prototype 作为原型,还会自动为 People 创建一个 constructor 函数,表示这个原型的创建函数是什么,所以,我们可以改成下面这样的了:const People = function People (name, sex, birth) { this.name = name || People.prototype.name this.sex = sex || People.prototype.sex this.birth = birth || People.prototype.birth}People.prototype.name = ‘‘People.prototype.sex = ‘unknown’People.prototype.birth = ‘‘People.prototype.walk = function () {}People.prototype.run = function () {}People.prototype.jump = function () {}People.prototype.speak = function () {}People.prototype.coding = function () {}people = people.map(p => new People(…p))总结new 到底干了什么?当 new People() 的时候创建临时变量 this,并将 this 绑定到 People 函数体里执行 People.prototype.constructor = People执行 this.proto = People.prototype执行 People 函数体中的自定义返回新创建的对象 ...

April 18, 2019 · 3 min · jiezi

javascript 面向对象 new 关键字 原型链 构造函数

JavaScript面向对象JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构构造函数的首字母大写,区分一般函数。函数体内部使用了this关键字,代表了所要生成的对象实例。生成对象的时候,必须使用new命令。构造函数内部使用严格模式 ‘use strict’,防止当做一般函数调用,这样就会报错。function Person(name, age, sex) { ‘use strict’; this.name = name; this.age = age; this.sex = sex;}Person() 报错new Person(“zhangxc”, 29, “male”);1、new关键字 命令内部实现function _new(constructor, params) { // 接受个数不确定参数,第一个参数:构造函数;第二个到第n个参数:构造函数传递的参数。 // 1. 首先将参数组成一个数组 // 首先 .slice 这个方法在不接受任何参数的时候会返回 this 本身,这是一个 Array.prototype 下的方法,因此 this 就是指向调用 .slice 方法的数组本身。 var args = Array.prototype.slice.call(arguments); // arguments伪数组,获取函数的所有参数的伪数组。 // 等价于 // [].slice.call(arguments); // 2. 获取构造函数 var constructor = args.shift(); // shift()返回数组第一个元素 // 3. 使用构造函数原型创建一个对象。我们希望以这个现有的对象作为模板,生成新的实例对象,这时就可以使用Object.create()方法。 var context = Object.create(constructor.prototype); // Object.create()参数是一个对象,新建的对象继承参数对象的所有属性 // 4. 将参数属性附加到对象上面 var result = constructor.apply(context, args); // 5. 返回一个对象 return (typeof result === ‘object’ && result != null) ? result : context;}function Person(name, age, sex) { this.name = name; this.age = age; this.sex = sex;}var args1 = _new(Person, “zhangxc”, 18, “male”);// {name: “zhangxc”, age: 18, sex: “male”}var args2 = new Person(“zhangxc”, 18, “male”);// {name: “zhangxc”, age: 18, sex: “male”}new.target属性如果当前函数是new命令调用,new.target指向当前函数(构造函数的名称),否则为undefined。function Test() { console.log(new.target === Test);}Test() // falsenew Test() // true2、this关键字…3、对象的继承… 待完善 ...

February 13, 2019 · 1 min · jiezi

重写JS中的apply,call,bind,new方法

在js中,经常会用到apply,call, bind, new,这几个方法在前端占据非常重要的作用,今天来看一下这些方法是如何实现,方便更加深入的理解它们的运作原理。this的绑定问题引用一点点其他知识点:一个方法的内部上下文this如何确定?一个方法的调用分为一下四种:方法直接调用,称之为函数调用,当前的上下文this,绑定在全局的window上,在严格模式use strict下,this为null方法作为一个对象的属性,这个是否通过对象调用方法,this绑定在当前对象上。如下:let dog = { name: ‘八公’, sayName: function() { console.log(this.name) }}dog.sayName() // 八公apply,call调用模式,当前的方法的上下文为方法调用的一个入参,如下:function sayHello() { console.log(this.hello)}let chineseMan = { hello: ‘你好啊’}sayHello.apply(chineseMan) // 你好啊let englishMan = { hello: ‘how are you’}sayHello.apply(englishMan) // how are you构造函数的调用,当前方法的上下文为新生的实例,如下// 声明构造函数function Animal(name) { this.name = name this.sayName = function() { console.log(this.name) }}let dog = new Animal(‘dog’)dog.sayName() // doglet cat = new Animal(‘cat’)cat.sayName() // cat正文apply实现思路:apply方法实现在Function.prototype中获取到当前调用方法体获取方法的入参绑定方法体中的上下文为传入的context–使用的方法就是对象调用属性方法的方式绑定调用方法Function.prototype.myApply = function() {let _fn = thisif (typeof _fn !== ‘function’) { throw new TypeError(’error’)}let ctx = […arguments].shift()// 因为apply的入参是数组,所有只需要取第一个let args = […arguments].slice(1).shift()ctx.myApplyFn = _fn// 由于apply会将原方法的参数用数组包裹一下,所以需要展开参数let res = ctx.myApplyFn(…args)delete ctx.myApplyFnreturn res}call实现思路:实现在Function.prototype中,大致和apply相似,却别在对于参数的处理上获取到当前调用方法体获取方法的入参绑定方法体中的上下文为传入的context调用方法Function.prototype.myCall = function() {let _fn = thisif (typeof _fn !== ‘function’) { throw new TypeError(’error’)}let ctx = […arguments].shift()// call使用的多个入参的方式,所有直接取参数第二个参数开始的所有入参,包装成一个数组let args = […arguments].slice(1)ctx.myCallFn = _fnlet res = ctx.myCallFn(…args) delete ctx.myCallFn return res}bind实现思路:实现在Function.prototype中,并且返回一个已经绑定了上下文的函数。利用闭包可以捕获函数上下文的变量来实现,总体上比起之前两个方法稍微复杂一些。获取调用bind的实例方法体获取需要绑定的上下文context声明闭包函数闭包函数中绑定context到实例方法体中闭包函数中调用原来的方法体返回闭包函数Function.prototype.myBind = function() { let _fn = this if (typeof _fn !== ‘function’) { throw new TypeError(’error’) } let ctx = […arguments].shift() let args = […arguments].slice(1) return function() { // 因为bind的调用方式,会有bind({}, ‘para1’, ‘para2’)(‘para3’, ‘para4’),这个时候需要将外面参数和内部参数拼接起来,之后调用原来方法 args = args.concat([…arguments]) ctx.myBindFn = _fn let res = ctx.myBindFn(…args) delete ctx.myBindFn return res }}codepen演示 需要翻墙<p class=“codepen” data-height=“265” data-theme-id=“0” data-default-tab=“js,result” data-user=“beyondverage0908” data-slug-hash=“PXMyqB” style=“height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid black; margin: 1em 0; padding: 1em;” data-pen-title=“rewrite bind”> <span>See the Pen rewrite bind by avg (@beyondverage0908) on CodePen.</span></p><script async src=“https://static.codepen.io/ass...;></script>new 方法实现思路:需要明白new到底做了什么生成一个新的实例对象实例对象__proto__链接到构造函数的prototype对象绑定构造函数的上下文为当前实例获取参数,传入参数,并调用构造函数function newObj() {let _o = {}let constructor = […arguments].shift()let args = […arguments].slice(1)if (typeof constructor !== ‘function’) { throw new TypeError(’error’)}_o.proto = constructor.prototype// 第一种调用方式:借助apply,call,或者bind实现绑定_o// constructor.apply(_o, args)// 第二种,使用属性方法绑定的方式_o.myNewFn = constructor_o.myNewFn(…args)delete _o.myNewFnreturn _o}// how to use - 如何使用function Animal(name, weight) {this.name = namethis.weight = weight}let dog = newObj(Animal, ‘dog’, ‘18kg’)// the animal name: dog weight: 18kgconsole.log(the animal name: ${dog.name} weight: ${dog.weight})let cat = newObj(Animal, ‘cat’, ‘11kg’)// the animal name: cat weight: 11kgconsole.log(the animal name: ${cat.name} weight: ${cat.weight})codepen需要翻墙<p class=“codepen” data-height=“265” data-theme-id=“0” data-default-tab=“js,result” data-user=“beyondverage0908” data-slug-hash=“MZNPoK” style=“height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid black; margin: 1em 0; padding: 1em;” data-pen-title=“MZNPoK”> <span>See the Pen MZNPoK by avg (@beyondverage0908) on CodePen.</span></p><script async src=“https://static.codepen.io/ass...;></script>结语熟悉函数内部的实现,了解内部的原理,对理解运行会有很多好处,亲自实现一遍会给你很多领悟。同时这些知识点又是非常重要的。 ...

January 22, 2019 · 2 min · jiezi