目录
用 constructor 构造函数的方式,判断某个实例对象是不是这个构造函数创建的。
通过Object.prototype.toString.call()。最全面的
1、Function内置的构造函数和Object内置的构造函数他们之间的原形链的关系什么?
- JSON 是对象的一个严格的子集
- JOSN 中的key 必须加 " "
- JSON 不能赋值
- 使用JSON.parse() 会把JSON数据中值为undefined的数据过滤掉
- function ss(ob) {
- let obj = {}
- for (let a in ob) {
- if (ob[a] === ob) return Object.assign({}, ob)
- }
- }
- var dd = { name: '搜索', age: 16 }
- // 通过它触发改变
- Object.defineProperty(dd, 'age', {
- get() { return this }
- })
-
- let aa = ss(dd)
- console.log('aa', aa);
- console.log('判断', aa === dd);
- console.log(typeof 2); // number
- console.log(typeof '字符'); // string
- console.log(typeof true); // boolean
- console.log(typeof null); // object
- console.log(typeof undefined); // undefined
- console.log(typeof Symbol(3)); // symbol
- console.log(typeof function(){}); // function
- console.log(typeof NaN); // number
- console.log(typeof {name:'老六',age:66}); // object
- console.log(typeof ['1',2,'3']); // object
用来判断基本数据类型,难以判断函数除外的复杂数据类型
- let a = 2
- let b = '字符'
- let c = true
- let obj = {name:'老六',age:66}
- let arr = ['1',2,'3']
- let fun = function(){}
-
- console.log(a.constructor == Number); // true
- console.log(b.constructor == String); // true
- console.log(c.constructor == Boolean); // true
- console.log(obj.constructor == Object); // true
- console.log(arr.constructor == Array); // true
- console.log(fun.constructor == Function); // true
原理:因为构造函数创建的时候,会有一个prototype属性指向原型对象,而原型对象中会有一个constructor属性指回来构造函数
- console.log(Object.prototype.toString.call(2)); // [object Number]
- console.log(Object.prototype.toString.call('字符')); // [object String]
- console.log(Object.prototype.toString.call(true)); // [object Boolean]
- console.log(Object.prototype.toString.call(null)); // [object Null]
- console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
- console.log(Object.prototype.toString.call(Symbol(3))); // [object Symbol]
- console.log(Object.prototype.toString.call(function(){})); // [object Function]
- console.log(Object.prototype.toString.call(NaN)); // [object Number]
- console.log(Object.prototype.toString.call({name:'老六',age:66})); // [object Object]
- console.log(Object.prototype.toString.call(['1',2,'3'])); // [object Array]
必须是Object原型对象上的toString方法才能判断,其他原型对象上的不能判断。
并且进行数据类型判断,我们需要借助apply方法或者call方法或者bind方法。
例如:Array原型对象上的toString方法是判断不了的,因为它重写了toString方法。
注意:判断不了自定义构造函数创建出来的实例对象
- console.log(function(){} instanceof Function); // true
- console.log(new Date instanceof Date); // true
- console.log({name:'老六',age:66} instanceof Object); // true
- console.log(['1',2,'3'] instanceof Array); // true
instanceof:检查一个对象(引用类型)是否为构造函数(类)的实例
可以用来判断内置对象
- 方法() 调用 this指向window
- 一个函数通过对象打点调用,this指向这个对象
- 事件处理函数,谁触发this就指向谁
- 定时器里面的匿名函数this指向window
- 一个方法在数组里面通过下标的方式去调用,this指向数组,
总结:谁调用 this就指向谁
call():可以调用函数,同时改变this的指向,只能传字符串形式参数
apply():可以调用函数,同时改变this指向,只能传数组形式参数
bind() :不能调用函数,它是重新定义一个函数并返回,只能传字符串形式参数
- function fun(a,b){
- console.log('this',this);
- console.log(a,b);
- // 调用fun(obj,'普通'),返回undefined,因为window身上没有age
- console.log(this.age);
- }
-
- var obj = {
- name: '小明',
- age:5
- }
-
- fun(obj,'普通') // this 指向window
- fun.call(obj,3,333) // this 指向obj
- fun.apply(obj,[6,666]) // this 指向obj
- fun.bind(obj)(9,999) // this 指向obj
在函数内部(内存)创建了一个局部变量,是一个空的对象
构造函数中的this(上下文)指向这个空的对象
在这新对象中添加一个__proto__属性,指向构造函数的原型对象prototype
所有的语句执行完毕,函数将自动return这个新对象(this),如果是引用类型,就返回这个引用类型的对象(值)
手写流程:
- // 手写new
- function newFunc(Func,...args) {
- // 1.创建一个新对象
- let newObj = {}
- // 2.将新对象和构造函数通过原型链连接
- newObj.__proto__ = Func.prototype
- // 3.将构造函数的this绑定到新对象上
- const result = Func.apply(newObj,args)
- // onsole.log(result) // 值为undefined
- // 4.根据返回值类型判断,,如果是引用类型直接返回值,否则返回this(创建的新对象)
- return result instanceof Object ? result : newObj
- }
-
- // 测试
- function Person(name, age) {
- this.name = name;
- this.age = age;
- // 若没有return 相当于 return undefined
- // return {sex:'男'} // 返会引用类型
- }
- // 添加往Person原型对象上添加一个sayName方法
- Person.prototype.sayName = function (){
- console.log('名字:',this.name);
- }
- const person1 = newFunc(Person, 'Tom', '18')
- console.log(person1);
- person1.sayName()
- 当一个函数被new调用的时候,这个函数就是一个构造函数
构造函数就是一个普通的函数,里面可以写任何的语句,只不过this指向的是一个对象
构造函数里面写了return的语句会发生两种不同的处理方式。如下:
- 如果return 基本数据类型(number null boolean string undefined)的话,程序会被打断执行
- 如果return 引用数据类型(Array Date Function Object。。。),那么return的值会覆盖掉原来的值
字面量形式创建的对象其实都是Object new的实例对象。
字面量创建的函数都是Function new出来的实例对象
任何的数组的字面量的形式的对象,都是Array new出来的实例对象
任何字面量创建number类型都是Number new出来的实例对象
注意:使用 Number 构造函数创建的对象,也可以进行计算,但有时候有坑
- var a = new Number(9)
- console.log(a);
- if(a) alert('success')
- else alert('fail')
字符串有很多方法,这些方法都是定义在String的prototype上面(原形上面)
Var a=true
Var a=new Boolean(true)
总结: 所有的内置的对象,其实都是内置的构造函数
Prototype:每一个构造函数都有属性,指向一个空的对象(原型)
Prototype 不需要去定义,天生就有
People.prototype 是People 构造函数的原型
People.prototype 是小明或者小红的原型对象
原型链:任何一个构造函数都有一个属性叫做prototype指向了一个对象,当这个构造函数被new出来的时候,它的每一个实例的__proto__属性 也指向这个对象
__proto__:原型链查找的功能,当小明或者小红没有这个属性或者方法的时候,她就会沿着原型链往上找,直到确定原型对象是否有这个属性或者方法。
Constructor属性:任何一个构造函数的prototype身上都有一个 constructor属性指向他的构造函数
function person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } person.prototype.toString = function () { console.log('aaa'); return "person[" + this.name + "," + this.age + "," + this.gender + "]" } var a = new person("猪八戒", 29, "男"); console.log(a); //一个实例化对象,a a.toString() // 打印 aaa console.log(a.__proto__.constructor); // 打印 person这个函数instanceof运算符:用来检测一个对象是不是某一个构造函数的实例
console.log(对象 instanceof 构造函数);
总结:函数的prototype指向谁,函数new出来的玩意的__proto__就指向谁
面试题:
缺点:多个实例化对象共享一个原型对象,一个发送改变,另一个也随着改变
- function Person() {
- this.name = 'xiangming'
- this.age = [1, 2, 3]
- }
- Person.prototype.shuohua = function () {
- console.log("说话")
- }
- function obj() {
- this.type = '老六'
- }
-
- obj.prototype = new Person()
- var a = new obj()
- console.log(a);
- a.shuohua()
优点:解决原型链继承不能传递参数给父类
缺点:父类定义方法只能定义在父类的构造函数里面,不能定义在原型上
总结:只能继承父类的实例属性和方法,不能继承原型属性或者方法。
- function Person(age){
- this.name = '小明'
- this.age = age
- this.sayName = function(){
- return this.name
- }
- }
- // 不能定义在外面
- // Person.prototype.sayName = function(){
- // return "this.name"
- // }
-
- function Chinese(name,age){
- Person.call(this,666)
- }
-
- var xiaoming = new Chinese(666)
- console.log(xiaoming);
- console.log(xiaoming.sayName()); // 小明
原型链继承与构造函数继承的组合
优点:能够解决原型链继承与构造函数继承的问题
缺点:构造函数多执行了一遍,另外进行了一次性能开销
- function Person() {
- this.name = '老六';
- this.play = [1, 2, 3];
- }
- // 往Person原型上添加方法
- Person.prototype.getName = function () {
- return this.name;
- }
- function Child3() {
- // 第二次调用 Person()
- Person.call(this);
- this.type = 'child3';
- }
-
- // 第一次调用 Person()
- Child3.prototype = new Person();
- // 手动挂上构造器,指向自己的构造函数
- Child3.prototype.constructor = Child3;
- var s3 = new Child3();
- var s4 = new Child3();
- s3.play.push(4);
- console.log(s3.play, s4.play); // 不互相影响
- console.log(s3.getName()); // 正常输出'老六'
- console.log(s4.getName()); // 正常输出'老六'
- console.log(Child3.prototype.constructor);
Object.create()方法,这个方法接收两个参数:一是用作新对象原型的对象、二是为新对象定义额外属性的对象(可选参数)。可以实现浅拷贝的作用
缺点:
- 多个实例化对象共享一个原型对象,一个发送改变,另一个也随着改变,存在篡改的可能
- 父类定义方法只能定义在父类的构造函数里面,不能定义在原型上
- 拷贝后的对象(子类)不能直接添加方法,需要添加到原型对象上
- let Person={
- name : 'xiangming',
- age : [1,2,3],
- getName: function () {
- console.log(this.name);
- }
- }
-
- let obj1 = Object.create(Person)
- obj1.age.push(4)
- let obj2 = Object.create(Person)
- obj2.age.push('44')
-
- obj1.str=function(){
- console.log('我是新增的方法');
- }
- console.log('obj',obj1.age); //[1,2,3,4,'44']
- console.log('obj',obj2.age); //[1,2,3,4,'44']
- console.log('obj',obj1.__proto__); // Person
- obj1.getName() // xiangming
- obj2.str() // 报错。需要添加到原型对象上 obj1.__proto__.str
-
-
- //【Object.create的实现原理?】
- function obj_create(o){
- function F(){} //构造函数
- F.prototype = o; // 把F的原型替换为传递过来的Person
- return new F()
- }
- let Person={
- name : 'xiangming',
- age : [1,2,3]
- }
- let AA = obj_create(Person) // AA 是F的实例化对象
- console.log(AA); // 可实现原型链的向上查找功能
寄生式继承:使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法。
优缺点和原型式继承一样
- function obj_create(o) {
- function F() { } //构造函数
- F.prototype = o; // 把F的原型替换为传递过来的Person
- return new F()
- }
-
- function obj(o) {
- var chone = obj_create(o)
- chone.sayHi = function () {
- console.log('hi')
- }
- return chone
- }
-
- var Person = {
- name: "jill",
- frindes: ["sk", "xiaoming", "xiaogang"],
- age: function () {
- console.log(this.name);
- }
- }
- var A = obj(Person)
- console.log(A);
- A.sayHi()
- A.age()
结合第四种中提及的继承方式,解决普通对象的继承问题的 Object.create 方法,在前面这几种继承方式的优缺点基础上进行改造,得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式
运行机制:所有的同步任务都是在主进程的执行中形成一个执行栈,主进程之外还有一个”任务队列(异步任务队列)“,在这个队列中先执行宏任务,在清空(执行)当前宏任务中的所有微任务,然后进行下一个tick形成循环。
闭包前我们需要清楚自执行函数、函数作用域、内存回收机制、作用域继承
- /* 特性:1.自执行函数是很自私的,它的内部可以访问全局变量。
- 2.但是除了自执行函数自身内部,是无法访问它的。
- 3.自执行函数无需给函数名,因为根本没办法在其他地方调用,它本身只会执行一次 */
-
- function s1(a1,b1){
- return sum1 = a1 + b1
- }
- (function s2(a2,b2){
- console.log('内部',s2) // 打印s2函数
- return sum2 = a2 + b2
- ;})()
- console.log(s1) // 打印s1函数
- console.log(s2) //报错:autoFunc.html:38 Uncaught ReferenceError: s2 is not defined
-
-
- /* 总结:1.能够实现作用域的绝对隔离和函数命名冲突
- 2.主要用于闭包和创建独立的命名空间两个方面
- 3.自执行函数将某些代码包裹起来可以实现块级作用域的效果,减少全局变量的数量
- 4.自执行函数执行结束后变量就会被内存释放掉,从而也会节省了内存。 */
- /*在函数执行完毕,这个独立作用域就会删除。
- 有一种情况下这个封闭的盒子是不会删除的,那就是“闭包”,*/
-
- // 函数执行
- function fn(){
- var a = 1
- }
- // 函数执行完毕
- /*内部函数引用外部的函数的变量,外部函数执行完毕,作用域也不会删除。
- 从而形成了一种不删除的独立作用域。*/
-
- function fn(){
- // b的独立作用域是不删除的,因为被内部函数引用了
- let b = 2
- return function(){
- // 内部函数引用了外部函数的变量 b
- console.log(b)
- }
- }
某一个变量或者对象被引用,因此在回收的时候不会释放它,因为被引用代表着被使用,回收机制不会对正在引用的变量或对象进行回收的。
- function fn(){
- // b的独立作用域是不删除的,因为被内部函数引用了
- let b = 2
- return function(){
- // 内部函数引用了外部函数的变量 b
- console.log(b)
- }
- }
在一个函数里边再定义一个函数。这个内部函数一直保持有对外部函数中作用域的访问(小盒子可以一直访问大盒子但大盒子不能访问小盒子)。
函数执行,形成一个独立作用域,保护里边的私有变量不受外界的干扰,除了保护私有变量外,还可以存储一些内容,这样的模式叫做闭包。
通过一系方法,将函数内部的变量(局部变量)转化为全局变量
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
- function f1() {
- var n = 999;
- nAdd = function () {
- alert(n += 1);
- }
- function f2() {
- alert(n);
- }
- return f2;
- }
- var result = f1();
- result(); //999
- nAdd();//1000
- result(); //1000
注意:上面代码,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。 其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个操控者,可以在函数外部对函数内部的局部变量进行操作。
- for (var i = 1; i < 5; i++) {
- setTimeout(function () {
- console.log(i); // 5 5 5 5
- }, 0);
- }
由于JavaScript是单线程的,setTimeout 是异步任务,JS会将其放入到任务队列中,待同步任务执行完毕后,才执行任务队列中的异步任务。
因为setTimeout函数也是一种闭包,往上找它的父级作用域链(window),因为变量 i 是用 var 声明的全局变量,会被挂载到 window 上,所以变量i的值变成了i = 5,最后执行setTimeout时输出4个5
解决方案:
- for (var i = 1; i < 5; i++) {
- (function (i) {
- setTimeout(function () {
- console.log(i);
- }, 0);
- })(i);
- }
- // 使用块级作用域变量关键字,会为每次循环创建独立的变量,从而每次打印都会有正确的索引值。
-
- for (let i = 1; i <= 5; i++) {
- setTimeout(function () {
- console.log(i);
- }, 0);
- }